Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move open-in-vscode-via-url to a separate command #6

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 23 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
# Open in VSCode

This plugin for [Obsidian](https://obsidian.md/) makes a ribbon button and a command to open your vault as a Visual Studio Code workspace.
This plugin for [Obsidian](https://obsidian.md/) makes a ribbon button and two commands to open your vault as a Visual Studio Code workspace:

- `open-vscode`: Uses `child_process` to launch VSCode with the `code` command. Currently, this is the command bound to the ribbon button.
- `open-vscode-via-url`: Open VSCode using a `vscode://` URL

It's functionality is probably made redundant now using the [Shell commands](https://github.com/Taitava/obsidian-shellcommands) and [Customizable Sidebar](https://github.com/phibr0/obsidian-customizable-sidebar) (or [Buttons](https://github.com/shabegom/buttons)) plugins, but it'll be maintained for the foreseeable future.

Expand All @@ -17,14 +20,6 @@ You can also use it as a command and assign hotkeys to it. You can disable the r

## Settings

### Open VSCode by executing the `code` command

By default the plugin uses `child_process` to launch VSCode with the `code` command, but the previous method using URLs can still be enabled by checking the following option:

### Open VSCode using a `vscode://` URL instead of executing the `code` command.

On some systems, this may be faster than using the `child_process` approach.

### Template for executing the `code` command

You can template the command opening VSCode however you like with its [provided command line arguments](https://code.visualstudio.com/docs/editor/command-line). This way you can technically launch any command you set, so take caution. Potential use cases include opening workspaces with `.code-workspace` files (e.g. for Dendron), opening specific files, folders, etc.
Expand All @@ -34,29 +29,39 @@ Note that on MacOS, a full path to the VSCode executable is required (generally
You can use the following variables: `{{vaultpath}}` (absolute), `{{filepath}}` (relative).
The default template is `code "{{vaultpath}}" "{{vaultpath}}/{{filepath}}"`, which opens the current file (if there is one) in the workspace that is the vault's root folder. This gets expanded to be executed in your shell as `code "C:\Users\YourUser\Documents\vault" "C:\Users\YourUser\Documents\vault/Note.md"`, for example.

### Path to VSCode Workspace
### Settings for `open-vscode-via-url`

If "Use URL" is checked, VSCode will open Obsidian files in this workspace (requires an absolute path).
On some systems, this may be faster than using the `child_process` approach.

### Open file
- **Open file**

If "Use URL" is checked, open the current file rather than the root of the Obsidian vault.
Open the current file rather than the root of the Obsidian vault.

## Installation
- **Path to VSCode Workspace**

You can install the plugin via the Community Plugins tab within Obsidian.
You can also manually copy from releases to your `.obsidian/plugins/open-vscode` folder.
Defaults to the {{vaultpath}} template variable. You can set this to an absolute
path to a ".code-workspace" file if you prefer to use a Multi Root workspace
file: https://code.visualstudio.com/docs/editor/workspaces#_multiroot-workspaces

## Caveats
- **Open VSCode using a `vscode-insiders://` URL**

The first time you use the URL method for opening, VSCode displays a confirmation dialog (that you just can hit enter on) for security reasons. See [this issue](https://github.com/microsoft/vscode/issues/95670) for more infomation.

## Installation

You can install the plugin via the Community Plugins tab within Obsidian.
You can also manually copy from releases to your `.obsidian/plugins/open-vscode` folder.

## Development

Run `npm install` for dependencies and `npm run build` to build.
Run `npm install` for dependencies and `npm run dev` to build and watch files.

This plugin follows the structure of the [Obsidian Sample Plugin](https://github.com/obsidianmd/obsidian-sample-plugin), see further details there. Contributions are welcome.

If [pjeby/hot-reload](https://github.com/pjeby/hot-reload) is installed,
activated, and open-vscode is registered with hot-reload, then extra logging
and DX commands to refresh settings are activated.

## Credits

Toggle ribbon setting by [@ozntel](https://github.com/ozntel).
Expand Down
218 changes: 159 additions & 59 deletions main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,18 @@ const svg = `
/>`;

addIcon('vscode-logo', svg);

type AppWithPlugins = App & {
plugins: {
disablePlugin: (id: string) => {};
enablePlugin: (id: string) => {};
enabledPlugins: Set<string>;
plugins: Record<string, any>;
};
};

let DEV: boolean = false;

export default class OpenVSCode extends Plugin {
ribbonIcon: HTMLElement;
settings: OpenVSCodeSettings;
Expand All @@ -23,52 +35,120 @@ export default class OpenVSCode extends Plugin {
name: 'Open as Visual Studio Code workspace',
callback: this.openVSCode.bind(this),
});

this.addCommand({
id: 'open-vscode-via-url',
name: 'Open as Visual Studio Code workspace using a vscode:// URL',
callback: this.openVSCodeUrl.bind(this),
});

DEV =
(this.app as AppWithPlugins).plugins.enabledPlugins.has('hot-reload') &&
(this.app as AppWithPlugins).plugins.plugins['hot-reload'].enabledPlugins.has(this.manifest.id);

if (DEV) {
this.addCommand({
id: 'open-vscode-reload',
name: 'Reload the plugin in dev',
callback: this.reload.bind(this),
});

this.addCommand({
id: 'open-vscode-reset-settings',
name: 'Reset plugins settings to default in dev',
callback: this.resetSettings.bind(this),
});
}
}

/**
* [pjeby](https://forum.obsidian.md/t/how-to-get-started-with-developing-a-custom-plugin/8157/7)
*
* > Of course, while doing development, you may need to reload your plugin
* > after making changes. You can do this by reloading, sure, but it’s easier
* > to just go to settings and then toggle the plugin off, then back on again.
* >
* > You can also automate this process from within the plugin itself, by
* > including a command that does something like this:
*/
async reload() {
const id = this.manifest.id;
const plugins = (this.app as AppWithPlugins).plugins;
await plugins.disablePlugin(id);
await plugins.enablePlugin(id);
console.log('[open-vscode] reloaded', this);
}

async openVSCode() {
if (!(this.app.vault.adapter instanceof FileSystemAdapter)) {
return;
}
const { executeTemplate, openFile, workspacePath, useURL } = this.settings;
const { executeTemplate } = this.settings;

const path = this.app.vault.adapter.getBasePath();
const file = this.app.workspace.getActiveFile();
const filePath = file?.path ?? '';

if (useURL) {
// https://code.visualstudio.com/docs/editor/command-line#_opening-vs-code-with-urls
const maybeFile = openFile ? '/' + filePath : '';
const url = `vscode://file/${path}${maybeFile}`;
// eslint-disable-next-line @typescript-eslint/no-var-requires
const { exec } = require('child_process');

let timeout = 0;
const useWorkspace = workspacePath.trim().length;
if (useWorkspace) {
window.open(`vscode://file/${workspacePath}`);
timeout = 200; // anecdotally, seems to be the min required for the workspace to activate
let command = executeTemplate.trim() === '' ? DEFAULT_SETTINGS.executeTemplate : executeTemplate;
command = replaceAll(command, '{{vaultpath}}', path);
command = replaceAll(command, '{{filepath}}', filePath);
if (DEV) console.log('[openVSCode]', { command });
exec(command, (error: never, stdout: never, stderr: never) => {
if (error) {
console.error(`[openVSCode] exec error: ${error}`);
}
// open in a setTimeout callback to allow time
// for the workspace to be activated first
});
}

async openVSCodeUrl() {
if (!(this.app.vault.adapter instanceof FileSystemAdapter)) {
return;
}
const { openFile, useUrlInsiders } = this.settings;

const path = this.app.vault.adapter.getBasePath();
const file = this.app.workspace.getActiveFile();
const filePath = file?.path ?? '';
if (DEV)
console.log('[open-vscode]', {
settings: this.settings,
path,
filePath,
});

// https://code.visualstudio.com/docs/editor/command-line#_opening-vs-code-with-urls
const protocol = useUrlInsiders ? 'vscode-insiders://' : 'vscode://';
let url = `${protocol}file/${path}`;

if (openFile) {
url += `/${filePath}`;
/*
By default, opening a file via the vscode:// URL will cause that file to open
in the front-most window in VSCode. We assume that it is preferred that files from
Obsidian should all open in the same workspace.

As a workaround, we issue two open requests to VSCode in quick succession: the first to
bring the workspace to front, the second to open the file.

There is a ticket requesting this feature for VSCode:
https://github.com/microsoft/vscode/issues/150078
*/

// HACK: first open the _workspace_ to bring the correct window to the front....
const workspacePath = replaceAll(this.settings.workspacePath, '{{vaultpath}}', path);
window.open(`vscode://file/${workspacePath}`);

// ...then open the _file_ in a setTimeout callback to allow time for the workspace to be activated
setTimeout(() => {
if (useWorkspace)
console.log('[openVSCode] waiting for workspace to be active', {
workspacePath,
});
console.log('[openVSCode]', { url });
if (DEV) console.log('[openVSCode]', { url });
window.open(url);
}, timeout);
}, 200); // anecdotally, this seems to be the min required for the workspace to activate
} else {
// eslint-disable-next-line @typescript-eslint/no-var-requires
const { exec } = require('child_process');

let command = executeTemplate.trim() === '' ? DEFAULT_SETTINGS.executeTemplate : executeTemplate;
command = replaceAll(command, '{{vaultpath}}', path);
command = replaceAll(command, '{{filepath}}', filePath);
console.log('[openVSCode]', { command });
exec(command, (error: never, stdout: never, stderr: never) => {
if (error) {
console.error(`exec error: ${error}`);
}
});
if (DEV) console.log('[openVSCode]', { url });
window.open(url);
}
}

Expand All @@ -80,6 +160,12 @@ export default class OpenVSCode extends Plugin {
await this.saveData(this.settings);
}

async resetSettings() {
console.log('[open-vscode]', { old: this.settings, DEFAULT_SETTINGS });
this.settings = DEFAULT_SETTINGS;
await this.saveData(this.settings);
}

refreshIconRibbon = () => {
this.ribbonIcon?.remove();
if (this.settings.ribbonIcon) {
Expand All @@ -92,18 +178,18 @@ export default class OpenVSCode extends Plugin {

interface OpenVSCodeSettings {
ribbonIcon: boolean;
useURL: boolean;
executeTemplate: string;
workspacePath: string;
openFile: boolean;
workspacePath: string;
useUrlInsiders: boolean;
}

const DEFAULT_SETTINGS: OpenVSCodeSettings = {
ribbonIcon: true,
useURL: false,
executeTemplate: 'code "{{vaultpath}}" "{{vaultpath}}/{{filepath}}"',
workspacePath: '',
openFile: true,
workspacePath: '{{vaultpath}}',
useUrlInsiders: false,
};

class OpenVSCodeSettingsTab extends PluginSettingTab {
Expand All @@ -117,10 +203,10 @@ class OpenVSCodeSettingsTab extends PluginSettingTab {
display(): void {
const { containerEl } = this;
containerEl.empty();
containerEl.createEl('h2', { text: 'Settings' });
containerEl.createEl('h3', { text: 'General settings' });

new Setting(containerEl)
.setName('Ribbon Icon')
.setName('Display Ribbon Icon')
.setDesc('Turn on if you want to have a Ribbon Icon.')
.addToggle((toggle) =>
toggle.setValue(this.plugin.settings.ribbonIcon).onChange((value) => {
Expand All @@ -129,21 +215,13 @@ class OpenVSCodeSettingsTab extends PluginSettingTab {
this.plugin.refreshIconRibbon();
}),
);
new Setting(containerEl)
.setName('Use URL')
.setDesc(
'Open VSCode using a `vscode://` URL instead of executing the `code` command. Opening via URL may be faster than the alternative on some systems.',
)
.addToggle((toggle) =>
toggle.setValue(this.plugin.settings.useURL).onChange((value) => {
this.plugin.settings.useURL = value;
this.plugin.saveSettings();
}),
);

containerEl.createEl('h3', { text: 'Open via `code` CLI settings' });

new Setting(containerEl)
.setName('Template for executing the `code` command')
.setDesc(
'You can use the following variables: `{{vaultpath}} (absolute)`, `{{filepath}}` (relative). Note that on MacOS, a full path to the VSCode executable is required (generally "/usr/local/bin/code"). Example: `/usr/local/bin/code {{vaultpath}}/{{filepath}}`',
'You can use the following variables: `{{vaultpath}}` (absolute), `{{filepath}}` (relative). Note that on MacOS, a full path to the VSCode executable is required (generally "/usr/local/bin/code"). Example: `/usr/local/bin/code "{{vaultpath}}" "{{vaultpath}}/{{filepath}}"`',
)
.addText((text) =>
text
Expand All @@ -156,11 +234,25 @@ class OpenVSCodeSettingsTab extends PluginSettingTab {
this.plugin.saveData(this.plugin.settings);
}),
);

containerEl.createEl('h3', { text: 'Open via `vscode://` URL settings' });

new Setting(containerEl)
.setName('Open current file')
.setDesc('Open the current file rather than the root of the vault.')
.addToggle((toggle) =>
toggle.setValue(this.plugin.settings.openFile || DEFAULT_SETTINGS.openFile).onChange((value) => {
this.plugin.settings.openFile = value;
this.plugin.saveData(this.plugin.settings);
}),
);

new Setting(containerEl)
.setName('Path to VSCode Workspace (Use URL only)')
.setName('Path to VSCode Workspace')
.setDesc(
'If "Use URL" is checked, VSCode will open Obsidian files in this workspace (requires an absolute path)',
'Defaults to the {{vaultpath}} template variable. You can set this to an absolute path to a ".code-workspace" file if you prefer to use a Multi Root workspace file: ',
)
.setClass('setting-item--vscode-workspacePath')
.addText((text) =>
text
.setPlaceholder(DEFAULT_SETTINGS.workspacePath)
Expand All @@ -173,15 +265,23 @@ class OpenVSCodeSettingsTab extends PluginSettingTab {
}),
);

new Setting(containerEl)
.setName('Open current file (Use URL only)')
.setDesc('If "Use URL" is checked, open the current file rather than the root of the vault')
.addToggle((toggle) =>
toggle.setValue(this.plugin.settings.openFile || DEFAULT_SETTINGS.openFile).onChange((value) => {
this.plugin.settings.openFile = value;
this.plugin.saveData(this.plugin.settings);
}),
);
const workspacePathDescEl = containerEl.querySelector(
'.setting-item--vscode-workspacePath .setting-item-description',
);
workspacePathDescEl.appendChild(
createEl('a', {
href: 'https://code.visualstudio.com/docs/editor/workspaces#_multiroot-workspaces',
text: 'https://code.visualstudio.com/docs/editor/workspaces#_multiroot-workspaces',
}),
);
workspacePathDescEl.appendText('.');

new Setting(containerEl).setName('Open VSCode using a `vscode-insiders://` URL').addToggle((toggle) => {
toggle.setValue(this.plugin.settings.useUrlInsiders).onChange((value) => {
this.plugin.settings.useUrlInsiders = value;
this.plugin.saveSettings();
});
});
}
}

Expand Down