Insert predefined text at cursor position - visual-studio-code

I want to insert some text at the cursor's position but I didn't find the required code at the API docs.
Is there any function that I could put the parameters within, which solves my problem? This is just for testing.
import * as vscode from 'vscode';
var fs = require('fs');
var flow = require('xml-flow');
var inFile = fs.createReadStream('./your-xml-file.xml');
var xmlStream = flow(inFile);
// this method is called when your extension is activated
// your extension is activated the very first time the command is executed
export function activate(context: vscode.ExtensionContext) {
// Use the console to output diagnostic information (console.log) and errors (console.error)
// This line of code will only be executed once when your extension is activated
console.log('Congratulations, your extension "intrexx-js-lib" is now active!');
// The command has been defined in the package.json file
// Now provide the implementation of the command with registerCommand
// The commandId parameter must match the command field in package.json
let disposable = vscode.commands.registerCommand('intrexx-js-lib.start', () => {
const editor = vscode.window.activeTextEditor;
if(!editor){
vscode.window.showErrorMessage("Editor does not exist!");
return;
}
if (editor.selection.isEmpty) {
const position:vscode.Position = editor.selection.active;
//vscode.window.showInformationMessage(`line: ${position.line} character: ${position.character}`);
}
});
context.subscriptions.push(disposable);
}
// this method is called when your extension is deactivated
export function deactivate() {}

Although rioV8's code works, if any text is selected it will overwrite that text. If you want to ensure that no text is deleted by the insert operation, use edit.insert instead. For rioV8's second example, that means doing this:
vscode.commands.registerTextEditorCommand('intrexx-js-lib.start', (editor, edit) => {
editor.selections.forEach((selection, i) => {
let text = "FooBar " + i;
edit.insert(selection.active, text); // insert at current cursor
})
});

Use a different command register function, it gives you a TextEditorEdit:
vscode.commands.registerTextEditorCommand('intrexx-js-lib.start', (editor, edit) => {
let text = "FooBar";
edit.replace(editor.selection, text);
});
For use with multiple cursors, you'll have to iterate over editor.selections:
vscode.commands.registerTextEditorCommand('intrexx-js-lib.start', (editor, edit) => {
editor.selections.forEach((selection, i) => {
let text = "FooBar " + i
edit.replace(selection, text)
})
});

Related

VSCode extension development unable to use provideInlineCompletionItems

I've tried so many variations of this from code I found on github using the provideInlineCompletionItems function but cannot seem to get it to work. Is there something I am doing wrong?
const vscode = require('vscode');
function activate(context) {
const provider = {
provideInlineCompletionItems: async (document, position, context, token) => {
const txt = 'hi'
return [
{
text: txt,
insertText: txt,
range:new vscode.Range(position.translate(0, txt.length), position)
}
]
},
};
vscode.languages.registerInlineCompletionItemProvider({pattern: "**"}, provider);
}
exports.activate = activate;
function deactivate() {}
module.exports = {
activate,
deactivate
};
Even with https://github.com/microsoft/vscode/issues/125663 and "editor.inlineSuggest.enabled": true, set to true it doesn't work. I know inline suggestions works since I have github copilot, I just can't seem to get it to work. Copilot is also disabled so they don't interfere
I rollbacked a version of VSCode and it works now. this is the debug code I used.
// The module 'vscode' contains the VS Code extensibility API
// Import the module and reference it with the alias vscode in your code below
const vscode = require('vscode');
// This method is called when your extension is activated
// Your extension is activated the very first time the command is executed
/**
* #param {vscode.ExtensionContext} context
*/
function activate(context) {
// Use the console to output diagnostic information (console.log) and errors (console.error)
// This line of code will only be executed once when your extension is activated
console.log('Congratulations, your extension "seven" is now active!');
// The command has been defined in the package.json file
// Now provide the implementation of the command with registerCommand
// The commandId parameter must match the command field in package.json
let disposable = vscode.commands.registerCommand('seven.helloWorld', function () {
// The code you place here will be executed every time your command is executed
// Display a message box to the user
vscode.window.showInformationMessage('Hello World from seven!');
});
const provider = {
provideInlineCompletionItems: async (document, position, context, token) => {
const line = document.lineAt(position.line);
console.log("420")
if (line.text.startsWith('if')) {
console.log("421")
let range = line.range;
if (line.text.indexOf(";") !== -1) {
console.log("423")
range = new vscode.Range(range.start, range.end.with(undefined, line.text.indexOf(";") + 1));
}
console.log("424")
return [{ text: 'if (hello) {\n};', insertText: "if (hello)", range }];
}
}
};
vscode.languages.registerInlineCompletionItemProvider({ pattern: '**' }, provider);
context.subscriptions.push(disposable);
}
// This method is called when your extension is deactivated
function deactivate() {}
module.exports = {
activate,
deactivate
}

