diff --git a/src/parser/htmlScanner.ts b/src/parser/htmlScanner.ts index 01750bd..80f45db 100644 --- a/src/parser/htmlScanner.ts +++ b/src/parser/htmlScanner.ts @@ -139,7 +139,7 @@ class MultiLineStream { } const _BNG = '!'.charCodeAt(0); const _MIN = '-'.charCodeAt(0); -const _LAN = '<'.charCodeAt(0); +export const _LAN = '<'.charCodeAt(0); const _RAN = '>'.charCodeAt(0); const _FSL = '/'.charCodeAt(0); const _EQS = '='.charCodeAt(0); diff --git a/src/services/htmlCompletion.ts b/src/services/htmlCompletion.ts index d6e899b..f0f1fda 100644 --- a/src/services/htmlCompletion.ts +++ b/src/services/htmlCompletion.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { HTMLDocument, Node } from '../parser/htmlParser.js'; -import { createScanner } from '../parser/htmlScanner.js'; +import { createScanner, _LAN } from '../parser/htmlScanner.js'; import { CompletionConfiguration, ICompletionParticipant, ScannerState, TokenType, LanguageServiceOptions, DocumentContext, Position, CompletionList, CompletionItemKind, Range, TextEdit, InsertTextFormat, CompletionItem, MarkupKind, TextDocument @@ -293,7 +293,19 @@ export class HTMLCompletion { let valueContentEnd = valueEnd; // valueEnd points to the char after quote, which encloses the replace range if (valueEnd > valueStart && text[valueEnd - 1] === text[valueStart]) { - valueContentEnd--; + // Even when a matching quote is found, the scanner may have latched + // onto an unrelated later quote across HTML tag boundaries. If any + // `<` appears between the cursor and the alleged closing quote, + // treat the value as unclosed and clamp so the completion cannot + // delete subsequent markup. See microsoft/vscode#273226. + let crossesTagBoundary = false; + for (let i = offset; i < valueEnd - 1; i++) { + if (text.charCodeAt(i) === _LAN) { + crossesTagBoundary = true; + break; + } + } + valueContentEnd = crossesTagBoundary ? offset : valueContentEnd - 1; } else { // unclosed quote: clamp the end to the cursor position so that // the replace range does not extend into subsequent HTML content diff --git a/src/test/completion.test.ts b/src/test/completion.test.ts index fc7e2ab..7f6c1b2 100644 --- a/src/test/completion.test.ts +++ b/src/test/completion.test.ts @@ -172,6 +172,18 @@ suite('HTML Completion', () => { testCompletionFor('