Skip to content

Instantly share code, notes, and snippets.

@mwrouse
Last active April 25, 2024 08:47
Show Gist options
  • Star 47 You must be signed in to star a gist
  • Fork 6 You must be signed in to fork a gist
  • Save mwrouse/05d8c11cd3872c19c684bd1904a2202e to your computer and use it in GitHub Desktop.
Save mwrouse/05d8c11cd3872c19c684bd1904a2202e to your computer and use it in GitHub Desktop.
Autocompletion for an object in the monaco editor
function ShowAutocompletion(obj) {
// Disable default autocompletion for javascript
monaco.languages.typescript.javascriptDefaults.setCompilerOptions({ noLib: true });
// Helper function to return the monaco completion item type of a thing
function getType(thing, isMember) {
isMember = (isMember == undefined) ? (typeof isMember == "boolean") ? isMember : false : false; // Give isMember a default value of false
switch ((typeof thing).toLowerCase()) {
case "object":
return monaco.languages.CompletionItemKind.Class;
case "function":
return (isMember) ? monaco.languages.CompletionItemKind.Method : monaco.languages.CompletionItemKind.Function;
default:
return (isMember) ? monaco.languages.CompletionItemKind.Property : monaco.languages.CompletionItemKind.Variable;
}
}
// Register object that will return autocomplete items
monaco.languages.registerCompletionItemProvider('javascript', {
// Run this function when the period or open parenthesis is typed (and anything after a space)
triggerCharacters: ['.', '('],
// Function to generate autocompletion results
provideCompletionItems: function(model, position, token) {
// Split everything the user has typed on the current line up at each space, and only look at the last word
var last_chars = model.getValueInRange({startLineNumber: position.lineNumber, startColumn: 0, endLineNumber: position.lineNumber, endColumn: position.column});
var words = last_chars.replace("\t", "").split(" ");
var active_typing = words[words.length - 1]; // What the user is currently typing (everything after the last space)
// This if statement adds support for autocomplete inside if statements and stuff
if (active_typing.includes('(')) {
active_typing = active_typing.split('(');
active_typing = active_typing[active_typing.length - 1];
}
// If the last character typed is a period then we need to look at member objects of the obj object
var is_member = active_typing.charAt(active_typing.length - 1) == ".";
// Array of autocompletion results
var result = [];
// Used for generic handling between member and non-member objects
var last_token = obj;
var prefix = '';
if (is_member) {
// Is a member, get a list of all members, and the prefix
var parents = active_typing.substring(0, active_typing.length - 1).split(".");
last_token = obj[parents[0]];
prefix = parents[0];
// Loop through all the parents the current one will have (to generate prefix)
for (var i = 1; i < parents.length; i++) {
var propToLookFor = parents[i];
// Support for arrays
var isPropAnArray = propToLookFor.charAt(propToLookFor.length - 1) == ']';
if (isPropAnArray)
propToLookFor = propToLookFor.split('[')[0];
if (last_token.hasOwnProperty(propToLookFor)) {
prefix += '.' + propToLookFor;
last_token = last_token[propToLookFor];
if (isPropAnArray && Array.isArray(last_token))
{
last_token = last_token[0];
}
} else {
// Not valid
return result;
}
}
prefix += '.';
}
// Array properties
if (Array.isArray(last_token))
last_token = { length: 0 };
// Get all the child properties of the last token
for (var prop in last_token) {
// Do not show properites that begin with "__"
if (last_token.hasOwnProperty(prop) && !prop.startsWith("__")) {
// Get the detail type (try-catch) incase object does not have prototype
var details = '';
try {
details = last_token[prop].__proto__.constructor.name;
} catch (e) {
details = typeof last_token[prop];
}
// Create completion object
var to_push = {
label: prefix + prop,
kind: getType(last_token[prop], is_member),
detail: details,
insertText: prop
};
// Change insertText and documentation for functions
if (to_push.detail.toLowerCase() == 'function') {
to_push.insertText += "(";
to_push.documentation = (last_token[prop].toString()).split("{")[0]; // Show function prototype in the documentation popup
}
// Add to final results
result.push(to_push);
}
}
return {
suggestions: result
};
}
});
}
// This simple example will show autocompletion for "Person" with the given properties of name, age
ShowAutocompletion({
Person: {
name: "",
age: 0
}
});
@mwrouse
Copy link
Author

mwrouse commented Feb 17, 2022

I see, that is not was this was designed for. From the link you added looks like it would be fundamentally different.

This was made when a friend of mine was working on a little game editor, he wanted to expose the editor to only the game objects available to the user. This allowed him to write the code that works, and pass it directly to the monaco editor and it would add auto-completion for it to the user.

The things this is auto completing should already exist in the context of whatever the editor code is going to be run in once written.

@DaniShMyGit
Copy link

DaniShMyGit commented Feb 17, 2022

I understand, thanks for your response!

@velikayikci
Copy link

var code = 'const person = ' + JSON.stringify(yourObject);
var libUri = 'ts:filename/facts.d.ts';
monaco.languages.typescript.typescriptDefaults.addExtraLib(code, libUri);
monaco.editor.createModel(code, 'typescript', monaco.Uri.parse(libUri));

:)

@Haigos
Copy link

Haigos commented Nov 8, 2022

Just came across this, exactly what I needed. Thx for sharing!

@kankk
Copy link

kankk commented Nov 14, 2023

var code = 'const person = ' + JSON.stringify(yourObject); var libUri = 'ts:filename/facts.d.ts'; monaco.languages.typescript.typescriptDefaults.addExtraLib(code, libUri); monaco.editor.createModel(code, 'typescript', monaco.Uri.parse(libUri));

:)

This is the recommended usage of monaco-editor, but it cannot solve the variable autocompletion on this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment