From 7d9e09f5a7abad231a8f0e2bc86690e56cfc5ad9 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Mon, 10 Feb 2025 16:23:13 +0000 Subject: [PATCH] Handle long auto-complete suggests Fixes #5028 --- .../src/js/ui/common/autoComplete.js | 2 +- .../src/js/ui/common/typedInput.js | 42 ++++++++++++------- .../src/sass/ui/common/autoComplete.scss | 11 +++++ .../@node-red/runtime/lib/api/context.js | 20 +++++++-- 4 files changed, 56 insertions(+), 19 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/common/autoComplete.js b/packages/node_modules/@node-red/editor-client/src/js/ui/common/autoComplete.js index a3ce0bcd1..a4de3f2a9 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/common/autoComplete.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/common/autoComplete.js @@ -61,7 +61,7 @@ } this.menu = RED.popover.menu({ tabSelect: true, - width: 300, + width: Math.max(300, this.element.width()), maxHeight: 200, class: "red-ui-autoComplete-container", options: completions, diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/common/typedInput.js b/packages/node_modules/@node-red/editor-client/src/js/ui/common/typedInput.js index 47355565f..22e509d08 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/common/typedInput.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/common/typedInput.js @@ -63,6 +63,7 @@ pre: value.substring(0,idx), match: value.substring(idx,idx+len), post: value.substring(idx+len), + exact: idx === 0 && value.length === searchValue.length } } function generateSpans(match) { @@ -83,7 +84,7 @@ const srcMatch = getMatch(optSrc, val); if (valMatch.found || srcMatch.found) { const element = $('
',{style: "display: flex"}); - const valEl = $('
',{style:"font-family: var(--red-ui-monospace-font); white-space:nowrap; overflow: hidden; flex-grow:1"}); + const valEl = $('
',{ class: "red-ui-autoComplete-completion" }); valEl.append(generateSpans(valMatch)); valEl.appendTo(element); if (optSrc) { @@ -159,7 +160,7 @@ if (valMatch.found) { const optSrc = envVarsMap[v] const element = $('
',{style: "display: flex"}); - const valEl = $('
',{style:"font-family: var(--red-ui-monospace-font); white-space:nowrap; overflow: hidden; flex-grow:1"}); + const valEl = $('
',{ class: "red-ui-autoComplete-completion" }); valEl.append(generateSpans(valMatch)) valEl.appendTo(element) @@ -201,7 +202,7 @@ const that = this const getContextKeysFromRuntime = function(scope, store, searchKey, done) { contextKnownKeys[scope] = contextKnownKeys[scope] || {} - contextKnownKeys[scope][store] = contextKnownKeys[scope][store] || new Set() + contextKnownKeys[scope][store] = contextKnownKeys[scope][store] || new Map() if (searchKey.length > 0) { try { RED.utils.normalisePropertyExpression(searchKey) @@ -223,11 +224,12 @@ const result = data[store] || {} const keys = result.keys || [] const keyPrefix = searchKey + (searchKey.length > 0 ? '.' : '') - keys.forEach(key => { + keys.forEach(keyInfo => { + const key = keyInfo.key if (/^[a-zA-Z_$][0-9a-zA-Z_$]*$/.test(key)) { - contextKnownKeys[scope][store].add(keyPrefix + key) + contextKnownKeys[scope][store].set(keyPrefix + key, keyInfo) } else { - contextKnownKeys[scope][store].add(searchKey + "[\""+key.replace(/"/,"\\\"")+"\"]") + contextKnownKeys[scope][store].set(searchKey + "[\""+key.replace(/"/,"\\\"")+"\"]", keyInfo) } }) done() @@ -242,14 +244,14 @@ // Get the flow id of the node we're editing const editStack = RED.editor.getEditStack() if (editStack.length === 0) { - done([]) + done(new Map()) return } const editingNode = editStack.pop() if (editingNode.z) { scope = `${scope}/${editingNode.z}` } else { - done([]) + done(new Map()) return } } @@ -269,17 +271,29 @@ return function(val, done) { getContextKeys(val, function (keys) { const matches = [] - keys.forEach(v => { + keys.forEach((keyInfo, v) => { let optVal = v let valMatch = getMatch(optVal, val); - if (!valMatch.found && val.length > 0 && val.endsWith('.')) { - // Search key ends in '.' - but doesn't match. Check again - // with [" at the end instead so we match bracket notation - valMatch = getMatch(optVal, val.substring(0, val.length - 1) + '["') + if (!valMatch.found && val.length > 0) { + if (val.endsWith('.')) { + // Search key ends in '.' - but doesn't match. Check again + // with [" at the end instead so we match bracket notation + valMatch = getMatch(optVal, val.substring(0, val.length - 1) + '["') + // } else if (val.endsWith('[') && /^array/.test(keyInfo.format)) { + // console.log('this case') + } } if (valMatch.found) { const element = $('
',{style: "display: flex"}); - const valEl = $('
',{style:"font-family: var(--red-ui-monospace-font); white-space:nowrap; overflow: hidden; flex-grow:1"}); + const valEl = $('
',{ class: "red-ui-autoComplete-completion" }); + // if (keyInfo.format) { + // valMatch.post += ' ' + keyInfo.format + // } + if (valMatch.exact && /^array/.test(keyInfo.format)) { + valMatch.post += `[0-${keyInfo.length}]` + optVal += '[' + + } valEl.append(generateSpans(valMatch)) valEl.appendTo(element) matches.push({ diff --git a/packages/node_modules/@node-red/editor-client/src/sass/ui/common/autoComplete.scss b/packages/node_modules/@node-red/editor-client/src/sass/ui/common/autoComplete.scss index 0501bb6a2..85cb4f1db 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/ui/common/autoComplete.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/ui/common/autoComplete.scss @@ -2,4 +2,15 @@ &.red-ui-popover-panel { border-top: none; } + + +} +.red-ui-autoComplete-completion { + font-family: var(--red-ui-monospace-font); + white-space: nowrap; + overflow: hidden; + flex-grow: 1; + text-overflow: ellipsis; + direction: rtl; + text-align: left; } diff --git a/packages/node_modules/@node-red/runtime/lib/api/context.js b/packages/node_modules/@node-red/runtime/lib/api/context.js index f27075577..c13beb9f6 100644 --- a/packages/node_modules/@node-red/runtime/lib/api/context.js +++ b/packages/node_modules/@node-red/runtime/lib/api/context.js @@ -104,13 +104,25 @@ var api = module.exports = { store = store || availableStores.default; ctx.get(key,store,function(err, v) { if (opts.keysOnly) { + const result = {} if (Array.isArray(v)) { - resolve({ [store]: { format: `array[${v.length}]`}}) + result.format = `array[${v.length}]` } else if (typeof v === 'object') { - resolve({ [store]: { keys: Object.keys(v), format: 'Object' } }) + result.keys = Object.keys(v).map(k => { + if (Array.isArray(v[k])) { + return { key: k, format: `array[${v[k].length}]`, length: v[k].length } + } else if (typeof v[k] === 'object') { + return { key: k, format: 'object' } + } else { + return { key: k } + } + }) + result.format = 'object' } else { - resolve({ [store]: { keys: [] }}) + result.keys = [] } + resolve({ [store]: result }) + return } var encoded = util.encodeObject({msg:v}); if (store !== availableStores.default) { @@ -147,7 +159,7 @@ var api = module.exports = { } return } - result[store] = { keys } + result[store] = { keys: keys.map(key => { return { key }}) } c--; if (c === 0) { if (!errorReported) {