Autocomplete JS object properties

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.

1 Like

@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.

Hey can you help me with code like how to use scopeCompletionSource? @oferitz

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

This is working fine, but I am using this for array and not able to access the index of

eg. {
a: [1, 2, 3]
}

Then a. or a[ is not suggesting anything

and also what to do to implement same thing in html?