You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@daffodil.apache.org by rs...@apache.org on 2023/12/21 21:07:22 UTC

(daffodil-vscode) branch main updated: Implemented Indexable ByteValue Indications

This is an automated email from the ASF dual-hosted git repository.

rstrickland pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/daffodil-vscode.git


The following commit(s) were added to refs/heads/main by this push:
     new d7f7088  Implemented Indexable ByteValue Indications
d7f7088 is described below

commit d7f70889a07f5769e425fb03aa260a54ad8c0a8a
Author: Davin Shearer <sc...@gmail.com>
AuthorDate: Thu Oct 19 12:14:23 2023 -0400

    Implemented Indexable ByteValue Indications
    
    - Added indexable indication array for ByteValues within the viewport displays.
    - Created categorical byte indiciations values & CSS selectors.
    
    Closes #784
---
 src/dataEditor/dataEditorClient.ts                 |   9 ++
 .../CustomByteDisplay/DataLineFeed.svelte          | 155 ++++++++++--------
 .../CustomByteDisplay/DataValue.svelte             |  82 ++--------
 .../CustomByteDisplay/SelectedByteEdit.svelte      |   6 +-
 .../DataDisplays/Header/DisplayHeader.svelte       |  19 +--
 .../components/Header/fieldsets/FileMetrics.svelte |   2 +-
 .../Header/fieldsets/SearchReplace.svelte          |  38 +++--
 .../components/Header/fieldsets/SearchReplace.ts   | 112 ++++++++++---
 src/svelte/src/components/dataEditor.svelte        |   7 +-
 src/svelte/src/components/globalStyles.css         |   2 +
 src/svelte/src/components/layouts/Tooltip.svelte   |   2 +-
 src/svelte/src/stores/index.ts                     |  41 ++++-
 .../ByteCategories/CategoryIndications.ts          | 114 ++++++++++++++
 .../src/utilities/ByteCategories/IByteCategory.ts  |  84 ++++++++++
 .../src/utilities/ByteCategories/IIndication.ts    |  42 +++++
 src/svelte/src/utilities/display.ts                |   1 -
 src/svelte/src/utilities/highlights.ts             | 174 +++++++++++++++------
 17 files changed, 638 insertions(+), 252 deletions(-)

diff --git a/src/dataEditor/dataEditorClient.ts b/src/dataEditor/dataEditorClient.ts
index 75bdede..03f9eb8 100644
--- a/src/dataEditor/dataEditorClient.ts
+++ b/src/dataEditor/dataEditorClient.ts
@@ -459,11 +459,17 @@ export class DataEditorClient implements vscode.Disposable {
       case MessageCommand.undoChange:
         await undo(this.omegaSessionId)
         await this.sendChangesInfo()
+        this.panel.webview.postMessage({
+          command: MessageCommand.clearChanges,
+        })
         break
 
       case MessageCommand.redoChange:
         await redo(this.omegaSessionId)
         await this.sendChangesInfo()
+        this.panel.webview.postMessage({
+          command: MessageCommand.clearChanges,
+        })
         break
 
       case MessageCommand.profile:
@@ -525,6 +531,9 @@ export class DataEditorClient implements vscode.Disposable {
         ) {
           await clear(this.omegaSessionId)
           await this.sendChangesInfo()
+          this.panel.webview.postMessage({
+            command: MessageCommand.clearChanges,
+          })
         }
         break
 
diff --git a/src/svelte/src/components/DataDisplays/CustomByteDisplay/DataLineFeed.svelte b/src/svelte/src/components/DataDisplays/CustomByteDisplay/DataLineFeed.svelte
index 5dd49f1..065aed3 100644
--- a/src/svelte/src/components/DataDisplays/CustomByteDisplay/DataLineFeed.svelte
+++ b/src/svelte/src/components/DataDisplays/CustomByteDisplay/DataLineFeed.svelte
@@ -31,6 +31,8 @@ limitations under the License.
     seekOffsetInput,
     visableViewports,
     dataDislayLineAmount,
+    replaceQuery,
+    searchResultsUpdated,
   } from '../../../stores'
   import {
     EditByteModes,
@@ -62,10 +64,8 @@ limitations under the License.
     type CSSThemeClass,
   } from '../../../utilities/colorScheme'
   import {
-    selectionHighlights,
-    searchResultsHighlights,
-    updateSearchResultsHighlights,
-    searchResultsUpdated,
+    viewportByteIndicators,
+    categoryCSSSelectors,
   } from '../../../utilities/highlights'
   import { bytesPerRow } from '../../../stores'
   export let awaitViewportSeek: boolean
@@ -169,7 +169,7 @@ limitations under the License.
     bytes: Array<ByteValue>
     highlight: 'even' | 'odd'
   }
