VSCode: obtain editor content in Language Server - visual-studio-code

I'm trying to develop a Language Server to a new language in VS Code and I'm using the Microsoft sample as reference (https://github.com/microsoft/vscode-extension-samples/tree/master/lsp-sample).
In their sample the autocompletion is done in this chunk of code:
connection.onCompletion(
(_textDocumentPosition: TextDocumentPositionParams): CompletionItem[] => {
// The pass parameter contains the position of the text document in
// which code complete got requested. For the example we ignore this
// info and always provide the same completion items.
return [
{
label: 'TypeScript',
kind: CompletionItemKind.Text,
data: 1
},
{
label: 'JavaScript',
kind: CompletionItemKind.Text,
data: 2
}
];
}
);
As the comment says, it's a dumb autocompletion system, since it always provides the same suggestions.
I can see that there's an input parameter of type TextDocumentPositionParams and this type has the following interface:
export interface TextDocumentPositionParams {
/**
* The text document.
*/
textDocument: TextDocumentIdentifier;
/**
* The position inside the text document.
*/
position: Position;
}
It has the cursor position and a TextDocumentIdentifier but the last only has a uri property.
I want to create an intelligent autocomplete system, based on the type of the object of the word in the cursor position.
This sample is very limited and I'm kinda lost here. I guess I could read the file in the uri property and based on the cursor position I could figure out which items I should suggest. But how about when the file is not saved? If I read the file I would read the data that is on disk, and not what is currently shown in the editor.
What's the best approach to do that?

The Language Server Protocol supports text synchronization, see TextDocumentSyncOptions in ServerCapabilities and the corresponding methods (textDocument/didChange, didChange, didClose...). A Language Server will usually keep a copy of all open documents in memory.
The sample you linked actually makes use of this, but the synchronization itself is abstracted away into the TextDocuments class from vscode-languageserver. As a result server.ts doesn't have to do much more than this:
let documents: TextDocuments = new TextDocuments();
[...]
documents.listen(connection);
You can then simply use documents.get(uri).getText() to obtain the text shown in the editor.

Related

VsCode Extension custom CompletionItem disables built-in Intellisense

I am working on a VsCode extension in that I want to provide custom snippets for code completion.
I know about the option of using snippet json files directly, however those have the limitation of not being able to utilize the CompletionItemKind property that determines the icon next to the completion suggestion in the pop-up.
My issue:
If I implement a simple CompletionItemProvider like this:
context.subscriptions.push(
vscode.languages.registerCompletionItemProvider(
{scheme:"file",language:"MyLang"},
{
provideCompletionItems(document: vscode.TextDocument, position: vscode.Position) {
let item = new vscode.CompletionItem('test');
item.documentation = 'my test function';
item.kind = vscode.CompletionItemKind.Function;
return [item];
}
}
)
)
then the original VsCode IntelliSense text suggestions are not shown anymore, only my own. Should I just return a kind of an empty response, like
provideCompletionItems(document: vscode.TextDocument, position: vscode.Position) {
return [null|[]|undefined];
}
the suggestions appear again as they should. It seems to me that instead of merging the results of the built-in IntelliSense and my own provider, the built-in ones get simply overridden.
Question:
How can I keep the built-in IntelliSense suggestions while applying my own CompletionItems?
VsCode Version: v1.68.1 Ubuntu
I seem to have found the answer for my problem, so I will answer my question.
Multiple providers can be registered for a language. In that case providers are sorted
by their {#link languages.match score} and groups of equal score are sequentially asked for
completion items. The process stops when one or many providers of a group return a
result.
My provider seems to provide results that are just higher scored than those of IntelliSense.
Since I didn't provide any trigger characters, my CompletionItems were comteping directly with the words found by the built-in system by every single pressed key and won.My solution is to simply parse and register the words in my TextDocument myself and extend my provider results by them. I could probably just as well create and register a new CompletionItemProvider for them if I wanted to, however I decided to have a different structure for my project.

How to access the child nodes in a device tree (DTS) in Zephyr using DT_FOREACH_CHILD

I'm developing an application for an nRF52 SoC to access some external devices, kind of detectors in this case, so I have defined a custom format (and its corresponding yaml file) for my device access description node. It is kind of:
n: detectors {
compatible = "foo-detectors";
// Definition of first channel
det0: det_0 {
irq-pins = <13 (GPIO_PULL_UP | GPIO_ACTIVE_LOW)>;
label = "Bar detector channel 1";
};
// Definition of second channel
det1: det_1 {
irq-pins = <17 (GPIO_PULL_UP | GPIO_ACTIVE_LOW)>;
label = "Bar detector channel 2";
};
};
Suppose I have a structure definition for holding data about the pins to which those devices are physically connected, say:
struct foo_detector_desc {
int irqpin;
int irqpin_flags;
}
Using Zephyr macros, I can use from my code the individual values for those nodes. For instance, DT_PROP_BY_IDX(DT_NODELABEL(det0), irq_pins, 0) expands to 13, DT_PROP_BY_IDX(DT_NODELABEL(det0), irq_pins, 1) expands to the value of OR'ed flags GPIO_PULL_UP | GPIO_ACTIVE_LOW.
But I don't want to create a ten-page code full of conditionals in the form DT_NODE_EXISTS(DT_ALIAS(det#)), but something more compact, flexible and maintainable.
I came across Zephyr macro DT_FOREACH_CHILD that is intended for being used in that scenario with which I can create a list with the labels, as showcased in the example embedded in the documentation:
#define LABEL_AND_COMMA(node_id) DT_LABEL(node_id),
const char *child_labels[] = {
DT_FOREACH_CHILD(DT_NODELABEL(n), LABEL_AND_COMMA)
};
Trying to use that for filling a static structures array, I tried following code but, yet it expands to two elements in the array, the structure fields are not initialised with desired values.
#define PIN_INFO_AND_COMMA(node_id) \
{ \
.pin=DT_PROP_BY_IDX(node_id, irq_pins, 0),\
.flags=DT_PROP_BY_IDX(node_id, irq_pins, 1),\
},
const struct detector_data _det_data[] = {
DT_FOREACH_CHILD(DT_NODELABEL(n), PIN_INFO_AND_COMMA)
};
I'm using Visual Studio Code with the nRF Connect plugin.
Is there a way to generate/see how those macros expand when compiled?
What is the correct way for initialising the structure fields (pins, flags)?
BR
It is possible to expand the macros one level at a time in VS Code. It is needed to click over the macro, then select it by double-clicking on it, and a bulb light will, eventually, show up. The option Insert Macro will replace the macro with its expansion.
Regarding using the DTS in code, the code I wrote in my previous post is OK, but I think I found a bug in the debugger. If I don't include any code using the value of _det_data, the debugger doesn't say the constant was optimised-out and displays wrong values when inspecting its content. That made me waste a lot of time.
For anyone interested on using DTS files for nRF devices in Zephyr, I posted all details in a thread in Nordic's developers forum (here).
BR

vscode Editable Virtual Documents in Extensions

I am trying to use vscode to write an extension which interacts with my backend service.
The problem I am having is that my "documents" that I want to edit are actually nested JSON documents. The top level document I am managing looks something like this for instance:
{
'filename': 'My JSON info.exp',
'filecontent': '{"this": "that"}'
}
I'd like to open and edit these files in vscode.
Using Virtual Documents I got to the point where I could open a document from a custom tree, and display the filecontent in a document named with the filename. Worked perfectly until I tried to edit the document content.
I'd like to provide an editor for just the filecontent, then handle things like saves myself using my API.
I looked into the FileSystem Provider, but it doesn't look like what I am looking for as the uris have to actually exist... I think?
Any hints or suggestions?
I'm trying to do something similar with Joplin - opening, editing and saving notes via a REST API.
I'm not as far along as you, but it was my understanding from scanning through the docs that Virtual Documents were read-only. Because of that, I'm dropping my content into a TextDocument so it can be edited; I am super curious if there's a way to override a document's onSave() to do what we're both after.
Below is what I'm doing to open the text document; if you could share any insights into your progress (or better yet code) that'd be awesome.
async openNote(item: Omit<FolderOrNote, 'item'> & { item: JoplinListNote }) {
console.log('openNote: ', item.id, await
noteApi.get(item.id, [
'id',
'parent_id',
'title',
'is_todo',
'todo_completed',
'body'
]).then (
note => {
vscode.workspace.openTextDocument( {
content: note.body,
language: 'markdown'
} ).then(
doc => vscode.window.showTextDocument( doc )
);
}
)
)
}
Edit:
Digging around some more it looks like maybe a CustomEditor is needed. A CustomEditor requires the extension to implement things like loading and saving into the model because VS Code is unaware of how to deal with the raw data. There is also a CustomTextEditor but that appears to be tied to the local filesystem.
There's a YouTube video here that talks about it in detail:
https://youtu.be/Dekn2MHy9Os?t=1147
Here's the sample code:
https://github.com/microsoft/vscode-extension-samples/tree/main/custom-editor-sample

Variable lookup extension in Visual Studio Code

I am looking at some code where some of the variables are really obscure. For example,
h582=30
where h582 might mean temperature. I have a dictionary that tells me what each variable means. Is there any existing feature or would it be possible to extend visual studio code easily to show me the meaning of each variable on mouse hover?
I would recommend using find/replace on the workspace to rewrite the variables to their readable names (I would go crazy trying to read code like that).
But if you can't do that and want to see the real name when you hover the variable, you could write a vscode extension for this. It would not be too hard - you just need to implement a hover provider which would check the name of the token under the cursor, look it up in the dictionary, and return the result. Example:
vscode.languages.registerHoverProvider('javascript', {
provideHover(document, position, token) {
const hoveredWord = document.getText(document.getWordRangeAtPosition(position));
const mappedWord = dictionary[hoveredWord]
if (mappedWord) {
return new Hover(mappedWord);
} else {
return null;
}
}
});
See the docs here: https://code.visualstudio.com/docs/extensionAPI/vscode-api#_languages

writing a Jacada Interaction extension

I want to create an "extension" for a Jacada Interaction (to extend functionality), in my case to parse and assign the numerical part of serialNumber (a letter, followed by digits) to a numeric global ("system") variable, say serialNumeric. What I am lacking is the structure and syntax to make this work, including the way to reference interaction variables from within the extension.
Here is my failed attempt, with lines commented out to make it innocuous after failing; I think I removed "return page;" after crashing, whereupon it still crashed:
initExtensions("serialNumeric", function(app){
app.registerExtension("loaded", function(ctx, page) {
// Place your extension code here
//$('[data-refname="snum"]').val('serialNumber');
// snum = Number(substring(serialNumber,1))
});
});
Here is an example of one that works:
/**
* Description: Add swiping gestures to navigate the next/previous pages
*/
initExtensions("swipe", function(app) {
// Swipe gestures (mobile only)
app.registerExtension('pageRenderer', function(ctx, page) {
page.swipe(function(evt) {
(evt.swipestart.coords[0] - evt.swipestop.coords[0] > 0)
? app.nextButton.trigger('click')
: app.backButton.trigger('click')
});
return page;
});
});
After reading the comment below, I tried the following, unsuccessfully (the modified question variable is not written back to that variable). It rendered poorly in the comment section, so I am putting it here:
initExtensions("serialNumeric", function(app){
app.registerExtension("loaded", function(ctx, page) {
var sernum = new String($('[data-refname="enter"] input'));
var snumeric = new String(sernum.substr(1));
$('[data-refname="enter"] input').val(snumeric);
});
});
I would like to understand when this code will run: it seems logical that it would run when the variable is assigned. Thanks for any insight ~
In your case, you extend loaded event. You don't have to return the page from the extension like in your working example below.
The page argument contains the DOM of the page you have just loaded, the ctx argument contains the data of the page in JSON form. You can inspect the content of both arguments in the browser's inspection tools. I like Chrome. Press F12 on Windows or Shift+Ctrl+I on Mac.
The selector $('[data-refname="snum"] input') will get you the input field from the question with the name snum that you defined in the designer. You can then place the value in the input field with the value from the serialNumber variable.
$('[data-refname="snum"] input').val(serialNumber);
You can also read values in the same way.
You can't (at this point) access interaction variables in the extension, unless you place theses variables inside question fields.
Here is a simple example how to put your own value programmatically into a input field and cause it to read it into the model, so upon next it will be sent to the server. You are welcome to try more sophisticated selectors to accommodate for your own form.
initExtensions("sample", function(app){
app.registerExtension("loaded", function(ctx, page) {
// simple selector
var i = $('input');
// set new value
i.val('some new value');
// cause trigger so we can read into our model
i.trigger('change');
});
});