I’m trying to implement an autocomplete for JS object properties, meaning that the autocomplete function will get an arbitrary object (a representation of JSON) and will help the user to compose nested paths as she types along.
For example if this is the object:
{
a: {
b: {
c: 1
}
}
}
the function should autocomplete as you type a → will display a a. → will display b a.b. → will display c
This is what should be a working example of my experiments so far
The problem is that the autocomplete working only on the first type, although if you will examine the console.log you will see that the completions array is being populated as expected it just won’t show the popup any more after the first completion.
Am I missing something here?
Also i’m wondering if there’s a less hacky way or more conventional way to achieve that as my code feels a bit messy.
Thanks.
UPDATE
I made some progress by changing approach and using the syntaxTree example with minor modifications.
this partially working, it will complete twice but after 2 nested properties it will just stop showing completions.
The weird thing is that this problem also occurs in the official example!
In the bottom of the page in the live example try to type window.location.href it will complete window and location but will stop after that.
That’s how that completion source is defined (the code is right there on the page)—it doesn’t look further than a single member expression node.
I’ve been planning to add something to make this easier to @codemirror/lang-javascript, and finally went ahead and implemented this. See scopeCompletionSource.
@marijn Amazing! Thanks, this is exactly what i was looking for, I still need to apply my own modifications like ignoring all the properties that coming from the prototype, but this is a great direction.
function enumeratePropertyCompletions(obj: any, top: boolean): readonly Completion[] {
let options = [], seen: Set<string> = new Set
for (let depth = 0;; depth++) {
for (let name of (Object.getOwnPropertyNames || Object.keys)(obj)) {
if (seen.has(name)) continue
seen.add(name)
let value
try { value = obj[name] }
catch(_) { continue }
options.push({
label: name,
type: typeof value == "function" ? (/^[A-Z]/.test(name) ? "class" : top ? "function" : "method")
: top ? "variable" : "property",
boost: -depth
})
}
let next = Object.getPrototypeOf(obj)
if (!next) return options
obj = next
}
}
just remove the Object.getOwnPropertyNames (in 4th line) you’ll be fine, no more prototype names