Insert default text into newly created files using vscod extension api

I'm trying to create a simple vscode extension that will insert some default text into a newly created file. What I want is for the vscode.workspace.createFileSystemWatcher to call a function that gets the activeTextEditor and writes to the new file. Here is what I've tried:
import * as vscode from "vscode";
export function activate(context: vscode.ExtensionContext) {
let disposable = vscode.commands.registerCommand(
"default-text-generator.generate",
() => {
function _watcherChangeApplied(editor?: vscode.TextEditor) {
if (editor) {
editor.edit((editBuilder) => {
editBuilder.insert(editor.selection.active, "Hello World");
});
}
}
const editor = vscode.window.activeTextEditor;
let uri: vscode.Uri | undefined = editor?.document.uri;
if (uri) {
let watcher = vscode.workspace.createFileSystemWatcher(
new vscode.RelativePattern(
vscode.workspace.getWorkspaceFolder(uri)!,
"**/*.ts"
),
false,
false,
false
);
watcher.onDidCreate(() => _watcherChangeApplied(editor));
}
}
);
context.subscriptions.push(disposable);
}
// this method is called when your extension is deactivated
export function deactivate(): void {
//deactivate
}
Here's what's happening. The editor seems to insert the text, then immediately gets overwritten back to a blank page. I can't seem to figure out why.
The problem happens because the editor you are referring to is not what you think to be. It is not the newly created editor but instead, the editor that you are focused/active when the new .ts file is created.
The FileSystemWatcher.onDidCreate event provides you a Uri to the newly created file inside your workspace, but not necessarily opened in VS Code. Try creating a file via terminal and you will see what I mean. The file is created, the event is fired, but no editor is opened in VS Code.
So, you won't be able to use the editor.edit API to manipulate the file. Instead, you should edit the file using RAW/Node functions. But, in this case, maybe/probably you will clash with the external tool that is creating the .ts file (which may not be VS Code, if you use the FileWatcher). If only files created via VS Code must be detected, you should change to the workspace.onDidCreateFiles event instead. But yet, it also only provides you the Uri, not the Editor.
Hope this helps
This works:
let disposable2 = vscode.commands.registerCommand('yourCommand.here', async (...file) => {
async function _watcherChangeApplied(uri) {
if (uri) {
const editor = await vscode.window.showTextDocument(uri);
editor.edit((editBuilder) => {
editBuilder.insert(editor.selection.active, "Hello World");
});
await editor.document.save();
}
}
const editor = vscode.window.activeTextEditor;
let uri = editor?.document.uri;
if (uri) {
let watcher = vscode.workspace.createFileSystemWatcher(
new vscode.RelativePattern(
vscode.workspace.getWorkspaceFolder(uri),
"**/*.ts"
),
false,
false,
false
);
// watcher.onDidCreate(() => _watcherChangeApplied(editor));
watcher.onDidCreate((uri) => _watcherChangeApplied(uri));
}
}
The key point is that watcher.onDidCreate() will return the uri of the newly created file. You can pass that to your _watcherChangeApplied(uri) function.
In _watcherChangeApplied(uri) you can show the created file via await vscode.window.showTextDocument(uri) and that function returns an editor that you can use with its edit functions.
The code works whether you create the file within vscode (like the New File... icon button at the top of the explorer) or via the terminal (like touch test.ts).
If you want to enable creating new files through the terminal, for example, and NOT open them, try this _watcherChangeApplied(uri):
async function _watcherChangeApplied(uri) {
if (uri) {
const document = await vscode.workspace.openTextDocument(uri);
const strToAdd = "Hello World";
const wse = new vscode.WorkspaceEdit();
wse.insert(uri, new vscode.Position(0,0), strToAdd);
await vscode.workspace.applyEdit(wse);
await document.save();
}
}

Protractor: How do I create a function that receives an HTML element as a parameter?

I'm new to Protractor.
I'm trying to select a button based on the button title. I want to make this into a function, and pass the button title in as a parameter.
This is the hard-coded version which works:
it('I click on a button based on the button title', async function() {
let button = element(by.css('button[title=example_button_title]'));
await button.click();
});
I created a global variable and a function to try and replace this, where 'buttonTitle' is the parameter I'm passing into the function:
Variable:
let dynamicButton = buttonTitle => { return element(by.css("'button[title=" + buttonTitle + "]'")) };
Function:
this.selectDynamicButton = async function(buttonTitle) {
await browser.waitForAngularEnabled(false);
await dynamicButton(buttonTitle).click();
};
When I try this I get the following error:
Failed: invalid selector: An invalid or illegal selector was specified
Apologies if there appear to be basic errors here, I am still learning. I appreciate any help that anyone can give me. Thanks.
You can add a custom locator using protractors addLocator functionality. (this is actually a very similar use case to the example listed in the link)
This would look like the following:
onPrepare: function () {
by.addLocator('buttonTitle', function (titleText, opt_parentElement) {
// This function will be serialized as a string and will execute in the
// browser. The first argument is the text for the button. The second
// argument is the parent element, if any.
const using = opt_parentElement || document;
const matchingButtons = using.querySelectorAll(`button[title="${titleText}"]`);
let result = undefined;
if (matchingButtons.length === 0) {
result = null;
} else if (matchingButtons.length === 1) {
result = matchingButtons[0];
} else {
result = matchingButtons;
}
return result;
});
}
This is called like
const firstMatchingButton = element(by.buttonTitle('example_button_title'));
const allMatchingButtons = element.all(by.buttonTitle('example_button_title'));
I had to edit this code before posting so let me know if this does not work. My work here is largely based off this previous answer
let dynamicButton = buttonTitle => { return element(by.css('button[title=${buttonTitle} ]')) };
Use template literals instead of string concatenation with +.
Protractor already has a built in locator which allows you to get a button using the text. I think you are looking at something like that. See the element(by.buttonText('text of button')) locator.
For more reference see here.

VSCode extension - how to alter file's text

I have an extension that grabs the open file's text and alters it. Once the text is altered, how do I put it back into the file that is displayed in VSCode?
// this method is called when your extension is activated
// your extension is activated the very first time the command is executed
export function activate(context: vscode.ExtensionContext) {
// Use the console to output diagnostic information (console.log) and errors (console.error)
// This line of code will only be executed once when your extension is activated
console.log('Congratulations, your extension "myExtension" is now active!');
console.log(process.versions);
// The command has been defined in the package.json file
// Now provide the implementation of the command with registerCommand
// The commandId parameter must match the command field in package.json
let disposable = vscode.commands.registerCommand('extension.myExtension', () => {
// The code you place here will be executed every time your command is executed
let activeEditor = vscode.window.activeTextEditor;
if (!activeEditor) {
return;
}
let text = activeEditor.document.getText();
getAsyncApi(text).then((textToInsertIntoDoc) => {
let finaldoc = insertTextIntoDoc(text, textToInsertIntoDoc);
// not what I want - just used to see new text
vscode.window.showInformationMessage(textToInsertIntoDoc);
});
});
context.subscriptions.push(disposable);
}
The API you can use here is TextEditor.edit, whose definition is
edit(callback: (editBuilder: TextEditorEdit) => void, options?: { undoStopBefore: boolean; undoStopAfter: boolean; }): Thenable<boolean>;
It asks for a callback as the first parameter and in the callback, you can make edits to the document by visiting editBuilder.
I put a sample extension in https://github.com/Microsoft/vscode-extension-samples/tree/master/document-editing-sample which reverses the content in current selection, which is basically a simple use TextEditor.edit.
This is a revision of the main function in Rebornix's extension sample (included with the set of Microsoft extension samples) that handles the selection issues you raised. It reverses the content of the selection(s) (leaving the selections) or if a selection is empty it will reverse the word under the cursor at that selection without leaving anything selected. It often makes sense to leave a selection, but you can add code to remove selection.
let disposable = vscode.commands.registerCommand('extension.reverseWord', function () {
// Get the active text editor
const editor = vscode.window.activeTextEditor;
if (editor) {
const document = editor.document;
editor.edit(editBuilder => {
editor.selections.forEach(sel => {
const range = sel.isEmpty ? document.getWordRangeAtPosition(sel.start) || sel : sel;
let word = document.getText(range);
let reversed = word.split('').reverse().join('');
editBuilder.replace(range, reversed);
})
}) // apply the (accumulated) replacement(s) (if multiple cursors/selections)
}
});
Admittedly, while I could remove a single selection by setting .selection to a new empty selection that doesn't seem to work with .selections[i]. But you can make multiple changes without having selections in the first place.
What you don't want to do is make a selection through code just to alter text through code. Users make selections, you don't (unless the end purpose of the function is to make a selection).
I came to this question looking for a way to apply a textEdit[] array (which is normally returned by a provideDocumentRangeFormattingEdits callback function). If you build changes in the array you can apply them to your document in your own function:
const { activeTextEditor } = vscode.window;
if (activeTextEditor) {
const { document } = activeTextEditor;
if (document) {
/*
build your textEdits similarly to the above with insert, delete, replace
but not within an editBuilder arrow function
const textEdits: vscode.TextEdit[] = [];
textEdits.push(vscode.TextEdit.replace(...));
textEdits.push(vscode.TextEdit.insert(...));
*/
const workEdits = new vscode.WorkspaceEdit();
workEdits.set(document.uri, textEdits); // give the edits
vscode.workspace.applyEdit(workEdits); // apply the edits
}
}
So that's another way to apply edits to a document. Even though I got the editBuilder sample to work correctly without selecting text, I have had problems with selections in other cases. WorkspaceEdit doesn't select the changes.
Here is the code snippet that will solve your issue :
activeEditor.edit((selectedText) => {
selectedText.replace(activeEditor.selection, newText);
})
Due to the issues I commented about in the above answer, I ended up writing a quick function that does multi-cursor friendly insert, and if the selection was empty, then it does not leave the inserted text selected afterwards (i.e. it has the same intuitive behavior as if you had pressed CTRL + V, or typed text on the keyboard, etc.)
Invoking it is simple:
// x is the cursor index, it can be safely ignored if you don't need it.
InsertText(x => 'Hello World');
Implementation:
function InsertText(getText: (i:number) => string, i: number = 0, wasEmpty: boolean = false) {
let activeEditor = vscode.window.activeTextEditor;
if (!activeEditor) { return; }
let sels = activeEditor.selections;
if (i > 0 && wasEmpty)
{
sels[i - 1] = new vscode.Selection(sels[i - 1].end, sels[i - 1].end);
activeEditor.selections = sels; // required or the selection updates will be ignored! 😱
}
if (i < 0 || i >= sels.length) { return; }
let isEmpty = sels[i].isEmpty;
activeEditor.edit(edit => edit.replace(sels[i], getText(i))).then(x => {
InsertText(getText, i + 1, isEmpty);
});
}
let strContent = "hello world";
const edit = new vscode.WorkspaceEdit();
edit.insert(YOUR_URI, new vscode.Position(0, 0), strContent);
let success = await vscode.workspace.applyEdit(edit);

How to remove selection after replace in vscode API

while creating an extension for vscode I got stuck in selection, now the problem is when I replace some range of textEditor through an api it replaces that range as well as make that range selected. For snippets this is a good idea but my extension requirement is not to select replaced text, I searched in api but did't find anything related to remove text selection (Selection occurs when the document is empty)
editor.edit((editBuilder)=>{ //editor is an object of active text editor
editBuilder.replace(textRange,text) // text = 'dummydummydummy'
}) //after this I got the following output
editor.edit(builder => {
builder.replace(selection, newStr);
})
// The edit call returns a promise. When that resolves you can set
// the selection otherwise you interfere with the edit itself.
// So use "then" to sure edit call is done;
.then(success => {
console.log("success:", success);
// Change the selection: start and end position of the new
// selection is same, so it is not to select replaced text;
var postion = editor.selection.end;
editor.selection = new vscode.Selection(postion, postion);
});
I believe that this is happening because the edit is being applied within the current selection. edit returns a promise that is resolved when the edit is applied, and you can use this to set the selection after the edit is successful:
editor.edit((editBuilder) => {
editBuilder.replace(textRange, text)
}).then(success => {
if (success) {
// make selection empty
editor.selection.active = editor.selection.anchor
}
})
let editor = vscode.window.activeTextEditor;
let selection = editor.selection;
editor.edit(builder => {
builder.replace(selection, newStr);
});
see: TextEditorEdit API Doc