Skip to content

Commit 92d45b1

Browse files
Merge pull request #271 from eccenca/bugfix/text-marking-autocomplete-CMEM-6577
fixed text marking on text change
2 parents f7f8dd3 + cb5aa1e commit 92d45b1

2 files changed

Lines changed: 40 additions & 27 deletions

File tree

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,11 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p
1010

1111
- `intent` property to `Button`, `FieldItem`, `FieldSet`, `Notification`, and `Spinner`
1212

13+
### Fixed
14+
15+
- `<CodeAutocompleteField />` and `<AutoSuggestion />`:
16+
- Error highlighting is always visible by underlining the respective text
17+
1318
## [24.1.0] - 2025-04-16
1419

1520
### Added

src/components/AutoSuggestion/AutoSuggestion.tsx

Lines changed: 35 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
/* eslint-disable @typescript-eslint/no-explicit-any */
12
import React, { useEffect, useMemo, useState } from "react";
23
import { Classes as BlueprintClassNames } from "@blueprintjs/core";
34
import { EditorView, Rect } from "@codemirror/view";
@@ -207,7 +208,7 @@ const AutoSuggestion = ({
207208
const suggestionRequestData = React.useRef<RequestMetaData>({ requestId: undefined });
208209
const [pathValidationPending, setPathValidationPending] = React.useState(false);
209210
const validationRequestData = React.useRef<RequestMetaData>({ requestId: undefined });
210-
const [, setErrorMarkers] = React.useState<any[]>([]);
211+
const errorMarkers = React.useRef<any[]>([])
211212
const [validationResponse, setValidationResponse] = useState<CodeAutocompleteFieldValidationResult | undefined>(
212213
undefined
213214
);
@@ -219,8 +220,8 @@ const AutoSuggestion = ({
219220
CodeAutocompleteFieldSuggestionWithReplacementInfo | undefined
220221
>(undefined);
221222
const [cm, setCM] = React.useState<EditorView>();
222-
const currentCm = React.useRef<EditorView>()
223-
currentCm.current = cm
223+
const currentCm = React.useRef<EditorView>();
224+
currentCm.current = cm;
224225
const isFocused = React.useRef(false);
225226
const autoSuggestionDivRef = React.useRef<HTMLDivElement>(null);
226227
/** Mutable editor state, since this needs to be current in scope of the SingleLineEditorComponent. */
@@ -242,16 +243,16 @@ const AutoSuggestion = ({
242243
changes: { from: 0, to: currentCm.current.state?.doc.length, insert: initialValue },
243244
});
244245
// Validate initial value change
245-
checkValuePathValidity(initialValue)
246+
checkValuePathValidity(initialValue);
246247
}
247248
}, [initialValue, reInitOnInitialValueChange]);
248249

249250
React.useEffect(() => {
250-
if(currentCm.current) {
251+
if (currentCm.current) {
251252
// Validate initial value
252-
checkValuePathValidity(initialValue)
253+
checkValuePathValidity(initialValue);
253254
}
254-
}, [currentCm.current!!])
255+
}, [!!currentCm.current]);
255256

256257
const setCurrentIndex = (newIndex: number) => {
257258
editorState.index = newIndex;
@@ -263,10 +264,9 @@ const AutoSuggestion = ({
263264
editorState.cm = cm;
264265
}, [cm, editorState]);
265266

266-
const dispatch = // eslint-disable-next-line @typescript-eslint/no-empty-function
267-
(
268-
typeof editorState?.cm?.dispatch === "function" ? editorState?.cm?.dispatch : () => {}
269-
) as EditorView["dispatch"];
267+
const dispatch = (
268+
typeof editorState?.cm?.dispatch === "function" ? editorState?.cm?.dispatch : () => {}
269+
) as EditorView["dispatch"];
270270

271271
React.useEffect(() => {
272272
editorState.dropdownShown = shouldShowDropdown;
@@ -288,8 +288,9 @@ const AutoSuggestion = ({
288288
return () => removeMarkFromText({ view: cm, from, to });
289289
}
290290
} else {
291-
//remove redundant markers
292-
cm && removeMarkFromText({ view: cm, from: 0, to: cm.state?.doc.length });
291+
if (cm) {
292+
removeMarkFromText({ view: cm, from: 0, to: cm.state?.doc.length });
293+
}
293294
}
294295
return;
295296
}, [highlightedElement, selectedTextRanges, cm]);
@@ -298,32 +299,32 @@ const AutoSuggestion = ({
298299
React.useEffect(() => {
299300
const parseError = validationResponse?.parseError;
300301
if (cm) {
302+
const clearCurrentErrorMarker = () => {
303+
if(errorMarkers.current.length) {
304+
const [from, to] = errorMarkers.current;
305+
removeMarkFromText({ view: cm, from, to })
306+
errorMarkers.current = []
307+
}
308+
}
301309
if (parseError) {
302310
const { message, start, end } = parseError;
303311
const { toOffset, fromOffset } = getOffsetRange(cm, start, end);
304-
const { from, to } = markText({
312+
clearCurrentErrorMarker()
313+
const {from, to} = markText({
305314
view: cm,
306315
from: fromOffset,
307316
to: toOffset,
308317
className: `${eccgui}-autosuggestion__text--highlighted-error`,
309318
title: message,
310319
});
311-
312-
setErrorMarkers((previousMarkers) => {
313-
previousMarkers.forEach((m) => removeMarkFromText({ view: cm, from: m.from, to: m.to }));
314-
return [from, to];
315-
});
320+
errorMarkers.current = [from, to]
316321
} else {
317-
// Valid, clear all error markers
318-
setErrorMarkers((previous) => {
319-
previous.forEach((m) => removeMarkFromText({ view: cm, from: m.from, to: m.to }));
320-
return [];
321-
});
322+
clearCurrentErrorMarker()
322323
}
323324
}
324325

325326
const isValid = validationResponse?.valid === undefined || validationResponse.valid;
326-
onInputChecked && onInputChecked(isValid);
327+
onInputChecked?.(isValid);
327328
}, [validationResponse?.valid, validationResponse?.parseError, cm, onInputChecked]);
328329

329330
/** generate suggestions and also populate the replacement indexes dict */
@@ -390,6 +391,7 @@ const AutoSuggestion = ({
390391
try {
391392
const result: CodeAutocompleteFieldValidationResult | undefined = await checkInput(inputString);
392393
setValidationResponse(result);
394+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
393395
} catch (e) {
394396
setValidationResponse(undefined);
395397
// TODO: Error handling
@@ -425,6 +427,7 @@ const AutoSuggestion = ({
425427
setSuggestionResponse(result);
426428
}
427429
}
430+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
428431
} catch (e) {
429432
setSuggestionResponse(undefined);
430433
// TODO: Error handling
@@ -534,8 +537,13 @@ const AutoSuggestion = ({
534537
};
535538

536539
const handleInputFocus = (focusState: boolean) => {
537-
onFocusChange && onFocusChange(focusState);
538-
focusState ? setShouldShowDropdown(true) : closeDropDown();
540+
onFocusChange?.(focusState);
541+
if (focusState) {
542+
setShouldShowDropdown(true);
543+
} else {
544+
closeDropDown();
545+
}
546+
539547
if (!isFocused.current && focusState) {
540548
// Just got focus
541549
// Clear suggestions and repeat suggestion request, something else might have changed while this component was not focused

0 commit comments

Comments
 (0)