From a79a3efbab35ab10fe5a162246698a410604bd79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?N=C3=A9stor=20Sancho?= Date: Sun, 31 May 2026 00:03:37 +0200 Subject: [PATCH] Add color schemes and character sets --- media/editor/dataDisplay.css | 314 +++++++++++++++++++++++++++++++++++ media/editor/dataDisplay.tsx | 198 +++++++++++++++++++++- media/editor/util.ts | 41 ----- package.json | 92 ++++++++++ package.nls.json | 18 ++ shared/protocol.ts | 40 +++++ src/hexEditorProvider.ts | 16 +- 7 files changed, 672 insertions(+), 47 deletions(-) diff --git a/media/editor/dataDisplay.css b/media/editor/dataDisplay.css index 75e2904f..48139076 100644 --- a/media/editor/dataDisplay.css +++ b/media/editor/dataDisplay.css @@ -129,3 +129,317 @@ .data-cell-char { width: calc(var(--cell-size) * 0.7) !important; } + +/* + * Rainbow palettes (dark and light) + * Inspired by "your hex editor should color-code bytes" blogpost by Alice Pellerin + * (https://simonomi.dev/blog/color-code-your-bytes/) + */ +.colorSchemeRainbowDark00 { color: gray; } +.colorSchemeRainbowDark0 { color: oklch(75% .18 360); } /* */ +.colorSchemeRainbowDark1 { color: oklch(75% .18 23); } /* */ +.colorSchemeRainbowDark2 { color: oklch(75% .18 50); } /* */ +.colorSchemeRainbowDark3 { color: oklch(75% .18 65); } +.colorSchemeRainbowDark4 { color: oklch(75% .18 77); } +.colorSchemeRainbowDark5 { color: oklch(75% .18 103); } +.colorSchemeRainbowDark6 { color: oklch(75% .18 130); } +.colorSchemeRainbowDark7 { color: oklch(75% .18 142); } /* */ +.colorSchemeRainbowDark8 { color: oklch(75% .18 150); } /* */ +.colorSchemeRainbowDark9 { color: oklch(75% .18 163); } +.colorSchemeRainbowDarkA { color: oklch(75% .18 184); } +.colorSchemeRainbowDarkB { color: oklch(75% .18 209); } +.colorSchemeRainbowDarkC { color: oklch(75% .18 232); } +.colorSchemeRainbowDarkD { color: oklch(75% .18 254); } +.colorSchemeRainbowDarkE { color: oklch(75% .18 294); } +.colorSchemeRainbowDarkF { color: oklch(75% .18 328); } /* */ +.colorSchemeRainbowDarkFf { color: white; } + +.colorSchemeRainbowLight00 { color: gray; } +.colorSchemeRainbowLight0 { color: oklch(67% .18 360); } /* */ +.colorSchemeRainbowLight1 { color: oklch(67% .18 23); } /* */ +.colorSchemeRainbowLight2 { color: oklch(67% .18 50); } /* */ +.colorSchemeRainbowLight3 { color: oklch(67% .18 65); } +.colorSchemeRainbowLight4 { color: oklch(67% .18 77); } +.colorSchemeRainbowLight5 { color: oklch(67% .18 103); } +.colorSchemeRainbowLight6 { color: oklch(67% .18 130); } +.colorSchemeRainbowLight7 { color: oklch(67% .18 142); } /* */ +.colorSchemeRainbowLight8 { color: oklch(67% .18 150); } /* */ +.colorSchemeRainbowLight9 { color: oklch(67% .18 163); } +.colorSchemeRainbowLightA { color: oklch(67% .18 184); } +.colorSchemeRainbowLightB { color: oklch(67% .18 209); } +.colorSchemeRainbowLightC { color: oklch(67% .18 232); } +.colorSchemeRainbowLightD { color: oklch(67% .18 254); } +.colorSchemeRainbowLightE { color: oklch(67% .18 294); } +.colorSchemeRainbowLightF { color: oklch(67% .18 328); } /* */ +.colorSchemeRainbowLightFf { color: black; } + +.colorSchemeCategoriesDark00 { color: gray; } +.colorSchemeCategoriesDarkAsciiNonGraphic { color: #50fa7b; } +.colorSchemeCategoriesDarkAscii { color: #8be9fd; } +.colorSchemeCategoriesDarkNonAscii { color: #f1fa8c; } + +.colorSchemeCategoriesLight00 { color: gray; } +.colorSchemeCategoriesLightAsciiNonGraphic { color: #54ad45; } +.colorSchemeCategoriesLightAscii { color: #0773b2; } +.colorSchemeCategoriesLightNonAscii { color: #bbb31e; } + +/* + * Color gradients inspired by aticu's contribution to Hexyl + * (see https://github.com/sharkdp/hexyl/pull/247) + */ +.colorSchemeGradient00 { color: #646464; } /* NULL */ +.colorSchemeGradient01 { color: #ff00ff; } /* */ +.colorSchemeGradient02 { color: #fb00ff; } +.colorSchemeGradient03 { color: #f700ff; } +.colorSchemeGradient04 { color: #f200ff; } +.colorSchemeGradient05 { color: #ee00ff; } +.colorSchemeGradient06 { color: #ea00ff; } +.colorSchemeGradient07 { color: #e600ff; } +.colorSchemeGradient08 { color: #e100ff; } +.colorSchemeGradient09 { color: #dd00ff; } +.colorSchemeGradient0A { color: #d900ff; } +.colorSchemeGradient0B { color: #d500ff; } +.colorSchemeGradient0C { color: #d000ff; } +.colorSchemeGradient0D { color: #cc00ff; } +.colorSchemeGradient0E { color: #c800ff; } +.colorSchemeGradient0F { color: #c400ff; } +.colorSchemeGradient10 { color: #c000ff; } +.colorSchemeGradient11 { color: #bb00ff; } +.colorSchemeGradient12 { color: #b700ff; } +.colorSchemeGradient13 { color: #b300ff; } +.colorSchemeGradient14 { color: #af00ff; } +.colorSchemeGradient15 { color: #aa00ff; } +.colorSchemeGradient16 { color: #a600ff; } +.colorSchemeGradient17 { color: #a200ff; } +.colorSchemeGradient18 { color: #9e00ff; } +.colorSchemeGradient19 { color: #9900ff; } +.colorSchemeGradient1A { color: #9500ff; } +.colorSchemeGradient1B { color: #9100ff; } +.colorSchemeGradient1C { color: #8d00ff; } +.colorSchemeGradient1D { color: #8800ff; } +.colorSchemeGradient1E { color: #8400ff; } +.colorSchemeGradient1F { color: #8000ff; } /* */ +.colorSchemeGradient20 { color: #0080ff; } /* */ +.colorSchemeGradient21 { color: #0081fe; } +.colorSchemeGradient22 { color: #0083fd; } +.colorSchemeGradient23 { color: #0084fc; } +.colorSchemeGradient24 { color: #0085fb; } +.colorSchemeGradient25 { color: #0087fa; } +.colorSchemeGradient26 { color: #0088f9; } +.colorSchemeGradient27 { color: #0089f8; } +.colorSchemeGradient28 { color: #008af7; } +.colorSchemeGradient29 { color: #008cf6; } +.colorSchemeGradient2A { color: #008df5; } +.colorSchemeGradient2B { color: #008ef4; } +.colorSchemeGradient2C { color: #008ff3; } +.colorSchemeGradient2D { color: #0091f2; } +.colorSchemeGradient2E { color: #0092f1; } +.colorSchemeGradient2F { color: #0093f0; } +.colorSchemeGradient30 { color: #0094ef; } +.colorSchemeGradient31 { color: #0095ee; } +.colorSchemeGradient32 { color: #0097ed; } +.colorSchemeGradient33 { color: #0098ec; } +.colorSchemeGradient34 { color: #0099eb; } +.colorSchemeGradient35 { color: #009aea; } +.colorSchemeGradient36 { color: #009be9; } +.colorSchemeGradient37 { color: #009de8; } +.colorSchemeGradient38 { color: #009ee7; } +.colorSchemeGradient39 { color: #009fe6; } +.colorSchemeGradient3A { color: #00a0e5; } +.colorSchemeGradient3B { color: #00a2e4; } +.colorSchemeGradient3C { color: #00a3e3; } +.colorSchemeGradient3D { color: #00a4e2; } +.colorSchemeGradient3E { color: #00a5e1; } +.colorSchemeGradient3F { color: #00a6e0; } +.colorSchemeGradient40 { color: #00a8df; } +.colorSchemeGradient41 { color: #00a9de; } +.colorSchemeGradient42 { color: #00aadd; } +.colorSchemeGradient43 { color: #00abdc; } +.colorSchemeGradient44 { color: #00acdb; } +.colorSchemeGradient45 { color: #00aeda; } +.colorSchemeGradient46 { color: #00afd9; } +.colorSchemeGradient47 { color: #00b0d8; } +.colorSchemeGradient48 { color: #00b1d7; } +.colorSchemeGradient49 { color: #00b3d6; } +.colorSchemeGradient4A { color: #00b4d5; } +.colorSchemeGradient4B { color: #00b5d4; } +.colorSchemeGradient4C { color: #00b6d3; } +.colorSchemeGradient4D { color: #00b7d2; } +.colorSchemeGradient4E { color: #00b9d1; } +.colorSchemeGradient4F { color: #00bad0; } +.colorSchemeGradient50 { color: #00bbcf; } +.colorSchemeGradient51 { color: #00bcce; } +.colorSchemeGradient52 { color: #00bdcd; } +.colorSchemeGradient53 { color: #00bfcc; } +.colorSchemeGradient54 { color: #00c0cb; } +.colorSchemeGradient55 { color: #00c1ca; } +.colorSchemeGradient56 { color: #00c2c9; } +.colorSchemeGradient57 { color: #00c3c8; } +.colorSchemeGradient58 { color: #00c5c7; } +.colorSchemeGradient59 { color: #00c6c6; } +.colorSchemeGradient5A { color: #00c7c5; } +.colorSchemeGradient5B { color: #00c8c4; } +.colorSchemeGradient5C { color: #00cac3; } +.colorSchemeGradient5D { color: #00cbc2; } +.colorSchemeGradient5E { color: #00ccc1; } +.colorSchemeGradient5F { color: #00cdc0; } +.colorSchemeGradient60 { color: #00cebf; } +.colorSchemeGradient61 { color: #00d0be; } +.colorSchemeGradient62 { color: #00d1bd; } +.colorSchemeGradient63 { color: #00d2bc; } +.colorSchemeGradient64 { color: #00d3bb; } +.colorSchemeGradient65 { color: #00d4ba; } +.colorSchemeGradient66 { color: #00d6b9; } +.colorSchemeGradient67 { color: #00d7b8; } +.colorSchemeGradient68 { color: #00d8b7; } +.colorSchemeGradient69 { color: #00d9b6; } +.colorSchemeGradient6A { color: #00dab5; } +.colorSchemeGradient6B { color: #00dcb4; } +.colorSchemeGradient6C { color: #00ddb3; } +.colorSchemeGradient6D { color: #00deb2; } +.colorSchemeGradient6E { color: #00dfb1; } +.colorSchemeGradient6F { color: #00e0b0; } +.colorSchemeGradient70 { color: #00e2af; } +.colorSchemeGradient71 { color: #00e3ae; } +.colorSchemeGradient72 { color: #00e4ad; } +.colorSchemeGradient73 { color: #00e5ac; } +.colorSchemeGradient74 { color: #00e6ab; } +.colorSchemeGradient75 { color: #00e8aa; } +.colorSchemeGradient76 { color: #00e9a9; } +.colorSchemeGradient77 { color: #00eaa8; } +.colorSchemeGradient78 { color: #00eba7; } +.colorSchemeGradient79 { color: #00eca6; } +.colorSchemeGradient7A { color: #00eea5; } +.colorSchemeGradient7B { color: #00efa4; } +.colorSchemeGradient7C { color: #00f0a3; } +.colorSchemeGradient7D { color: #00f1a2; } +.colorSchemeGradient7E { color: #00ff80; } /* */ +.colorSchemeGradient7F { color: #408000; } /* DEL */ +.colorSchemeGradient80 { color: #ff0000; } /* */ +.colorSchemeGradient81 { color: #ff0300; } +.colorSchemeGradient82 { color: #ff0600; } +.colorSchemeGradient83 { color: #ff0900; } +.colorSchemeGradient84 { color: #ff0c00; } +.colorSchemeGradient85 { color: #ff0f00; } +.colorSchemeGradient86 { color: #ff1200; } +.colorSchemeGradient87 { color: #ff1500; } +.colorSchemeGradient88 { color: #ff1800; } +.colorSchemeGradient89 { color: #ff1b00; } +.colorSchemeGradient8A { color: #ff1e00; } +.colorSchemeGradient8B { color: #ff2100; } +.colorSchemeGradient8C { color: #ff2400; } +.colorSchemeGradient8D { color: #ff2700; } +.colorSchemeGradient8E { color: #ff2a00; } +.colorSchemeGradient8F { color: #ff2d00; } +.colorSchemeGradient90 { color: #ff3000; } +.colorSchemeGradient91 { color: #ff3300; } +.colorSchemeGradient92 { color: #ff3600; } +.colorSchemeGradient93 { color: #ff3900; } +.colorSchemeGradient94 { color: #ff3c00; } +.colorSchemeGradient95 { color: #ff3f00; } +.colorSchemeGradient96 { color: #ff4200; } +.colorSchemeGradient97 { color: #ff4500; } +.colorSchemeGradient98 { color: #ff4800; } +.colorSchemeGradient99 { color: #ff4b00; } +.colorSchemeGradient9A { color: #ff4e00; } +.colorSchemeGradient9B { color: #ff5100; } +.colorSchemeGradient9C { color: #ff5400; } +.colorSchemeGradient9D { color: #ff5700; } +.colorSchemeGradient9E { color: #ff5a00; } +.colorSchemeGradient9F { color: #ff5d00; } +.colorSchemeGradientA0 { color: #ff6000; } +.colorSchemeGradientA1 { color: #ff6300; } +.colorSchemeGradientA2 { color: #ff6600; } +.colorSchemeGradientA3 { color: #ff6900; } +.colorSchemeGradientA4 { color: #ff6c00; } +.colorSchemeGradientA5 { color: #ff6f00; } +.colorSchemeGradientA6 { color: #ff7200; } +.colorSchemeGradientA7 { color: #ff7500; } +.colorSchemeGradientA8 { color: #ff7800; } +.colorSchemeGradientA9 { color: #ff7b00; } +.colorSchemeGradientAa { color: #ff7e00; } +.colorSchemeGradientAb { color: #ff8100; } +.colorSchemeGradientAc { color: #ff8400; } +.colorSchemeGradientAd { color: #ff8700; } +.colorSchemeGradientAe { color: #ff8a00; } +.colorSchemeGradientAf { color: #ff8d00; } +.colorSchemeGradientB0 { color: #ff9000; } +.colorSchemeGradientB1 { color: #ff9300; } +.colorSchemeGradientB2 { color: #ff9600; } +.colorSchemeGradientB3 { color: #ff9900; } +.colorSchemeGradientB4 { color: #ff9c00; } +.colorSchemeGradientB5 { color: #ff9f00; } +.colorSchemeGradientB6 { color: #ffa200; } +.colorSchemeGradientB7 { color: #ffa500; } +.colorSchemeGradientB8 { color: #ffa800; } +.colorSchemeGradientB9 { color: #ffab00; } +.colorSchemeGradientBa { color: #ffae00; } +.colorSchemeGradientBb { color: #ffb100; } +.colorSchemeGradientBc { color: #ffb400; } +.colorSchemeGradientBd { color: #ffb700; } +.colorSchemeGradientBe { color: #ffba00; } +.colorSchemeGradientBf { color: #ffbd00; } +.colorSchemeGradientC0 { color: #ffc000; } +.colorSchemeGradientC1 { color: #ffc300; } +.colorSchemeGradientC2 { color: #ffc600; } +.colorSchemeGradientC3 { color: #ffc900; } +.colorSchemeGradientC4 { color: #ffcc00; } +.colorSchemeGradientC5 { color: #ffcf00; } +.colorSchemeGradientC6 { color: #ffd200; } +.colorSchemeGradientC7 { color: #ffd500; } +.colorSchemeGradientC8 { color: #ffd800; } +.colorSchemeGradientC9 { color: #ffdb00; } +.colorSchemeGradientCa { color: #ffde00; } +.colorSchemeGradientCb { color: #ffe100; } +.colorSchemeGradientCc { color: #ffe400; } +.colorSchemeGradientCd { color: #ffe700; } +.colorSchemeGradientCe { color: #ffea00; } +.colorSchemeGradientCf { color: #ffed00; } +.colorSchemeGradientD0 { color: #fff000; } +.colorSchemeGradientD1 { color: #fff300; } +.colorSchemeGradientD2 { color: #fff600; } +.colorSchemeGradientD3 { color: #fff900; } +.colorSchemeGradientD4 { color: #ffff00; } +.colorSchemeGradientD5 { color: #ffff06; } +.colorSchemeGradientD6 { color: #ffff0c; } +.colorSchemeGradientD7 { color: #ffff12; } +.colorSchemeGradientD8 { color: #ffff18; } +.colorSchemeGradientD9 { color: #ffff1e; } +.colorSchemeGradientDa { color: #ffff24; } +.colorSchemeGradientDb { color: #ffff2a; } +.colorSchemeGradientDc { color: #ffff30; } +.colorSchemeGradientDd { color: #ffff36; } +.colorSchemeGradientDe { color: #ffff3c; } +.colorSchemeGradientDf { color: #ffff42; } +.colorSchemeGradientE0 { color: #ffff48; } +.colorSchemeGradientE1 { color: #ffff4e; } +.colorSchemeGradientE2 { color: #ffff54; } +.colorSchemeGradientE3 { color: #ffff5a; } +.colorSchemeGradientE4 { color: #ffff60; } +.colorSchemeGradientE5 { color: #ffff66; } +.colorSchemeGradientE6 { color: #ffff6c; } +.colorSchemeGradientE7 { color: #ffff72; } +.colorSchemeGradientE8 { color: #ffff78; } +.colorSchemeGradientE9 { color: #ffff7e; } +.colorSchemeGradientEa { color: #ffff84; } +.colorSchemeGradientEb { color: #ffff8a; } +.colorSchemeGradientEc { color: #ffff90; } +.colorSchemeGradientEd { color: #ffff96; } +.colorSchemeGradientEe { color: #ffff9c; } +.colorSchemeGradientEf { color: #ffffa2; } +.colorSchemeGradientF0 { color: #ffffa8; } +.colorSchemeGradientF1 { color: #ffffae; } +.colorSchemeGradientF2 { color: #ffffb4; } +.colorSchemeGradientF3 { color: #ffffba; } +.colorSchemeGradientF4 { color: #ffffc0; } +.colorSchemeGradientF5 { color: #ffffc6; } +.colorSchemeGradientF6 { color: #ffffcc; } +.colorSchemeGradientF7 { color: #ffffd2; } +.colorSchemeGradientF8 { color: #ffffd8; } +.colorSchemeGradientF9 { color: #ffffde; } +.colorSchemeGradientFa { color: #ffffe4; } +.colorSchemeGradientFb { color: #ffffea; } +.colorSchemeGradientFc { color: #fffff0; } +.colorSchemeGradientFd { color: #fffff6; } +.colorSchemeGradientFe { color: #fffffc; } +.colorSchemeGradientFf { color: #ffffff; } /* */ diff --git a/media/editor/dataDisplay.tsx b/media/editor/dataDisplay.tsx index fe81526d..1e51bf76 100644 --- a/media/editor/dataDisplay.tsx +++ b/media/editor/dataDisplay.tsx @@ -6,10 +6,15 @@ import { useRecoilValue, useSetRecoilState } from "recoil"; import { HexDecorator } from "../../shared/decorators"; import { EditRangeOp, HexDocumentEditOp } from "../../shared/hexDocumentModel"; import { + ColorScheme, CopyFormat, DeleteAcceptedMessage, InspectorLocation, + IStyleSettings, MessageType, + NonAsciiStyleCharacter, + NonPrintableAsciiStyleCharacter, + PrintableAsciiStyleCharacter } from "../../shared/protocol"; import { binarySearch } from "../../shared/util/binarySearch"; import { Range } from "../../shared/util/range"; @@ -31,7 +36,6 @@ import { strings } from "./strings"; import { clamp, clsx, - getAsciiCharacter, getScrollDimensions, HexDecoratorStyles, parseHexDigit, @@ -699,6 +703,7 @@ const DataRowContents: React.FC<{ }> = ({ offset, width, showDecodedText, rawBytes, isRowWithInsertDataCell, decorators }) => { let memoValue = ""; const ctx = useDisplayContext(); + const styleSettings = useRecoilValue(select.editorSettings).style; for (const byte of rawBytes) { memoValue += "," + byte; } @@ -745,7 +750,10 @@ const DataRowContents: React.FC<{ bytes.push( - {char === undefined ? "." : char} + {computeDecodedTextCharacter(value, styleSettings)} , ); } @@ -785,3 +792,184 @@ const DataRowContents: React.FC<{ ); }; + +const computeDecodedTextCharacter = (byte: number, styleSettings: IStyleSettings): string => { + + // Non-printable ASCII + if ((byte <= 0x1F) || (byte === 0x7F)) { + switch (styleSettings.nonPrintableAsciiCharacter) { + + case NonPrintableAsciiStyleCharacter.Dot: + return dedicatedGlyphOrElse(byte, '.'); + + // U+00B7 MIDDLE DOT + case NonPrintableAsciiStyleCharacter.Middot: + return dedicatedGlyphOrElse(byte, `\u00B7`); + + // U+2022 BULLET + case NonPrintableAsciiStyleCharacter.Bullet: + return dedicatedGlyphOrElse(byte, '\u2022'); + + // U+00D7 MULTIPLICATION SIGN + case NonPrintableAsciiStyleCharacter.Cross: + return dedicatedGlyphOrElse(byte, '\u00D7'); + + // Control Pictures (U+2400..U+243F) + case NonPrintableAsciiStyleCharacter.Symbol: + return (byte === 0x7F) ? '\u2421' : String.fromCharCode(0x2400 + byte); + + // Braille Patterns (U+2800..U+28FF) + case NonPrintableAsciiStyleCharacter.Braille: + return asBraillePattern(byte); + } + } + + // Printable ASCII + if ((byte >= 0x20) && (byte < 0x7F)) { + switch (styleSettings.printableAsciiCharacter) { + + case PrintableAsciiStyleCharacter.Ascii: + return String.fromCharCode(byte); + + // Braille Patterns (U+2800..U+28FF) + case PrintableAsciiStyleCharacter.Braille: + return asBraillePattern(byte); + } + } + + // Non-ASCII + switch (styleSettings.nonAsciiCharacter) { + + case NonAsciiStyleCharacter.Dot: + return '.'; + + // U+00B7 MIDDLE DOT + case NonAsciiStyleCharacter.Middot: + return `\u00B7`; + + // U+2022 BULLET + case NonAsciiStyleCharacter.Bullet: + return '\u2022'; + + // U+00D7 MULTIPLICATION SIGN + case NonAsciiStyleCharacter.Cross: + return '\u00D7'; + + // Braille Patterns (U+2800..U+28FF) + case NonAsciiStyleCharacter.Braille: + return asBraillePattern(byte); + } +} + +function dedicatedGlyphOrElse(byte: number, defaultCharacter: string): string { + switch (byte) { + + // NUL: U+22C4 Diamond Operator + case 0x00: + return '\u22C4'; + + // TAB "\t": U+21E5 (rightwards arrow to bar) + case 0x09: + return '\u21E5'; + + // LF "\n": U+2190 (leftwards arrow) + // DEL: U+2190 (leftwards arrow) + case 0x0A: + case 0x7F: + return '\u2190'; + + // CR "\r": U+21B5 (downwards arrow corner lefwards) + case 0x0D: + return '\u21B5'; + + // " ": U+00B7 Middle Dot + case 0x20: + return defaultCharacter !== `\u00B7` ? '\u00B7' : ' '; + + default: + return defaultCharacter; + } +} + +/* + * Braille glyph rendering inspired by aticu's contribution to Hexyl + * (see https://github.com/sharkdp/hexyl/pull/247) + */ +function asBraillePattern(byte: number): string { + // Braille bit positions <=> byte bit positions: + // 0 3 <=> 0 1 + // 1 4 <=> 2 3 + // 2 5 <=> 4 5 + // 6 7 <=> 6 7 + const brailleBits: number[] = [0, 3, 1, 4, 2, 5, 6, 7]; + + let brailleCharCode = 0x2800; + for (let bit = 0; bit < 8; bit++) { + brailleCharCode |= (byte >> bit & 1) << brailleBits[bit]; + } + return String.fromCharCode(brailleCharCode); +} + +const computeByteColorClass = (byte: number, styleSettings: IStyleSettings): string | undefined => { + + return computeColorClass(byte, styleSettings.byteColorScheme); +} + +const computeDecodedTextColorClass = (byte: number, styleSettings: IStyleSettings): string | undefined => { + + return computeColorClass(byte, styleSettings.decodedTextColorScheme) + // Falls back to legacy decoded text color style + ?? ((byte < 0x20) || (byte >= 0x7F) ? style.nonGraphicChar : undefined); +} + +const computeColorClass = (byte: number, colorScheme: ColorScheme): string | undefined => { + + // CategoriesDark, CategoriesLight + if (colorScheme === ColorScheme.CategoriesDark + || colorScheme === ColorScheme.CategoriesLight) { + + const prefix = colorScheme === ColorScheme.CategoriesDark + ? "colorSchemeCategoriesDark" + : "colorSchemeCategoriesLight"; + + return (byte === 0x00) ? style[`${prefix}00` as keyof typeof style] + : (byte <= 0x1F) ? style[`${prefix}AsciiNonGraphic` as keyof typeof style] + : (byte <= 0x7E) ? style[`${prefix}Ascii` as keyof typeof style] + : style[`${prefix}NonAscii` as keyof typeof style]; + } + + // RainbowDark, RainbowLight + if (colorScheme === ColorScheme.RainbowDark + || colorScheme === ColorScheme.RainbowLight) { + + const prefix = colorScheme === ColorScheme.RainbowDark + ? "colorSchemeRainbowDark" + : "colorSchemeRainbowLight"; + + switch (byte) { + case 0x00: + return style[`${prefix}00` as keyof typeof style]; + case 0xff: + return style[`${prefix}Ff` as keyof typeof style]; + default: { + const highNibble = (byte >> 4).toString(16).toUpperCase(); + return style[`${prefix}${highNibble}` as keyof typeof style]; + } + } + } + + // Gradient + if (colorScheme === ColorScheme.Gradient) { + + const highNibble = (byte >> 4).toString(16); + const lowNibble = (byte & 0x0f).toString(16); + const suffix = (highNibble >= '0' && highNibble <= '9') + ? highNibble + lowNibble.toUpperCase() + : highNibble.toUpperCase() + lowNibble.toLowerCase(); + return style[`colorSchemeGradient${suffix}` as keyof typeof style]; + } + + // (Default) + return undefined; +}; + diff --git a/media/editor/util.ts b/media/editor/util.ts index 79d2cea2..1efa6e7b 100644 --- a/media/editor/util.ts +++ b/media/editor/util.ts @@ -4,7 +4,6 @@ // Assorted helper functions import { HexDecoratorType } from "../../shared/decorators"; -import { Range } from "../../shared/util/range"; import _style from "./util.css"; /** @@ -44,46 +43,6 @@ export const clsx = (...classes: (string | false | undefined | null)[]): string return out; }; -/** - * @description Checks if the given number is in any of the ranges - * @param {number} num The number to use when checking the ranges - * @param {Range[]} ranges The ranges to check the number against - * @returns {boolean} True if the number is in any of the ranges, false otherwise - */ -export function withinAnyRange(num: number, ranges: Range[]): boolean { - for (const range of ranges) { - if (range.includes(num)) { - return true; - } - } - return false; -} - -/** - * @description Creates a list of ranges containing the non renderable 8 bit char codes - * @returns {Range[]} The ranges which represent the non renderable 8 bit char codes - */ -export function generateCharacterRanges(): Range[] { - const ranges: Range[] = []; - ranges.push(new Range(0, 32)); - ranges.push(new Range(127)); - return ranges; -} - -const nonPrintableAsciiRange = generateCharacterRanges(); - -/** - * Gets the ascii character for the byte, if it's printable. - * @returns - */ -export const getAsciiCharacter = (byte: number): string | undefined => { - if (withinAnyRange(byte, nonPrintableAsciiRange)) { - return undefined; - } else { - return String.fromCharCode(byte); - } -}; - /** * Returns `x` clamped between the provided lower and upper bounds. */ diff --git a/package.json b/package.json index 1acf4546..9a5a1c86 100644 --- a/package.json +++ b/package.json @@ -97,6 +97,98 @@ "type": "boolean", "default": false, "description": "%hexeditor.showOpenFileButton%" + }, + "hexeditor.style.byteColorScheme": { + "type": "string", + "enum": [ + "default", + "rainbow", + "rainbowLight", + "categories", + "categoriesLight", + "gradient" + ], + "default": "gradient", + "description": "%hexeditor.style.byteColorScheme%", + "enumDescriptions": [ + "%hexeditor.style.colorscheme.default%", + "%hexeditor.style.colorscheme.rainbow%", + "%hexeditor.style.colorscheme.rainbowLight%", + "%hexeditor.style.colorscheme.categories%", + "%hexeditor.style.colorscheme.categoriesLight%", + "%hexeditor.style.colorscheme.gradient%" + ] + }, + "hexeditor.style.decodedTextColorScheme": { + "type": "string", + "enum": [ + "default", + "rainbow", + "rainbowLight", + "categories", + "categoriesLight", + "gradient" + ], + "default": "default", + "description": "%hexeditor.style.decodedTextColorScheme%", + "enumDescriptions": [ + "%hexeditor.style.colorscheme.default%", + "%hexeditor.style.colorscheme.rainbow%", + "%hexeditor.style.colorscheme.rainbowLight%", + "%hexeditor.style.colorscheme.categories%", + "%hexeditor.style.colorscheme.categoriesLight%", + "%hexeditor.style.colorscheme.gradient%" + ] + }, + "hexeditor.style.nonPrintableAsciiCharacter": { + "type": "string", + "enum": [ + "dot", + "middot", + "bullet", + "cross", + "symbol", + "braille" + ], + "default": "bullet", + "enumDescriptions": [ + "%hexeditor.style.character.dot%", + "%hexeditor.style.character.middot%", + "%hexeditor.style.character.bullet%", + "%hexeditor.style.character.cross%", + "%hexeditor.style.character.symbol%", + "%hexeditor.style.character.braille%" + ] + }, + "hexeditor.style.printableAsciiCharacter": { + "type": "string", + "enum": [ + "ascii", + "braille" + ], + "default": "ascii", + "enumDescriptions": [ + "%hexeditor.style.character.ascii%", + "%hexeditor.style.character.braille%" + ] + }, + "hexeditor.style.nonAsciiCharacter": { + "type": "string", + "enum": [ + "dot", + "middot", + "bullet", + "cross", + "braille" + ], + "default": "cross", + "enumDescriptions": [ + "%hexeditor.style.character.dot%", + "%hexeditor.style.character.middot%", + "%hexeditor.style.character.bullet%", + "%hexeditor.style.character.cross%", + "%hexeditor.style.character.braille%" + ] } } } diff --git a/package.nls.json b/package.nls.json index 84ef5ed8..81b1937c 100644 --- a/package.nls.json +++ b/package.nls.json @@ -9,6 +9,24 @@ "hexeditor.columnWidth": "The number of bytes per row to show in the editor.", "hexeditor.showDecodedText": "Whether decoded text should be shown in the editor.", "hexeditor.showOpenFileButton": "Show Hex Editor button in editor menu.", + "hexeditor.style.byteColorScheme": "Color style for bytes", + "hexeditor.style.decodedTextColorScheme": "Color style for decoded text", + "hexeditor.style.nonPrintableAsciiCharacter": "Rendering style for non-printable ASCII within the decoded text", + "hexeditor.style.printableAsciiCharacter": "Rendering style for printable ASCII within the decoded text", + "hexeditor.style.nonAsciiCharacter": "Rendering style for non-ASCII within the decoded text", + "hexeditor.style.colorscheme.default": "Default (monochrome)", + "hexeditor.style.colorscheme.rainbow": "Color choosen by high nibble", + "hexeditor.style.colorscheme.rainbowLight": "Color choosen by high nibble (light background)", + "hexeditor.style.colorscheme.categories": "Color choosen by category (non-printable ASCII, printable ASCII, non-ASCII)", + "hexeditor.style.colorscheme.categoriesLight": "Color choosen by category (non-printable ASCII, printable ASCII, non-ASCII) (light background)", + "hexeditor.style.colorscheme.gradient": "Color gradients", + "hexeditor.style.character.dot": "Renders a regular dot", + "hexeditor.style.character.middot": "Renders a middle dot (lighter than a regular dot)", + "hexeditor.style.character.bullet": "Renders a bullet", + "hexeditor.style.character.cross": "Renders a cross (a multiplication sign)", + "hexeditor.style.character.symbol": "Renders a glyph that represents the control code", + "hexeditor.style.character.ascii": "Renders the actual ASCII character", + "hexeditor.style.character.braille": "Renders a braille glyph based on the byte value", "hexEditor.openFile": "Open Active File in Hex Editor", "hexEditor.goToOffset": "Go To Offset", "hexEditor.selectBetweenOffsets": "Select Between Offsets", diff --git a/shared/protocol.ts b/shared/protocol.ts index 86c46100..84cbf9a3 100644 --- a/shared/protocol.ts +++ b/shared/protocol.ts @@ -58,12 +58,52 @@ export const enum InspectorLocation { Sidebar = "sidebar", } +export const enum ColorScheme { + Default = "default", + RainbowDark = "rainbow", + RainbowLight = "rainbowLight", + CategoriesDark = "categories", + CategoriesLight = "categoriesLight", + Gradient = "gradient" +} + +export const enum NonPrintableAsciiStyleCharacter { + Dot = "dot", + Middot = "middot", + Bullet = "bullet", + Cross = "cross", + Symbol = "symbol", + Braille = "braille" +} + +export const enum PrintableAsciiStyleCharacter { + Ascii = "ascii", + Braille = "braille" +} + +export const enum NonAsciiStyleCharacter { + Dot = "dot", + Middot = "middot", + Bullet = "bullet", + Cross = "cross", + Braille = "braille" +} + +export interface IStyleSettings { + byteColorScheme: ColorScheme; + decodedTextColorScheme: ColorScheme; + nonPrintableAsciiCharacter: NonPrintableAsciiStyleCharacter; + printableAsciiCharacter: PrintableAsciiStyleCharacter; + nonAsciiCharacter: NonAsciiStyleCharacter; +} + export interface IEditorSettings { copyType: CopyFormat; showDecodedText: boolean; columnWidth: number; inspectorType: InspectorLocation; defaultEndianness: Endianness; + style: IStyleSettings; } export interface ICodeSettings { diff --git a/src/hexEditorProvider.ts b/src/hexEditorProvider.ts index 140d7a54..aba4aecc 100644 --- a/src/hexEditorProvider.ts +++ b/src/hexEditorProvider.ts @@ -9,6 +9,7 @@ import { HexDocumentEditReference, } from "../shared/hexDocumentModel"; import { + ColorScheme, CopyFormat, Endianness, ExtensionHostMessageHandler, @@ -16,10 +17,14 @@ import { ICodeSettings, IEditorSettings, InspectorLocation, + IStyleSettings, MessageHandler, MessageType, + NonAsciiStyleCharacter, + NonPrintableAsciiStyleCharacter, PasteMode, - ToWebviewMessage, + PrintableAsciiStyleCharacter, + ToWebviewMessage } from "../shared/protocol"; import { deserializeEdits, serializeEdits } from "../shared/serialization"; import { ILocalizedStrings, placeholder1 } from "../shared/strings"; @@ -31,11 +36,20 @@ import { HexEditorRegistry } from "./hexEditorRegistry"; import { ISearchRequest, LiteralSearchRequest, RegexSearchRequest } from "./searchRequest"; import { flattenBuffers, getBaseName, getCorrectArrayBuffer, randomString } from "./util"; +const defaultStyleSettings: Readonly = { + byteColorScheme: ColorScheme.Default, + decodedTextColorScheme: ColorScheme.Default, + nonPrintableAsciiCharacter: NonPrintableAsciiStyleCharacter.Dot, + printableAsciiCharacter: PrintableAsciiStyleCharacter.Ascii, + nonAsciiCharacter: NonAsciiStyleCharacter.Dot +}; + const defaultEditorSettings: Readonly = { columnWidth: 16, copyType: CopyFormat.HexOctets, showDecodedText: true, defaultEndianness: Endianness.Little, + style: defaultStyleSettings, inspectorType: InspectorLocation.Aside, };