diff --git a/src/language/dfdl.ts b/src/language/dfdl.ts index f940ee19d..66284164a 100644 --- a/src/language/dfdl.ts +++ b/src/language/dfdl.ts @@ -22,7 +22,6 @@ import { getAttributeCompletionProvider } from './providers/attributeCompletion' import { getCloseElementProvider } from './providers/closeElement' import { getAttributeValueCompletionProvider } from './providers/attributeValueCompletion' import { getCloseElementSlashProvider } from './providers/closeElementSlash' -import { getAttributeHoverProvider } from './providers/attributeHover' export function activate(context: vscode.ExtensionContext) { let dfdlFormat = fs @@ -38,7 +37,6 @@ export function activate(context: vscode.ExtensionContext) { getAttributeCompletionProvider(), getAttributeValueCompletionProvider(), getCloseElementProvider(), - getCloseElementSlashProvider(), - getAttributeHoverProvider() + getCloseElementSlashProvider() ) } diff --git a/src/language/providers/attributeHover.ts b/src/language/providers/attributeHover.ts deleted file mode 100644 index d923449eb..000000000 --- a/src/language/providers/attributeHover.ts +++ /dev/null @@ -1,39 +0,0 @@ -/* - * 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 * as vscode from 'vscode' -import { attributeHoverValues } from './intellisense/attributeHoverItems' - -export function getAttributeHoverProvider() { - return vscode.languages.registerHoverProvider('dfdl', { - provideHover( - document: vscode.TextDocument, - position: vscode.Position, - token: vscode.CancellationToken - ) { - const range = document.getWordRangeAtPosition(position) - const word = document.getText(range) - - if (word.length > 0) { - return new vscode.Hover({ - language: 'dfdl', - value: attributeHoverValues(word), - }) - } - }, - }) -} diff --git a/src/language/providers/intellisense/attributeHoverItems.ts b/src/language/providers/intellisense/attributeHoverItems.ts deleted file mode 100644 index 4395c67ee..000000000 --- a/src/language/providers/intellisense/attributeHoverItems.ts +++ /dev/null @@ -1,231 +0,0 @@ -/* - * 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 function attributeHoverValues(attributeName: string): string { - switch (attributeName) { - case 'name': - return 'specify name' - case 'ref': - return 'Specifies the name of an element in this schema' - case 'minOccurs': - return 'Minimum number of times element will occur' - case 'maxOccurs': - return 'Maximum number of times element will occur' - case 'dfdl:occursCount': - return 'dfdl:occursCount property takes an expression which commonly looks in the Infoset via an expression to obtain the count from another element.' - case 'dfdl:byteOrder': - return 'This property applies to all Number Calendar (date and time and Boolean types with representation binary' - case 'dfdl:bitOrder': - return 'Determines the specific bits of any grammar region' - case 'dfdl:occursCountKind': - return 'Specifies how the actual number of occurrences is to be established' - case 'dfdl:length': - return 'length can be an expression that resolves to an unsigned integer or a literal unsigned integer' - case 'dfdl:lengthKind': - return 'lengthKind can be delimited fixed explicit implicit prefixedpattern or endOfParent' - case 'dfdl:prefixIncludesPrefixLength': - return 'Specifies whether the length given by a prefix includes the length of the prefix as well as the length of the content region' - case 'dfdl:prefixLengthType': - return 'Name of a simple type derived from xs:integer or any subtype of it.' - case 'dfdl:utf16Width': - return 'Specifies whether the encoding UTF-16 is treated as a fixed or variable width encoding' - case 'dfdl:encoding': - return 'This property can be computed by way of an expression which returns an appropriate string value' - case 'dfdl:encodingErrorPolicy': - return 'This property provides control of how decoding and encoding errors are handled when converting the data to text or text to data' - case 'dfdl:nilKind': - return 'Specifies how dfdl: is interpreted to represent the nil value in the data stream' - case 'dfdl:nilValue': - return 'Used to provide a logical value that is used to indicate the data is nilled' - case 'dfdl:nilValueDelimiterPolicy': - return 'Controls whether matching one of the nil values also involves matching the initiator or terminator specified by the element' - case 'dfdl:useNilForDefault': - return 'Controls whether to set the Infoset item [nilled] boolean member or to use the XSD default or fixed properties to obtain a data value' - case 'dfdl:alignment': - return "Alignment required for the beginning of the item.\nCan be non-negative integer or 'implicit'." - case 'dfdl:lengthUnits': - return 'lengthUnits can be specified as bits bytes or characters' - case 'dfdl:lengthPattern': - return 'lengthPattern takes a regular expression which is used to scan the data stream for matching data' - case 'dfdl:inputValueCalc': - return 'An expression that calculates the value of the element when parsing' - case 'dfdl:outputValueCalc': - return 'An expression that calculates the value of the current element when unparsing' - case 'dfdl:alignmentUnits': - return "Scales the alignment.\nCan only be used when alignment is bits or bytes.\nValid values are 'bits or 'bytes'." - case 'dfdl:outputNewLine': - return 'Specifies the character or characters that are used to replace the %NL; character class entity during unparse' - case 'dfdl:choiceBranchKey': - return 'List of DFDL String Literals' - case 'dfdl:representation': - return 'Identifies the physical representation of the element as text or binary' - case 'dfdl:textStringJustification': - return 'Specifies the string justification' - case 'dfdl:textStringPadCharacter': - return 'Specifies the string justification' - case 'dfdl:textStandardZeroRep': - return 'Specifies the whitespace separated list of alternative DFDL String Literals that are equivalent to zero ' - case 'dfdl:textStandardInfinityRep': - return 'The value used to represent infinity.' - case 'dfdl:textStandardExponentRep': - return 'Defines the actual character(s that appear in the data as the exponent indicator' - case 'dfdl:textStandardNaNRep': - return 'Specifies the value used to represent NaN' - case 'dfdl:textNumberPattern': - return 'Indicates whether an xs:decimal element is signed' - case 'dfdl:decimalSigned': - return 'Represented as standard characters in the character set encoding or represented as a zoned decimal in the character set encoding' - case 'dfdl:textNumberRep': - return 'Represented as standard characters in the character set encoding or represented as a zoned decimal in the character set encoding' - case 'dfdl:textNumberJustification': - return 'Controls how the data is padded or trimmed on parsing and unparsing' - case 'dfdl:textNumberRoundingMode': - return 'Specifies how rounding occurs during unparsing' - case 'dfdl:textNumberRoundingIncrement': - return 'Specifies the rounding increment to use during unparsing' - case 'dfdl:textNumberRounding': - return 'Specifies how rounding is controlled during unparsing' - case 'dfdl:textNumberCheckPolicy': - return 'Indicates how lenient to be when parsing against the dfdl:textNumberPattern' - case 'dfdl:textOutputMinLength': - return 'Specifies the minimum content length during unparsing for simple types that do not allow the XSD minLength facet to be specified' - case 'dfdl:textStandardDecimalSeparator': - return 'Defines a whitespace separated list of single characters that appear (individually in the data as the decimal separator' - case 'dfdl:textStandardGroupingSeparator': - return 'Specifies the single character that can appear in the data as the grouping separator' - case 'dfdl:textPadKind': - return 'Indicates whether to pad the data value on unparsing' - case 'dfdl:textStandardBase': - return 'Indicates the number base' - case 'dfdl:textZonedSignStyle': - return 'Specifies the code points that are used to modify the sign nibble of the byte containing the sign' - case 'dfdl:textTrimKind': - return 'Indicates whether to trim data on parsing' - case 'dfdl:textBooleanTrueRep': - return 'A whitespace separated list of representations to be used for true' - case 'dfdl:textBooleanFalseRep': - return 'A whitespace separated list of representations to be used for false' - case 'dfdl:textBooleanJustification': - return 'Controls how the data is padded or trimmed on parsing and unparsing' - case 'dfdl:textBooleanPadCharacter': - return 'The value that is used when padding or trimming boolean elements' - case 'dfdl:leadingSkip': - return 'A non-negative number of bytes or bits to skip before alignment is applied' - case 'dfdl:trailingSkip': - return 'A non-negative number of bytes or bits to skip after the element' - case 'dfdl:truncateSpecifiedLengthString': - return 'This property provides the means to express an error or the strings can be truncated to fit when the strings in an Infoset being unparsed do not fit within those specified lengths' - case 'dfdl:sequenceKind': - return 'Defines whether the items are expected in the same order that they appear in the schema or in any order' - case 'dfdl:separator': - return 'Specifies a whitespace separated list of alternative DFDL String Literals that are the possible separators for the sequence' - case 'dfdl:separatorPosition': - return 'specifies where the separator occurs between the elements' - case 'dfdl:separatorSuppressionPolicy': - return 'Controls the circumstances when separators are expected in the data when parsing or generated when unparsing' - case 'dfdl:terminator': - return 'charater or bytes found in the input stream that designate termination of an element' - case 'dfdl:textBidi': - return 'This property exists in anticipation of future DFDL features that enable bidirectional text processing' - case 'dfdl:hiddenGroupRef': - return 'Reference to a global model group definition' - case 'dfdl:choiceLengthKind': - return 'Determines whether the branches of the choice are always filled (explicit to the fixed-length specified by dfdl:choiceLength or not filled (implicit' - case 'dfdl:choiceLength': - return 'Specifies the length of the choice in bytes only used when dfdl:choiceLengthKind is explicit' - case 'dfdl:fillByte': - return 'A single byte specified as a DFDL byte value entity or a single character used on unparsing to fill empty space' - case 'dfdl:ignoreCase': - return 'Whether mixed case data is accepted when matching delimiters and data values on input' - case 'dfdl:initiatedContent': - return 'yes indicates all branches of a choice are initiated\nno indicates the branch dfdl:initator property may be ste to empty string' - case 'dfdl:initiator': - return 'Specifies an ordered whitespace separated list of alternative DFDL String Literals one of which marks the beginning of the element or group of elements ' - case 'dfdl:choiceDispatchKey': - return 'The expression must evaluate to a string the string must match one of the dfdl:choiceBranchKey property values of one of the branches of the choice' - case 'dfdl:binaryNumberRep': - return 'binarypackedbcd or ibm4690Packed' - case 'dfdl:floating': - return 'yes or no' - case 'dfdl:binaryFloatRep': - return 'ieee or ibm390Hex' - case 'dfdl:binaryDecimalVirtualPoint': - return 'An integer that represents the position of an implied decimal point within a number' - case 'dfdl:binaryPackedSignCodes': - return 'A whitespace separated string giving the hex sign nibbles to use for a positive value a negative value an unsigned value and zero' - case 'dfdl:binaryNumberCheckPolicy': - return 'Indicates how lenient to be when parsing binary numbers' - case 'dfdl:binaryBooleanTrueRep': - return 'A binary xs:unsignedInt gives the representation for true' - case 'dfdl:binaryBooleanFalseRep': - return 'A binary xs:unsignedInt gives the representation for false' - case 'dfdl:calendarPattern': - return 'Defines the ICU pattern that describes the format of the calendar. The pattern defines where the year month day hour minute second fractional second and time zone components appear' - case 'dfdl:calendarPatternKind': - return 'The pattern is given by dfdl:calendarPattern explicit or the pattern is derived from the XML schema date/time type (implicit' - case 'dfdl:calendarCheckPolicy': - return 'Indicates how lenient to be when parsing against the pattern' - case 'dfdl:calendarTimeZone': - return 'Provides the time zone that is assumed if no time zone explicitly occurs in the data' - case 'dfdl:calendarObserveDST': - return 'Whether the time zone given in dfdl:calendarTimeZone observes daylight savings time' - case 'dfdl:calendarFirstDayOfWeek': - return 'The day of the week upon which a new week is considered to start' - case 'dfdl:calendarDaysInFirstWeek': - return 'Specify the number of days of the new year that must fall within the first week' - case 'dfdl:calendarCenturyStart': - return 'specifies the two digits that start a 100-year window that contains the current year' - case 'dfdl:calendarLanguage': - return 'The language that is used when the pattern produces a presentation in text' - case 'dfdl:documentFinalTerminatorCanBeMissing': - return 'Specifies whether the final line can be missing' - case 'dfdl:emptyValueDelimiterPolicy': - return 'Indicates which of initiator terminator both or neither must be present when an element in the data stream is empty.' - case 'dfdl:emptyElementParsePolicy': - return 'Indicates which of initiator terminator both or neither must be present when an element in the data stream is empty.' - case 'dfdl:escapeSchemeRef': - return 'Refers to a named escape scheme definition via its qualified name' - case 'dfdl:escapeKind': - return 'The type of escape mechanism defined in the escape scheme' - case 'dfdl:escapeCharacter': - return 'Specifies one character that escapes the subsequent character' - case 'dfdl:escapeBlockStart': - return 'The string of characters that denotes the beginning of a sequence of characters escaped by a pair of escape strings' - case 'dfdl:escapeBlockEnd': - return 'The string of characters that denotes the end of a sequence of characters escaped by a pair of escape strings' - case 'dfdl:escapeEscapeCharacter': - return 'Specifies one character that escapes an immediately following dfdl:escapeCharacter' - case 'dfdl:extraEscapedCharacters': - return 'A whitespace separated list of single characters that must be escaped in addition to the in-scope delimiters' - case 'dfdl:generateEscapeBlock': - return 'The type of escape mechanism defined in the escape scheme' - case 'dfdl:escapeCharacterPolicy': - return 'The type of escape mechanism defined in the escape scheme' - case 'testKind': - return 'Specifies whether a DFDL expression or DFDL regular expression pattern is used in the dfdl:assert' - case 'test': - return 'A DFDL expression that evaluates to true or false.' - case 'testPattern': - return 'A DFDL regular expression that is applied against the data stream' - case 'message': - return 'Defines text for use in an error message' - case 'failureType': - return 'Specifies the type of failure that occurs when the dfdl:assert is unsuccessful' - default: - return 'No definition available' - } -} diff --git a/src/svelte/src/components/DataDisplays/CustomByteDisplay/DataLineFeed.svelte b/src/svelte/src/components/DataDisplays/CustomByteDisplay/DataLineFeed.svelte index 644ec8eae..dfd8ff098 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,12 @@ limitations under the License. type CSSThemeClass, } from '../../../utilities/colorScheme' import { - selectionHighlights, - searchResultsHighlights, - updateSearchResultsHighlights, - searchResultsUpdated, + CATEGORY_ONE_MASK, + CATEGORY_TWO_MASK, + viewportByteIndicators, + type ByteValueIndications, + IndicatorCategoryShift, + byteCategoryValue, } from '../../../utilities/highlights' import { bytesPerRow } from '../../../stores' export let awaitViewportSeek: boolean @@ -169,7 +173,7 @@ limitations under the License. bytes: Array highlight: 'even' | 'odd' } - + enum ViewportScrollDirection { DECREMENT = -1, NONE = 0, @@ -181,10 +185,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 +222,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 +247,11 @@ limitations under the License. } } $: byteElementWidth = byteDivWidthFromRadix(dataRadix) + $: { + viewportByteIndicators.updateSelectionIndications($selectionDataStore.startOffset, $selectionDataStore.endOffset) + viewportByteIndicators.updateSearchIndications($searchQuery.searchResults, $searchQuery.byteLength) + viewportByteIndicators.updateReplaceIndications($replaceQuery.results, viewportData.fileOffset) + } function generate_line_data( startIndex: number, @@ -359,21 +368,21 @@ limitations under the License. : atViewportHead && !atFileHead } - function mousedown(event: CustomEvent) { + 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) { + 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 +392,7 @@ limitations under the License. return } - set_byte_selection(event.detail) + set_byte_selection(event) } function adjust_event_offsets() { @@ -471,6 +480,48 @@ 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) + + if (targetElement.id.includes('logical')) byteText = String.fromCharCode(byteValue) + 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,16 +536,11 @@ limitations under the License. selectedByteElement = document.getElementById( $selectedByte.offset.toString() ) as HTMLDivElement - - updateSearchResultsHighlights( - $searchQuery.searchResults, - viewportData.fileOffset, - $searchQuery.byteLength - ) } break } }) + {#if $selectionDataStore.active && $editMode == EditByteModes.Single} @@ -508,7 +554,13 @@ limitations under the License. {/key} {/if} -
+ + + +
{#each viewportLines as viewportLine, i}
@@ -523,17 +575,12 @@ limitations under the License. {#each viewportLine.bytes as byte} > - activeSelection[byte.offset]} id={'physical'} - radix={dataRadix} + indicators={{ + cat1: byteCategoryValue($viewportByteIndicators[byte.offset] & CATEGORY_ONE_MASK, IndicatorCategoryShift.CategoryOne), + cat2: byteCategoryValue($viewportByteIndicators[byte.offset] & CATEGORY_TWO_MASK, IndicatorCategoryShift.CategoryTwo) }} width={byteElementWidth} disabled={byte.value === -1} - bind:selectionData={$selectionDataStore} - on:mouseup={mouseup} - on:mousedown={mousedown} /> {/each}
@@ -546,17 +593,12 @@ limitations under the License. {#each viewportLine.bytes as byte} > - activeSelection[byte.offset]} + indicators={{ + cat1: byteCategoryValue($viewportByteIndicators[byte.offset] & CATEGORY_ONE_MASK, IndicatorCategoryShift.CategoryOne), + cat2: byteCategoryValue($viewportByteIndicators[byte.offset] & CATEGORY_TWO_MASK, IndicatorCategoryShift.CategoryTwo) }} id={'logical'} - radix={dataRadix} width={byteElementWidth} disabled={byte.value === -1} - bind:selectionData={$selectionDataStore} - on:mouseup={mouseup} - on:mousedown={mousedown} /> {/each}
@@ -697,15 +739,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 ab12df431..f8a94298a 100644 --- a/src/svelte/src/components/DataDisplays/CustomByteDisplay/DataValue.svelte +++ b/src/svelte/src/components/DataDisplays/CustomByteDisplay/DataValue.svelte @@ -15,61 +15,23 @@ See the License for the specific language governing permissions and limitations under the License. --> @@ -78,33 +40,22 @@ limitations under the License. {:else if id === 'physical'}
{byte.text}
{:else}
{latin1Undefined(byte.value) ? '' : String.fromCharCode(byte.value)}
@@ -118,7 +69,6 @@ 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; @@ -126,21 +76,20 @@ limitations under the License. text-align: center; transition: all 0.25s; } - div.byte.isSelected, + div.byte.selected, div.byte.isSearchResult, - div.byte.possibleSelection { + div.byte.isReplaceResult { border-radius: 5px; } - 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); + border-color: var(--color-search-result); } - div.byte.possibleSelection { - border-color: var(--color-secondary-light); + div.byte.isReplaceResult { + border-color: var(--color-replace-result); } div.byte:hover { border-color: var(--color-secondary-mid); diff --git a/src/svelte/src/components/DataDisplays/Header/DisplayHeader.svelte b/src/svelte/src/components/DataDisplays/Header/DisplayHeader.svelte index 4628e7858..86552922b 100644 --- a/src/svelte/src/components/DataDisplays/Header/DisplayHeader.svelte +++ b/src/svelte/src/components/DataDisplays/Header/DisplayHeader.svelte @@ -127,14 +127,14 @@ limitations under the License. {#if $displayRadix === RADIX_OPTIONS.Binary} {#each offsetLine as offset}
-
{offset}
-
{bitIndexStr}
+
{offset}
+
{bitIndexStr}
{/each} {:else} {#each offsetLine as offset}
- {offset} + {offset}
{/each} {/if} diff --git a/src/svelte/src/components/Header/fieldsets/FileMetrics.svelte b/src/svelte/src/components/Header/fieldsets/FileMetrics.svelte index 21dc3b1cc..62ff4c54c 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 12c8c90b4..d73623ea5 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.add_result({ + byteLength: msg.data.data.replaceDataBytesLength, + offset: msg.data.data.nextOffset - msg.data.data.replaceDataBytesLength + }) preReplaceHasPrev = hasPrev justReplaced = true searchNext() diff --git a/src/svelte/src/components/Header/fieldsets/SearchReplace.ts b/src/svelte/src/components/Header/fieldsets/SearchReplace.ts index e1f739b94..9ead5dbb0 100644 --- a/src/svelte/src/components/Header/fieldsets/SearchReplace.ts +++ b/src/svelte/src/components/Header/fieldsets/SearchReplace.ts @@ -16,8 +16,7 @@ */ import { SimpleWritable } from '../../../stores/localStore' -import { addressRadix, seekOffsetInput } from '../../../stores' -import { get } from 'svelte/store' +import { replaceQuery, searchQuery } from '../../../stores' export enum OffsetSearchType { ABSOLUTE, @@ -52,34 +51,51 @@ export class SearchQuery extends SimpleWritable { 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.searchResults = msgData.searchResults + query.byteLength = msgData.searchDataBytesLength + query.overflow = msgData.overflow return query }) } } +/** +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 +} + class ReplaceData implements QueryableData { input: string = '' processing: boolean = false isValid: boolean = false + results: Array = [] } export class ReplaceQuery extends SimpleWritable { protected init(): ReplaceData { return new ReplaceData() } + public add_result(result: DataReplacement) { + this.update((data) => { + data.results.push(result) + return data + }) + } + public clear() { + this.update((data) => { + data.results = [] + 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 32b52854b..33d89a12a 100644 --- a/src/svelte/src/components/dataEditor.svelte +++ b/src/svelte/src/components/dataEditor.svelte @@ -61,7 +61,6 @@ 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' $: $UIThemeCSSClass = $darkUITheme ? CSSThemeClass.Dark : CSSThemeClass.Light @@ -248,13 +247,13 @@ 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 diff --git a/src/svelte/src/components/globalStyles.css b/src/svelte/src/components/globalStyles.css index 916f9b3e3..765897f77 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/stores/index.ts b/src/svelte/src/stores/index.ts index 66b3c8ea0..a0e8c14ad 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' @@ -76,6 +75,10 @@ export enum EditModeRestrictions { OverwriteOnly, } +/**************************************************************************/ +/* Writable Stores */ +/**************************************************************************/ + // noinspection JSUnusedGlobalSymbols // theme to use for the UI @@ -155,6 +158,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 diff --git a/src/svelte/src/utilities/display.ts b/src/svelte/src/utilities/display.ts index 6b0f522a3..481d5390b 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 bb00644db..7940d6a89 100644 --- a/src/svelte/src/utilities/highlights.ts +++ b/src/svelte/src/utilities/highlights.ts @@ -15,62 +15,157 @@ * limitations under the License. */ -import { derived, readable, writable } from 'svelte/store' -import { selectionDataStore } from '../stores' +import { get } from 'svelte/store' +import { searchResultsUpdated, selectionDataStore, viewport } from '../stores' +import { VIEWPORT_CAPACITY_MAX } from '../stores/configuration' +import { SimpleWritable } from '../stores/localStore' +import type { DataReplacement } from '../components/Header/fieldsets/SearchReplace' -let selectionHighlightLUT = new Uint8Array(1024) -export let selectionHighlightMask = writable(0) +/** `0b0000 1111` */ +export const CATEGORY_ONE_MASK = 0x0f +/** `0b1111 0000` */ +export const CATEGORY_TWO_MASK = 0xf0 -let searchResultsHighlightLUT = new Uint8Array(1024).fill(0) +export type ByteValueIndications = { + cat1: number + cat2: number +} + +export enum IndicatorCategoryShift { + CategoryOne = 0, + CategoryTwo = 4, +} + +/** +`Uint8Array` byte index's map for bits[0-3]: `bbbb 0000`. Category 1 attributes change +the background color of a `ByteValue`. + +- `bbbb 0000`: (0) Viewport byte index has no category 1 attribute. +- `bbbb 0001`: (1) Viewport byte index is a byte within the user's selection range. +*/ +enum CategoryOneIndications { + None = 0, + Selected = 1, +} +export const CategoryOneIndicators = ['', 'selected'] + +/** +`Uint8Array` byte index's map for bits[4-7]: `0000 bbbb`. Category 2 attributes change +the border color of a `ByteValue`. + +- `0000 bbbb`: (0) Viewport byte index has no category 2 attribute. +- `0001 bbbb`: (16) Viewport byte index is a search result. +- `0010 bbbb`: (17) Viewport byte index is a replace result. -export enum HightlightCategoryMasks { +> **NOTE:** bits[4-7] get shifted by 4 bits when querying categories. +*/ +enum CategoryTwoIndications { None = 0, - ActiveSelection = 1, - ConsideredForSelection = 2, - SearchResult = 4, + IsSearchResult = 1, + IsReplaceResult = 2, +} +export const CategoryTwoIndicators = ['', 'isSearchResult', 'isReplaceResult'] + +export function byteCategoryValue( + indicationValue: number, + category: IndicatorCategoryShift +): number { + return indicationValue >> category +} + +type IndexCriteria = { + start: number + end: number + data: any[] } -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] - - for (let i = 0; i < 1024; i++) { - selectionHighlightLUT[i] = - i >= start && i <= end ? 1 << $selectionHighlightMask : 0 +class ViewportByteIndications extends SimpleWritable { + protected init(): Uint8Array { + return new Uint8Array(VIEWPORT_CAPACITY_MAX).fill(0) + } + private obtainIterationCriteria( + data: any[], + viewportFileOffset: number + ): IndexCriteria { + const start = data.findIndex((x) => x >= viewportFileOffset) + const end = data.findIndex( + (x) => x >= viewportFileOffset + VIEWPORT_CAPACITY_MAX + ) + let ret: IndexCriteria = { + start: start, + end: end, + data: data.slice(start, end >= 0 ? end : data.length), } + return ret + } + public clearSearchIndications() { + this.store.update((indications) => { + return indications.map((byte) => { + byte &= ~(byte & CATEGORY_TWO_MASK) + return byte + }) + }) + } + public updateSearchIndications(data: number[], byteWidth: number) { + this.store.update((indications) => { + const viewportFileOffset = get(viewport).fileOffset + const searchCriteria = this.obtainIterationCriteria( + data, + viewportFileOffset + ) + + this.clearSearchIndications() - return selectionHighlightLUT + searchCriteria.data.forEach((offset) => { + for (let i = 0; i < byteWidth; i++) + indications[offset - viewportFileOffset + i] |= + CategoryTwoIndications.IsSearchResult << + IndicatorCategoryShift.CategoryTwo + }) + searchResultsUpdated.set(true) + return indications + }) + } + public updateReplaceIndications( + data: DataReplacement[], + viewportFileOffset: number + ) { + this.update((indications) => { + const criteriaStart = data.findIndex( + (replacementData) => replacementData.offset >= viewportFileOffset + ) + const criteriaEnd = data.findIndex( + (replacementData) => + replacementData.offset >= viewportFileOffset + VIEWPORT_CAPACITY_MAX + ) + const matchingReplacements = data.slice( + criteriaStart, + criteriaEnd >= 0 ? criteriaEnd : data.length + ) + + matchingReplacements.forEach((replacementData) => { + for (let i = 0; i < replacementData.byteLength; i++) + indications[replacementData.offset - viewportFileOffset + i] |= + CategoryTwoIndications.IsReplaceResult << + IndicatorCategoryShift.CategoryTwo + }) + return indications + }) + } + public updateSelectionIndications(start: number, end: number) { + this.store.update((indications) => { + if (start > end && end > -1) [start, end] = [end, start] + + for (let i = 0; i < VIEWPORT_CAPACITY_MAX; i++) { + indications[i] = + i >= start && i <= end + ? CategoryOneIndications.Selected & CATEGORY_ONE_MASK + : CategoryOneIndications.None & CATEGORY_ONE_MASK + } + + return indications + }) } -) - -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 - ) - - searchResultsHighlightLUT.fill(0) - - searchCriteria.forEach((offset) => { - for (let i = 0; i < byteWidth; i++) - searchResultsHighlightLUT[offset - viewportFileOffset + i] = 1 - }) - searchResultsUpdated.set(true) -} -export function clearSearchResultsHighlights() { - searchResultsHighlightLUT.fill(0) } + +export const viewportByteIndicators = new ViewportByteIndications()