How can I tell my extension to look first for &s_?
One solution would be to rename all variable to capital prefix S_, but this is not an option in my scenario.
I am working on an extension for Visual Studio Code(VSC). Variable are defined as followed:
&i_ Var_Six`
&s_ SampelTwo
&...
The implementation in VSC is done by "direct implementation" like that:
let provider1 = vscode.languages.registerCompletionItemProvider({ language: 'test' }, {
provideCompletionItems(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken, context: vscode.CompletionContext) {
vscode.languages.registerCompletionItemProvider
var completions = new Array();
for (let knownElement of TestVar) {
completions.push(new vscode.CompletionItem(knownElement));
}
return completions;
}
});
context.subscriptions.push(provider1);
Now the problem I have is, that the autocompleting looks first for capital letters but also recognizes the & not as part of the variable.
What I want is that I can start typing "s_" and it would suggest all known variables starting with "&s_".
What VSC actually is suggesting for input “s_” is "i_Var_Six". This is because it looks for the capital letter “S” (camelcase) and the char “ _ ” it does not recognize the char “&” because it is defined as a “word separator”.
i'm not sure why & doesn't work, this is the closest way of implementing it, that i know of.
(read the comments)
import * as vscode from 'vscode';
export function activate(context: vscode.ExtensionContext) {
const TestVar: string[] = ['&_awd', '&_Awd', '&_213', '$_34', '$_23'];
let provider1 = vscode.languages.registerCompletionItemProvider(
{ language: 'plaintext' },
{
provideCompletionItems(
document: vscode.TextDocument,
position: vscode.Position,
token: vscode.CancellationToken,
context: vscode.CompletionContext
) {
var completions = [];
for (let knownElement of TestVar) {
const item = new vscode.CompletionItem(knownElement);
// i added this so that the first & sign gets removed,
// you might want to add a check so that this edit only gets added when the user typed a & sign.
item.additionalTextEdits = [
{ newText: '', range: new vscode.Range(new vscode.Position(position.line, position.character - 1), position), },
];
completions.push(item);
}
return completions;
},
},
'.',
'&' // add this so that the completions get triggered when the user types the & sign.
);
context.subscriptions.push(provider1);
}
Related
public var O_RDONLY: Int32 { get }
When I'm looking at stuff inside Darwin.sys.* or Darwin.POSIX.* for example, a lot of these constants are defined as getters. But how does one see the actual value without evaluating the code?
public var O_RDONLY: Int32 { get }
is what the Swift importer generates from the macro definition
#define O_RDONLY 0x0000 /* open for reading only */
in the <sys/fcntl.h> include file. Although this is a fixed value, known at compile time, the Swift importer does not show the value in the generated Swift interface.
Note also that a macro definition in a C header file may depend on other macros, and on other “variables” such as compiler flags, the processor architecture, etc.
I am not aware of a way to navigate to that C definition from a Swift file, or any other way to show the defined value in a pure Swift project. As a workaround, one can
add a C file to the project,
use the macro in some C function, and
“jump to definition” from there.
I ended up with the following solution:
const fs = require('fs');
const { exec } = require("child_process");
const getterRegEx = /^(.*)public var (.+): (.+) { get }(.*)$/;
const code = String(fs.readFileSync('./generatedSwift.swift'));
const lines = code.split('\n').map((line, i) => {
const arr = getterRegEx.exec(line);
if (arr) {
const [all, prefix, constant, type, suffix] = arr;
return `print("let ${constant}: ${type} = ", ${constant}, separator: "")`;
}
return `print("""\n${line}\n""")`;
});
lines.unshift('import Foundation');
fs.writeFileSync('./regeneratedSwift.swift', lines.join('\n'));
exec('swift ./regeneratedSwift.swift', (err, stdout, stderr) => {
if (err) {
console.error(`exec error: ${err}`);
return;
}
if (stderr) {
console.log(`stderr: ${stderr}`);
return;
}
console.log(`stdout: ${stdout}`);
});
Copy definitions generated by the XCode and save into a file named generatedSwift.swift the run node index.js in the same folder.
The output will contain the Swift code where all
public var Constant: Type { get }
are replaced with
let Constant = Value
and all other lines will remain the same.
I am creating a vscode extension that does some custom auto-completing of files paths.
I want to take what the user has typed, and if that value resolves to a folder in the workspace, I want to list all the files in that folder for auto-complete.
For example, given:
a workspace located at: /home/me/my-vs-project
with files:
/home/me/my-vs-project/assets/dog.png
/home/me/my-vs-project/assets/cat.jpeg
If I type in 'assets' or './assets' into vscode, the extension should be able to provide me an autocomplete list of:
'./assets/dog.png'
'./assets/cat.png'
Here's a snippet of the code that doesn't work (returns 0 results)..
let inputAsWorkspaceRelativeFolder = getInput(document, position); // for example, would return: '/home/me/my-vs-project/assets' for input of './assets'
let glob = inputAsWorkspaceRelativeFolder + '/*';
vscode.workspace.findFiles(glob, null, 100).then((uris: vscode.Uri[] ) => {
uris.forEach((uri: vscode.Uri) => {
console.log(uri);
});
});
For some reason, the above code is returning 0 uris though. Thoughts on how I have to format the glob to make this happen? and/or if there is a better approach?
I was able to do this using vscode.RelativePattern -- I'm sure I could've done it using generic GlobPatterns but im still not clear what the findFiles(..) consider the 'root' when matching files; RelativePattern is explicitly relative to the workspace root.
let workspaceFolder: vscode.WorkspaceFolder | undefined = vscode.workspace.getWorkspaceFolder(document.uri);
if (!workspaceFolder || document.isUntitled) {
return undefined;
}
// Workspace folder: /home/me/my-project
let workspaceFolderPath: string = workspaceFolder.uri.path;
let relativeSearchFolderPrefix = path.normalize(path.dirname(document.uri.path) + '/' + searchText);
relativeSearchFolderPrefix = path.relative(workspaceFolderPath, relativeSearchFolderPrefix);
let relativePattern: vscode.RelativePattern = new vscode.RelativePattern(
workspaceFolderPath,
relativeSearchFolderPrefix + '/**/*.{png,jpeg,jpg,gif}');
return vscode.workspace.findFiles(globPattern, null, 50).then((uris: vscode.Uri[] ) => {
let relativePaths: string[] = [];
uris.forEach((uri: vscode.Uri) => {
relativePaths.push(path.relative(current, uri.path));
});
// trivial custom function that turns an array of strings into CompletionItems
return getCompletionItems(relativePaths, vscode.CompletionItemKind.File);
});
😊👋🏻
I think you wronged the glob.
I found this intresting wiki about Glob pattern composition.
let inputAsWorkspaceRelativeFolder = 'asset'; // for example, would return: '/home/me/my-vs-project/assets' for input of './assets'
//https://github.com/ev3dev/vscode-ev3dev-browser/wiki/Glob-Patterns
let glob = '**/'+inputAsWorkspaceRelativeFolder+'/*.*';//or +'/{*.png,*.jpeg}';
Or you can use the node built-in fs
import * as fs from 'fs';
fs.readdir(inputAsWorkspaceRelativeFolder, (err, files: string[]) => {
files.forEach((file: path) => {
const uri = vscode.Uri.file(file);
console.log(uri);
});
});
More simple, if you want to get all the files in the asset folder and don't want to filter for extension.
Is it possible to jump to an existing symbol in file from Extension?
Something of the sort:
goToSymbol(symbol: string)
P.S. I don't want to provide a DocumentSymbolProvider. How to use existing ones?
async function getSymbols(document: TextDocument): Promise<DocumentSymbol[]> {
return await commands.executeCommand<DocumentSymbol[]>('vscode.executeDocumentSymbolProvider', document.uri) || [];
}
async function goToSymbol(document: TextDocument, symbolName: string) {
const symbols = await getSymbols(document);
const findSymbol = symbols.find(symbol => symbol.name === symbolName);
const activeTextEditor = window.activeTextEditor;
if (findSymbol && activeTextEditor) {
activeTextEditor.revealRange(findSymbol.range, vscode.TextEditorRevealType.AtTop);
activeTextEditor.selection = new Selection(findSymbol.range.start, findSymbol.range.start);
}
}
Note: the code above should be enough to go to symbol of 1 lvl.
Nested symbols accessible as .children (on every element of symbols)
When programming a vscode extension...
Is there a programmatic way to find the keybinding for a provided command?
I would like to be able to see if a user has updated the key mapping from default for a command so that the UI can display the up-to-date binding. (and if not, look up the default binding)
Here are the APIs I've looked into so far:
vscode.workspace.getConfiguration() - I cannot determine how to access the keybindings.json file / perform a lookup.
vscode.extensions.getExtension(name/id) allows access to the package.json, but not the command or keybinding override.
vscode.getCommands does not provide access to the keybinding values either...
You can get keybinding values from the keybindings.json file using NodeJS.
keybindings.json path on diferrent systems:
Windows: %APPDATA%\Code\User\keybindings.json
Mac: $HOME/Library/Application Support/Code/User/keybindings.json
Linux: $HOME/.config/Code/User/keybindings.json
To build the path you'll need to get Environment variables using process.env.{variableName}.
For example for MacOS it'll be:
var process = require('process');
//...
var keybindingsPath = process.env.HOME + "/Library/Application Support/Code/User/keybindings.json";
vscode.workspace.openTextDocument(keybindingsPath).then((document) => {
let text = document.getText();
//then use this JSON file for your needs
//...
});
Inspired by #nikita-kunevich answer Here is a code I use in autoit extension.
First it gets default keybindings from package.json, then it parses keybindings.json via JSON5 library (can't use JSON.parse() because file may contain comments) and replaces default keys with new keys.
//get keybindings
const keybindings = new Promise(resolve => {
//default keybindings
const data = require("../package.json").contributes.keybindings.reduce((a,b)=>(a[b.command]=b.key,a),{});
const parse = list => {
for(let i = 0; i < list.length; i++) {
if (list[i].command in data)
data[list[i].command] = list[i].key;
}
for(let i in data) {
//capitalize first letter
data[i] = data[i].replace(/\w+/g, w => (w.substring(0,1).toUpperCase()) + w.substring(1));
//add spaces around "+"
// data[i] = data[i].replace(/\+/g, " $& ");
}
Object.assign(keybindings, data);
resolve(data);
};
const path = {
windows: process.env.APPDATA + "/Code",
macos: process.env.HOME + "/Library/Application Support/Code",
linux: process.env.HOME + "/config/Code"
}[{
aix: "linux",
darwin: "macos",
freebsd: "linux",
linux: "linux",
openbsd: "linux",
sunos: "linux",
win32: "windows"
}[process.platform]||"windows"];
const file = ((process.env.VSCODE_PORTABLE ? process.env.VSCODE_PORTABLE + "/user-data/User/" : path) + "/User/keybindings.json")
.replace(/\//g, process.platform == "win32" ? "\\" : "/");
//read file
workspace.openTextDocument(file).then(doc => {
//we can't use JSON.parse() because file may contain comments
const JSON5 = require("json5").default;
parse(JSON5.parse(doc.getText()));
}).catch(er => {
parse([]);
});
});
To install JSON5 package use:
npm install json5
Usage:
keybindings.then(list => {
console.log(list);
});
I am currently adding a new language mode in CodeMirror for my current project. This is a proprietary language in which user can create a new keyword. Basically I am trying to update existing keyword list at the runtime and my syntax highlighter can pick this new keyword.
var mode = editor.doc.modeOption;
if(mode === "dmsrl") mode = "text/dmsrl";
var keyWords = CodeMirror.resolveMode(mode).keywords;
keyWords[x]=true;
I am currently trying to add new keyword like above, but somehow the list is not getting updated and new keyword is unavailable in my tokebase() method.
Any help would be appreciated.
You can try to redefine hintOptions object, that pass to Codemirror's init function and than building hints in your specific hint addon with this data. Just try this
cm.setOption("hintOptions", { "keywords" : ["k1", "k2"] });
Look at first in sql-hint for example (link):
cm.setOption("hintOptions", { "tables" : ["k1", "k2"] });
For sql-mode this is not heavy operation
I wanted to reach the same goal as yours but with some more degree of freedom, consisting in inputting a container, which I can re-define along the run.
1) Put the following code into a file custom.mode.js, to be loaded from your web page
var _glob_keywords = [ [ "key1", "keyword1" ],
[ "key2", "keyword2" ]
] ;
var cm_custom_check_stream_fn = function( stream )
{
for( var _i = 0 ; _i < _glob_keywords.length ; _i++ )
{
if ( stream.match( _glob_keywords[_i][0] ) ) return _glob_keywords[_i][1] ;
}
return "" ;
}
CodeMirror.defineMode("custom.mode", function()
{
return {
token: function(stream,state)
{
var _ret = cm_custom_check_stream_fn( stream ) ;
if ( _ret.length > 0 ) return _ret ;
else { stream.next(); return null; }
}
};
});
This code will be automatically embedded into the Codemirror object to dynamically handle the input in the textbox.
Example: if "key1" is found, then "keyword1" is returned.
We assume that "keyword1", "keyword2" refer to entries inside a custom css definitions file, as explained in the codemirror documentation, that is,
.cm-keyword1 { color:#8BA8C4; }
.cm-keyword2 { color:lime; }
Hope it helps!