Open text document in custom editor from vscode extension - visual-studio-code

I'm developing a Visual Studio Code extension that opens a webview custom editor.
Part of this is a command that prompts the user for a filename, creates the file, and then opens it.
It looks something like this:
window
.showInputBox({
prompt: "Enter name for file",
})
.then((title) => {
if (!title) {
return;
}
const fileContent = generateDefaultFileContent();
const filePath = path.join(folder.uri.path, `${title}.custom_file_format`);
workspace.fs
.writeFile(
folder.uri.with({
path: filePath,
}),
Buffer.from(fileContent, "utf8")
)
.then(() => {
workspace.openTextDocument(filePath).then((doc) =>
window.showTextDocument(doc, {
viewColumn: ViewColumn.Active,
})
);
});
});
The file gets properly created, however the call to window.showTextDocument opens the editor in the text editor and not my registered custom editor (in the example above, the custom editor will open .custom_file_format files). If you click on the newly created file in the file explorer, it will open in the custom editor.
Is there a way to get it to open the new file in the custom editor?

Turns out this can be done with...
commands.executeCommand(
"vscode.openWith",
folder.uri.with({
path: filePath,
}),
MyCustomEditor.viewType
);

Related

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();
}
}

Handle CustomTextEditor after opening

I want to open a file with my custom text editor and after opening, the editor should scroll to a position given by an element id.
I use the vscode command
vscode.commands.executeCommand('vscode.openWith', fileURI, EditorProvider.viewType); to open the file, but the command does not return the opened editor like showTextDocument.
Is there a way to open the file like the following example but with my custom editor?
vscode.workspace
.openTextDocument(path)
.then((textDocument) => {
vscode.window.showTextDocument(textDocument, 2, false)
.then((editor) => {
//scroll to element
});
Or alternatively: Is there a possibility to pass on the element id and to handle the parameter after opening the file?
Like this:
vscode.commands.executeCommand('vscode.openWith', fileURI, EditorProvider.viewType, scrollToElement);

VSCode Extension: How to open a file if no workspace folders are available?

I've been working on an extension that allows adding files with pre-defined content and modifying them using a custom web editor.
The custom command "Add new XXX file" looks like the following:
let disposable = vscode.commands.registerCommand('myextension.add-new-file', () => {
if(vscode.workspace.workspaceFolders?.length){
const rootPath = vscode.workspace.workspaceFolders[0].uri.fsPath ;
let counter = 0;
let filePath = '';
do{
counter++;
filePath = path.join(rootPath, `NewFile${counter}.my-ext`);
}while(fs.existsSync(filePath));
fs.writeFileSync(filePath, JSON.stringify(newFileContent), 'utf8');
const openPath = vscode.Uri.file(filePath);
vscode.commands.executeCommand('vscode.openWith', openPath, 'myextension.custom-designer');
}
});
It works OK if a folder is opened in VS Code. However, if no folder is opened, the rootPath can't be resolved. What's the solution for such a scenario? Does the 'vscode.openWith' accept the file content instead of the path to open?
You can accomplish your goal using the untitled scheme:
vscode.commands.executeCommand(
'vscode.openWith',
vscode.Uri.parse("untitled:FooBar"),
'myextension.custom-designer',
);
You can change your custom editor so that it detects when empty document gets passed to it and replaces the empty document with newFileContent.

Is there a vscode startup complete event or folder open event?

Can extension code be set to run when startup of vscode has completed? Or when a folder has been opened?
How to write an extension that opens a folder in a new vscode window, and then opens a text file in that folder?
I have the open the folder part working. And I am using global state to store the name of the file to open.
// store in name of file to open in global state.
context.globalState.update('fileToOpen', './src/index.html');
// open folder in a new vscode instance.
const uri_path = `file:///c:/web/tester/parcel`;
const uri = vscode.Uri.parse(uri_path);
await vscode.commands.executeCommand('vscode.openFolder', uri, true);
Then, when my extension is activated in the new vscode instance, I want to read the file name from global state, wait for vscode to open the folder, then run openTextDocument to open the file.
Since v1.46 there's a onStartupFinished activation event. So your package.json would have it:
...
"activationEvents": [
"onStartupFinished"
]
...
Then proceed to check the state upon activation
export function activate(context: vscode.ExtensionContext) {
const fileToOpen = context.globalState.get('fileToOpen')
if (fileToOpen) {
// open file
let document = await vscode.workspace.openTextDocument(fileToOpen);
await vscode.window.showTextDocument(document);
// reset state
context.globalState.update('fileToOpen', undefined);
} else {
// store in name of file to open in global state.
context.globalState.update('fileToOpen', './src/index.html');
// open folder in a new vscode instance.
const uri_path = `file:///c:/web/tester/parcel`;
const uri = vscode.Uri.parse(uri_path);
await vscode.commands.executeCommand('vscode.openFolder', uri, true);
}
...
}

Create duplicate tab of an already open file [duplicate]

We can use the "split editor" option to make two views into one file.
I'm looking for an option to open the same file in separated tabs like I can do in Sublime Text (open new view of file). Is that possible?
Note: I want to do this without splitting the view, so there should be two tabs for the same file within the same view container.
I couldn't find anything built-in that lets you do this, nor an existing extension in the marketplace. I thought it should be quite trivial to implement a "Duplicate Tab" command yourself in a custom extension, but it turns out VSCode only allows the same resource to be opened once within the same view column.
It's still possible to do this on Windows or macOS, but only by abusing this bug:
Issues with not case/fragment-normalizing file paths (macOS, Windows) #12448
Here's what the code for the extension looks like:
'use strict';
import * as vscode from 'vscode';
export function activate(context: vscode.ExtensionContext) {
vscode.commands.registerCommand("duplicateTab", () => {
var activeEditor = vscode.window.activeTextEditor;
if (activeEditor == null) {
return;
}
// HACK!
const sameFileNameButDifferent = activeEditor.document.fileName.toUpperCase();
vscode.workspace.openTextDocument(sameFileNameButDifferent).then(document => {
vscode.window.showTextDocument(document, {preview: false});
});
});
}
In package.json:
"contributes": {
"commands": [
{
"title": "Duplicate Tab",
"command": "duplicateTab"
}
]
},