-
+  
   enum ViewportScrollDirection {
     DECREMENT = -1,
     NONE = 0,
@@ -181,10 +181,13 @@ limitations under the License.
   let viewportDataContainer: HTMLDivElement
   let selectedByteElement: HTMLDivElement
   let themeClass: CSSThemeClass
-  let activeSelection: Uint8Array
   let lineTopFileOffset: number
-  let searchResults: Uint8Array
+  let makingSelection = false
 
+  $: {
+    makingSelection =
+      $selectionDataStore.startOffset >= 0 && $selectionDataStore.active === false
+  }
   onMount(() => {
     viewportDataContainer = document.getElementById(
       CONTAINER_ID
@@ -215,13 +218,10 @@ limitations under the License.
   }
 
   $: {
-    activeSelection = $selectionHighlights
-    searchResults = $searchResultsHighlights
     if (
-      (viewportData.fileOffset >= 0 &&
-        !awaitViewportSeek &&
-        $dataFeedLineTop >= 0) ||
-      $searchResultsUpdated
+      viewportData.fileOffset >= 0 &&
+      !awaitViewportSeek &&
+      $dataFeedLineTop >= 0
     ) {
       if (
         viewportLines.length !== 0 &&
@@ -243,6 +243,10 @@ limitations under the License.
     }
   }
   $: byteElementWidth = byteDivWidthFromRadix(dataRadix)
+  $: viewportByteIndicators.updateSelectionIndications($selectionDataStore)
+  $: viewportByteIndicators.updateSearchIndications($searchQuery, viewportData.fileOffset)
+  $: viewportByteIndicators.updateReplaceIndications($replaceQuery, viewportData.fileOffset)
+  
 
   function generate_line_data(
     startIndex: number,
@@ -359,21 +363,21 @@ limitations under the License.
       : atViewportHead && !atFileHead
   }
 
-  function mousedown(event: CustomEvent<ByteSelectionEvent>) {
+  function mousedown(event: ByteSelectionEvent) {
     selectionDataStore.update((selections) => {
       selections.active = false
-      selections.startOffset = event.detail.targetByte.offset
+      selections.startOffset = event.targetByte.offset
       selections.endOffset = -1
       selections.originalEndOffset = -1
       return selections
     })
   }
 
-  function mouseup(event: CustomEvent<ByteSelectionEvent>) {
+  function mouseup(event: ByteSelectionEvent) {
     selectionDataStore.update((selections) => {
       selections.active = true
-      selections.endOffset = event.detail.targetByte.offset
-      selections.originalEndOffset = event.detail.targetByte.offset
+      selections.endOffset = event.targetByte.offset
+      selections.originalEndOffset = event.targetByte.offset
       adjust_event_offsets()
       return selections
     })
@@ -383,7 +387,7 @@ limitations under the License.
       return
     }
 
-    set_byte_selection(event.detail)
+    setByteSelection(event)
   }
 
   function adjust_event_offsets() {
@@ -391,13 +395,15 @@ limitations under the License.
     const end = $selectionDataStore.endOffset
 
     if (start > end) {
-      $selectionDataStore.startOffset = end
-      $selectionDataStore.originalEndOffset = start
-      $selectionDataStore.endOffset = start
+      selectionDataStore.update( selections => {
+        selections.startOffset = end
+        selections.endOffset = start
+        return selections
+      })
     }
   }
 
-  function set_byte_selection(selectionEvent: ByteSelectionEvent) {
+  function setByteSelection(selectionEvent: ByteSelectionEvent) {
     $focusedViewportId = selectionEvent.fromViewport
 
     $selectedByte =
@@ -471,6 +477,47 @@ limitations under the License.
     }
   }
 
+  function mouseover_handler(e: Event) {
+    if(!makingSelection) return 
+
+    const target = e.target as HTMLDivElement
+    let targetViewportIndex = parseInt(target.getAttribute('offset')!)
+    
+    selectionDataStore.update((selections) => {
+      selections.endOffset = targetViewportIndex
+      adjust_event_offsets()
+      return selections
+    })
+  }
+
+  function mouseclick_handler(e: Event) {
+    const type = e.type
+    const targetElement = e.target as HTMLDivElement
+    let targetViewportIndex = parseInt(targetElement.getAttribute('offset')!)
+    let byteText: string | undefined = targetElement.innerHTML
+    let byteValue: number = byteText === undefined ? -1 : parseInt(byteText)
+
+    let targetByte: ByteValue = {
+      offset: targetViewportIndex,
+      text: byteText,
+      value: byteValue
+    }
+    const byteSelectionEvent: ByteSelectionEvent = 
+      {
+        targetElement: targetElement,
+        targetByte: targetByte,
+        fromViewport: targetElement.id.includes('logical') ? 'logical' : 'physical',
+      } 
+
+    switch(type) {
+      case 'mousedown':
+        mousedown(byteSelectionEvent)
+        break
+      case 'mouseup':
+        mouseup(byteSelectionEvent)
+    }
+  }
+
   window.addEventListener('keydown', navigation_keydown_event)
   window.addEventListener('message', (msg) => {
     switch (msg.data.command) {
@@ -485,30 +532,31 @@ limitations under the License.
             selectedByteElement = document.getElementById(
               $selectedByte.offset.toString()
             ) as HTMLDivElement
-
-          updateSearchResultsHighlights(
-            $searchQuery.searchResults,
-            viewportData.fileOffset,
-            $searchQuery.byteLength
-          )
         }
         break
     }
   })
+
 </script>
 
-{#if $selectionDataStore.active && $editMode === EditByteModes.Single}
-  {#key $selectedByte || selectedByteElement || dataRadix || $editorActionsAllowed === EditActionRestrictions.None}
-    <SelectedByteEdit
-      byte={$selectedByte}
-      on:seek
-      on:applyChanges
-      on:handleEditorEvent
-    />
-  {/key}
-{/if}
 
-<div class="container" style:height id={CONTAINER_ID}>
+<svelte:window on:mousemove={mouseover_handler}/>
+<!-- svelte-ignore a11y-mouse-events-have-key-events -->
+<!-- svelte-ignore a11y-click-events-have-key-events -->
+<div class="container" style:height id={CONTAINER_ID} 
+  on:mousedown={mouseclick_handler}
+  on:mouseup={mouseclick_handler}
+>
+  {#if $selectionDataStore.active && $editMode == EditByteModes.Single}
+    {#key $selectedByte || selectedByteElement || dataRadix || $editorActionsAllowed == EditActionRestrictions.None}
+      <SelectedByteEdit
+        byte={$selectedByte}
+        on:seek
+        on:applyChanges
+        on:handleEditorEvent
+      />
+    {/key}
+  {/if}
   {#each viewportLines as viewportLine, i}
     <div class={`line ${viewportLine.highlight} ${themeClass}`}>
       <div class="address" id="address">
@@ -523,17 +571,10 @@ limitations under the License.
         {#each viewportLine.bytes as byte}
           <DataValue
             {byte}
-            isSelected={activeSelection[byte.offset] === 1}
-            possibleSelection={activeSelection[byte.offset] === 2}
-            isSearchResult={searchResults[byte.offset] >>
-              activeSelection[byte.offset]}
             id={'physical'}
-            radix={dataRadix}
+            categoryIndicationSelectors={categoryCSSSelectors($viewportByteIndicators[byte.offset])}
             width={byteElementWidth}
             disabled={byte.value === -1}
-            bind:selectionData={$selectionDataStore}
-            on:mouseup={mouseup}
-            on:mousedown={mousedown}
           />
         {/each}
       </div>
@@ -546,17 +587,10 @@ limitations under the License.
         {#each viewportLine.bytes as byte}
           <DataValue
             {byte}
-            isSelected={activeSelection[byte.offset] === 1}
-            possibleSelection={activeSelection[byte.offset] === 2}
-            isSearchResult={searchResults[byte.offset] >>
-              activeSelection[byte.offset]}
+            categoryIndicationSelectors={categoryCSSSelectors($viewportByteIndicators[byte.offset])}
             id={'logical'}
-            radix={dataRadix}
             width={byteElementWidth}
             disabled={byte.value === -1}
-            bind:selectionData={$selectionDataStore}
-            on:mouseup={mouseup}
-            on:mousedown={mousedown}
           />
         {/each}
       </div>
@@ -697,15 +731,6 @@ limitations under the License.
     flex-direction: column;
     margin: 0 5px;
   }
-  span.submit-bpr-input {
-    font-size: 14px;
-    cursor: pointer;
-    margin: 0 5px;
-  }
-  span.submit-bpr-input:hover {
-    font-weight: bold;
-    cursor: pointer;
-  }
   div.container {
     display: flex;
     flex-direction: column;
diff --git a/src/svelte/src/components/DataDisplays/CustomByteDisplay/DataValue.svelte b/src/svelte/src/components/DataDisplays/CustomByteDisplay/DataValue.svelte
index ab12df4..e839ff1 100644
--- a/src/svelte/src/components/DataDisplays/CustomByteDisplay/DataValue.svelte
+++ b/src/svelte/src/components/DataDisplays/CustomByteDisplay/DataValue.svelte
@@ -15,61 +15,18 @@ See the License for the specific language governing permissions and
 limitations under the License.
 -->
 <script lang="ts">
-  import { createEventDispatcher } from 'svelte'
   import {
     latin1Undefined,
-    type ByteSelectionEvent,
     type ByteValue,
     type ViewportDataType,
   } from './BinaryData'
-  import type { SelectionData_t } from '../../../stores'
   import type { ByteDivWidth } from '../../../utilities/display'
-  import type { RadixValues } from '../../../stores/configuration'
-  import { selectionHighlightMask } from '../../../utilities/highlights'
 
   export let id: ViewportDataType
   export let byte: ByteValue
-  export let selectionData: SelectionData_t
-  export let radix: RadixValues
   export let disabled = false
   export let width: ByteDivWidth = '20px'
-  export let isSelected = false
-  export let possibleSelection = false
-  export let isSearchResult = 0
-
-  const eventDispatcher = createEventDispatcher()
-
-  let makingSelection = false
-
-  $: {
-    makingSelection =
-      selectionData.startOffset >= 0 && selectionData.active === false
-    $selectionHighlightMask = makingSelection === true ? 1 : 0
-  }
-
-  function mouse_enter_handle(event: MouseEvent) {
-    if (!makingSelection) return
-    if (disabled) {
-      selectionData.endOffset = -1
-      makingSelection = false
-      return
-    }
-    selectionData.endOffset = byte.offset
-  }
-  function mouse_leave_handle(event: MouseEvent) {
-    if (!makingSelection) return
-    selectionData.endOffset = -1
-  }
-  function mouse_event_handle(event: MouseEvent) {
-    const type = event.type
-    const targetElement = event.target
-    if (id === 'logical') byte.text = String.fromCharCode(byte.value)
-    eventDispatcher(type, {
-      targetElement: targetElement,
-      targetByte: byte,
-      fromViewport: id,
-    } as ByteSelectionEvent)
-  }
+  export let categoryIndicationSelectors: string
 </script>
 
 <!-- svelte-ignore a11y-click-events-have-key-events -->
@@ -78,33 +35,21 @@ limitations under the License.
 {:else if id === 'physical'}
   <!-- svelte-ignore a11y-no-static-element-interactions -->
   <div
-    class="byte"
-    class:isSelected
-    class:isSearchResult
-    class:possibleSelection
+    class={'byte ' + categoryIndicationSelectors}
     id={id + '-' + byte.offset.toString()}
     style:width
-    on:mouseup={mouse_event_handle}
-    on:mousedown={mouse_event_handle}
-    on:mouseenter={mouse_enter_handle}
-    on:mouseleave={mouse_leave_handle}
+    offset={byte.offset}
   >
     {byte.text}
   </div>
 {:else}
   <!-- svelte-ignore a11y-no-static-element-interactions -->
   <div
-    class="byte"
-    class:isSelected
-    class:isSearchResult
-    class:possibleSelection
+    class={'byte ' + categoryIndicationSelectors}
     id={id + '-' + byte.offset.toString()}
     style:width={'20px'}
     class:latin1Undefined={latin1Undefined(byte.value)}
-    on:mouseup={mouse_event_handle}
-    on:mousedown={mouse_event_handle}
-    on:mouseenter={mouse_enter_handle}
-    on:mouseleave={mouse_leave_handle}
+    offset={byte.offset}
   >
     {latin1Undefined(byte.value) ? '' : String.fromCharCode(byte.value)}
   </div>
@@ -118,29 +63,24 @@ limitations under the License.
     align-items: center;
     flex-direction: row;
     font-family: var(--monospace-font);
-    /* border-radius: 5px; */
     border-style: solid;
     border-width: 2px;
     border-color: transparent;
     height: 20px;
     text-align: center;
     transition: all 0.25s;
-  }
-  div.byte.isSelected,
-  div.byte.isSearchResult,
-  div.byte.possibleSelection {
     border-radius: 5px;
+    user-select: none;
   }
-  div.byte.isSelected {
+  div.byte.selected {
     background-color: var(--color-secondary-light);
     color: var(--color-secondary-darkest);
   }
-  div.byte.isSearchResult {
-    background-color: var(--color-tertiary-light);
-    color: var(--color-secondary-darkest);
+  div.byte.searchresult {
+    border-color: var(--color-search-result);
   }
-  div.byte.possibleSelection {
-    border-color: var(--color-secondary-light);
+  div.byte.replacement {
+    border-color: var(--color-replace-result);
   }
   div.byte:hover {
     border-color: var(--color-secondary-mid);
diff --git a/src/svelte/src/components/DataDisplays/CustomByteDisplay/SelectedByteEdit.svelte b/src/svelte/src/components/DataDisplays/CustomByteDisplay/SelectedByteEdit.svelte
index 251b0f7..6915110 100644
--- a/src/svelte/src/components/DataDisplays/CustomByteDisplay/SelectedByteEdit.svelte
+++ b/src/svelte/src/components/DataDisplays/CustomByteDisplay/SelectedByteEdit.svelte
@@ -343,7 +343,7 @@ limitations under the License.
       class="delete {themeClass}"
       id={actionElements['delete'].id}
       style:width={elementDivWidth}
-      on:click={send_delete}
+      on:mousedown|stopPropagation={send_delete}
     >
       <Tooltip alwaysEnabled={true} description={'Delete byte'}>
         &#10006;
@@ -358,7 +358,7 @@ limitations under the License.
       class="insert-before {themeClass}"
       id={actionElements['insert-before'].id}
       style:width={elementDivWidth}
-      on:click={send_insert}
+      on:mousedown|stopPropagation={send_insert}
     >
       <Tooltip alwaysEnabled={true} description={'Insert as preceding byte'}>
         &#8676;
@@ -372,7 +372,7 @@ limitations under the License.
       class="insert-after {themeClass}"
       id={actionElements['insert-after'].id}
       style:width={elementDivWidth}
-      on:click={send_insert}
+      on:mousedown|stopPropagation={send_insert}
     >
       <Tooltip alwaysEnabled={true} description={'Insert as following byte'}>
         &#8677;
diff --git a/src/svelte/src/components/DataDisplays/Header/DisplayHeader.svelte b/src/svelte/src/components/DataDisplays/Header/DisplayHeader.svelte
index 4628e78..1bf2900 100644
--- a/src/svelte/src/components/DataDisplays/Header/DisplayHeader.svelte
+++ b/src/svelte/src/components/DataDisplays/Header/DisplayHeader.svelte
@@ -20,11 +20,8 @@ limitations under the License.
     displayRadix,
     seekOffset,
     seekOffsetInput,
-    selectionDataStore,
     seekOffsetSearchType,
-    selectionSize,
     bytesPerRow,
-    viewport,
     visableViewports
   } from '../../../stores'
   import {
@@ -34,11 +31,9 @@ limitations under the License.
     RADIX_OPTIONS,
   } from '../../../stores/configuration'
   import { UIThemeCSSClass } from '../../../utilities/colorScheme'
-  import { createEventDispatcher } from 'svelte'
   import { OffsetSearchType } from '../../Header/fieldsets/SearchReplace'
   import { byteDivWidthFromRadix } from '../../../utilities/display'
   let bitIndexStr = '01234567'
-  let selectionOffsetText: string
   let offsetLine: string[] = []
 
   $: {
@@ -49,14 +44,6 @@ limitations under the License.
     )
   }
 
-  $: selectionOffsetText = setSelectionOffsetInfo(
-    'Selection',
-    $viewport.fileOffset + $selectionDataStore.startOffset,
-    $viewport.fileOffset + $selectionDataStore.endOffset,
-    $selectionSize,
-    $addressRadix
-  )
-
   function generate_offset_headers(
     addressRadix: RadixValues,
     displayRadix: RadixValues,
@@ -127,14 +114,14 @@ limitations under the License.
       {#if $displayRadix === RADIX_OPTIONS.Binary}
         {#each offsetLine as offset}
           <div class="physical-addr-seg binary" style:min-width={byteDivWidthFromRadix($displayRadix)}>
-            <div>{offset}</div>
-            <div>{bitIndexStr}</div>
+            <div><b>{offset}</b></div>
+            <div><b>{bitIndexStr}</b></div>
           </div>
         {/each}
       {:else}
         {#each offsetLine as offset}
           <div class="physical-addr-seg" style:min-width={byteDivWidthFromRadix($displayRadix)}>
-            {offset}
+            <b>{offset}</b>
           </div>
         {/each}
       {/if}
diff --git a/src/svelte/src/components/Header/fieldsets/FileMetrics.svelte b/src/svelte/src/components/Header/fieldsets/FileMetrics.svelte
index 83d6365..73e714f 100644
--- a/src/svelte/src/components/Header/fieldsets/FileMetrics.svelte
+++ b/src/svelte/src/components/Header/fieldsets/FileMetrics.svelte
@@ -19,7 +19,7 @@ limitations under the License.
   import FlexContainer from '../../layouts/FlexContainer.svelte'
   import { MessageCommand } from '../../../utilities/message'
   import { vscode } from '../../../utilities/vscode'
-  import { saveable, fileMetrics } from '../../../stores'
+  import { saveable, fileMetrics, replaceQuery } from '../../../stores'
   import { createEventDispatcher } from 'svelte'
   import SidePanel from '../../layouts/SidePanel.svelte'
   import ByteFrequencyGraph from '../../DataMetrics/DataMetrics.svelte'
diff --git a/src/svelte/src/components/Header/fieldsets/SearchReplace.svelte b/src/svelte/src/components/Header/fieldsets/SearchReplace.svelte
index 12c8c90..16f24c7 100644
--- a/src/svelte/src/components/Header/fieldsets/SearchReplace.svelte
+++ b/src/svelte/src/components/Header/fieldsets/SearchReplace.svelte
@@ -42,13 +42,8 @@ limitations under the License.
   import { createEventDispatcher } from 'svelte'
   import { UIThemeCSSClass } from '../../../utilities/colorScheme'
   import ToggleableButton from '../../Inputs/Buttons/ToggleableButton.svelte'
-  import {
-    clearSearchResultsHighlights,
-    updateSearchResultsHighlights,
-  } from '../../../utilities/highlights'
-  import { viewport } from '../../../stores'
   import { EditActionRestrictions } from '../../../stores/configuration'
-  import { OffsetSearchType } from './SearchReplace'
+  import { OffsetSearchType, clear_queryable_results } from './SearchReplace'
   import Tooltip from '../../layouts/Tooltip.svelte'
 
   const eventDispatcher = createEventDispatcher()
@@ -201,7 +196,8 @@ limitations under the License.
     searchStarted = false
     replaceStarted = false
     matchOffset = -1
-    clearSearchResultsHighlights()
+    clear_queryable_results()
+
     eventDispatcher('clearDataDisplays')
   }
 
@@ -210,25 +206,24 @@ limitations under the License.
       // handle search results
       case MessageCommand.searchResults:
         if (msg.data.data.searchResults.length > 0) {
-          $searchQuery.searchResults = msg.data.data.searchResults
-          $searchQuery.byteLength = msg.data.data.searchDataBytesLength
+          searchQuery.updateSearchResults(msg.data.data)
           switch (direction) {
             case 'Home':
-              hasNext = msg.data.data.overflow
+              hasNext = $searchQuery.overflow
               hasPrev = false
               break
             case 'End':
               hasNext = false
-              hasPrev = msg.data.data.overflow
+              hasPrev = $searchQuery.overflow
               break
             case 'Forward':
-              hasNext = msg.data.data.overflow
+              hasNext = $searchQuery.overflow
               hasPrev = justReplaced ? preReplaceHasPrev : true
               justReplaced = false
               break
             case 'Backward':
               hasNext = true
-              hasPrev = msg.data.data.overflow
+              hasPrev = $searchQuery.overflow
               break
           }
           matchOffset = $searchQuery.searchResults[0]
@@ -240,17 +235,12 @@ limitations under the License.
             showReplaceOptions = true
             showSearchOptions = false
           }
-          $searchQuery.overflow = msg.data.data.overflow
         } else {
           matchOffset = -1
           $searchQuery.overflow = showSearchOptions = showReplaceOptions = false
+          searchQuery.clear()
         }
         searchStarted = replaceStarted = false
-        updateSearchResultsHighlights(
-          $searchQuery.searchResults,
-          $viewport.fileOffset,
-          $searchQuery.byteLength
-        )
         $searchQuery.processing = false
         break
 
@@ -259,8 +249,11 @@ limitations under the License.
         searchStarted = replaceStarted = false
         if (msg.data.data.replacementsCount > 0) {
           // subtract 1 from the next offset because search next will add 1
-          clearSearchResultsHighlights()
           matchOffset = msg.data.data.nextOffset - 1
+          replaceQuery.addResult({
+            byteLength: msg.data.data.replaceDataBytesLength, 
+            offset: msg.data.data.nextOffset - msg.data.data.replaceDataBytesLength
+          })
           preReplaceHasPrev = hasPrev
           justReplaced = true
           searchNext()
@@ -270,6 +263,11 @@ limitations under the License.
         }
         $replaceQuery.processing = false
         break
+  
+      case MessageCommand.clearChanges:
+        cancel()
+        break
+
     }
   })
 </script>
diff --git a/src/svelte/src/components/Header/fieldsets/SearchReplace.ts b/src/svelte/src/components/Header/fieldsets/SearchReplace.ts
index e1f739b..c09193a 100644
--- a/src/svelte/src/components/Header/fieldsets/SearchReplace.ts
+++ b/src/svelte/src/components/Header/fieldsets/SearchReplace.ts
@@ -16,8 +16,9 @@
  */
 
 import { SimpleWritable } from '../../../stores/localStore'
-import { addressRadix, seekOffsetInput } from '../../../stores'
-import { get } from 'svelte/store'
+import { replaceQuery, searchQuery } from '../../../stores'
+import { VIEWPORT_CAPACITY_MAX } from '../../../stores/configuration'
+import { viewportByteIndicators } from '../../../utilities/highlights'
 
 export enum OffsetSearchType {
   ABSOLUTE,
@@ -29,16 +30,38 @@ export type RelativeSeekSign = '+' | '-'
 interface QueryableData {
   input: string
   processing: boolean
-  isValid: boolean
+  initiaited: boolean
+  iterableDataFromOffset(offset: number): IndexCriteria
 }
-class SearchData implements QueryableData {
+
+export type IndexCriteria = {
+  start: number
+  end: number
+  data: any[]
+}
+export class SearchData implements QueryableData {
   input: string = ''
   processing: boolean = false
-  isValid: boolean = false
+  initiaited: boolean = false
   searchIndex: number = 0
   searchResults: Array<number> = []
   overflow: boolean = false
   byteLength: number = 0
+  iterableDataFromOffset(offset: number): IndexCriteria {
+    const start = this.searchResults.findIndex((x) => x >= offset)
+    const end = this.searchResults.findIndex(
+      (x) => x >= offset + VIEWPORT_CAPACITY_MAX
+    )
+    let ret: IndexCriteria = {
+      start: start,
+      end: end,
+      data: this.searchResults.slice(
+        start,
+        end >= 0 ? end : this.searchResults.length
+      ),
+    }
+    return ret
+  }
 }
 export class SearchQuery extends SimpleWritable<SearchData> {
   protected init(): SearchData {
@@ -47,39 +70,84 @@ export class SearchQuery extends SimpleWritable<SearchData> {
   public clear() {
     this.update((query) => {
       query.processing = false
+      query.initiaited = false
       query.searchIndex = 0
       query.searchResults = []
+      viewportByteIndicators.clearIndication('searchresult')
       return query
     })
   }
-  public updateSearchResults(offset?: number) {
+  public updateSearchResults(msgData: any) {
     this.update((query) => {
-      query.searchIndex = !offset
-        ? Math.abs(
-            (query.searchResults.length + query.searchIndex) %
-              query.searchResults.length
-          )
-        : Math.abs(
-            (query.searchResults.length + offset) % query.searchResults.length
-          )
-
-      seekOffsetInput.update((_) => {
-        return query.searchResults[query.searchIndex].toString(
-          get(addressRadix)
-        )
-      })
+      query.initiaited = true
+      query.searchResults = msgData.searchResults
+      query.byteLength = msgData.searchDataBytesLength
+      query.overflow = msgData.overflow
       return query
     })
   }
 }
 
-class ReplaceData implements QueryableData {
+/**
+Object that defines describes an instance of a replacement that occured during a Search & Replace query.
+@param offset **File** offset of where the replacement occured.
+@param byteLength Byte length of the replacement data.
+*/
+export type DataReplacement = {
+  offset: number
+  byteLength: number
+}
+
+export class ReplaceData implements QueryableData {
   input: string = ''
   processing: boolean = false
-  isValid: boolean = false
+  initiaited: boolean = false
+  results: Array<DataReplacement> = []
+  iterableDataFromOffset(offset: number): IndexCriteria {
+    const start = this.results.findIndex((x) => x.offset >= offset)
+    const end = this.results.findIndex(
+      (x) => x.offset >= offset + VIEWPORT_CAPACITY_MAX
+    )
+    const withinRange = start >= 0
+    const data = withinRange
+      ? this.results.slice(start, end >= 0 ? end : this.results.length)
+      : []
+    let ret: IndexCriteria = {
+      start: start,
+      end: end,
+      data: data,
+    }
+    return ret
+  }
 }
 export class ReplaceQuery extends SimpleWritable<ReplaceData> {
   protected init(): ReplaceData {
     return new ReplaceData()
   }
+  public addResult(result: DataReplacement) {
+    this.update((data) => {
+      data.initiaited = true
+      data.results.push(result)
+      return data
+    })
+  }
+  public pop() {
+    this.update((data) => {
+      data.results.pop()
+      return data
+    })
+  }
+  public clear() {
+    this.update((data) => {
+      data.processing = false
+      data.results = []
+      data.initiaited = false
+      return data
+    })
+  }
+}
+
+export function clear_queryable_results() {
+  searchQuery.clear()
+  replaceQuery.clear()
 }
diff --git a/src/svelte/src/components/dataEditor.svelte b/src/svelte/src/components/dataEditor.svelte
index 32b5285..4b4ee42 100644
--- a/src/svelte/src/components/dataEditor.svelte
+++ b/src/svelte/src/components/dataEditor.svelte
@@ -61,8 +61,8 @@ limitations under the License.
     ViewportData_t,
   } from './DataDisplays/CustomByteDisplay/BinaryData'
   import { byte_count_divisible_offset } from '../utilities/display'
-  import { clearSearchResultsHighlights } from '../utilities/highlights'
   import Help from './layouts/Help.svelte'
+  import { viewportByteIndicators } from '../utilities/highlights'
 
   $: $UIThemeCSSClass = $darkUITheme ? CSSThemeClass.Dark : CSSThemeClass.Light
 
@@ -248,16 +248,15 @@ limitations under the License.
 
   function clearQueryableData() {
     searchQuery.clear()
-    clearSearchResultsHighlights()
   }
 
   function handleKeyBind(event: Event) {
     const kbdEvent = event as KeyboardEvent
     if (key_is_mappable(kbdEvent.key)) {
-      elementKeypressEventMap.run(document.activeElement.id, kbdEvent)
+      if(document.activeElement) // document.activeElement is possibly undefined / null
+        elementKeypressEventMap.run(document.activeElement.id, kbdEvent)
       return
     }
-    if ($editMode === EditByteModes.Multiple) return
     switch (kbdEvent.key) {
       case 'Escape':
         clearDataDisplays()
diff --git a/src/svelte/src/components/globalStyles.css b/src/svelte/src/components/globalStyles.css
index 916f9b3..765897f 100644
--- a/src/svelte/src/components/globalStyles.css
+++ b/src/svelte/src/components/globalStyles.css
@@ -60,6 +60,8 @@ html {
   --color-tertiary-dark: #5f816b;
   --color-tertiary-darkest: #232f27;
   --color-alternate-grey: #bbb5bd;
+  --color-search-result: #69a0a7;
+  --color-replace-result: #5f816b;
 }
 
 /* Global LEGEND Styles */
diff --git a/src/svelte/src/components/layouts/Tooltip.svelte b/src/svelte/src/components/layouts/Tooltip.svelte
index f04abff..e313e34 100644
--- a/src/svelte/src/components/layouts/Tooltip.svelte
+++ b/src/svelte/src/components/layouts/Tooltip.svelte
@@ -101,7 +101,7 @@ limitations under the License.
   }
 
   .fit-content {
-    max-width: 150px;
+    max-width: 250px;
     min-width: 50px;
     max-height: 50px;
     min-height: 25px;
diff --git a/src/svelte/src/stores/index.ts b/src/svelte/src/stores/index.ts
index 66b3c8e..fe60eb1 100644
--- a/src/svelte/src/stores/index.ts
+++ b/src/svelte/src/stores/index.ts
@@ -43,7 +43,6 @@ import {
   type RadixValues,
   type BytesPerRow,
   EditActionRestrictions,
-  VIEWPORT_CAPACITY_MAX,
 } from './configuration'
 import type { AvailableHelpSections } from '../components/layouts/Help'
 
@@ -60,6 +59,34 @@ export class SelectionData_t {
       this.originalEndOffset >= 0
     )
   }
+  public editedLength(): number {
+    return this.endOffset - this.startOffset
+  }
+  public originalLength(): number {
+    return this.originalEndOffset - this.startOffset
+  }
+  public selectionIndexIsAnEdit(index: number): boolean {
+    const editOffsetLength = this.originalLength() - this.editedLength()
+    const editStartOffset = this.originalEndOffset - editOffsetLength
+
+    return index <= this.originalEndOffset
+      ? index >= editStartOffset
+      : index <= editStartOffset
+  }
+  public isEmpty(): boolean {
+    return this.editedLength() + 1 == 0
+  }
+  public editStartOffset() {
+    return (
+      this.originalEndOffset - (this.originalLength() - this.editedLength())
+    )
+  }
+  public editLengthDelta() {
+    return this.endOffset - this.originalEndOffset + 1
+  }
+  public makingSelection() {
+    return this.startOffset >= 0 && this.originalEndOffset === -1
+  }
 }
 
 class SelectionData extends SimpleWritable<SelectionData_t> {
@@ -76,6 +103,10 @@ export enum EditModeRestrictions {
   OverwriteOnly,
 }
 
+/**************************************************************************/
+/*                          Writable Stores                               */
+/**************************************************************************/
+
 // noinspection JSUnusedGlobalSymbols
 
 // theme to use for the UI
@@ -155,6 +186,12 @@ export const dataDislayLineAmount = writable(20)
 
 export type VisibleViewports = 'physical' | 'logical' | 'all'
 export const visableViewports = writable('all' as VisibleViewports)
+
+export const searchResultsUpdated = writable(false)
+
+/**************************************************************************/
+/*                          Derived Stores                                */
+/**************************************************************************/
 // Can the user's selection derive both edit modes?
 export const regularSizedFile = derived(fileMetrics, ($fileMetrics) => {
   return $fileMetrics.computedSize >= 2
@@ -204,7 +241,7 @@ export const replaceable = derived(
     }
     if ($selectionData.active) {
       replaceErr.update(() => {
-        return 'Cannot replace while viewport data is selected'
+        return "Can't replace while selection active"
       })
       return false
     }
diff --git a/src/svelte/src/utilities/ByteCategories/CategoryIndications.ts b/src/svelte/src/utilities/ByteCategories/CategoryIndications.ts
new file mode 100644
index 0000000..72e57fc
--- /dev/null
+++ b/src/svelte/src/utilities/ByteCategories/CategoryIndications.ts
@@ -0,0 +1,114 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { CategoryOne, type ByteCategory, CategoryTwo } from './IByteCategory'
+
+class ByteIndicationCategories {
+  private _categories: {
+    [key: string]: ByteCategory
+  } = {}
+  private _bitsUtilized: number = 0
+
+  public addIndicationCategory(category: ByteCategory) {
+    if (this._bitsUtilized + category.bitLength() > 8)
+      throw new Error('Category addition would exceed bit limit')
+    this._categories[category.name()] = category
+    this._bitsUtilized += category.bitLength()
+
+    return this
+  }
+  public category(name: string): ByteCategory {
+    return this._categories[name]
+  }
+  public categoryOfIndication(indicationName: string): ByteCategory {
+    for (const category in this._categories)
+      if (this._categories[category].contains(indicationName))
+        return this._categories[category]
+
+    throw new Error(
+      `No ByteCategory found with ByteIndication named ${indicationName}`
+    )
+  }
+  public categoryValueByIndication(
+    category: ByteCategory,
+    indicationName: string
+  ): number {
+    return category.indexOf(indicationName) << this.categoryBitPos(category)
+  }
+  public clearIndication(data: Uint8Array, indicationName: string) {
+    const category = this.categoryOfIndication(indicationName)
+    data.forEach((byte, i, data) => {
+      const categoryValueOfByte = byte & this.categoryMask(category)
+      if (
+        categoryValueOfByte ===
+        this.categoryValueByIndication(category, indicationName)
+      )
+        data[i] &= this.categoryMask(category) ^ 0xff
+    })
+  }
+  public clearAndSetIf(
+    data: Uint8Array,
+    indication: string,
+    predicate: (byte: number, index: number) => boolean
+  ) {
+    const category = this.categoryOfIndication(indication)
+    if (!category) {
+      console.error(`No ByteCategory contains the indication: ${indication}`)
+      return
+    }
+
+    data.forEach((byte, i, data) => {
+      data[i] &= this.categoryMask(category) ^ 0xff
+      if (predicate(byte, i))
+        data[i] |= this.categoryValueByIndication(category, indication)
+    })
+  }
+  public categoryCSSSelector(
+    category: ByteCategory,
+    byteIndicationValue: number
+  ) {
+    const maskedByteValue = byteIndicationValue & this.categoryMask(category)
+    const indicationIndex = maskedByteValue >> this.categoryBitPos(category)
+    const indication = category.indicators()[indicationIndex]
+    return indication != undefined
+      ? indication.selector()
+      : category.indexOf('none')
+  }
+  private categoryMask(category: ByteCategory): number {
+    const categoryBitPos = this.categoryBitPos(category)
+    const categoryBitLength = category.bitLength()
+    return (Math.pow(2, categoryBitLength) - 1) << categoryBitPos
+  }
+  private indexOf(category: ByteCategory) {
+    let i = 0
+    for (let _category in this._categories) {
+      if (category.name() === this._categories[_category].name()) {
+        return i
+      }
+      i++
+    }
+    return -1
+  }
+  private categoryBitPos(category: ByteCategory): number {
+    return this.indexOf(category) * category.bitLength()
+  }
+}
+
+export const ViewportByteCategories = new ByteIndicationCategories()
+ViewportByteCategories.addIndicationCategory(CategoryOne).addIndicationCategory(
+  CategoryTwo
+)
diff --git a/src/svelte/src/utilities/ByteCategories/IByteCategory.ts b/src/svelte/src/utilities/ByteCategories/IByteCategory.ts
new file mode 100644
index 0000000..55b29fa
--- /dev/null
+++ b/src/svelte/src/utilities/ByteCategories/IByteCategory.ts
@@ -0,0 +1,84 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import {
+  NoIndication,
+  type IByteIndication,
+  ByteIndication,
+} from './IIndication'
+
+export interface IByteCategory {
+  addIndication(selectorName: string): void
+  bitLength(): number
+  name(): string
+  indicators(): readonly IByteIndication[]
+  at(index: number): IByteIndication
+  indexOf(selectorName: string): number
+  contains(selectorName: string): boolean
+}
+
+export class ByteCategory implements IByteCategory {
+  private _indicators: IByteIndication[] = [NoIndication]
+
+  constructor(
+    private _name: string,
+    private _bitLength: number
+  ) {
+    if (_bitLength > 8)
+      throw new Error('Byte category indications cannot exceed 8 bits.')
+  }
+  addIndication(selectorName: string) {
+    this._indicators.push(new ByteIndication(selectorName))
+    return this
+  }
+  bitLength() {
+    return this._bitLength
+  }
+  name() {
+    return this._name
+  }
+  indicators() {
+    return this._indicators
+  }
+  at(index: number) {
+    return index >= this._indicators.length || index < 0
+      ? this._indicators[0]
+      : this._indicators[index]
+  }
+  indexOf(selectorName: string) {
+    const target = selectorName
+    let ret = -1
+
+    this._indicators.forEach((categoryObj, i) => {
+      if (categoryObj.selector() === target) ret = i
+    })
+    if (ret < 0)
+      throw new Error(`Indication category "${selectorName}" not found.`)
+    else return ret
+  }
+  contains(selectorName: string): boolean {
+    for (let i = 0; i < this._indicators.length; i++)
+      if (this._indicators[i].selector() == selectorName) return true
+    return false
+  }
+}
+
+export const CategoryOne = new ByteCategory('one', 4)
+CategoryOne.addIndication('selected')
+
+export const CategoryTwo = new ByteCategory('two', 4)
+CategoryTwo.addIndication('searchresult').addIndication('replacement')
diff --git a/src/svelte/src/utilities/ByteCategories/IIndication.ts b/src/svelte/src/utilities/ByteCategories/IIndication.ts
new file mode 100644
index 0000000..7a6dede
--- /dev/null
+++ b/src/svelte/src/utilities/ByteCategories/IIndication.ts
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+export interface IByteIndication {
+  selector(): string
+  equals(categoryArg: IByteIndication): boolean
+}
+
+class NullIndication implements IByteIndication {
+  equals(categoryArg: IByteIndication): boolean {
+    return this.selector() === categoryArg.selector()
+  }
+  selector(): string {
+    return 'none'
+  }
+}
+
+export class ByteIndication implements IByteIndication {
+  constructor(private _selector: string) {}
+  equals(categoryArg: IByteIndication): boolean {
+    return this.selector() === categoryArg.selector()
+  }
+  selector(): string {
+    return this._selector.toLowerCase()
+  }
+}
+
+export const NoIndication: NullIndication = new NullIndication()
diff --git a/src/svelte/src/utilities/display.ts b/src/svelte/src/utilities/display.ts
index 6b0f522..481d539 100644
--- a/src/svelte/src/utilities/display.ts
+++ b/src/svelte/src/utilities/display.ts
@@ -12,7 +12,6 @@
 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 // See the License for the specific language governing permissions and
 // limitations under the License.
-
 import {
   EditByteModes,
   type BytesPerRow,
diff --git a/src/svelte/src/utilities/highlights.ts b/src/svelte/src/utilities/highlights.ts
index bb00644..4442a6d 100644
--- a/src/svelte/src/utilities/highlights.ts
+++ b/src/svelte/src/utilities/highlights.ts
@@ -15,62 +15,144 @@
  * limitations under the License.
  */
 
-import { derived, readable, writable } from 'svelte/store'
-import { selectionDataStore } from '../stores'
+import { SelectionData_t, searchResultsUpdated } from '../stores'
+import { VIEWPORT_CAPACITY_MAX } from '../stores/configuration'
+import { SimpleWritable } from '../stores/localStore'
+import type {
+  DataReplacement,
+  ReplaceData,
+  SearchData,
+} from '../components/Header/fieldsets/SearchReplace'
+import { ViewportByteCategories } from './ByteCategories/CategoryIndications'
 
-let selectionHighlightLUT = new Uint8Array(1024)
-export let selectionHighlightMask = writable(0)
+class ViewportByteIndications extends SimpleWritable<Uint8Array> {
+  protected init(): Uint8Array {
+    return new Uint8Array(VIEWPORT_CAPACITY_MAX).fill(0)
+  }
+  public clearIndication(indicationName: string) {
+    this.store.update((indications) => {
+      ViewportByteCategories.clearIndication(indications, indicationName)
+      return indications
+    })
+  }
+  public updateSearchIndications(
+    searchQuery: SearchData,
+    viewportFileOffset: number
+  ) {
+    if (searchQuery.searchResults.length > 0) {
+      const resultsIterable =
+        searchQuery.iterableDataFromOffset(viewportFileOffset)
+      const { data } = resultsIterable
+      const start = data[0]
+      const byteLength = searchQuery.byteLength
 
-let searchResultsHighlightLUT = new Uint8Array(1024).fill(0)
+      this.store.update((indications) => {
+        ViewportByteCategories.clearAndSetIf(
+          indications,
+          'searchresult',
+          (_, i) => {
+            const adjustIndex = i + viewportFileOffset
+            return adjustIndex >= start && adjustIndex < start + byteLength
+          }
+        )
+        searchResultsUpdated.set(true)
+        return indications
+      })
+    }
+  }
 
-export enum HightlightCategoryMasks {
-  None = 0,
-  ActiveSelection = 1,
-  ConsideredForSelection = 2,
-  SearchResult = 4,
-}
+  public updateReplaceIndications(
+    replaceData: ReplaceData,
+    viewportFileOffset: number
+  ) {
+    const resultsIterable =
+      replaceData.iterableDataFromOffset(viewportFileOffset)
 
-export const selectionHighlights = derived(
-  [selectionDataStore, selectionHighlightMask],
-  ([$selectionData, $selectionHighlightMask]) => {
-    let start = $selectionData.startOffset
-    let end =
-      $selectionHighlightMask === 0
-        ? $selectionData.originalEndOffset
-        : $selectionData.endOffset
-    if (start > end && end > -1) [start, end] = [end, start]
+    if (resultsIterable.data.length > 0) {
+      const { offset, byteLength } = resultsIterable.data[0] as DataReplacement
+      this.store.update((indications) => {
+        ViewportByteCategories.clearAndSetIf(
+          indications,
+          'replacement',
+          (_, i) => {
+            const adjustIndex = i + viewportFileOffset
+            return adjustIndex >= offset && adjustIndex < offset + byteLength
+          }
+        )
+        return indications
+      })
+    }
+  }
+  public updateSelectionIndications(selectionData: SelectionData_t) {
+    const category1 = ViewportByteCategories.category('one')
+    const start = selectionData.startOffset
+    const editedEnd = selectionData.endOffset + 1
+    const originalEnd = selectionData.originalEndOffset
 
-    for (let i = 0; i < 1024; i++) {
-      selectionHighlightLUT[i] =
-        i >= start && i <= end ? 1 << $selectionHighlightMask : 0
+    if (!selectionData.makingSelection() && !selectionData.active) {
+      this.store.update((indications) => {
+        ViewportByteCategories.clearIndication(indications, 'selected')
+        return indications
+      })
+    }
+    if (selectionData.active || selectionData.makingSelection()) {
+      const offsetPartitions = [
+        generateSelectionCategoryParition(0, start, (byte) => {
+          byte[0] &= ~category1.indexOf('selected')
+        }),
+        generateSelectionCategoryParition(start, editedEnd, (byte) => {
+          byte[0] |= category1.indexOf('selected')
+        }),
+        generateSelectionCategoryParition(
+          Math.max(originalEnd, editedEnd),
+          VIEWPORT_CAPACITY_MAX - start,
+          (byte) => {
+            byte[0] &= ~category1.indexOf('selected')
+          }
+        ),
+      ]
+      this.store.update((indications) => {
+        for (const partition of offsetPartitions) {
+          for (let i = partition.start; i < partition.end; i++)
+            partition.assignByte(indications.subarray(i, i + 1))
+        }
+        return indications
+      })
     }
+  }
+}
 
-    return selectionHighlightLUT
+export const viewportByteIndicators = new ViewportByteIndications()
+
+type CategoryOffsetParition = {
+  start: number
+  end: number
+  assignByte: (byte: Uint8Array) => void
+}
+function generateSelectionCategoryParition(
+  start: number,
+  end: number,
+  assignmentFn: (byte: Uint8Array) => void
+): CategoryOffsetParition {
+  return {
+    start,
+    end,
+    assignByte: assignmentFn,
   }
-)
+}
 
-export const searchResultsHighlights = readable(searchResultsHighlightLUT)
-export const searchResultsUpdated = writable(false)
-export function updateSearchResultsHighlights(
-  data: number[],
-  viewportFileOffset: number,
-  byteWidth: number
-) {
-  const criteriaStart = data.findIndex((x) => x >= viewportFileOffset)
-  const criteriaEnd = data.findIndex((x) => x >= viewportFileOffset + 1024)
-  const searchCriteria = data.slice(
-    criteriaStart,
-    criteriaEnd >= 0 ? criteriaEnd : data.length
+export function categoryCSSSelectors(byteIndicationValue: number): string {
+  let ret = ''
+  const CategoryOneSelector = ViewportByteCategories.categoryCSSSelector(
+    ViewportByteCategories.category('one'),
+    byteIndicationValue
+  )
+  const CategoryTwoSelector = ViewportByteCategories.categoryCSSSelector(
+    ViewportByteCategories.category('two'),
+    byteIndicationValue
   )
 
-  searchResultsHighlightLUT.fill(0)
+  ret += CategoryOneSelector + ' ' + CategoryTwoSelector
 
-  searchCriteria.forEach((offset) => {
-    for (let i = 0; i < byteWidth; i++)
-      searchResultsHighlightLUT[offset - viewportFileOffset + i] = 1
-  })
-  searchResultsUpdated.set(true)
-}
-export function clearSearchResultsHighlights() {
-  searchResultsHighlightLUT.fill(0)
+  return ret
 }