From 688a003d1d7dab24d46bec6b22f127f13be38ad8 Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Tue, 9 Jun 2026 17:06:55 -0400 Subject: [PATCH] fix(adminui): tag-path completion replaces the whole dotted path, not just the last segment Monaco's word definition splits on '.', so accepting a full tag path while a partial path was typed (e.g. "X.Protected") duplicated the prefix (-> "X.X.ProtectedValue"). Tag-path items now replace the whole literal content from the opening quote to the caret. --- .../wwwroot/js/monaco-init.js | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/wwwroot/js/monaco-init.js b/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/wwwroot/js/monaco-init.js index f14ab926..714a7e37 100644 --- a/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/wwwroot/js/monaco-init.js +++ b/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/wwwroot/js/monaco-init.js @@ -36,17 +36,30 @@ if (!resp.ok) return { suggestions: [] }; const data = await resp.json(); const word = model.getWordUntilPosition(position); - const range = { + const wordRange = { startLineNumber: position.lineNumber, endLineNumber: position.lineNumber, startColumn: word.startColumn, endColumn: word.endColumn }; + // Tag-path items (detail "tag path") are full dotted paths inside a string literal. + // Monaco's word definition splits on '.', so the word range would cover only the last + // segment and accepting the full path would duplicate the prefix + // (e.g. "X.Protected" + insert "X.ProtectedValue" -> "X.X.ProtectedValue"). + // Replace the whole partial path instead: from just after the opening quote to the caret. + const lineText = model.getLineContent(position.lineNumber); + const beforeCaret = lineText.substring(0, position.column - 1); + const quoteIdx = Math.max(beforeCaret.lastIndexOf('"'), beforeCaret.lastIndexOf("'")); + const literalRange = { + startLineNumber: position.lineNumber, endLineNumber: position.lineNumber, + startColumn: quoteIdx >= 0 ? quoteIdx + 2 : position.column, + endColumn: position.column + }; return { suggestions: (data.items || []).map(function (it) { return { label: it.label, insertText: it.insertText, detail: it.detail, kind: KIND_MAP[it.kind] != null ? KIND_MAP[it.kind] : 18, insertTextRules: it.insertTextRules || 0, - range: range + range: it.detail === "tag path" ? literalRange : wordRange }; }) };