mirror of
https://github.com/microsoft/vscode-extension-samples.git
synced 2026-04-27 16:55:44 +08:00
Merge pull request #736 from mjbvz/dev/mjbvz/tsfmt
Run tsfmt on all samples in repo
This commit is contained in:
3
.editorconfig
Normal file
3
.editorconfig
Normal file
@ -0,0 +1,3 @@
|
||||
[*.ts]
|
||||
indent_style = tab
|
||||
tab_width = 4
|
||||
@ -1,137 +1,137 @@
|
||||
import {
|
||||
authentication,
|
||||
AuthenticationProvider,
|
||||
AuthenticationProviderAuthenticationSessionsChangeEvent,
|
||||
AuthenticationSession,
|
||||
Disposable,
|
||||
Event,
|
||||
EventEmitter,
|
||||
SecretStorage,
|
||||
window,
|
||||
} from 'vscode';
|
||||
|
||||
class AzureDevOpsPatSession implements AuthenticationSession {
|
||||
// We don't know the user's account name, so we'll just use a constant
|
||||
readonly account = { id: AzureDevOpsAuthenticationProvider.id, label: 'Personal Access Token' };
|
||||
// This id isn't used for anything in this example, so we set it to a constant
|
||||
readonly id = AzureDevOpsAuthenticationProvider.id;
|
||||
// We don't know what scopes the PAT has, so we have an empty array here.
|
||||
readonly scopes = [];
|
||||
|
||||
/**
|
||||
*
|
||||
* @param accessToken The personal access token to use for authentication
|
||||
*/
|
||||
constructor(public readonly accessToken: string) {}
|
||||
}
|
||||
|
||||
export class AzureDevOpsAuthenticationProvider implements AuthenticationProvider, Disposable {
|
||||
static id = 'AzureDevOpsPAT';
|
||||
private static secretKey = 'AzureDevOpsPAT';
|
||||
|
||||
// this property is used to determine if the token has been changed in another window of VS Code.
|
||||
// It is used in the checkForUpdates function.
|
||||
private currentToken: Promise<string | undefined> | undefined;
|
||||
private initializedDisposable: Disposable | undefined;
|
||||
|
||||
private _onDidChangeSessions = new EventEmitter<AuthenticationProviderAuthenticationSessionsChangeEvent>();
|
||||
get onDidChangeSessions(): Event<AuthenticationProviderAuthenticationSessionsChangeEvent> {
|
||||
return this._onDidChangeSessions.event;
|
||||
}
|
||||
|
||||
constructor(private readonly secretStorage: SecretStorage) { }
|
||||
|
||||
dispose(): void {
|
||||
this.initializedDisposable?.dispose();
|
||||
}
|
||||
|
||||
private ensureInitialized(): void {
|
||||
if (this.initializedDisposable === undefined) {
|
||||
void this.cacheTokenFromStorage();
|
||||
|
||||
this.initializedDisposable = Disposable.from(
|
||||
// This onDidChange event happens when the secret storage changes in _any window_ since
|
||||
// secrets are shared across all open windows.
|
||||
this.secretStorage.onDidChange(e => {
|
||||
if (e.key === AzureDevOpsAuthenticationProvider.secretKey) {
|
||||
void this.checkForUpdates();
|
||||
}
|
||||
}),
|
||||
// This fires when the user initiates a "silent" auth flow via the Accounts menu.
|
||||
authentication.onDidChangeSessions(e => {
|
||||
if (e.provider.id === AzureDevOpsAuthenticationProvider.id) {
|
||||
void this.checkForUpdates();
|
||||
}
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// This is a crucial function that handles whether or not the token has changed in
|
||||
// a different window of VS Code and sends the necessary event if it has.
|
||||
private async checkForUpdates(): Promise<void> {
|
||||
const added: AuthenticationSession[] = [];
|
||||
const removed: AuthenticationSession[] = [];
|
||||
const changed: AuthenticationSession[] = [];
|
||||
|
||||
const previousToken = await this.currentToken;
|
||||
const session = (await this.getSessions())[0];
|
||||
|
||||
if (session?.accessToken && !previousToken) {
|
||||
added.push(session);
|
||||
} else if (!session?.accessToken && previousToken) {
|
||||
removed.push(session);
|
||||
} else if (session?.accessToken !== previousToken) {
|
||||
changed.push(session);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
void this.cacheTokenFromStorage();
|
||||
this._onDidChangeSessions.fire({ added: added, removed: removed, changed: changed });
|
||||
}
|
||||
|
||||
private cacheTokenFromStorage() {
|
||||
this.currentToken = this.secretStorage.get(AzureDevOpsAuthenticationProvider.secretKey) as Promise<string | undefined>;
|
||||
return this.currentToken;
|
||||
}
|
||||
|
||||
// This function is called first when `vscode.authentication.getSessions` is called.
|
||||
async getSessions(_scopes?: string[]): Promise<readonly AuthenticationSession[]> {
|
||||
this.ensureInitialized();
|
||||
const token = await this.cacheTokenFromStorage();
|
||||
return token ? [new AzureDevOpsPatSession(token)] : [];
|
||||
}
|
||||
|
||||
// This function is called after `this.getSessions` is called and only when:
|
||||
// - `this.getSessions` returns nothing but `createIfNone` was set to `true` in `vscode.authentication.getSessions`
|
||||
// - `vscode.authentication.getSessions` was called with `forceNewSession: true`
|
||||
// - The end user initiates the "silent" auth flow via the Accounts menu
|
||||
async createSession(_scopes: string[]): Promise<AuthenticationSession> {
|
||||
this.ensureInitialized();
|
||||
|
||||
// Prompt for the PAT.
|
||||
const token = await window.showInputBox({
|
||||
ignoreFocusOut: true,
|
||||
placeHolder: 'Personal access token',
|
||||
prompt: 'Enter an Azure DevOps Personal Access Token (PAT).',
|
||||
password: true,
|
||||
});
|
||||
|
||||
// Note: this example doesn't do any validation of the token beyond making sure it's not empty.
|
||||
if (!token) {
|
||||
throw new Error('PAT is required');
|
||||
}
|
||||
|
||||
// Don't set `currentToken` here, since we want to fire the proper events in the `checkForUpdates` call
|
||||
await this.secretStorage.store(AzureDevOpsAuthenticationProvider.secretKey, token);
|
||||
console.log('Successfully logged in to Azure DevOps');
|
||||
|
||||
return new AzureDevOpsPatSession(token);
|
||||
}
|
||||
|
||||
// This function is called when the end user signs out of the account.
|
||||
async removeSession(_sessionId: string): Promise<void> {
|
||||
await this.secretStorage.delete(AzureDevOpsAuthenticationProvider.secretKey);
|
||||
}
|
||||
}
|
||||
import {
|
||||
authentication,
|
||||
AuthenticationProvider,
|
||||
AuthenticationProviderAuthenticationSessionsChangeEvent,
|
||||
AuthenticationSession,
|
||||
Disposable,
|
||||
Event,
|
||||
EventEmitter,
|
||||
SecretStorage,
|
||||
window,
|
||||
} from 'vscode';
|
||||
|
||||
class AzureDevOpsPatSession implements AuthenticationSession {
|
||||
// We don't know the user's account name, so we'll just use a constant
|
||||
readonly account = { id: AzureDevOpsAuthenticationProvider.id, label: 'Personal Access Token' };
|
||||
// This id isn't used for anything in this example, so we set it to a constant
|
||||
readonly id = AzureDevOpsAuthenticationProvider.id;
|
||||
// We don't know what scopes the PAT has, so we have an empty array here.
|
||||
readonly scopes = [];
|
||||
|
||||
/**
|
||||
*
|
||||
* @param accessToken The personal access token to use for authentication
|
||||
*/
|
||||
constructor(public readonly accessToken: string) { }
|
||||
}
|
||||
|
||||
export class AzureDevOpsAuthenticationProvider implements AuthenticationProvider, Disposable {
|
||||
static id = 'AzureDevOpsPAT';
|
||||
private static secretKey = 'AzureDevOpsPAT';
|
||||
|
||||
// this property is used to determine if the token has been changed in another window of VS Code.
|
||||
// It is used in the checkForUpdates function.
|
||||
private currentToken: Promise<string | undefined> | undefined;
|
||||
private initializedDisposable: Disposable | undefined;
|
||||
|
||||
private _onDidChangeSessions = new EventEmitter<AuthenticationProviderAuthenticationSessionsChangeEvent>();
|
||||
get onDidChangeSessions(): Event<AuthenticationProviderAuthenticationSessionsChangeEvent> {
|
||||
return this._onDidChangeSessions.event;
|
||||
}
|
||||
|
||||
constructor(private readonly secretStorage: SecretStorage) { }
|
||||
|
||||
dispose(): void {
|
||||
this.initializedDisposable?.dispose();
|
||||
}
|
||||
|
||||
private ensureInitialized(): void {
|
||||
if (this.initializedDisposable === undefined) {
|
||||
void this.cacheTokenFromStorage();
|
||||
|
||||
this.initializedDisposable = Disposable.from(
|
||||
// This onDidChange event happens when the secret storage changes in _any window_ since
|
||||
// secrets are shared across all open windows.
|
||||
this.secretStorage.onDidChange(e => {
|
||||
if (e.key === AzureDevOpsAuthenticationProvider.secretKey) {
|
||||
void this.checkForUpdates();
|
||||
}
|
||||
}),
|
||||
// This fires when the user initiates a "silent" auth flow via the Accounts menu.
|
||||
authentication.onDidChangeSessions(e => {
|
||||
if (e.provider.id === AzureDevOpsAuthenticationProvider.id) {
|
||||
void this.checkForUpdates();
|
||||
}
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// This is a crucial function that handles whether or not the token has changed in
|
||||
// a different window of VS Code and sends the necessary event if it has.
|
||||
private async checkForUpdates(): Promise<void> {
|
||||
const added: AuthenticationSession[] = [];
|
||||
const removed: AuthenticationSession[] = [];
|
||||
const changed: AuthenticationSession[] = [];
|
||||
|
||||
const previousToken = await this.currentToken;
|
||||
const session = (await this.getSessions())[0];
|
||||
|
||||
if (session?.accessToken && !previousToken) {
|
||||
added.push(session);
|
||||
} else if (!session?.accessToken && previousToken) {
|
||||
removed.push(session);
|
||||
} else if (session?.accessToken !== previousToken) {
|
||||
changed.push(session);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
void this.cacheTokenFromStorage();
|
||||
this._onDidChangeSessions.fire({ added: added, removed: removed, changed: changed });
|
||||
}
|
||||
|
||||
private cacheTokenFromStorage() {
|
||||
this.currentToken = this.secretStorage.get(AzureDevOpsAuthenticationProvider.secretKey) as Promise<string | undefined>;
|
||||
return this.currentToken;
|
||||
}
|
||||
|
||||
// This function is called first when `vscode.authentication.getSessions` is called.
|
||||
async getSessions(_scopes?: string[]): Promise<readonly AuthenticationSession[]> {
|
||||
this.ensureInitialized();
|
||||
const token = await this.cacheTokenFromStorage();
|
||||
return token ? [new AzureDevOpsPatSession(token)] : [];
|
||||
}
|
||||
|
||||
// This function is called after `this.getSessions` is called and only when:
|
||||
// - `this.getSessions` returns nothing but `createIfNone` was set to `true` in `vscode.authentication.getSessions`
|
||||
// - `vscode.authentication.getSessions` was called with `forceNewSession: true`
|
||||
// - The end user initiates the "silent" auth flow via the Accounts menu
|
||||
async createSession(_scopes: string[]): Promise<AuthenticationSession> {
|
||||
this.ensureInitialized();
|
||||
|
||||
// Prompt for the PAT.
|
||||
const token = await window.showInputBox({
|
||||
ignoreFocusOut: true,
|
||||
placeHolder: 'Personal access token',
|
||||
prompt: 'Enter an Azure DevOps Personal Access Token (PAT).',
|
||||
password: true,
|
||||
});
|
||||
|
||||
// Note: this example doesn't do any validation of the token beyond making sure it's not empty.
|
||||
if (!token) {
|
||||
throw new Error('PAT is required');
|
||||
}
|
||||
|
||||
// Don't set `currentToken` here, since we want to fire the proper events in the `checkForUpdates` call
|
||||
await this.secretStorage.store(AzureDevOpsAuthenticationProvider.secretKey, token);
|
||||
console.log('Successfully logged in to Azure DevOps');
|
||||
|
||||
return new AzureDevOpsPatSession(token);
|
||||
}
|
||||
|
||||
// This function is called when the end user signs out of the account.
|
||||
async removeSession(_sessionId: string): Promise<void> {
|
||||
await this.secretStorage.delete(AzureDevOpsAuthenticationProvider.secretKey);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,53 +1,53 @@
|
||||
// The module 'vscode' contains the VS Code extensibility API
|
||||
// Import the module and reference it with the alias vscode in your code below
|
||||
import 'isomorphic-fetch';
|
||||
import * as vscode from 'vscode';
|
||||
import { AzureDevOpsAuthenticationProvider } from './authProvider';
|
||||
|
||||
// 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) {
|
||||
|
||||
console.log('Congratulations, your extension "vscode-authenticationprovider-sample" is now active!');
|
||||
|
||||
// Register our authentication provider. NOTE: this will register the provider globally which means that
|
||||
// any other extension can use this provider via the `getSession` API.
|
||||
// NOTE: when implementing an auth provider, don't forget to register an activation event for that provider
|
||||
// in your package.json file: "onAuthenticationRequest:AzureDevOpsPAT"
|
||||
context.subscriptions.push(vscode.authentication.registerAuthenticationProvider(
|
||||
AzureDevOpsAuthenticationProvider.id,
|
||||
'Azure Repos',
|
||||
new AzureDevOpsAuthenticationProvider(context.secrets),
|
||||
));
|
||||
|
||||
let disposable = vscode.commands.registerCommand('vscode-authenticationprovider-sample.login', async () => {
|
||||
// Get our PAT session.
|
||||
const session = await vscode.authentication.getSession(AzureDevOpsAuthenticationProvider.id, [], { createIfNone: true });
|
||||
|
||||
try {
|
||||
// Make a request to the Azure DevOps API. Keep in mind that this particular API only works with PAT's with
|
||||
// 'all organizations' access.
|
||||
const req = await fetch('https://app.vssps.visualstudio.com/_apis/profile/profiles/me?api-version=6.0', {
|
||||
headers: {
|
||||
authorization: `Basic ${Buffer.from(`:${session.accessToken}`).toString('base64')}`,
|
||||
'content-type': 'application/json',
|
||||
},
|
||||
});
|
||||
if (!req.ok) {
|
||||
throw new Error(req.statusText);
|
||||
}
|
||||
const res = await req.json() as { displayName: string };
|
||||
vscode.window.showInformationMessage(`Hello ${res.displayName}`);
|
||||
} catch (e: any) {
|
||||
if (e.message === 'Unauthorized') {
|
||||
vscode.window.showErrorMessage('Failed to get profile. You need to use a PAT that has access to all organizations. Please sign out and try again.');
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
});
|
||||
|
||||
context.subscriptions.push(disposable);
|
||||
}
|
||||
|
||||
// this method is called when your extension is deactivated
|
||||
export function deactivate() {}
|
||||
// The module 'vscode' contains the VS Code extensibility API
|
||||
// Import the module and reference it with the alias vscode in your code below
|
||||
import 'isomorphic-fetch';
|
||||
import * as vscode from 'vscode';
|
||||
import { AzureDevOpsAuthenticationProvider } from './authProvider';
|
||||
|
||||
// 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) {
|
||||
|
||||
console.log('Congratulations, your extension "vscode-authenticationprovider-sample" is now active!');
|
||||
|
||||
// Register our authentication provider. NOTE: this will register the provider globally which means that
|
||||
// any other extension can use this provider via the `getSession` API.
|
||||
// NOTE: when implementing an auth provider, don't forget to register an activation event for that provider
|
||||
// in your package.json file: "onAuthenticationRequest:AzureDevOpsPAT"
|
||||
context.subscriptions.push(vscode.authentication.registerAuthenticationProvider(
|
||||
AzureDevOpsAuthenticationProvider.id,
|
||||
'Azure Repos',
|
||||
new AzureDevOpsAuthenticationProvider(context.secrets),
|
||||
));
|
||||
|
||||
let disposable = vscode.commands.registerCommand('vscode-authenticationprovider-sample.login', async () => {
|
||||
// Get our PAT session.
|
||||
const session = await vscode.authentication.getSession(AzureDevOpsAuthenticationProvider.id, [], { createIfNone: true });
|
||||
|
||||
try {
|
||||
// Make a request to the Azure DevOps API. Keep in mind that this particular API only works with PAT's with
|
||||
// 'all organizations' access.
|
||||
const req = await fetch('https://app.vssps.visualstudio.com/_apis/profile/profiles/me?api-version=6.0', {
|
||||
headers: {
|
||||
authorization: `Basic ${Buffer.from(`:${session.accessToken}`).toString('base64')}`,
|
||||
'content-type': 'application/json',
|
||||
},
|
||||
});
|
||||
if (!req.ok) {
|
||||
throw new Error(req.statusText);
|
||||
}
|
||||
const res = await req.json() as { displayName: string };
|
||||
vscode.window.showInformationMessage(`Hello ${res.displayName}`);
|
||||
} catch (e: any) {
|
||||
if (e.message === 'Unauthorized') {
|
||||
vscode.window.showErrorMessage('Failed to get profile. You need to use a PAT that has access to all organizations. Please sign out and try again.');
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
});
|
||||
|
||||
context.subscriptions.push(disposable);
|
||||
}
|
||||
|
||||
// this method is called when your extension is deactivated
|
||||
export function deactivate() { }
|
||||
|
||||
@ -1,72 +1,72 @@
|
||||
/*---------------------------------------------------------
|
||||
* Copyright (C) Microsoft Corporation. All rights reserved.
|
||||
*--------------------------------------------------------*/
|
||||
|
||||
import { ExtensionContext, StatusBarAlignment, window, StatusBarItem, Selection, workspace, TextEditor, commands } from 'vscode';
|
||||
import { basename } from 'path';
|
||||
|
||||
export function activate(context: ExtensionContext) {
|
||||
|
||||
// Create a status bar item
|
||||
const status = window.createStatusBarItem(StatusBarAlignment.Left, 1000000);
|
||||
context.subscriptions.push(status);
|
||||
|
||||
// Update status bar item based on events for multi root folder changes
|
||||
context.subscriptions.push(workspace.onDidChangeWorkspaceFolders(e => updateStatus(status)));
|
||||
|
||||
// Update status bar item based on events for configuration
|
||||
context.subscriptions.push(workspace.onDidChangeConfiguration(e => updateStatus(status)));
|
||||
|
||||
// Update status bar item based on events around the active editor
|
||||
context.subscriptions.push(window.onDidChangeActiveTextEditor(e => updateStatus(status)));
|
||||
context.subscriptions.push(window.onDidChangeTextEditorViewColumn(e => updateStatus(status)));
|
||||
context.subscriptions.push(workspace.onDidOpenTextDocument(e => updateStatus(status)));
|
||||
context.subscriptions.push(workspace.onDidCloseTextDocument(e => updateStatus(status)));
|
||||
|
||||
updateStatus(status);
|
||||
}
|
||||
|
||||
function updateStatus(status: StatusBarItem): void {
|
||||
const info = getEditorInfo();
|
||||
status.text = info ? info.text || '' : '';
|
||||
status.tooltip = info ? info.tooltip : undefined;
|
||||
status.color = info ? info.color : undefined;
|
||||
|
||||
if (info) {
|
||||
status.show();
|
||||
} else {
|
||||
status.hide();
|
||||
}
|
||||
}
|
||||
|
||||
function getEditorInfo(): { text?: string; tooltip?: string; color?: string; } | null {
|
||||
const editor = window.activeTextEditor;
|
||||
|
||||
// If no workspace is opened or just a single folder, we return without any status label
|
||||
// because our extension only works when more than one folder is opened in a workspace.
|
||||
if (!editor || !workspace.workspaceFolders || workspace.workspaceFolders.length < 2) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let text: string | undefined;
|
||||
let tooltip: string | undefined;
|
||||
let color: string | undefined;
|
||||
|
||||
// If we have a file:// resource we resolve the WorkspaceFolder this file is from and update
|
||||
// the status accordingly.
|
||||
const resource = editor.document.uri;
|
||||
if (resource.scheme === 'file') {
|
||||
const folder = workspace.getWorkspaceFolder(resource);
|
||||
if (!folder) {
|
||||
text = `$(alert) <outside workspace> → ${basename(resource.fsPath)}`;
|
||||
} else {
|
||||
text = `$(file-submodule) ${basename(folder.uri.fsPath)} (${folder.index + 1} of ${workspace.workspaceFolders.length}) → $(file-code) ${basename(resource.fsPath)}`;
|
||||
tooltip = resource.fsPath;
|
||||
|
||||
const multiRootConfigForResource = workspace.getConfiguration('multiRootSample', resource);
|
||||
color = multiRootConfigForResource.get('statusColor');
|
||||
}
|
||||
}
|
||||
|
||||
return { text, tooltip, color };
|
||||
}
|
||||
/*---------------------------------------------------------
|
||||
* Copyright (C) Microsoft Corporation. All rights reserved.
|
||||
*--------------------------------------------------------*/
|
||||
|
||||
import { ExtensionContext, StatusBarAlignment, window, StatusBarItem, Selection, workspace, TextEditor, commands } from 'vscode';
|
||||
import { basename } from 'path';
|
||||
|
||||
export function activate(context: ExtensionContext) {
|
||||
|
||||
// Create a status bar item
|
||||
const status = window.createStatusBarItem(StatusBarAlignment.Left, 1000000);
|
||||
context.subscriptions.push(status);
|
||||
|
||||
// Update status bar item based on events for multi root folder changes
|
||||
context.subscriptions.push(workspace.onDidChangeWorkspaceFolders(e => updateStatus(status)));
|
||||
|
||||
// Update status bar item based on events for configuration
|
||||
context.subscriptions.push(workspace.onDidChangeConfiguration(e => updateStatus(status)));
|
||||
|
||||
// Update status bar item based on events around the active editor
|
||||
context.subscriptions.push(window.onDidChangeActiveTextEditor(e => updateStatus(status)));
|
||||
context.subscriptions.push(window.onDidChangeTextEditorViewColumn(e => updateStatus(status)));
|
||||
context.subscriptions.push(workspace.onDidOpenTextDocument(e => updateStatus(status)));
|
||||
context.subscriptions.push(workspace.onDidCloseTextDocument(e => updateStatus(status)));
|
||||
|
||||
updateStatus(status);
|
||||
}
|
||||
|
||||
function updateStatus(status: StatusBarItem): void {
|
||||
const info = getEditorInfo();
|
||||
status.text = info ? info.text || '' : '';
|
||||
status.tooltip = info ? info.tooltip : undefined;
|
||||
status.color = info ? info.color : undefined;
|
||||
|
||||
if (info) {
|
||||
status.show();
|
||||
} else {
|
||||
status.hide();
|
||||
}
|
||||
}
|
||||
|
||||
function getEditorInfo(): { text?: string; tooltip?: string; color?: string; } | null {
|
||||
const editor = window.activeTextEditor;
|
||||
|
||||
// If no workspace is opened or just a single folder, we return without any status label
|
||||
// because our extension only works when more than one folder is opened in a workspace.
|
||||
if (!editor || !workspace.workspaceFolders || workspace.workspaceFolders.length < 2) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let text: string | undefined;
|
||||
let tooltip: string | undefined;
|
||||
let color: string | undefined;
|
||||
|
||||
// If we have a file:// resource we resolve the WorkspaceFolder this file is from and update
|
||||
// the status accordingly.
|
||||
const resource = editor.document.uri;
|
||||
if (resource.scheme === 'file') {
|
||||
const folder = workspace.getWorkspaceFolder(resource);
|
||||
if (!folder) {
|
||||
text = `$(alert) <outside workspace> → ${basename(resource.fsPath)}`;
|
||||
} else {
|
||||
text = `$(file-submodule) ${basename(folder.uri.fsPath)} (${folder.index + 1} of ${workspace.workspaceFolders.length}) → $(file-code) ${basename(resource.fsPath)}`;
|
||||
tooltip = resource.fsPath;
|
||||
|
||||
const multiRootConfigForResource = workspace.getConfiguration('multiRootSample', resource);
|
||||
color = multiRootConfigForResource.get('statusColor');
|
||||
}
|
||||
}
|
||||
|
||||
return { text, tooltip, color };
|
||||
}
|
||||
|
||||
@ -1,125 +1,125 @@
|
||||
import * as vscode from 'vscode';
|
||||
import { FoodPyramid, FoodRelation } from './model';
|
||||
|
||||
export class FoodPyramidHierarchyProvider implements vscode.CallHierarchyProvider {
|
||||
|
||||
prepareCallHierarchy(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken): vscode.CallHierarchyItem | undefined {
|
||||
const range = document.getWordRangeAtPosition(position);
|
||||
if (range) {
|
||||
const word = document.getText(range);
|
||||
return this.createCallHierarchyItem(word, '', document, range);
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
async provideCallHierarchyOutgoingCalls(item: vscode.CallHierarchyItem, token: vscode.CancellationToken): Promise<vscode.CallHierarchyOutgoingCall[] | undefined> {
|
||||
const document = await vscode.workspace.openTextDocument(item.uri);
|
||||
const parser = new FoodPyramidParser();
|
||||
parser.parse(document);
|
||||
const model = parser.getModel();
|
||||
const originRelation = model.getRelationAt(item.range);
|
||||
|
||||
const outgoingCallItems: vscode.CallHierarchyOutgoingCall[] = [];
|
||||
|
||||
if (model.isVerb(item.name)) {
|
||||
const outgoingCalls = model.getVerbRelations(item.name)
|
||||
.filter(relation => relation.subject === originRelation!.subject);
|
||||
|
||||
outgoingCalls.forEach(relation => {
|
||||
const outgoingCallRange = relation.getRangeOf(relation.object);
|
||||
const verbItem = this.createCallHierarchyItem(relation.object, 'noun', document, outgoingCallRange);
|
||||
const outgoingCallItem = new vscode.CallHierarchyOutgoingCall(verbItem, [outgoingCallRange]);
|
||||
outgoingCallItems.push(outgoingCallItem);
|
||||
});
|
||||
}
|
||||
else if (model.isNoun(item.name)) {
|
||||
const outgoingCallMap = groupBy(model.getSubjectRelations(item.name), relation => relation.verb);
|
||||
|
||||
outgoingCallMap.forEach((relations, verb) => {
|
||||
const outgoingCallRanges = relations.map(relation => relation.getRangeOf(verb));
|
||||
const verbItem = this.createCallHierarchyItem(verb, 'verb', document, outgoingCallRanges[0]);
|
||||
const outgoingCallItem = new vscode.CallHierarchyOutgoingCall(verbItem, outgoingCallRanges);
|
||||
outgoingCallItems.push(outgoingCallItem);
|
||||
});
|
||||
}
|
||||
|
||||
return outgoingCallItems;
|
||||
}
|
||||
|
||||
async provideCallHierarchyIncomingCalls(item: vscode.CallHierarchyItem, token: vscode.CancellationToken): Promise<vscode.CallHierarchyIncomingCall[]> {
|
||||
const document = await vscode.workspace.openTextDocument(item.uri);
|
||||
const parser = new FoodPyramidParser();
|
||||
parser.parse(document);
|
||||
const model = parser.getModel();
|
||||
const originRelation = model.getRelationAt(item.range);
|
||||
|
||||
const outgoingCallItems: vscode.CallHierarchyIncomingCall[] = [];
|
||||
|
||||
if (model.isVerb(item.name)) {
|
||||
const outgoingCalls = model.getVerbRelations(item.name)
|
||||
.filter(relation => relation.object === originRelation!.object);
|
||||
|
||||
outgoingCalls.forEach(relation => {
|
||||
const outgoingCallRange = relation.getRangeOf(relation.subject);
|
||||
const verbItem = this.createCallHierarchyItem(relation.subject, 'noun', document, outgoingCallRange);
|
||||
const outgoingCallItem = new vscode.CallHierarchyIncomingCall(verbItem, [outgoingCallRange]);
|
||||
outgoingCallItems.push(outgoingCallItem);
|
||||
});
|
||||
}
|
||||
else if (model.isNoun(item.name)) {
|
||||
const outgoingCallMap = groupBy(model.getObjectRelations(item.name), relation => relation.verb);
|
||||
|
||||
outgoingCallMap.forEach((relations, verb) => {
|
||||
const outgoingCallRanges = relations.map(relation => relation.getRangeOf(verb));
|
||||
const verbItem = this.createCallHierarchyItem(verb, 'verb-inverted', document, outgoingCallRanges[0]);
|
||||
const outgoingCallItem = new vscode.CallHierarchyIncomingCall(verbItem, outgoingCallRanges);
|
||||
outgoingCallItems.push(outgoingCallItem);
|
||||
});
|
||||
}
|
||||
|
||||
return outgoingCallItems;
|
||||
}
|
||||
|
||||
private createCallHierarchyItem(word: string, type: string, document: vscode.TextDocument, range: vscode.Range): vscode.CallHierarchyItem {
|
||||
return new vscode.CallHierarchyItem(vscode.SymbolKind.Object, word, `(${type})`, document.uri, range, range);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Sample parser of the document text into the [FoodPyramid](#FoodPyramid) model.
|
||||
*/
|
||||
class FoodPyramidParser {
|
||||
private _model = new FoodPyramid();
|
||||
|
||||
getModel(): FoodPyramid {
|
||||
return this._model;
|
||||
}
|
||||
|
||||
parse(textDocument: vscode.TextDocument): void {
|
||||
const pattern = /^(\w+)\s+(\w+)\s+(\w+).$/gm;
|
||||
let match: RegExpExecArray | null;
|
||||
while ((match = pattern.exec(textDocument.getText()))) {
|
||||
const startPosition = textDocument.positionAt(match.index);
|
||||
const range = new vscode.Range(startPosition, startPosition.translate({ characterDelta: match[0].length }));
|
||||
this._model.addRelation(new FoodRelation(match[1], match[2], match[3], match[0], range));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Groups array items by a field defined using a key selector.
|
||||
* @param array array to be grouped
|
||||
* @param keyGetter grouping key selector
|
||||
*/
|
||||
function groupBy<K, V>(array: Array<V>, keyGetter: (value: V) => K): Map<K, V[]> {
|
||||
const map = new Map();
|
||||
array.forEach((item) => {
|
||||
const key = keyGetter(item);
|
||||
const groupForKey = map.get(key) || [];
|
||||
groupForKey.push(item);
|
||||
map.set(key, groupForKey);
|
||||
});
|
||||
return map;
|
||||
}
|
||||
import * as vscode from 'vscode';
|
||||
import { FoodPyramid, FoodRelation } from './model';
|
||||
|
||||
export class FoodPyramidHierarchyProvider implements vscode.CallHierarchyProvider {
|
||||
|
||||
prepareCallHierarchy(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken): vscode.CallHierarchyItem | undefined {
|
||||
const range = document.getWordRangeAtPosition(position);
|
||||
if (range) {
|
||||
const word = document.getText(range);
|
||||
return this.createCallHierarchyItem(word, '', document, range);
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
async provideCallHierarchyOutgoingCalls(item: vscode.CallHierarchyItem, token: vscode.CancellationToken): Promise<vscode.CallHierarchyOutgoingCall[] | undefined> {
|
||||
const document = await vscode.workspace.openTextDocument(item.uri);
|
||||
const parser = new FoodPyramidParser();
|
||||
parser.parse(document);
|
||||
const model = parser.getModel();
|
||||
const originRelation = model.getRelationAt(item.range);
|
||||
|
||||
const outgoingCallItems: vscode.CallHierarchyOutgoingCall[] = [];
|
||||
|
||||
if (model.isVerb(item.name)) {
|
||||
const outgoingCalls = model.getVerbRelations(item.name)
|
||||
.filter(relation => relation.subject === originRelation!.subject);
|
||||
|
||||
outgoingCalls.forEach(relation => {
|
||||
const outgoingCallRange = relation.getRangeOf(relation.object);
|
||||
const verbItem = this.createCallHierarchyItem(relation.object, 'noun', document, outgoingCallRange);
|
||||
const outgoingCallItem = new vscode.CallHierarchyOutgoingCall(verbItem, [outgoingCallRange]);
|
||||
outgoingCallItems.push(outgoingCallItem);
|
||||
});
|
||||
}
|
||||
else if (model.isNoun(item.name)) {
|
||||
const outgoingCallMap = groupBy(model.getSubjectRelations(item.name), relation => relation.verb);
|
||||
|
||||
outgoingCallMap.forEach((relations, verb) => {
|
||||
const outgoingCallRanges = relations.map(relation => relation.getRangeOf(verb));
|
||||
const verbItem = this.createCallHierarchyItem(verb, 'verb', document, outgoingCallRanges[0]);
|
||||
const outgoingCallItem = new vscode.CallHierarchyOutgoingCall(verbItem, outgoingCallRanges);
|
||||
outgoingCallItems.push(outgoingCallItem);
|
||||
});
|
||||
}
|
||||
|
||||
return outgoingCallItems;
|
||||
}
|
||||
|
||||
async provideCallHierarchyIncomingCalls(item: vscode.CallHierarchyItem, token: vscode.CancellationToken): Promise<vscode.CallHierarchyIncomingCall[]> {
|
||||
const document = await vscode.workspace.openTextDocument(item.uri);
|
||||
const parser = new FoodPyramidParser();
|
||||
parser.parse(document);
|
||||
const model = parser.getModel();
|
||||
const originRelation = model.getRelationAt(item.range);
|
||||
|
||||
const outgoingCallItems: vscode.CallHierarchyIncomingCall[] = [];
|
||||
|
||||
if (model.isVerb(item.name)) {
|
||||
const outgoingCalls = model.getVerbRelations(item.name)
|
||||
.filter(relation => relation.object === originRelation!.object);
|
||||
|
||||
outgoingCalls.forEach(relation => {
|
||||
const outgoingCallRange = relation.getRangeOf(relation.subject);
|
||||
const verbItem = this.createCallHierarchyItem(relation.subject, 'noun', document, outgoingCallRange);
|
||||
const outgoingCallItem = new vscode.CallHierarchyIncomingCall(verbItem, [outgoingCallRange]);
|
||||
outgoingCallItems.push(outgoingCallItem);
|
||||
});
|
||||
}
|
||||
else if (model.isNoun(item.name)) {
|
||||
const outgoingCallMap = groupBy(model.getObjectRelations(item.name), relation => relation.verb);
|
||||
|
||||
outgoingCallMap.forEach((relations, verb) => {
|
||||
const outgoingCallRanges = relations.map(relation => relation.getRangeOf(verb));
|
||||
const verbItem = this.createCallHierarchyItem(verb, 'verb-inverted', document, outgoingCallRanges[0]);
|
||||
const outgoingCallItem = new vscode.CallHierarchyIncomingCall(verbItem, outgoingCallRanges);
|
||||
outgoingCallItems.push(outgoingCallItem);
|
||||
});
|
||||
}
|
||||
|
||||
return outgoingCallItems;
|
||||
}
|
||||
|
||||
private createCallHierarchyItem(word: string, type: string, document: vscode.TextDocument, range: vscode.Range): vscode.CallHierarchyItem {
|
||||
return new vscode.CallHierarchyItem(vscode.SymbolKind.Object, word, `(${type})`, document.uri, range, range);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Sample parser of the document text into the [FoodPyramid](#FoodPyramid) model.
|
||||
*/
|
||||
class FoodPyramidParser {
|
||||
private _model = new FoodPyramid();
|
||||
|
||||
getModel(): FoodPyramid {
|
||||
return this._model;
|
||||
}
|
||||
|
||||
parse(textDocument: vscode.TextDocument): void {
|
||||
const pattern = /^(\w+)\s+(\w+)\s+(\w+).$/gm;
|
||||
let match: RegExpExecArray | null;
|
||||
while ((match = pattern.exec(textDocument.getText()))) {
|
||||
const startPosition = textDocument.positionAt(match.index);
|
||||
const range = new vscode.Range(startPosition, startPosition.translate({ characterDelta: match[0].length }));
|
||||
this._model.addRelation(new FoodRelation(match[1], match[2], match[3], match[0], range));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Groups array items by a field defined using a key selector.
|
||||
* @param array array to be grouped
|
||||
* @param keyGetter grouping key selector
|
||||
*/
|
||||
function groupBy<K, V>(array: Array<V>, keyGetter: (value: V) => K): Map<K, V[]> {
|
||||
const map = new Map();
|
||||
array.forEach((item) => {
|
||||
const key = keyGetter(item);
|
||||
const groupForKey = map.get(key) || [];
|
||||
groupForKey.push(item);
|
||||
map.set(key, groupForKey);
|
||||
});
|
||||
return map;
|
||||
}
|
||||
|
||||
@ -1,26 +1,26 @@
|
||||
// The module 'vscode' contains the VS Code extensibility API
|
||||
// Import the module and reference it with the alias vscode in your code below
|
||||
import * as vscode from 'vscode';
|
||||
import { FoodPyramidHierarchyProvider } from './FoodPyramidHierarchyProvider';
|
||||
import { TextDecoder } from 'util';
|
||||
|
||||
// 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 "call-hierarchy-sample" is now active!');
|
||||
|
||||
const disposable = vscode.languages.registerCallHierarchyProvider('plaintext', new FoodPyramidHierarchyProvider());
|
||||
|
||||
context.subscriptions.push(disposable);
|
||||
|
||||
showSampleText(context);
|
||||
}
|
||||
|
||||
async function showSampleText(context: vscode.ExtensionContext): Promise<void> {
|
||||
const sampleTextEncoded = await vscode.workspace.fs.readFile(vscode.Uri.file(context.asAbsolutePath('sample.txt')));
|
||||
const sampleText = new TextDecoder('utf-8').decode(sampleTextEncoded);
|
||||
const doc = await vscode.workspace.openTextDocument({ language: 'plaintext', content: sampleText });
|
||||
vscode.window.showTextDocument(doc);
|
||||
// The module 'vscode' contains the VS Code extensibility API
|
||||
// Import the module and reference it with the alias vscode in your code below
|
||||
import * as vscode from 'vscode';
|
||||
import { FoodPyramidHierarchyProvider } from './FoodPyramidHierarchyProvider';
|
||||
import { TextDecoder } from 'util';
|
||||
|
||||
// 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 "call-hierarchy-sample" is now active!');
|
||||
|
||||
const disposable = vscode.languages.registerCallHierarchyProvider('plaintext', new FoodPyramidHierarchyProvider());
|
||||
|
||||
context.subscriptions.push(disposable);
|
||||
|
||||
showSampleText(context);
|
||||
}
|
||||
|
||||
async function showSampleText(context: vscode.ExtensionContext): Promise<void> {
|
||||
const sampleTextEncoded = await vscode.workspace.fs.readFile(vscode.Uri.file(context.asAbsolutePath('sample.txt')));
|
||||
const sampleText = new TextDecoder('utf-8').decode(sampleTextEncoded);
|
||||
const doc = await vscode.workspace.openTextDocument({ language: 'plaintext', content: sampleText });
|
||||
vscode.window.showTextDocument(doc);
|
||||
}
|
||||
@ -1,88 +1,88 @@
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
/**
|
||||
* Sample model of what the text in the document contains.
|
||||
*/
|
||||
export class FoodPyramid {
|
||||
private _relations: FoodRelation[] = [];
|
||||
private _nouns = new Set<string>();
|
||||
private _verbs = new Set<string>();
|
||||
|
||||
getRelationAt(wordRange: vscode.Range): FoodRelation | undefined {
|
||||
return this._relations.find(relation => relation.range.contains(wordRange));
|
||||
}
|
||||
|
||||
addRelation(relation: FoodRelation): void {
|
||||
this._relations.push(relation);
|
||||
this._nouns.add(relation.object).add(relation.subject);
|
||||
this._verbs.add(relation.verb);
|
||||
}
|
||||
|
||||
isVerb(name: string): boolean {
|
||||
return this._verbs.has(name.toLowerCase());
|
||||
}
|
||||
|
||||
isNoun(name: string): boolean {
|
||||
return this._nouns.has(name.toLowerCase());
|
||||
}
|
||||
|
||||
getVerbRelations(verb: string): FoodRelation[] {
|
||||
return this._relations
|
||||
.filter(relation => relation.verb === verb.toLowerCase());
|
||||
}
|
||||
|
||||
getNounRelations(noun: string): FoodRelation[] {
|
||||
return this._relations
|
||||
.filter(relation => relation.involves(noun));
|
||||
}
|
||||
|
||||
getSubjectRelations(subject: string): FoodRelation[] {
|
||||
return this._relations
|
||||
.filter(relation => relation.subject === subject.toLowerCase());
|
||||
}
|
||||
|
||||
getObjectRelations(object: string): FoodRelation[] {
|
||||
return this._relations
|
||||
.filter(relation => relation.object === object.toLowerCase());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Model element.
|
||||
*/
|
||||
export class FoodRelation {
|
||||
private _subject: string;
|
||||
private _verb: string;
|
||||
private _object: string;
|
||||
|
||||
constructor(subject: string, verb: string, object: string,
|
||||
private readonly originalText: string, public readonly range: vscode.Range) {
|
||||
|
||||
this._subject = subject.toLowerCase();
|
||||
this._verb = verb.toLowerCase();
|
||||
this._object = object.toLowerCase();
|
||||
}
|
||||
|
||||
get subject(): string {
|
||||
return this._subject;
|
||||
}
|
||||
|
||||
get object(): string {
|
||||
return this._object;
|
||||
}
|
||||
|
||||
get verb(): string {
|
||||
return this._verb;
|
||||
}
|
||||
|
||||
involves(noun: string): boolean {
|
||||
const needle = noun.toLowerCase();
|
||||
return this._subject === needle || this._object === needle;
|
||||
}
|
||||
|
||||
getRangeOf(word: string): vscode.Range {
|
||||
const indexOfWord = new RegExp("\\b" + word + "\\b", "i").exec(this.originalText)!.index;
|
||||
return new vscode.Range(this.range.start.translate({ characterDelta: indexOfWord }),
|
||||
this.range.start.translate({ characterDelta: indexOfWord + word.length }));
|
||||
}
|
||||
}
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
/**
|
||||
* Sample model of what the text in the document contains.
|
||||
*/
|
||||
export class FoodPyramid {
|
||||
private _relations: FoodRelation[] = [];
|
||||
private _nouns = new Set<string>();
|
||||
private _verbs = new Set<string>();
|
||||
|
||||
getRelationAt(wordRange: vscode.Range): FoodRelation | undefined {
|
||||
return this._relations.find(relation => relation.range.contains(wordRange));
|
||||
}
|
||||
|
||||
addRelation(relation: FoodRelation): void {
|
||||
this._relations.push(relation);
|
||||
this._nouns.add(relation.object).add(relation.subject);
|
||||
this._verbs.add(relation.verb);
|
||||
}
|
||||
|
||||
isVerb(name: string): boolean {
|
||||
return this._verbs.has(name.toLowerCase());
|
||||
}
|
||||
|
||||
isNoun(name: string): boolean {
|
||||
return this._nouns.has(name.toLowerCase());
|
||||
}
|
||||
|
||||
getVerbRelations(verb: string): FoodRelation[] {
|
||||
return this._relations
|
||||
.filter(relation => relation.verb === verb.toLowerCase());
|
||||
}
|
||||
|
||||
getNounRelations(noun: string): FoodRelation[] {
|
||||
return this._relations
|
||||
.filter(relation => relation.involves(noun));
|
||||
}
|
||||
|
||||
getSubjectRelations(subject: string): FoodRelation[] {
|
||||
return this._relations
|
||||
.filter(relation => relation.subject === subject.toLowerCase());
|
||||
}
|
||||
|
||||
getObjectRelations(object: string): FoodRelation[] {
|
||||
return this._relations
|
||||
.filter(relation => relation.object === object.toLowerCase());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Model element.
|
||||
*/
|
||||
export class FoodRelation {
|
||||
private _subject: string;
|
||||
private _verb: string;
|
||||
private _object: string;
|
||||
|
||||
constructor(subject: string, verb: string, object: string,
|
||||
private readonly originalText: string, public readonly range: vscode.Range) {
|
||||
|
||||
this._subject = subject.toLowerCase();
|
||||
this._verb = verb.toLowerCase();
|
||||
this._object = object.toLowerCase();
|
||||
}
|
||||
|
||||
get subject(): string {
|
||||
return this._subject;
|
||||
}
|
||||
|
||||
get object(): string {
|
||||
return this._object;
|
||||
}
|
||||
|
||||
get verb(): string {
|
||||
return this._verb;
|
||||
}
|
||||
|
||||
involves(noun: string): boolean {
|
||||
const needle = noun.toLowerCase();
|
||||
return this._subject === needle || this._object === needle;
|
||||
}
|
||||
|
||||
getRangeOf(word: string): vscode.Range {
|
||||
const indexOfWord = new RegExp("\\b" + word + "\\b", "i").exec(this.originalText)!.index;
|
||||
return new vscode.Range(this.range.start.translate({ characterDelta: indexOfWord }),
|
||||
this.range.start.translate({ characterDelta: indexOfWord + word.length }));
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,67 +1,67 @@
|
||||
/*---------------------------------------------------------
|
||||
* Copyright (C) Microsoft Corporation. All rights reserved.
|
||||
*--------------------------------------------------------*/
|
||||
|
||||
/** To demonstrate code actions associated with Diagnostics problems, this file provides a mock diagnostics entries. */
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
/** Code that is used to associate diagnostic entries with code actions. */
|
||||
export const EMOJI_MENTION = 'emoji_mention';
|
||||
|
||||
/** String to detect in the text document. */
|
||||
const EMOJI = 'emoji';
|
||||
|
||||
/**
|
||||
* Analyzes the text document for problems.
|
||||
* This demo diagnostic problem provider finds all mentions of 'emoji'.
|
||||
* @param doc text document to analyze
|
||||
* @param emojiDiagnostics diagnostic collection
|
||||
*/
|
||||
export function refreshDiagnostics(doc: vscode.TextDocument, emojiDiagnostics: vscode.DiagnosticCollection): void {
|
||||
const diagnostics: vscode.Diagnostic[] = [];
|
||||
|
||||
for (let lineIndex = 0; lineIndex < doc.lineCount; lineIndex++) {
|
||||
const lineOfText = doc.lineAt(lineIndex);
|
||||
if (lineOfText.text.includes(EMOJI)) {
|
||||
diagnostics.push(createDiagnostic(doc, lineOfText, lineIndex));
|
||||
}
|
||||
}
|
||||
|
||||
emojiDiagnostics.set(doc.uri, diagnostics);
|
||||
}
|
||||
|
||||
function createDiagnostic(doc: vscode.TextDocument, lineOfText: vscode.TextLine, lineIndex: number): vscode.Diagnostic {
|
||||
// find where in the line of that the 'emoji' is mentioned
|
||||
const index = lineOfText.text.indexOf(EMOJI);
|
||||
|
||||
// create range that represents, where in the document the word is
|
||||
const range = new vscode.Range(lineIndex, index, lineIndex, index + EMOJI.length);
|
||||
|
||||
const diagnostic = new vscode.Diagnostic(range, "When you say 'emoji', do you want to find out more?",
|
||||
vscode.DiagnosticSeverity.Information);
|
||||
diagnostic.code = EMOJI_MENTION;
|
||||
return diagnostic;
|
||||
}
|
||||
|
||||
export function subscribeToDocumentChanges(context: vscode.ExtensionContext, emojiDiagnostics: vscode.DiagnosticCollection): void {
|
||||
if (vscode.window.activeTextEditor) {
|
||||
refreshDiagnostics(vscode.window.activeTextEditor.document, emojiDiagnostics);
|
||||
}
|
||||
context.subscriptions.push(
|
||||
vscode.window.onDidChangeActiveTextEditor(editor => {
|
||||
if (editor) {
|
||||
refreshDiagnostics(editor.document, emojiDiagnostics);
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
context.subscriptions.push(
|
||||
vscode.workspace.onDidChangeTextDocument(e => refreshDiagnostics(e.document, emojiDiagnostics))
|
||||
);
|
||||
|
||||
context.subscriptions.push(
|
||||
vscode.workspace.onDidCloseTextDocument(doc => emojiDiagnostics.delete(doc.uri))
|
||||
);
|
||||
|
||||
/*---------------------------------------------------------
|
||||
* Copyright (C) Microsoft Corporation. All rights reserved.
|
||||
*--------------------------------------------------------*/
|
||||
|
||||
/** To demonstrate code actions associated with Diagnostics problems, this file provides a mock diagnostics entries. */
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
/** Code that is used to associate diagnostic entries with code actions. */
|
||||
export const EMOJI_MENTION = 'emoji_mention';
|
||||
|
||||
/** String to detect in the text document. */
|
||||
const EMOJI = 'emoji';
|
||||
|
||||
/**
|
||||
* Analyzes the text document for problems.
|
||||
* This demo diagnostic problem provider finds all mentions of 'emoji'.
|
||||
* @param doc text document to analyze
|
||||
* @param emojiDiagnostics diagnostic collection
|
||||
*/
|
||||
export function refreshDiagnostics(doc: vscode.TextDocument, emojiDiagnostics: vscode.DiagnosticCollection): void {
|
||||
const diagnostics: vscode.Diagnostic[] = [];
|
||||
|
||||
for (let lineIndex = 0; lineIndex < doc.lineCount; lineIndex++) {
|
||||
const lineOfText = doc.lineAt(lineIndex);
|
||||
if (lineOfText.text.includes(EMOJI)) {
|
||||
diagnostics.push(createDiagnostic(doc, lineOfText, lineIndex));
|
||||
}
|
||||
}
|
||||
|
||||
emojiDiagnostics.set(doc.uri, diagnostics);
|
||||
}
|
||||
|
||||
function createDiagnostic(doc: vscode.TextDocument, lineOfText: vscode.TextLine, lineIndex: number): vscode.Diagnostic {
|
||||
// find where in the line of that the 'emoji' is mentioned
|
||||
const index = lineOfText.text.indexOf(EMOJI);
|
||||
|
||||
// create range that represents, where in the document the word is
|
||||
const range = new vscode.Range(lineIndex, index, lineIndex, index + EMOJI.length);
|
||||
|
||||
const diagnostic = new vscode.Diagnostic(range, "When you say 'emoji', do you want to find out more?",
|
||||
vscode.DiagnosticSeverity.Information);
|
||||
diagnostic.code = EMOJI_MENTION;
|
||||
return diagnostic;
|
||||
}
|
||||
|
||||
export function subscribeToDocumentChanges(context: vscode.ExtensionContext, emojiDiagnostics: vscode.DiagnosticCollection): void {
|
||||
if (vscode.window.activeTextEditor) {
|
||||
refreshDiagnostics(vscode.window.activeTextEditor.document, emojiDiagnostics);
|
||||
}
|
||||
context.subscriptions.push(
|
||||
vscode.window.onDidChangeActiveTextEditor(editor => {
|
||||
if (editor) {
|
||||
refreshDiagnostics(editor.document, emojiDiagnostics);
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
context.subscriptions.push(
|
||||
vscode.workspace.onDidChangeTextDocument(e => refreshDiagnostics(e.document, emojiDiagnostics))
|
||||
);
|
||||
|
||||
context.subscriptions.push(
|
||||
vscode.workspace.onDidCloseTextDocument(doc => emojiDiagnostics.delete(doc.uri))
|
||||
);
|
||||
|
||||
}
|
||||
@ -1,108 +1,108 @@
|
||||
/*---------------------------------------------------------
|
||||
* Copyright (C) Microsoft Corporation. All rights reserved.
|
||||
*--------------------------------------------------------*/
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import { subscribeToDocumentChanges, EMOJI_MENTION } from './diagnostics';
|
||||
|
||||
const COMMAND = 'code-actions-sample.command';
|
||||
|
||||
export function activate(context: vscode.ExtensionContext) {
|
||||
context.subscriptions.push(
|
||||
vscode.languages.registerCodeActionsProvider('markdown', new Emojizer(), {
|
||||
providedCodeActionKinds: Emojizer.providedCodeActionKinds
|
||||
}));
|
||||
|
||||
const emojiDiagnostics = vscode.languages.createDiagnosticCollection("emoji");
|
||||
context.subscriptions.push(emojiDiagnostics);
|
||||
|
||||
subscribeToDocumentChanges(context, emojiDiagnostics);
|
||||
|
||||
context.subscriptions.push(
|
||||
vscode.languages.registerCodeActionsProvider('markdown', new Emojinfo(), {
|
||||
providedCodeActionKinds: Emojinfo.providedCodeActionKinds
|
||||
})
|
||||
);
|
||||
|
||||
context.subscriptions.push(
|
||||
vscode.commands.registerCommand(COMMAND, () => vscode.env.openExternal(vscode.Uri.parse('https://unicode.org/emoji/charts-12.0/full-emoji-list.html')))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides code actions for converting :) to a smiley emoji.
|
||||
*/
|
||||
export class Emojizer implements vscode.CodeActionProvider {
|
||||
|
||||
public static readonly providedCodeActionKinds = [
|
||||
vscode.CodeActionKind.QuickFix
|
||||
];
|
||||
|
||||
public provideCodeActions(document: vscode.TextDocument, range: vscode.Range): vscode.CodeAction[] | undefined {
|
||||
if (!this.isAtStartOfSmiley(document, range)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const replaceWithSmileyCatFix = this.createFix(document, range, '😺');
|
||||
|
||||
const replaceWithSmileyFix = this.createFix(document, range, '😀');
|
||||
// Marking a single fix as `preferred` means that users can apply it with a
|
||||
// single keyboard shortcut using the `Auto Fix` command.
|
||||
replaceWithSmileyFix.isPreferred = true;
|
||||
|
||||
const replaceWithSmileyHankyFix = this.createFix(document, range, '💩');
|
||||
|
||||
const commandAction = this.createCommand();
|
||||
|
||||
return [
|
||||
replaceWithSmileyCatFix,
|
||||
replaceWithSmileyFix,
|
||||
replaceWithSmileyHankyFix,
|
||||
commandAction
|
||||
];
|
||||
}
|
||||
|
||||
private isAtStartOfSmiley(document: vscode.TextDocument, range: vscode.Range) {
|
||||
const start = range.start;
|
||||
const line = document.lineAt(start.line);
|
||||
return line.text[start.character] === ':' && line.text[start.character + 1] === ')';
|
||||
}
|
||||
|
||||
private createFix(document: vscode.TextDocument, range: vscode.Range, emoji: string): vscode.CodeAction {
|
||||
const fix = new vscode.CodeAction(`Convert to ${emoji}`, vscode.CodeActionKind.QuickFix);
|
||||
fix.edit = new vscode.WorkspaceEdit();
|
||||
fix.edit.replace(document.uri, new vscode.Range(range.start, range.start.translate(0, 2)), emoji);
|
||||
return fix;
|
||||
}
|
||||
|
||||
private createCommand(): vscode.CodeAction {
|
||||
const action = new vscode.CodeAction('Learn more...', vscode.CodeActionKind.Empty);
|
||||
action.command = { command: COMMAND, title: 'Learn more about emojis', tooltip: 'This will open the unicode emoji page.' };
|
||||
return action;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides code actions corresponding to diagnostic problems.
|
||||
*/
|
||||
export class Emojinfo implements vscode.CodeActionProvider {
|
||||
|
||||
public static readonly providedCodeActionKinds = [
|
||||
vscode.CodeActionKind.QuickFix
|
||||
];
|
||||
|
||||
provideCodeActions(document: vscode.TextDocument, range: vscode.Range | vscode.Selection, context: vscode.CodeActionContext, token: vscode.CancellationToken): vscode.CodeAction[] {
|
||||
// for each diagnostic entry that has the matching `code`, create a code action command
|
||||
return context.diagnostics
|
||||
.filter(diagnostic => diagnostic.code === EMOJI_MENTION)
|
||||
.map(diagnostic => this.createCommandCodeAction(diagnostic));
|
||||
}
|
||||
|
||||
private createCommandCodeAction(diagnostic: vscode.Diagnostic): vscode.CodeAction {
|
||||
const action = new vscode.CodeAction('Learn more...', vscode.CodeActionKind.QuickFix);
|
||||
action.command = { command: COMMAND, title: 'Learn more about emojis', tooltip: 'This will open the unicode emoji page.' };
|
||||
action.diagnostics = [diagnostic];
|
||||
action.isPreferred = true;
|
||||
return action;
|
||||
}
|
||||
/*---------------------------------------------------------
|
||||
* Copyright (C) Microsoft Corporation. All rights reserved.
|
||||
*--------------------------------------------------------*/
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import { subscribeToDocumentChanges, EMOJI_MENTION } from './diagnostics';
|
||||
|
||||
const COMMAND = 'code-actions-sample.command';
|
||||
|
||||
export function activate(context: vscode.ExtensionContext) {
|
||||
context.subscriptions.push(
|
||||
vscode.languages.registerCodeActionsProvider('markdown', new Emojizer(), {
|
||||
providedCodeActionKinds: Emojizer.providedCodeActionKinds
|
||||
}));
|
||||
|
||||
const emojiDiagnostics = vscode.languages.createDiagnosticCollection("emoji");
|
||||
context.subscriptions.push(emojiDiagnostics);
|
||||
|
||||
subscribeToDocumentChanges(context, emojiDiagnostics);
|
||||
|
||||
context.subscriptions.push(
|
||||
vscode.languages.registerCodeActionsProvider('markdown', new Emojinfo(), {
|
||||
providedCodeActionKinds: Emojinfo.providedCodeActionKinds
|
||||
})
|
||||
);
|
||||
|
||||
context.subscriptions.push(
|
||||
vscode.commands.registerCommand(COMMAND, () => vscode.env.openExternal(vscode.Uri.parse('https://unicode.org/emoji/charts-12.0/full-emoji-list.html')))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides code actions for converting :) to a smiley emoji.
|
||||
*/
|
||||
export class Emojizer implements vscode.CodeActionProvider {
|
||||
|
||||
public static readonly providedCodeActionKinds = [
|
||||
vscode.CodeActionKind.QuickFix
|
||||
];
|
||||
|
||||
public provideCodeActions(document: vscode.TextDocument, range: vscode.Range): vscode.CodeAction[] | undefined {
|
||||
if (!this.isAtStartOfSmiley(document, range)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const replaceWithSmileyCatFix = this.createFix(document, range, '😺');
|
||||
|
||||
const replaceWithSmileyFix = this.createFix(document, range, '😀');
|
||||
// Marking a single fix as `preferred` means that users can apply it with a
|
||||
// single keyboard shortcut using the `Auto Fix` command.
|
||||
replaceWithSmileyFix.isPreferred = true;
|
||||
|
||||
const replaceWithSmileyHankyFix = this.createFix(document, range, '💩');
|
||||
|
||||
const commandAction = this.createCommand();
|
||||
|
||||
return [
|
||||
replaceWithSmileyCatFix,
|
||||
replaceWithSmileyFix,
|
||||
replaceWithSmileyHankyFix,
|
||||
commandAction
|
||||
];
|
||||
}
|
||||
|
||||
private isAtStartOfSmiley(document: vscode.TextDocument, range: vscode.Range) {
|
||||
const start = range.start;
|
||||
const line = document.lineAt(start.line);
|
||||
return line.text[start.character] === ':' && line.text[start.character + 1] === ')';
|
||||
}
|
||||
|
||||
private createFix(document: vscode.TextDocument, range: vscode.Range, emoji: string): vscode.CodeAction {
|
||||
const fix = new vscode.CodeAction(`Convert to ${emoji}`, vscode.CodeActionKind.QuickFix);
|
||||
fix.edit = new vscode.WorkspaceEdit();
|
||||
fix.edit.replace(document.uri, new vscode.Range(range.start, range.start.translate(0, 2)), emoji);
|
||||
return fix;
|
||||
}
|
||||
|
||||
private createCommand(): vscode.CodeAction {
|
||||
const action = new vscode.CodeAction('Learn more...', vscode.CodeActionKind.Empty);
|
||||
action.command = { command: COMMAND, title: 'Learn more about emojis', tooltip: 'This will open the unicode emoji page.' };
|
||||
return action;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides code actions corresponding to diagnostic problems.
|
||||
*/
|
||||
export class Emojinfo implements vscode.CodeActionProvider {
|
||||
|
||||
public static readonly providedCodeActionKinds = [
|
||||
vscode.CodeActionKind.QuickFix
|
||||
];
|
||||
|
||||
provideCodeActions(document: vscode.TextDocument, range: vscode.Range | vscode.Selection, context: vscode.CodeActionContext, token: vscode.CancellationToken): vscode.CodeAction[] {
|
||||
// for each diagnostic entry that has the matching `code`, create a code action command
|
||||
return context.diagnostics
|
||||
.filter(diagnostic => diagnostic.code === EMOJI_MENTION)
|
||||
.map(diagnostic => this.createCommandCodeAction(diagnostic));
|
||||
}
|
||||
|
||||
private createCommandCodeAction(diagnostic: vscode.Diagnostic): vscode.CodeAction {
|
||||
const action = new vscode.CodeAction('Learn more...', vscode.CodeActionKind.QuickFix);
|
||||
action.command = { command: COMMAND, title: 'Learn more about emojis', tooltip: 'This will open the unicode emoji page.' };
|
||||
action.diagnostics = [diagnostic];
|
||||
action.isPreferred = true;
|
||||
return action;
|
||||
}
|
||||
}
|
||||
@ -1,55 +1,55 @@
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
/**
|
||||
* CodelensProvider
|
||||
*/
|
||||
export class CodelensProvider implements vscode.CodeLensProvider {
|
||||
|
||||
private codeLenses: vscode.CodeLens[] = [];
|
||||
private regex: RegExp;
|
||||
private _onDidChangeCodeLenses: vscode.EventEmitter<void> = new vscode.EventEmitter<void>();
|
||||
public readonly onDidChangeCodeLenses: vscode.Event<void> = this._onDidChangeCodeLenses.event;
|
||||
|
||||
constructor() {
|
||||
this.regex = /(.+)/g;
|
||||
|
||||
vscode.workspace.onDidChangeConfiguration((_) => {
|
||||
this._onDidChangeCodeLenses.fire();
|
||||
});
|
||||
}
|
||||
|
||||
public provideCodeLenses(document: vscode.TextDocument, token: vscode.CancellationToken): vscode.CodeLens[] | Thenable<vscode.CodeLens[]> {
|
||||
|
||||
if (vscode.workspace.getConfiguration("codelens-sample").get("enableCodeLens", true)) {
|
||||
this.codeLenses = [];
|
||||
const regex = new RegExp(this.regex);
|
||||
const text = document.getText();
|
||||
let matches;
|
||||
while ((matches = regex.exec(text)) !== null) {
|
||||
const line = document.lineAt(document.positionAt(matches.index).line);
|
||||
const indexOf = line.text.indexOf(matches[0]);
|
||||
const position = new vscode.Position(line.lineNumber, indexOf);
|
||||
const range = document.getWordRangeAtPosition(position, new RegExp(this.regex));
|
||||
if (range) {
|
||||
this.codeLenses.push(new vscode.CodeLens(range));
|
||||
}
|
||||
}
|
||||
return this.codeLenses;
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
public resolveCodeLens(codeLens: vscode.CodeLens, token: vscode.CancellationToken) {
|
||||
if (vscode.workspace.getConfiguration("codelens-sample").get("enableCodeLens", true)) {
|
||||
codeLens.command = {
|
||||
title: "Codelens provided by sample extension",
|
||||
tooltip: "Tooltip provided by sample extension",
|
||||
command: "codelens-sample.codelensAction",
|
||||
arguments: ["Argument 1", false]
|
||||
};
|
||||
return codeLens;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
/**
|
||||
* CodelensProvider
|
||||
*/
|
||||
export class CodelensProvider implements vscode.CodeLensProvider {
|
||||
|
||||
private codeLenses: vscode.CodeLens[] = [];
|
||||
private regex: RegExp;
|
||||
private _onDidChangeCodeLenses: vscode.EventEmitter<void> = new vscode.EventEmitter<void>();
|
||||
public readonly onDidChangeCodeLenses: vscode.Event<void> = this._onDidChangeCodeLenses.event;
|
||||
|
||||
constructor() {
|
||||
this.regex = /(.+)/g;
|
||||
|
||||
vscode.workspace.onDidChangeConfiguration((_) => {
|
||||
this._onDidChangeCodeLenses.fire();
|
||||
});
|
||||
}
|
||||
|
||||
public provideCodeLenses(document: vscode.TextDocument, token: vscode.CancellationToken): vscode.CodeLens[] | Thenable<vscode.CodeLens[]> {
|
||||
|
||||
if (vscode.workspace.getConfiguration("codelens-sample").get("enableCodeLens", true)) {
|
||||
this.codeLenses = [];
|
||||
const regex = new RegExp(this.regex);
|
||||
const text = document.getText();
|
||||
let matches;
|
||||
while ((matches = regex.exec(text)) !== null) {
|
||||
const line = document.lineAt(document.positionAt(matches.index).line);
|
||||
const indexOf = line.text.indexOf(matches[0]);
|
||||
const position = new vscode.Position(line.lineNumber, indexOf);
|
||||
const range = document.getWordRangeAtPosition(position, new RegExp(this.regex));
|
||||
if (range) {
|
||||
this.codeLenses.push(new vscode.CodeLens(range));
|
||||
}
|
||||
}
|
||||
return this.codeLenses;
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
public resolveCodeLens(codeLens: vscode.CodeLens, token: vscode.CancellationToken) {
|
||||
if (vscode.workspace.getConfiguration("codelens-sample").get("enableCodeLens", true)) {
|
||||
codeLens.command = {
|
||||
title: "Codelens provided by sample extension",
|
||||
tooltip: "Tooltip provided by sample extension",
|
||||
command: "codelens-sample.codelensAction",
|
||||
arguments: ["Argument 1", false]
|
||||
};
|
||||
return codeLens;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,35 +1,35 @@
|
||||
// The module 'vscode' contains the VS Code extensibility API
|
||||
// Import the module and reference it with the alias vscode in your code below
|
||||
import { ExtensionContext, languages, commands, Disposable, workspace, window } from 'vscode';
|
||||
import { CodelensProvider } from './CodelensProvider';
|
||||
|
||||
// this method is called when your extension is activated
|
||||
// your extension is activated the very first time the command is executed
|
||||
|
||||
let disposables: Disposable[] = [];
|
||||
|
||||
export function activate(context: ExtensionContext) {
|
||||
const codelensProvider = new CodelensProvider();
|
||||
|
||||
languages.registerCodeLensProvider("*", codelensProvider);
|
||||
|
||||
commands.registerCommand("codelens-sample.enableCodeLens", () => {
|
||||
workspace.getConfiguration("codelens-sample").update("enableCodeLens", true, true);
|
||||
});
|
||||
|
||||
commands.registerCommand("codelens-sample.disableCodeLens", () => {
|
||||
workspace.getConfiguration("codelens-sample").update("enableCodeLens", false, true);
|
||||
});
|
||||
|
||||
commands.registerCommand("codelens-sample.codelensAction", (args: any) => {
|
||||
window.showInformationMessage(`CodeLens action clicked with args=${args}`);
|
||||
});
|
||||
}
|
||||
|
||||
// this method is called when your extension is deactivated
|
||||
export function deactivate() {
|
||||
if (disposables) {
|
||||
disposables.forEach(item => item.dispose());
|
||||
}
|
||||
disposables = [];
|
||||
}
|
||||
// The module 'vscode' contains the VS Code extensibility API
|
||||
// Import the module and reference it with the alias vscode in your code below
|
||||
import { ExtensionContext, languages, commands, Disposable, workspace, window } from 'vscode';
|
||||
import { CodelensProvider } from './CodelensProvider';
|
||||
|
||||
// this method is called when your extension is activated
|
||||
// your extension is activated the very first time the command is executed
|
||||
|
||||
let disposables: Disposable[] = [];
|
||||
|
||||
export function activate(context: ExtensionContext) {
|
||||
const codelensProvider = new CodelensProvider();
|
||||
|
||||
languages.registerCodeLensProvider("*", codelensProvider);
|
||||
|
||||
commands.registerCommand("codelens-sample.enableCodeLens", () => {
|
||||
workspace.getConfiguration("codelens-sample").update("enableCodeLens", true, true);
|
||||
});
|
||||
|
||||
commands.registerCommand("codelens-sample.disableCodeLens", () => {
|
||||
workspace.getConfiguration("codelens-sample").update("enableCodeLens", false, true);
|
||||
});
|
||||
|
||||
commands.registerCommand("codelens-sample.codelensAction", (args: any) => {
|
||||
window.showInformationMessage(`CodeLens action clicked with args=${args}`);
|
||||
});
|
||||
}
|
||||
|
||||
// this method is called when your extension is deactivated
|
||||
export function deactivate() {
|
||||
if (disposables) {
|
||||
disposables.forEach(item => item.dispose());
|
||||
}
|
||||
disposables = [];
|
||||
}
|
||||
|
||||
@ -1,144 +1,144 @@
|
||||
'use strict';
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
let commentId = 1;
|
||||
|
||||
class NoteComment implements vscode.Comment {
|
||||
id: number;
|
||||
label: string | undefined;
|
||||
savedBody: string | vscode.MarkdownString; // for the Cancel button
|
||||
constructor(
|
||||
public body: string | vscode.MarkdownString,
|
||||
public mode: vscode.CommentMode,
|
||||
public author: vscode.CommentAuthorInformation,
|
||||
public parent?: vscode.CommentThread,
|
||||
public contextValue?: string
|
||||
) {
|
||||
this.id = ++commentId;
|
||||
this.savedBody = this.body;
|
||||
}
|
||||
}
|
||||
|
||||
export function activate(context: vscode.ExtensionContext) {
|
||||
// A `CommentController` is able to provide comments for documents.
|
||||
const commentController = vscode.comments.createCommentController('comment-sample', 'Comment API Sample');
|
||||
context.subscriptions.push(commentController);
|
||||
|
||||
// A `CommentingRangeProvider` controls where gutter decorations that allow adding comments are shown
|
||||
commentController.commentingRangeProvider = {
|
||||
provideCommentingRanges: (document: vscode.TextDocument, token: vscode.CancellationToken) => {
|
||||
const lineCount = document.lineCount;
|
||||
return [new vscode.Range(0, 0, lineCount - 1, 0)];
|
||||
}
|
||||
};
|
||||
|
||||
context.subscriptions.push(vscode.commands.registerCommand('mywiki.createNote', (reply: vscode.CommentReply) => {
|
||||
replyNote(reply);
|
||||
}));
|
||||
|
||||
context.subscriptions.push(vscode.commands.registerCommand('mywiki.replyNote', (reply: vscode.CommentReply) => {
|
||||
replyNote(reply);
|
||||
}));
|
||||
|
||||
context.subscriptions.push(vscode.commands.registerCommand('mywiki.startDraft', (reply: vscode.CommentReply) => {
|
||||
const thread = reply.thread;
|
||||
thread.contextValue = 'draft';
|
||||
const newComment = new NoteComment(reply.text, vscode.CommentMode.Preview, { name: 'vscode' }, thread);
|
||||
newComment.label = 'pending';
|
||||
thread.comments = [...thread.comments, newComment];
|
||||
}));
|
||||
|
||||
context.subscriptions.push(vscode.commands.registerCommand('mywiki.finishDraft', (reply: vscode.CommentReply) => {
|
||||
const thread = reply.thread;
|
||||
|
||||
if (!thread) {
|
||||
return;
|
||||
}
|
||||
|
||||
thread.contextValue = undefined;
|
||||
thread.collapsibleState = vscode.CommentThreadCollapsibleState.Collapsed;
|
||||
if (reply.text) {
|
||||
const newComment = new NoteComment(reply.text, vscode.CommentMode.Preview, { name: 'vscode' }, thread);
|
||||
thread.comments = [...thread.comments, newComment].map(comment => {
|
||||
comment.label = undefined;
|
||||
return comment;
|
||||
});
|
||||
}
|
||||
}));
|
||||
|
||||
context.subscriptions.push(vscode.commands.registerCommand('mywiki.deleteNoteComment', (comment: NoteComment) => {
|
||||
const thread = comment.parent;
|
||||
if (!thread) {
|
||||
return;
|
||||
}
|
||||
|
||||
thread.comments = thread.comments.filter(cmt => (cmt as NoteComment).id !== comment.id);
|
||||
|
||||
if (thread.comments.length === 0) {
|
||||
thread.dispose();
|
||||
}
|
||||
}));
|
||||
|
||||
context.subscriptions.push(vscode.commands.registerCommand('mywiki.deleteNote', (thread: vscode.CommentThread) => {
|
||||
thread.dispose();
|
||||
}));
|
||||
|
||||
context.subscriptions.push(vscode.commands.registerCommand('mywiki.cancelsaveNote', (comment: NoteComment) => {
|
||||
if (!comment.parent) {
|
||||
return;
|
||||
}
|
||||
|
||||
comment.parent.comments = comment.parent.comments.map(cmt => {
|
||||
if ((cmt as NoteComment).id === comment.id) {
|
||||
cmt.body = (cmt as NoteComment).savedBody;
|
||||
cmt.mode = vscode.CommentMode.Preview;
|
||||
}
|
||||
|
||||
return cmt;
|
||||
});
|
||||
}));
|
||||
|
||||
context.subscriptions.push(vscode.commands.registerCommand('mywiki.saveNote', (comment: NoteComment) => {
|
||||
if (!comment.parent) {
|
||||
return;
|
||||
}
|
||||
|
||||
comment.parent.comments = comment.parent.comments.map(cmt => {
|
||||
if ((cmt as NoteComment).id === comment.id) {
|
||||
(cmt as NoteComment).savedBody = cmt.body;
|
||||
cmt.mode = vscode.CommentMode.Preview;
|
||||
}
|
||||
|
||||
return cmt;
|
||||
});
|
||||
}));
|
||||
|
||||
context.subscriptions.push(vscode.commands.registerCommand('mywiki.editNote', (comment: NoteComment) => {
|
||||
if (!comment.parent) {
|
||||
return;
|
||||
}
|
||||
|
||||
comment.parent.comments = comment.parent.comments.map(cmt => {
|
||||
if ((cmt as NoteComment).id === comment.id) {
|
||||
cmt.mode = vscode.CommentMode.Editing;
|
||||
}
|
||||
|
||||
return cmt;
|
||||
});
|
||||
}));
|
||||
|
||||
context.subscriptions.push(vscode.commands.registerCommand('mywiki.dispose', () => {
|
||||
commentController.dispose();
|
||||
}));
|
||||
|
||||
function replyNote(reply: vscode.CommentReply) {
|
||||
const thread = reply.thread;
|
||||
const newComment = new NoteComment(reply.text, vscode.CommentMode.Preview, { name: 'vscode' }, thread, thread.comments.length ? 'canDelete' : undefined);
|
||||
if (thread.contextValue === 'draft') {
|
||||
newComment.label = 'pending';
|
||||
}
|
||||
|
||||
thread.comments = [...thread.comments, newComment];
|
||||
}
|
||||
}
|
||||
'use strict';
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
let commentId = 1;
|
||||
|
||||
class NoteComment implements vscode.Comment {
|
||||
id: number;
|
||||
label: string | undefined;
|
||||
savedBody: string | vscode.MarkdownString; // for the Cancel button
|
||||
constructor(
|
||||
public body: string | vscode.MarkdownString,
|
||||
public mode: vscode.CommentMode,
|
||||
public author: vscode.CommentAuthorInformation,
|
||||
public parent?: vscode.CommentThread,
|
||||
public contextValue?: string
|
||||
) {
|
||||
this.id = ++commentId;
|
||||
this.savedBody = this.body;
|
||||
}
|
||||
}
|
||||
|
||||
export function activate(context: vscode.ExtensionContext) {
|
||||
// A `CommentController` is able to provide comments for documents.
|
||||
const commentController = vscode.comments.createCommentController('comment-sample', 'Comment API Sample');
|
||||
context.subscriptions.push(commentController);
|
||||
|
||||
// A `CommentingRangeProvider` controls where gutter decorations that allow adding comments are shown
|
||||
commentController.commentingRangeProvider = {
|
||||
provideCommentingRanges: (document: vscode.TextDocument, token: vscode.CancellationToken) => {
|
||||
const lineCount = document.lineCount;
|
||||
return [new vscode.Range(0, 0, lineCount - 1, 0)];
|
||||
}
|
||||
};
|
||||
|
||||
context.subscriptions.push(vscode.commands.registerCommand('mywiki.createNote', (reply: vscode.CommentReply) => {
|
||||
replyNote(reply);
|
||||
}));
|
||||
|
||||
context.subscriptions.push(vscode.commands.registerCommand('mywiki.replyNote', (reply: vscode.CommentReply) => {
|
||||
replyNote(reply);
|
||||
}));
|
||||
|
||||
context.subscriptions.push(vscode.commands.registerCommand('mywiki.startDraft', (reply: vscode.CommentReply) => {
|
||||
const thread = reply.thread;
|
||||
thread.contextValue = 'draft';
|
||||
const newComment = new NoteComment(reply.text, vscode.CommentMode.Preview, { name: 'vscode' }, thread);
|
||||
newComment.label = 'pending';
|
||||
thread.comments = [...thread.comments, newComment];
|
||||
}));
|
||||
|
||||
context.subscriptions.push(vscode.commands.registerCommand('mywiki.finishDraft', (reply: vscode.CommentReply) => {
|
||||
const thread = reply.thread;
|
||||
|
||||
if (!thread) {
|
||||
return;
|
||||
}
|
||||
|
||||
thread.contextValue = undefined;
|
||||
thread.collapsibleState = vscode.CommentThreadCollapsibleState.Collapsed;
|
||||
if (reply.text) {
|
||||
const newComment = new NoteComment(reply.text, vscode.CommentMode.Preview, { name: 'vscode' }, thread);
|
||||
thread.comments = [...thread.comments, newComment].map(comment => {
|
||||
comment.label = undefined;
|
||||
return comment;
|
||||
});
|
||||
}
|
||||
}));
|
||||
|
||||
context.subscriptions.push(vscode.commands.registerCommand('mywiki.deleteNoteComment', (comment: NoteComment) => {
|
||||
const thread = comment.parent;
|
||||
if (!thread) {
|
||||
return;
|
||||
}
|
||||
|
||||
thread.comments = thread.comments.filter(cmt => (cmt as NoteComment).id !== comment.id);
|
||||
|
||||
if (thread.comments.length === 0) {
|
||||
thread.dispose();
|
||||
}
|
||||
}));
|
||||
|
||||
context.subscriptions.push(vscode.commands.registerCommand('mywiki.deleteNote', (thread: vscode.CommentThread) => {
|
||||
thread.dispose();
|
||||
}));
|
||||
|
||||
context.subscriptions.push(vscode.commands.registerCommand('mywiki.cancelsaveNote', (comment: NoteComment) => {
|
||||
if (!comment.parent) {
|
||||
return;
|
||||
}
|
||||
|
||||
comment.parent.comments = comment.parent.comments.map(cmt => {
|
||||
if ((cmt as NoteComment).id === comment.id) {
|
||||
cmt.body = (cmt as NoteComment).savedBody;
|
||||
cmt.mode = vscode.CommentMode.Preview;
|
||||
}
|
||||
|
||||
return cmt;
|
||||
});
|
||||
}));
|
||||
|
||||
context.subscriptions.push(vscode.commands.registerCommand('mywiki.saveNote', (comment: NoteComment) => {
|
||||
if (!comment.parent) {
|
||||
return;
|
||||
}
|
||||
|
||||
comment.parent.comments = comment.parent.comments.map(cmt => {
|
||||
if ((cmt as NoteComment).id === comment.id) {
|
||||
(cmt as NoteComment).savedBody = cmt.body;
|
||||
cmt.mode = vscode.CommentMode.Preview;
|
||||
}
|
||||
|
||||
return cmt;
|
||||
});
|
||||
}));
|
||||
|
||||
context.subscriptions.push(vscode.commands.registerCommand('mywiki.editNote', (comment: NoteComment) => {
|
||||
if (!comment.parent) {
|
||||
return;
|
||||
}
|
||||
|
||||
comment.parent.comments = comment.parent.comments.map(cmt => {
|
||||
if ((cmt as NoteComment).id === comment.id) {
|
||||
cmt.mode = vscode.CommentMode.Editing;
|
||||
}
|
||||
|
||||
return cmt;
|
||||
});
|
||||
}));
|
||||
|
||||
context.subscriptions.push(vscode.commands.registerCommand('mywiki.dispose', () => {
|
||||
commentController.dispose();
|
||||
}));
|
||||
|
||||
function replyNote(reply: vscode.CommentReply) {
|
||||
const thread = reply.thread;
|
||||
const newComment = new NoteComment(reply.text, vscode.CommentMode.Preview, { name: 'vscode' }, thread, thread.comments.length ? 'canDelete' : undefined);
|
||||
if (thread.contextValue === 'draft') {
|
||||
newComment.label = 'pending';
|
||||
}
|
||||
|
||||
thread.comments = [...thread.comments, newComment];
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,74 +1,74 @@
|
||||
/*---------------------------------------------------------
|
||||
* Copyright (C) Microsoft Corporation. All rights reserved.
|
||||
*--------------------------------------------------------*/
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
export function activate(context: vscode.ExtensionContext) {
|
||||
|
||||
const provider1 = vscode.languages.registerCompletionItemProvider('plaintext', {
|
||||
|
||||
provideCompletionItems(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken, context: vscode.CompletionContext) {
|
||||
|
||||
// a simple completion item which inserts `Hello World!`
|
||||
const simpleCompletion = new vscode.CompletionItem('Hello World!');
|
||||
|
||||
// a completion item that inserts its text as snippet,
|
||||
// the `insertText`-property is a `SnippetString` which will be
|
||||
// honored by the editor.
|
||||
const snippetCompletion = new vscode.CompletionItem('Good part of the day');
|
||||
snippetCompletion.insertText = new vscode.SnippetString('Good ${1|morning,afternoon,evening|}. It is ${1}, right?');
|
||||
const docs : any = new vscode.MarkdownString("Inserts a snippet that lets you select [link](x.ts).");
|
||||
snippetCompletion.documentation = docs;
|
||||
docs.baseUri = vscode.Uri.parse('http://example.com/a/b/c/');
|
||||
|
||||
// a completion item that can be accepted by a commit character,
|
||||
// the `commitCharacters`-property is set which means that the completion will
|
||||
// be inserted and then the character will be typed.
|
||||
const commitCharacterCompletion = new vscode.CompletionItem('console');
|
||||
commitCharacterCompletion.commitCharacters = ['.'];
|
||||
commitCharacterCompletion.documentation = new vscode.MarkdownString('Press `.` to get `console.`');
|
||||
|
||||
// a completion item that retriggers IntelliSense when being accepted,
|
||||
// the `command`-property is set which the editor will execute after
|
||||
// completion has been inserted. Also, the `insertText` is set so that
|
||||
// a space is inserted after `new`
|
||||
const commandCompletion = new vscode.CompletionItem('new');
|
||||
commandCompletion.kind = vscode.CompletionItemKind.Keyword;
|
||||
commandCompletion.insertText = 'new ';
|
||||
commandCompletion.command = { command: 'editor.action.triggerSuggest', title: 'Re-trigger completions...' };
|
||||
|
||||
// return all completion items as array
|
||||
return [
|
||||
simpleCompletion,
|
||||
snippetCompletion,
|
||||
commitCharacterCompletion,
|
||||
commandCompletion
|
||||
];
|
||||
}
|
||||
});
|
||||
|
||||
const provider2 = vscode.languages.registerCompletionItemProvider(
|
||||
'plaintext',
|
||||
{
|
||||
provideCompletionItems(document: vscode.TextDocument, position: vscode.Position) {
|
||||
|
||||
// get all text until the `position` and check if it reads `console.`
|
||||
// and if so then complete if `log`, `warn`, and `error`
|
||||
const linePrefix = document.lineAt(position).text.substr(0, position.character);
|
||||
if (!linePrefix.endsWith('console.')) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return [
|
||||
new vscode.CompletionItem('log', vscode.CompletionItemKind.Method),
|
||||
new vscode.CompletionItem('warn', vscode.CompletionItemKind.Method),
|
||||
new vscode.CompletionItem('error', vscode.CompletionItemKind.Method),
|
||||
];
|
||||
}
|
||||
},
|
||||
'.' // triggered whenever a '.' is being typed
|
||||
);
|
||||
|
||||
context.subscriptions.push(provider1, provider2);
|
||||
}
|
||||
/*---------------------------------------------------------
|
||||
* Copyright (C) Microsoft Corporation. All rights reserved.
|
||||
*--------------------------------------------------------*/
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
export function activate(context: vscode.ExtensionContext) {
|
||||
|
||||
const provider1 = vscode.languages.registerCompletionItemProvider('plaintext', {
|
||||
|
||||
provideCompletionItems(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken, context: vscode.CompletionContext) {
|
||||
|
||||
// a simple completion item which inserts `Hello World!`
|
||||
const simpleCompletion = new vscode.CompletionItem('Hello World!');
|
||||
|
||||
// a completion item that inserts its text as snippet,
|
||||
// the `insertText`-property is a `SnippetString` which will be
|
||||
// honored by the editor.
|
||||
const snippetCompletion = new vscode.CompletionItem('Good part of the day');
|
||||
snippetCompletion.insertText = new vscode.SnippetString('Good ${1|morning,afternoon,evening|}. It is ${1}, right?');
|
||||
const docs: any = new vscode.MarkdownString("Inserts a snippet that lets you select [link](x.ts).");
|
||||
snippetCompletion.documentation = docs;
|
||||
docs.baseUri = vscode.Uri.parse('http://example.com/a/b/c/');
|
||||
|
||||
// a completion item that can be accepted by a commit character,
|
||||
// the `commitCharacters`-property is set which means that the completion will
|
||||
// be inserted and then the character will be typed.
|
||||
const commitCharacterCompletion = new vscode.CompletionItem('console');
|
||||
commitCharacterCompletion.commitCharacters = ['.'];
|
||||
commitCharacterCompletion.documentation = new vscode.MarkdownString('Press `.` to get `console.`');
|
||||
|
||||
// a completion item that retriggers IntelliSense when being accepted,
|
||||
// the `command`-property is set which the editor will execute after
|
||||
// completion has been inserted. Also, the `insertText` is set so that
|
||||
// a space is inserted after `new`
|
||||
const commandCompletion = new vscode.CompletionItem('new');
|
||||
commandCompletion.kind = vscode.CompletionItemKind.Keyword;
|
||||
commandCompletion.insertText = 'new ';
|
||||
commandCompletion.command = { command: 'editor.action.triggerSuggest', title: 'Re-trigger completions...' };
|
||||
|
||||
// return all completion items as array
|
||||
return [
|
||||
simpleCompletion,
|
||||
snippetCompletion,
|
||||
commitCharacterCompletion,
|
||||
commandCompletion
|
||||
];
|
||||
}
|
||||
});
|
||||
|
||||
const provider2 = vscode.languages.registerCompletionItemProvider(
|
||||
'plaintext',
|
||||
{
|
||||
provideCompletionItems(document: vscode.TextDocument, position: vscode.Position) {
|
||||
|
||||
// get all text until the `position` and check if it reads `console.`
|
||||
// and if so then complete if `log`, `warn`, and `error`
|
||||
const linePrefix = document.lineAt(position).text.substr(0, position.character);
|
||||
if (!linePrefix.endsWith('console.')) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return [
|
||||
new vscode.CompletionItem('log', vscode.CompletionItemKind.Method),
|
||||
new vscode.CompletionItem('warn', vscode.CompletionItemKind.Method),
|
||||
new vscode.CompletionItem('error', vscode.CompletionItemKind.Method),
|
||||
];
|
||||
}
|
||||
},
|
||||
'.' // triggered whenever a '.' is being typed
|
||||
);
|
||||
|
||||
context.subscriptions.push(provider1, provider2);
|
||||
}
|
||||
|
||||
@ -1,225 +1,225 @@
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
export function activate(context: vscode.ExtensionContext) {
|
||||
|
||||
// Example: Reading Window scoped configuration
|
||||
const configuredView = vscode.workspace.getConfiguration().get('conf.view.showOnWindowOpen');
|
||||
switch (configuredView) {
|
||||
case 'explorer':
|
||||
vscode.commands.executeCommand('workbench.view.explorer');
|
||||
break;
|
||||
case 'search':
|
||||
vscode.commands.executeCommand('workbench.view.search');
|
||||
break;
|
||||
case 'scm':
|
||||
vscode.commands.executeCommand('workbench.view.scm');
|
||||
break;
|
||||
case 'debug':
|
||||
vscode.commands.executeCommand('workbench.view.debug');
|
||||
break;
|
||||
case 'extensions':
|
||||
vscode.commands.executeCommand('workbench.view.extensions');
|
||||
break;
|
||||
}
|
||||
|
||||
// Example: Updating Window scoped configuration
|
||||
vscode.commands.registerCommand('config.commands.configureViewOnWindowOpen', async () => {
|
||||
|
||||
// 1) Getting the value
|
||||
const value = await vscode.window.showQuickPick(['explorer', 'search', 'scm', 'debug', 'extensions'], { placeHolder: 'Select the view to show when opening a window.' });
|
||||
|
||||
if (vscode.workspace.workspaceFolders) {
|
||||
|
||||
// 2) Getting the Configuration target
|
||||
const target = await vscode.window.showQuickPick(
|
||||
[
|
||||
{ label: 'User', description: 'User Settings', target: vscode.ConfigurationTarget.Global },
|
||||
{ label: 'Workspace', description: 'Workspace Settings', target: vscode.ConfigurationTarget.Workspace }
|
||||
],
|
||||
{ placeHolder: 'Select the view to show when opening a window.' });
|
||||
|
||||
if (value && target) {
|
||||
|
||||
// 3) Update the configuration value in the target
|
||||
await vscode.workspace.getConfiguration().update('conf.view.showOnWindowOpen', value, target.target);
|
||||
|
||||
/*
|
||||
// Default is to update in Workspace
|
||||
await vscode.workspace.getConfiguration().update('conf.view.showOnWindowOpen', value);
|
||||
*/
|
||||
}
|
||||
} else {
|
||||
|
||||
// 2) Update the configuration value in User Setting in case of no workspace folders
|
||||
await vscode.workspace.getConfiguration().update('conf.view.showOnWindowOpen', value, vscode.ConfigurationTarget.Global);
|
||||
}
|
||||
|
||||
|
||||
});
|
||||
|
||||
// Example: Reading Resource scoped configuration for a file
|
||||
context.subscriptions.push(vscode.workspace.onDidOpenTextDocument(e => {
|
||||
|
||||
// 1) Get the configured glob pattern value for the current file
|
||||
const value: any = vscode.workspace.getConfiguration('', e.uri).get('conf.resource.insertEmptyLastLine');
|
||||
|
||||
// 2) Check if the current resource matches the glob pattern
|
||||
const matches = value ? value[e.fileName] : undefined;
|
||||
|
||||
// 3) If matches, insert empty last line
|
||||
if (matches) {
|
||||
vscode.window.showInformationMessage('An empty line will be added to the document ' + e.fileName);
|
||||
}
|
||||
|
||||
}));
|
||||
|
||||
// Example: Updating Resource scoped Configuration for current file
|
||||
vscode.commands.registerCommand('config.commands.configureEmptyLastLineCurrentFile', async () => {
|
||||
|
||||
if (vscode.window.activeTextEditor) {
|
||||
const currentDocument = vscode.window.activeTextEditor.document;
|
||||
|
||||
// 1) Get the configuration for the current document
|
||||
const configuration = vscode.workspace.getConfiguration('', currentDocument.uri);
|
||||
|
||||
// 2) Get the configiuration value
|
||||
const currentValue = configuration.get('conf.resource.insertEmptyLastLine', {});
|
||||
|
||||
// 3) Choose target to Global when there are no workspace folders
|
||||
const target = vscode.workspace.workspaceFolders ? vscode.ConfigurationTarget.WorkspaceFolder : vscode.ConfigurationTarget.Global;
|
||||
|
||||
const value = { ...currentValue, ...{ [currentDocument.fileName]: true } };
|
||||
|
||||
// 4) Update the configuration
|
||||
await configuration.update('conf.resource.insertEmptyLastLine', value, target);
|
||||
}
|
||||
});
|
||||
|
||||
// Example: Updating Resource scoped Configuration
|
||||
vscode.commands.registerCommand('config.commands.configureEmptyLastLineFiles', async () => {
|
||||
|
||||
// 1) Getting the value
|
||||
const value = await vscode.window.showInputBox({ prompt: 'Provide glob pattern of files to have empty last line.' });
|
||||
|
||||
if (vscode.workspace.workspaceFolders) {
|
||||
|
||||
// 2) Getting the target
|
||||
const target = await vscode.window.showQuickPick(
|
||||
[
|
||||
{ label: 'Application', description: 'User Settings', target: vscode.ConfigurationTarget.Global },
|
||||
{ label: 'Workspace', description: 'Workspace Settings', target: vscode.ConfigurationTarget.Workspace },
|
||||
{ label: 'Workspace Folder', description: 'Workspace Folder Settings', target: vscode.ConfigurationTarget.WorkspaceFolder }
|
||||
],
|
||||
{ placeHolder: 'Select the target to which this setting should be applied' });
|
||||
|
||||
if (value && target) {
|
||||
|
||||
if (target.target === vscode.ConfigurationTarget.WorkspaceFolder) {
|
||||
|
||||
// 3) Getting the workspace folder
|
||||
const workspaceFolder = await vscode.window.showWorkspaceFolderPick({ placeHolder: 'Pick Workspace Folder to which this setting should be applied' });
|
||||
if (workspaceFolder) {
|
||||
|
||||
// 4) Get the configuration for the workspace folder
|
||||
const configuration = vscode.workspace.getConfiguration('', workspaceFolder.uri);
|
||||
|
||||
// 5) Get the current value
|
||||
const currentValue = configuration.get<{}>('conf.resource.insertEmptyLastLine');
|
||||
|
||||
const newValue = { ...currentValue, ...{ [value]: true } };
|
||||
|
||||
// 6) Update the configuration value
|
||||
await configuration.update('conf.resource.insertEmptyLastLine', newValue, target.target);
|
||||
}
|
||||
} else {
|
||||
|
||||
// 3) Get the configuration
|
||||
const configuration = vscode.workspace.getConfiguration();
|
||||
|
||||
// 4) Get the current value
|
||||
const currentValue = configuration.get<{}>('conf.resource.insertEmptyLastLine');
|
||||
|
||||
const newValue = { ...currentValue, ...(value ? { [value]: true } : {}) };
|
||||
|
||||
// 3) Update the value in the target
|
||||
await vscode.workspace.getConfiguration().update('conf.resource.insertEmptyLastLine', newValue, target.target);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
||||
// 2) Get the configuration
|
||||
const configuration = vscode.workspace.getConfiguration();
|
||||
|
||||
// 3) Get the current value
|
||||
const currentValue = configuration.get<{}>('conf.resource.insertEmptyLastLine');
|
||||
|
||||
const newValue = { ...currentValue, ...(value ? { [value]: true } : {}) };
|
||||
|
||||
// 4) Update the value in the User Settings
|
||||
await vscode.workspace.getConfiguration().update('conf.resource.insertEmptyLastLine', newValue, vscode.ConfigurationTarget.Global);
|
||||
}
|
||||
});
|
||||
|
||||
let statusSizeDisposable: vscode.Disposable;
|
||||
// Example: Reading language overridable configuration for a document
|
||||
context.subscriptions.push(vscode.workspace.onDidOpenTextDocument(e => {
|
||||
|
||||
if (statusSizeDisposable) {
|
||||
statusSizeDisposable.dispose();
|
||||
}
|
||||
|
||||
// 1) Check if showing size is configured for current file
|
||||
const showSize: any = vscode.workspace.getConfiguration('', e).get('conf.language.showSize');
|
||||
|
||||
// 3) If matches, insert empty last line
|
||||
if (showSize) {
|
||||
statusSizeDisposable = vscode.window.setStatusBarMessage(`${e.getText().length}`);
|
||||
}
|
||||
|
||||
}));
|
||||
|
||||
// Example: Overriding configuration value for a language
|
||||
context.subscriptions.push(vscode.commands.registerCommand('config.commands.overrideLanguageValue', async () => {
|
||||
|
||||
// 1) Getting the languge id
|
||||
const languageId = await vscode.window.showInputBox({ placeHolder: 'Enter the language id' });
|
||||
|
||||
// 2) Update
|
||||
vscode.workspace.getConfiguration('', { languageId: languageId! }).update('conf.language.showSize', true, false, true);
|
||||
|
||||
}));
|
||||
|
||||
// Example: Listening to configuration changes
|
||||
context.subscriptions.push(vscode.workspace.onDidChangeConfiguration(e => {
|
||||
|
||||
if (e.affectsConfiguration('conf.resource.insertEmptyLastLine')) {
|
||||
if (vscode.window.activeTextEditor) {
|
||||
|
||||
const currentDocument = vscode.window.activeTextEditor.document;
|
||||
|
||||
// 1) Get the configured glob pattern value for the current file
|
||||
const value: any = vscode.workspace.getConfiguration('', currentDocument.uri).get('conf.resource.insertEmptyLastLine');
|
||||
|
||||
// 2) Check if the current resource matches the glob pattern
|
||||
const matches = value[currentDocument.fileName];
|
||||
|
||||
// 3) If matches, insert empty last line
|
||||
if (matches) {
|
||||
vscode.window.showInformationMessage('An empty line will be added to the document ' + currentDocument.fileName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check if a language configuration is changed for a text document
|
||||
if (e.affectsConfiguration('conf.language.showSize', vscode.window.activeTextEditor?.document)) {
|
||||
// noop
|
||||
}
|
||||
|
||||
// Check if a language configuration is changed for a language
|
||||
if (e.affectsConfiguration('conf.language.showSize', { languageId: 'typescript' })) {
|
||||
// noop
|
||||
}
|
||||
|
||||
}));
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
export function activate(context: vscode.ExtensionContext) {
|
||||
|
||||
// Example: Reading Window scoped configuration
|
||||
const configuredView = vscode.workspace.getConfiguration().get('conf.view.showOnWindowOpen');
|
||||
switch (configuredView) {
|
||||
case 'explorer':
|
||||
vscode.commands.executeCommand('workbench.view.explorer');
|
||||
break;
|
||||
case 'search':
|
||||
vscode.commands.executeCommand('workbench.view.search');
|
||||
break;
|
||||
case 'scm':
|
||||
vscode.commands.executeCommand('workbench.view.scm');
|
||||
break;
|
||||
case 'debug':
|
||||
vscode.commands.executeCommand('workbench.view.debug');
|
||||
break;
|
||||
case 'extensions':
|
||||
vscode.commands.executeCommand('workbench.view.extensions');
|
||||
break;
|
||||
}
|
||||
|
||||
// Example: Updating Window scoped configuration
|
||||
vscode.commands.registerCommand('config.commands.configureViewOnWindowOpen', async () => {
|
||||
|
||||
// 1) Getting the value
|
||||
const value = await vscode.window.showQuickPick(['explorer', 'search', 'scm', 'debug', 'extensions'], { placeHolder: 'Select the view to show when opening a window.' });
|
||||
|
||||
if (vscode.workspace.workspaceFolders) {
|
||||
|
||||
// 2) Getting the Configuration target
|
||||
const target = await vscode.window.showQuickPick(
|
||||
[
|
||||
{ label: 'User', description: 'User Settings', target: vscode.ConfigurationTarget.Global },
|
||||
{ label: 'Workspace', description: 'Workspace Settings', target: vscode.ConfigurationTarget.Workspace }
|
||||
],
|
||||
{ placeHolder: 'Select the view to show when opening a window.' });
|
||||
|
||||
if (value && target) {
|
||||
|
||||
// 3) Update the configuration value in the target
|
||||
await vscode.workspace.getConfiguration().update('conf.view.showOnWindowOpen', value, target.target);
|
||||
|
||||
/*
|
||||
// Default is to update in Workspace
|
||||
await vscode.workspace.getConfiguration().update('conf.view.showOnWindowOpen', value);
|
||||
*/
|
||||
}
|
||||
} else {
|
||||
|
||||
// 2) Update the configuration value in User Setting in case of no workspace folders
|
||||
await vscode.workspace.getConfiguration().update('conf.view.showOnWindowOpen', value, vscode.ConfigurationTarget.Global);
|
||||
}
|
||||
|
||||
|
||||
});
|
||||
|
||||
// Example: Reading Resource scoped configuration for a file
|
||||
context.subscriptions.push(vscode.workspace.onDidOpenTextDocument(e => {
|
||||
|
||||
// 1) Get the configured glob pattern value for the current file
|
||||
const value: any = vscode.workspace.getConfiguration('', e.uri).get('conf.resource.insertEmptyLastLine');
|
||||
|
||||
// 2) Check if the current resource matches the glob pattern
|
||||
const matches = value ? value[e.fileName] : undefined;
|
||||
|
||||
// 3) If matches, insert empty last line
|
||||
if (matches) {
|
||||
vscode.window.showInformationMessage('An empty line will be added to the document ' + e.fileName);
|
||||
}
|
||||
|
||||
}));
|
||||
|
||||
// Example: Updating Resource scoped Configuration for current file
|
||||
vscode.commands.registerCommand('config.commands.configureEmptyLastLineCurrentFile', async () => {
|
||||
|
||||
if (vscode.window.activeTextEditor) {
|
||||
const currentDocument = vscode.window.activeTextEditor.document;
|
||||
|
||||
// 1) Get the configuration for the current document
|
||||
const configuration = vscode.workspace.getConfiguration('', currentDocument.uri);
|
||||
|
||||
// 2) Get the configiuration value
|
||||
const currentValue = configuration.get('conf.resource.insertEmptyLastLine', {});
|
||||
|
||||
// 3) Choose target to Global when there are no workspace folders
|
||||
const target = vscode.workspace.workspaceFolders ? vscode.ConfigurationTarget.WorkspaceFolder : vscode.ConfigurationTarget.Global;
|
||||
|
||||
const value = { ...currentValue, ...{ [currentDocument.fileName]: true } };
|
||||
|
||||
// 4) Update the configuration
|
||||
await configuration.update('conf.resource.insertEmptyLastLine', value, target);
|
||||
}
|
||||
});
|
||||
|
||||
// Example: Updating Resource scoped Configuration
|
||||
vscode.commands.registerCommand('config.commands.configureEmptyLastLineFiles', async () => {
|
||||
|
||||
// 1) Getting the value
|
||||
const value = await vscode.window.showInputBox({ prompt: 'Provide glob pattern of files to have empty last line.' });
|
||||
|
||||
if (vscode.workspace.workspaceFolders) {
|
||||
|
||||
// 2) Getting the target
|
||||
const target = await vscode.window.showQuickPick(
|
||||
[
|
||||
{ label: 'Application', description: 'User Settings', target: vscode.ConfigurationTarget.Global },
|
||||
{ label: 'Workspace', description: 'Workspace Settings', target: vscode.ConfigurationTarget.Workspace },
|
||||
{ label: 'Workspace Folder', description: 'Workspace Folder Settings', target: vscode.ConfigurationTarget.WorkspaceFolder }
|
||||
],
|
||||
{ placeHolder: 'Select the target to which this setting should be applied' });
|
||||
|
||||
if (value && target) {
|
||||
|
||||
if (target.target === vscode.ConfigurationTarget.WorkspaceFolder) {
|
||||
|
||||
// 3) Getting the workspace folder
|
||||
const workspaceFolder = await vscode.window.showWorkspaceFolderPick({ placeHolder: 'Pick Workspace Folder to which this setting should be applied' });
|
||||
if (workspaceFolder) {
|
||||
|
||||
// 4) Get the configuration for the workspace folder
|
||||
const configuration = vscode.workspace.getConfiguration('', workspaceFolder.uri);
|
||||
|
||||
// 5) Get the current value
|
||||
const currentValue = configuration.get<{}>('conf.resource.insertEmptyLastLine');
|
||||
|
||||
const newValue = { ...currentValue, ...{ [value]: true } };
|
||||
|
||||
// 6) Update the configuration value
|
||||
await configuration.update('conf.resource.insertEmptyLastLine', newValue, target.target);
|
||||
}
|
||||
} else {
|
||||
|
||||
// 3) Get the configuration
|
||||
const configuration = vscode.workspace.getConfiguration();
|
||||
|
||||
// 4) Get the current value
|
||||
const currentValue = configuration.get<{}>('conf.resource.insertEmptyLastLine');
|
||||
|
||||
const newValue = { ...currentValue, ...(value ? { [value]: true } : {}) };
|
||||
|
||||
// 3) Update the value in the target
|
||||
await vscode.workspace.getConfiguration().update('conf.resource.insertEmptyLastLine', newValue, target.target);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
||||
// 2) Get the configuration
|
||||
const configuration = vscode.workspace.getConfiguration();
|
||||
|
||||
// 3) Get the current value
|
||||
const currentValue = configuration.get<{}>('conf.resource.insertEmptyLastLine');
|
||||
|
||||
const newValue = { ...currentValue, ...(value ? { [value]: true } : {}) };
|
||||
|
||||
// 4) Update the value in the User Settings
|
||||
await vscode.workspace.getConfiguration().update('conf.resource.insertEmptyLastLine', newValue, vscode.ConfigurationTarget.Global);
|
||||
}
|
||||
});
|
||||
|
||||
let statusSizeDisposable: vscode.Disposable;
|
||||
// Example: Reading language overridable configuration for a document
|
||||
context.subscriptions.push(vscode.workspace.onDidOpenTextDocument(e => {
|
||||
|
||||
if (statusSizeDisposable) {
|
||||
statusSizeDisposable.dispose();
|
||||
}
|
||||
|
||||
// 1) Check if showing size is configured for current file
|
||||
const showSize: any = vscode.workspace.getConfiguration('', e).get('conf.language.showSize');
|
||||
|
||||
// 3) If matches, insert empty last line
|
||||
if (showSize) {
|
||||
statusSizeDisposable = vscode.window.setStatusBarMessage(`${e.getText().length}`);
|
||||
}
|
||||
|
||||
}));
|
||||
|
||||
// Example: Overriding configuration value for a language
|
||||
context.subscriptions.push(vscode.commands.registerCommand('config.commands.overrideLanguageValue', async () => {
|
||||
|
||||
// 1) Getting the languge id
|
||||
const languageId = await vscode.window.showInputBox({ placeHolder: 'Enter the language id' });
|
||||
|
||||
// 2) Update
|
||||
vscode.workspace.getConfiguration('', { languageId: languageId! }).update('conf.language.showSize', true, false, true);
|
||||
|
||||
}));
|
||||
|
||||
// Example: Listening to configuration changes
|
||||
context.subscriptions.push(vscode.workspace.onDidChangeConfiguration(e => {
|
||||
|
||||
if (e.affectsConfiguration('conf.resource.insertEmptyLastLine')) {
|
||||
if (vscode.window.activeTextEditor) {
|
||||
|
||||
const currentDocument = vscode.window.activeTextEditor.document;
|
||||
|
||||
// 1) Get the configured glob pattern value for the current file
|
||||
const value: any = vscode.workspace.getConfiguration('', currentDocument.uri).get('conf.resource.insertEmptyLastLine');
|
||||
|
||||
// 2) Check if the current resource matches the glob pattern
|
||||
const matches = value[currentDocument.fileName];
|
||||
|
||||
// 3) If matches, insert empty last line
|
||||
if (matches) {
|
||||
vscode.window.showInformationMessage('An empty line will be added to the document ' + currentDocument.fileName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check if a language configuration is changed for a text document
|
||||
if (e.affectsConfiguration('conf.language.showSize', vscode.window.activeTextEditor?.document)) {
|
||||
// noop
|
||||
}
|
||||
|
||||
// Check if a language configuration is changed for a language
|
||||
if (e.affectsConfiguration('conf.language.showSize', { languageId: 'typescript' })) {
|
||||
// noop
|
||||
}
|
||||
|
||||
}));
|
||||
|
||||
}
|
||||
@ -1,31 +1,31 @@
|
||||
/*---------------------------------------------------------
|
||||
* Copyright (C) Microsoft Corporation. All rights reserved.
|
||||
*--------------------------------------------------------*/
|
||||
|
||||
import { workspace, languages, window, commands, ExtensionContext, Disposable } from 'vscode';
|
||||
import ContentProvider, { encodeLocation } from './provider';
|
||||
|
||||
export function activate(context: ExtensionContext) {
|
||||
|
||||
const provider = new ContentProvider();
|
||||
|
||||
// register content provider for scheme `references`
|
||||
// register document link provider for scheme `references`
|
||||
const providerRegistrations = Disposable.from(
|
||||
workspace.registerTextDocumentContentProvider(ContentProvider.scheme, provider),
|
||||
languages.registerDocumentLinkProvider({ scheme: ContentProvider.scheme }, provider)
|
||||
);
|
||||
|
||||
// register command that crafts an uri with the `references` scheme,
|
||||
// open the dynamic document, and shows it in the next editor
|
||||
const commandRegistration = commands.registerTextEditorCommand('editor.printReferences', editor => {
|
||||
const uri = encodeLocation(editor.document.uri, editor.selection.active);
|
||||
return workspace.openTextDocument(uri).then(doc => window.showTextDocument(doc, editor.viewColumn! + 1));
|
||||
});
|
||||
|
||||
context.subscriptions.push(
|
||||
provider,
|
||||
commandRegistration,
|
||||
providerRegistrations
|
||||
);
|
||||
}
|
||||
/*---------------------------------------------------------
|
||||
* Copyright (C) Microsoft Corporation. All rights reserved.
|
||||
*--------------------------------------------------------*/
|
||||
|
||||
import { workspace, languages, window, commands, ExtensionContext, Disposable } from 'vscode';
|
||||
import ContentProvider, { encodeLocation } from './provider';
|
||||
|
||||
export function activate(context: ExtensionContext) {
|
||||
|
||||
const provider = new ContentProvider();
|
||||
|
||||
// register content provider for scheme `references`
|
||||
// register document link provider for scheme `references`
|
||||
const providerRegistrations = Disposable.from(
|
||||
workspace.registerTextDocumentContentProvider(ContentProvider.scheme, provider),
|
||||
languages.registerDocumentLinkProvider({ scheme: ContentProvider.scheme }, provider)
|
||||
);
|
||||
|
||||
// register command that crafts an uri with the `references` scheme,
|
||||
// open the dynamic document, and shows it in the next editor
|
||||
const commandRegistration = commands.registerTextEditorCommand('editor.printReferences', editor => {
|
||||
const uri = encodeLocation(editor.document.uri, editor.selection.active);
|
||||
return workspace.openTextDocument(uri).then(doc => window.showTextDocument(doc, editor.viewColumn! + 1));
|
||||
});
|
||||
|
||||
context.subscriptions.push(
|
||||
provider,
|
||||
commandRegistration,
|
||||
providerRegistrations
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,99 +1,99 @@
|
||||
/*---------------------------------------------------------
|
||||
* Copyright (C) Microsoft Corporation. All rights reserved.
|
||||
*--------------------------------------------------------*/
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import ReferencesDocument from './referencesDocument';
|
||||
|
||||
export default class Provider implements vscode.TextDocumentContentProvider, vscode.DocumentLinkProvider {
|
||||
|
||||
static scheme = 'references';
|
||||
|
||||
private _onDidChange = new vscode.EventEmitter<vscode.Uri>();
|
||||
private _documents = new Map<string, ReferencesDocument>();
|
||||
private _editorDecoration = vscode.window.createTextEditorDecorationType({ textDecoration: 'underline' });
|
||||
private _subscriptions: vscode.Disposable;
|
||||
|
||||
constructor() {
|
||||
|
||||
// Listen to the `closeTextDocument`-event which means we must
|
||||
// clear the corresponding model object - `ReferencesDocument`
|
||||
this._subscriptions = vscode.workspace.onDidCloseTextDocument(doc => this._documents.delete(doc.uri.toString()));
|
||||
}
|
||||
|
||||
dispose() {
|
||||
this._subscriptions.dispose();
|
||||
this._documents.clear();
|
||||
this._editorDecoration.dispose();
|
||||
this._onDidChange.dispose();
|
||||
}
|
||||
|
||||
// Expose an event to signal changes of _virtual_ documents
|
||||
// to the editor
|
||||
get onDidChange() {
|
||||
return this._onDidChange.event;
|
||||
}
|
||||
|
||||
// Provider method that takes an uri of the `references`-scheme and
|
||||
// resolves its content by (1) running the reference search command
|
||||
// and (2) formatting the results
|
||||
provideTextDocumentContent(uri: vscode.Uri): string | Thenable<string> {
|
||||
|
||||
// already loaded?
|
||||
const document = this._documents.get(uri.toString());
|
||||
if (document) {
|
||||
return document.value;
|
||||
}
|
||||
|
||||
// Decode target-uri and target-position from the provided uri and execute the
|
||||
// `reference provider` command (https://code.visualstudio.com/api/references/commands).
|
||||
// From the result create a references document which is in charge of loading,
|
||||
// printing, and formatting references
|
||||
const [target, pos] = decodeLocation(uri);
|
||||
return vscode.commands.executeCommand<vscode.Location[]>('vscode.executeReferenceProvider', target, pos).then(locations => {
|
||||
locations = locations || [];
|
||||
|
||||
// sort by locations and shuffle to begin from target resource
|
||||
let idx = 0;
|
||||
locations.sort(Provider._compareLocations).find((loc, i) => loc.uri.toString() === target.toString() && !!(idx = i) && true);
|
||||
locations.push(...locations.splice(0, idx));
|
||||
|
||||
// create document and return its early state
|
||||
const document = new ReferencesDocument(uri, locations, this._onDidChange);
|
||||
this._documents.set(uri.toString(), document);
|
||||
return document.value;
|
||||
});
|
||||
}
|
||||
|
||||
private static _compareLocations(a: vscode.Location, b: vscode.Location): number {
|
||||
if (a.uri.toString() < b.uri.toString()) {
|
||||
return -1;
|
||||
} else if (a.uri.toString() > b.uri.toString()) {
|
||||
return 1;
|
||||
} else {
|
||||
return a.range.start.compareTo(b.range.start);
|
||||
}
|
||||
}
|
||||
|
||||
provideDocumentLinks(document: vscode.TextDocument, token: vscode.CancellationToken): vscode.DocumentLink[] | undefined {
|
||||
// While building the virtual document we have already created the links.
|
||||
// Those are composed from the range inside the document and a target uri
|
||||
// to which they point
|
||||
const doc = this._documents.get(document.uri.toString());
|
||||
if (doc) {
|
||||
return doc.links;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let seq = 0;
|
||||
|
||||
export function encodeLocation(uri: vscode.Uri, pos: vscode.Position): vscode.Uri {
|
||||
const query = JSON.stringify([uri.toString(), pos.line, pos.character]);
|
||||
return vscode.Uri.parse(`${Provider.scheme}:References.locations?${query}#${seq++}`);
|
||||
}
|
||||
|
||||
export function decodeLocation(uri: vscode.Uri): [vscode.Uri, vscode.Position] {
|
||||
const [target, line, character] = <[string, number, number]>JSON.parse(uri.query);
|
||||
return [vscode.Uri.parse(target), new vscode.Position(line, character)];
|
||||
}
|
||||
/*---------------------------------------------------------
|
||||
* Copyright (C) Microsoft Corporation. All rights reserved.
|
||||
*--------------------------------------------------------*/
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import ReferencesDocument from './referencesDocument';
|
||||
|
||||
export default class Provider implements vscode.TextDocumentContentProvider, vscode.DocumentLinkProvider {
|
||||
|
||||
static scheme = 'references';
|
||||
|
||||
private _onDidChange = new vscode.EventEmitter<vscode.Uri>();
|
||||
private _documents = new Map<string, ReferencesDocument>();
|
||||
private _editorDecoration = vscode.window.createTextEditorDecorationType({ textDecoration: 'underline' });
|
||||
private _subscriptions: vscode.Disposable;
|
||||
|
||||
constructor() {
|
||||
|
||||
// Listen to the `closeTextDocument`-event which means we must
|
||||
// clear the corresponding model object - `ReferencesDocument`
|
||||
this._subscriptions = vscode.workspace.onDidCloseTextDocument(doc => this._documents.delete(doc.uri.toString()));
|
||||
}
|
||||
|
||||
dispose() {
|
||||
this._subscriptions.dispose();
|
||||
this._documents.clear();
|
||||
this._editorDecoration.dispose();
|
||||
this._onDidChange.dispose();
|
||||
}
|
||||
|
||||
// Expose an event to signal changes of _virtual_ documents
|
||||
// to the editor
|
||||
get onDidChange() {
|
||||
return this._onDidChange.event;
|
||||
}
|
||||
|
||||
// Provider method that takes an uri of the `references`-scheme and
|
||||
// resolves its content by (1) running the reference search command
|
||||
// and (2) formatting the results
|
||||
provideTextDocumentContent(uri: vscode.Uri): string | Thenable<string> {
|
||||
|
||||
// already loaded?
|
||||
const document = this._documents.get(uri.toString());
|
||||
if (document) {
|
||||
return document.value;
|
||||
}
|
||||
|
||||
// Decode target-uri and target-position from the provided uri and execute the
|
||||
// `reference provider` command (https://code.visualstudio.com/api/references/commands).
|
||||
// From the result create a references document which is in charge of loading,
|
||||
// printing, and formatting references
|
||||
const [target, pos] = decodeLocation(uri);
|
||||
return vscode.commands.executeCommand<vscode.Location[]>('vscode.executeReferenceProvider', target, pos).then(locations => {
|
||||
locations = locations || [];
|
||||
|
||||
// sort by locations and shuffle to begin from target resource
|
||||
let idx = 0;
|
||||
locations.sort(Provider._compareLocations).find((loc, i) => loc.uri.toString() === target.toString() && !!(idx = i) && true);
|
||||
locations.push(...locations.splice(0, idx));
|
||||
|
||||
// create document and return its early state
|
||||
const document = new ReferencesDocument(uri, locations, this._onDidChange);
|
||||
this._documents.set(uri.toString(), document);
|
||||
return document.value;
|
||||
});
|
||||
}
|
||||
|
||||
private static _compareLocations(a: vscode.Location, b: vscode.Location): number {
|
||||
if (a.uri.toString() < b.uri.toString()) {
|
||||
return -1;
|
||||
} else if (a.uri.toString() > b.uri.toString()) {
|
||||
return 1;
|
||||
} else {
|
||||
return a.range.start.compareTo(b.range.start);
|
||||
}
|
||||
}
|
||||
|
||||
provideDocumentLinks(document: vscode.TextDocument, token: vscode.CancellationToken): vscode.DocumentLink[] | undefined {
|
||||
// While building the virtual document we have already created the links.
|
||||
// Those are composed from the range inside the document and a target uri
|
||||
// to which they point
|
||||
const doc = this._documents.get(document.uri.toString());
|
||||
if (doc) {
|
||||
return doc.links;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let seq = 0;
|
||||
|
||||
export function encodeLocation(uri: vscode.Uri, pos: vscode.Position): vscode.Uri {
|
||||
const query = JSON.stringify([uri.toString(), pos.line, pos.character]);
|
||||
return vscode.Uri.parse(`${Provider.scheme}:References.locations?${query}#${seq++}`);
|
||||
}
|
||||
|
||||
export function decodeLocation(uri: vscode.Uri): [vscode.Uri, vscode.Position] {
|
||||
const [target, line, character] = <[string, number, number]>JSON.parse(uri.query);
|
||||
return [vscode.Uri.parse(target), new vscode.Position(line, character)];
|
||||
}
|
||||
|
||||
@ -1,113 +1,113 @@
|
||||
/*---------------------------------------------------------
|
||||
* Copyright (C) Microsoft Corporation. All rights reserved.
|
||||
*--------------------------------------------------------*/
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
export default class ReferencesDocument {
|
||||
|
||||
private readonly _uri: vscode.Uri;
|
||||
private readonly _emitter: vscode.EventEmitter<vscode.Uri>;
|
||||
private readonly _locations: vscode.Location[];
|
||||
|
||||
private readonly _lines: string[];
|
||||
private readonly _links: vscode.DocumentLink[];
|
||||
|
||||
constructor(uri: vscode.Uri, locations: vscode.Location[], emitter: vscode.EventEmitter<vscode.Uri>) {
|
||||
this._uri = uri;
|
||||
this._locations = locations;
|
||||
|
||||
// The ReferencesDocument has access to the event emitter from
|
||||
// the containing provider. This allows it to signal changes
|
||||
this._emitter = emitter;
|
||||
|
||||
// Start with printing a header and start resolving
|
||||
this._lines = [`Found ${this._locations.length} references`];
|
||||
this._links = [];
|
||||
this._populate();
|
||||
}
|
||||
|
||||
get value() {
|
||||
return this._lines.join('\n');
|
||||
}
|
||||
|
||||
get links() {
|
||||
return this._links;
|
||||
}
|
||||
|
||||
private async _populate() {
|
||||
|
||||
// group all locations by files containing them
|
||||
const groups: vscode.Location[][] = [];
|
||||
let group: vscode.Location[] = [];
|
||||
for (const loc of this._locations) {
|
||||
if (group.length === 0 || group[0].uri.toString() !== loc.uri.toString()) {
|
||||
group = [];
|
||||
groups.push(group);
|
||||
}
|
||||
group.push(loc);
|
||||
}
|
||||
|
||||
//
|
||||
for (const group of groups) {
|
||||
const uri = group[0].uri;
|
||||
const ranges = group.map(loc => loc.range);
|
||||
await this._fetchAndFormatLocations(uri, ranges);
|
||||
this._emitter.fire(this._uri);
|
||||
}
|
||||
}
|
||||
|
||||
private async _fetchAndFormatLocations(uri: vscode.Uri, ranges: vscode.Range[]): Promise<void> {
|
||||
// Fetch the document denoted by the uri and format the matches
|
||||
// with leading and trailing content form the document. Make sure
|
||||
// to not duplicate lines
|
||||
try {
|
||||
const doc = await vscode.workspace.openTextDocument(uri);
|
||||
this._lines.push('', uri.toString());
|
||||
for (let i = 0; i < ranges.length; i++) {
|
||||
const { start: { line } } = ranges[i];
|
||||
this._appendLeading(doc, line, ranges[i - 1]);
|
||||
this._appendMatch(doc, line, ranges[i], uri);
|
||||
this._appendTrailing(doc, line, ranges[i + 1]);
|
||||
}
|
||||
} catch (err) {
|
||||
this._lines.push('', `Failed to load '${uri.toString()}'\n\n${String(err)}`, '');
|
||||
}
|
||||
}
|
||||
|
||||
private _appendLeading(doc: vscode.TextDocument, line: number, previous: vscode.Range): void {
|
||||
let from = Math.max(0, line - 3, previous && previous.end.line || 0);
|
||||
while (++from < line) {
|
||||
const text = doc.lineAt(from).text;
|
||||
this._lines.push(` ${from + 1}` + (text && ` ${text}`));
|
||||
}
|
||||
}
|
||||
|
||||
private _appendMatch(doc: vscode.TextDocument, line: number, match: vscode.Range, target: vscode.Uri) {
|
||||
const text = doc.lineAt(line).text;
|
||||
const preamble = ` ${line + 1}: `;
|
||||
|
||||
// Append line, use new length of lines-array as line number
|
||||
// for a link that point to the reference
|
||||
const len = this._lines.push(preamble + text);
|
||||
|
||||
// Create a document link that will reveal the reference
|
||||
const linkRange = new vscode.Range(len - 1, preamble.length + match.start.character, len - 1, preamble.length + match.end.character);
|
||||
const linkTarget = target.with({ fragment: String(1 + match.start.line) });
|
||||
this._links.push(new vscode.DocumentLink(linkRange, linkTarget));
|
||||
}
|
||||
|
||||
private _appendTrailing(doc: vscode.TextDocument, line: number, next: vscode.Range): void {
|
||||
const to = Math.min(doc.lineCount, line + 3);
|
||||
if (next && next.start.line - to <= 2) {
|
||||
return; // next is too close, _appendLeading does the work
|
||||
}
|
||||
while (++line < to) {
|
||||
const text = doc.lineAt(line).text;
|
||||
this._lines.push(` ${line + 1}` + (text && ` ${text}`));
|
||||
}
|
||||
if (next) {
|
||||
this._lines.push(` ...`);
|
||||
}
|
||||
}
|
||||
}
|
||||
/*---------------------------------------------------------
|
||||
* Copyright (C) Microsoft Corporation. All rights reserved.
|
||||
*--------------------------------------------------------*/
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
export default class ReferencesDocument {
|
||||
|
||||
private readonly _uri: vscode.Uri;
|
||||
private readonly _emitter: vscode.EventEmitter<vscode.Uri>;
|
||||
private readonly _locations: vscode.Location[];
|
||||
|
||||
private readonly _lines: string[];
|
||||
private readonly _links: vscode.DocumentLink[];
|
||||
|
||||
constructor(uri: vscode.Uri, locations: vscode.Location[], emitter: vscode.EventEmitter<vscode.Uri>) {
|
||||
this._uri = uri;
|
||||
this._locations = locations;
|
||||
|
||||
// The ReferencesDocument has access to the event emitter from
|
||||
// the containing provider. This allows it to signal changes
|
||||
this._emitter = emitter;
|
||||
|
||||
// Start with printing a header and start resolving
|
||||
this._lines = [`Found ${this._locations.length} references`];
|
||||
this._links = [];
|
||||
this._populate();
|
||||
}
|
||||
|
||||
get value() {
|
||||
return this._lines.join('\n');
|
||||
}
|
||||
|
||||
get links() {
|
||||
return this._links;
|
||||
}
|
||||
|
||||
private async _populate() {
|
||||
|
||||
// group all locations by files containing them
|
||||
const groups: vscode.Location[][] = [];
|
||||
let group: vscode.Location[] = [];
|
||||
for (const loc of this._locations) {
|
||||
if (group.length === 0 || group[0].uri.toString() !== loc.uri.toString()) {
|
||||
group = [];
|
||||
groups.push(group);
|
||||
}
|
||||
group.push(loc);
|
||||
}
|
||||
|
||||
//
|
||||
for (const group of groups) {
|
||||
const uri = group[0].uri;
|
||||
const ranges = group.map(loc => loc.range);
|
||||
await this._fetchAndFormatLocations(uri, ranges);
|
||||
this._emitter.fire(this._uri);
|
||||
}
|
||||
}
|
||||
|
||||
private async _fetchAndFormatLocations(uri: vscode.Uri, ranges: vscode.Range[]): Promise<void> {
|
||||
// Fetch the document denoted by the uri and format the matches
|
||||
// with leading and trailing content form the document. Make sure
|
||||
// to not duplicate lines
|
||||
try {
|
||||
const doc = await vscode.workspace.openTextDocument(uri);
|
||||
this._lines.push('', uri.toString());
|
||||
for (let i = 0; i < ranges.length; i++) {
|
||||
const { start: { line } } = ranges[i];
|
||||
this._appendLeading(doc, line, ranges[i - 1]);
|
||||
this._appendMatch(doc, line, ranges[i], uri);
|
||||
this._appendTrailing(doc, line, ranges[i + 1]);
|
||||
}
|
||||
} catch (err) {
|
||||
this._lines.push('', `Failed to load '${uri.toString()}'\n\n${String(err)}`, '');
|
||||
}
|
||||
}
|
||||
|
||||
private _appendLeading(doc: vscode.TextDocument, line: number, previous: vscode.Range): void {
|
||||
let from = Math.max(0, line - 3, previous && previous.end.line || 0);
|
||||
while (++from < line) {
|
||||
const text = doc.lineAt(from).text;
|
||||
this._lines.push(` ${from + 1}` + (text && ` ${text}`));
|
||||
}
|
||||
}
|
||||
|
||||
private _appendMatch(doc: vscode.TextDocument, line: number, match: vscode.Range, target: vscode.Uri) {
|
||||
const text = doc.lineAt(line).text;
|
||||
const preamble = ` ${line + 1}: `;
|
||||
|
||||
// Append line, use new length of lines-array as line number
|
||||
// for a link that point to the reference
|
||||
const len = this._lines.push(preamble + text);
|
||||
|
||||
// Create a document link that will reveal the reference
|
||||
const linkRange = new vscode.Range(len - 1, preamble.length + match.start.character, len - 1, preamble.length + match.end.character);
|
||||
const linkTarget = target.with({ fragment: String(1 + match.start.line) });
|
||||
this._links.push(new vscode.DocumentLink(linkRange, linkTarget));
|
||||
}
|
||||
|
||||
private _appendTrailing(doc: vscode.TextDocument, line: number, next: vscode.Range): void {
|
||||
const to = Math.min(doc.lineCount, line + 3);
|
||||
if (next && next.start.line - to <= 2) {
|
||||
return; // next is too close, _appendLeading does the work
|
||||
}
|
||||
while (++line < to) {
|
||||
const text = doc.lineAt(line).text;
|
||||
this._lines.push(` ${line + 1}` + (text && ` ${text}`));
|
||||
}
|
||||
if (next) {
|
||||
this._lines.push(` ...`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,205 +1,205 @@
|
||||
import * as vscode from 'vscode';
|
||||
import { getNonce } from './util';
|
||||
|
||||
/**
|
||||
* Provider for cat scratch editors.
|
||||
*
|
||||
* Cat scratch editors are used for `.cscratch` files, which are just json files.
|
||||
* To get started, run this extension and open an empty `.cscratch` file in VS Code.
|
||||
*
|
||||
* This provider demonstrates:
|
||||
*
|
||||
* - Setting up the initial webview for a custom editor.
|
||||
* - Loading scripts and styles in a custom editor.
|
||||
* - Synchronizing changes between a text document and a custom editor.
|
||||
*/
|
||||
export class CatScratchEditorProvider implements vscode.CustomTextEditorProvider {
|
||||
|
||||
public static register(context: vscode.ExtensionContext): vscode.Disposable {
|
||||
const provider = new CatScratchEditorProvider(context);
|
||||
const providerRegistration = vscode.window.registerCustomEditorProvider(CatScratchEditorProvider.viewType, provider);
|
||||
return providerRegistration;
|
||||
}
|
||||
|
||||
private static readonly viewType = 'catCustoms.catScratch';
|
||||
|
||||
private static readonly scratchCharacters = ['😸', '😹', '😺', '😻', '😼', '😽', '😾', '🙀', '😿', '🐱'];
|
||||
|
||||
constructor(
|
||||
private readonly context: vscode.ExtensionContext
|
||||
) { }
|
||||
|
||||
/**
|
||||
* Called when our custom editor is opened.
|
||||
*
|
||||
*
|
||||
*/
|
||||
public async resolveCustomTextEditor(
|
||||
document: vscode.TextDocument,
|
||||
webviewPanel: vscode.WebviewPanel,
|
||||
_token: vscode.CancellationToken
|
||||
): Promise<void> {
|
||||
// Setup initial content for the webview
|
||||
webviewPanel.webview.options = {
|
||||
enableScripts: true,
|
||||
};
|
||||
webviewPanel.webview.html = this.getHtmlForWebview(webviewPanel.webview);
|
||||
|
||||
function updateWebview() {
|
||||
webviewPanel.webview.postMessage({
|
||||
type: 'update',
|
||||
text: document.getText(),
|
||||
});
|
||||
}
|
||||
|
||||
// Hook up event handlers so that we can synchronize the webview with the text document.
|
||||
//
|
||||
// The text document acts as our model, so we have to sync change in the document to our
|
||||
// editor and sync changes in the editor back to the document.
|
||||
//
|
||||
// Remember that a single text document can also be shared between multiple custom
|
||||
// editors (this happens for example when you split a custom editor)
|
||||
|
||||
const changeDocumentSubscription = vscode.workspace.onDidChangeTextDocument(e => {
|
||||
if (e.document.uri.toString() === document.uri.toString()) {
|
||||
updateWebview();
|
||||
}
|
||||
});
|
||||
|
||||
// Make sure we get rid of the listener when our editor is closed.
|
||||
webviewPanel.onDidDispose(() => {
|
||||
changeDocumentSubscription.dispose();
|
||||
});
|
||||
|
||||
// Receive message from the webview.
|
||||
webviewPanel.webview.onDidReceiveMessage(e => {
|
||||
switch (e.type) {
|
||||
case 'add':
|
||||
this.addNewScratch(document);
|
||||
return;
|
||||
|
||||
case 'delete':
|
||||
this.deleteScratch(document, e.id);
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
updateWebview();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the static html used for the editor webviews.
|
||||
*/
|
||||
private getHtmlForWebview(webview: vscode.Webview): string {
|
||||
// Local path to script and css for the webview
|
||||
const scriptUri = webview.asWebviewUri(vscode.Uri.joinPath(
|
||||
this.context.extensionUri, 'media', 'catScratch.js'));
|
||||
|
||||
const styleResetUri = webview.asWebviewUri(vscode.Uri.joinPath(
|
||||
this.context.extensionUri, 'media', 'reset.css'));
|
||||
|
||||
const styleVSCodeUri = webview.asWebviewUri(vscode.Uri.joinPath(
|
||||
this.context.extensionUri, 'media', 'vscode.css'));
|
||||
|
||||
const styleMainUri = webview.asWebviewUri(vscode.Uri.joinPath(
|
||||
this.context.extensionUri, 'media', 'catScratch.css'));
|
||||
|
||||
// Use a nonce to whitelist which scripts can be run
|
||||
const nonce = getNonce();
|
||||
|
||||
return /* html */`
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
|
||||
<!--
|
||||
Use a content security policy to only allow loading images from https or from our extension directory,
|
||||
and only allow scripts that have a specific nonce.
|
||||
-->
|
||||
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; img-src ${webview.cspSource}; style-src ${webview.cspSource}; script-src 'nonce-${nonce}';">
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
|
||||
<link href="${styleResetUri}" rel="stylesheet" />
|
||||
<link href="${styleVSCodeUri}" rel="stylesheet" />
|
||||
<link href="${styleMainUri}" rel="stylesheet" />
|
||||
|
||||
<title>Cat Scratch</title>
|
||||
</head>
|
||||
<body>
|
||||
<div class="notes">
|
||||
<div class="add-button">
|
||||
<button>Scratch!</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script nonce="${nonce}" src="${scriptUri}"></script>
|
||||
</body>
|
||||
</html>`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new scratch to the current document.
|
||||
*/
|
||||
private addNewScratch(document: vscode.TextDocument) {
|
||||
const json = this.getDocumentAsJson(document);
|
||||
const character = CatScratchEditorProvider.scratchCharacters[Math.floor(Math.random() * CatScratchEditorProvider.scratchCharacters.length)];
|
||||
json.scratches = [
|
||||
...(Array.isArray(json.scratches) ? json.scratches : []),
|
||||
{
|
||||
id: getNonce(),
|
||||
text: character,
|
||||
created: Date.now(),
|
||||
}
|
||||
];
|
||||
|
||||
return this.updateTextDocument(document, json);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete an existing scratch from a document.
|
||||
*/
|
||||
private deleteScratch(document: vscode.TextDocument, id: string) {
|
||||
const json = this.getDocumentAsJson(document);
|
||||
if (!Array.isArray(json.scratches)) {
|
||||
return;
|
||||
}
|
||||
|
||||
json.scratches = json.scratches.filter((note: any) => note.id !== id);
|
||||
|
||||
return this.updateTextDocument(document, json);
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to get a current document as json text.
|
||||
*/
|
||||
private getDocumentAsJson(document: vscode.TextDocument): any {
|
||||
const text = document.getText();
|
||||
if (text.trim().length === 0) {
|
||||
return {};
|
||||
}
|
||||
|
||||
try {
|
||||
return JSON.parse(text);
|
||||
} catch {
|
||||
throw new Error('Could not get document as json. Content is not valid json');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Write out the json to a given document.
|
||||
*/
|
||||
private updateTextDocument(document: vscode.TextDocument, json: any) {
|
||||
const edit = new vscode.WorkspaceEdit();
|
||||
|
||||
// Just replace the entire document every time for this example extension.
|
||||
// A more complete extension should compute minimal edits instead.
|
||||
edit.replace(
|
||||
document.uri,
|
||||
new vscode.Range(0, 0, document.lineCount, 0),
|
||||
JSON.stringify(json, null, 2));
|
||||
|
||||
return vscode.workspace.applyEdit(edit);
|
||||
}
|
||||
}
|
||||
import * as vscode from 'vscode';
|
||||
import { getNonce } from './util';
|
||||
|
||||
/**
|
||||
* Provider for cat scratch editors.
|
||||
*
|
||||
* Cat scratch editors are used for `.cscratch` files, which are just json files.
|
||||
* To get started, run this extension and open an empty `.cscratch` file in VS Code.
|
||||
*
|
||||
* This provider demonstrates:
|
||||
*
|
||||
* - Setting up the initial webview for a custom editor.
|
||||
* - Loading scripts and styles in a custom editor.
|
||||
* - Synchronizing changes between a text document and a custom editor.
|
||||
*/
|
||||
export class CatScratchEditorProvider implements vscode.CustomTextEditorProvider {
|
||||
|
||||
public static register(context: vscode.ExtensionContext): vscode.Disposable {
|
||||
const provider = new CatScratchEditorProvider(context);
|
||||
const providerRegistration = vscode.window.registerCustomEditorProvider(CatScratchEditorProvider.viewType, provider);
|
||||
return providerRegistration;
|
||||
}
|
||||
|
||||
private static readonly viewType = 'catCustoms.catScratch';
|
||||
|
||||
private static readonly scratchCharacters = ['😸', '😹', '😺', '😻', '😼', '😽', '😾', '🙀', '😿', '🐱'];
|
||||
|
||||
constructor(
|
||||
private readonly context: vscode.ExtensionContext
|
||||
) { }
|
||||
|
||||
/**
|
||||
* Called when our custom editor is opened.
|
||||
*
|
||||
*
|
||||
*/
|
||||
public async resolveCustomTextEditor(
|
||||
document: vscode.TextDocument,
|
||||
webviewPanel: vscode.WebviewPanel,
|
||||
_token: vscode.CancellationToken
|
||||
): Promise<void> {
|
||||
// Setup initial content for the webview
|
||||
webviewPanel.webview.options = {
|
||||
enableScripts: true,
|
||||
};
|
||||
webviewPanel.webview.html = this.getHtmlForWebview(webviewPanel.webview);
|
||||
|
||||
function updateWebview() {
|
||||
webviewPanel.webview.postMessage({
|
||||
type: 'update',
|
||||
text: document.getText(),
|
||||
});
|
||||
}
|
||||
|
||||
// Hook up event handlers so that we can synchronize the webview with the text document.
|
||||
//
|
||||
// The text document acts as our model, so we have to sync change in the document to our
|
||||
// editor and sync changes in the editor back to the document.
|
||||
//
|
||||
// Remember that a single text document can also be shared between multiple custom
|
||||
// editors (this happens for example when you split a custom editor)
|
||||
|
||||
const changeDocumentSubscription = vscode.workspace.onDidChangeTextDocument(e => {
|
||||
if (e.document.uri.toString() === document.uri.toString()) {
|
||||
updateWebview();
|
||||
}
|
||||
});
|
||||
|
||||
// Make sure we get rid of the listener when our editor is closed.
|
||||
webviewPanel.onDidDispose(() => {
|
||||
changeDocumentSubscription.dispose();
|
||||
});
|
||||
|
||||
// Receive message from the webview.
|
||||
webviewPanel.webview.onDidReceiveMessage(e => {
|
||||
switch (e.type) {
|
||||
case 'add':
|
||||
this.addNewScratch(document);
|
||||
return;
|
||||
|
||||
case 'delete':
|
||||
this.deleteScratch(document, e.id);
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
updateWebview();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the static html used for the editor webviews.
|
||||
*/
|
||||
private getHtmlForWebview(webview: vscode.Webview): string {
|
||||
// Local path to script and css for the webview
|
||||
const scriptUri = webview.asWebviewUri(vscode.Uri.joinPath(
|
||||
this.context.extensionUri, 'media', 'catScratch.js'));
|
||||
|
||||
const styleResetUri = webview.asWebviewUri(vscode.Uri.joinPath(
|
||||
this.context.extensionUri, 'media', 'reset.css'));
|
||||
|
||||
const styleVSCodeUri = webview.asWebviewUri(vscode.Uri.joinPath(
|
||||
this.context.extensionUri, 'media', 'vscode.css'));
|
||||
|
||||
const styleMainUri = webview.asWebviewUri(vscode.Uri.joinPath(
|
||||
this.context.extensionUri, 'media', 'catScratch.css'));
|
||||
|
||||
// Use a nonce to whitelist which scripts can be run
|
||||
const nonce = getNonce();
|
||||
|
||||
return /* html */`
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
|
||||
<!--
|
||||
Use a content security policy to only allow loading images from https or from our extension directory,
|
||||
and only allow scripts that have a specific nonce.
|
||||
-->
|
||||
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; img-src ${webview.cspSource}; style-src ${webview.cspSource}; script-src 'nonce-${nonce}';">
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
|
||||
<link href="${styleResetUri}" rel="stylesheet" />
|
||||
<link href="${styleVSCodeUri}" rel="stylesheet" />
|
||||
<link href="${styleMainUri}" rel="stylesheet" />
|
||||
|
||||
<title>Cat Scratch</title>
|
||||
</head>
|
||||
<body>
|
||||
<div class="notes">
|
||||
<div class="add-button">
|
||||
<button>Scratch!</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script nonce="${nonce}" src="${scriptUri}"></script>
|
||||
</body>
|
||||
</html>`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new scratch to the current document.
|
||||
*/
|
||||
private addNewScratch(document: vscode.TextDocument) {
|
||||
const json = this.getDocumentAsJson(document);
|
||||
const character = CatScratchEditorProvider.scratchCharacters[Math.floor(Math.random() * CatScratchEditorProvider.scratchCharacters.length)];
|
||||
json.scratches = [
|
||||
...(Array.isArray(json.scratches) ? json.scratches : []),
|
||||
{
|
||||
id: getNonce(),
|
||||
text: character,
|
||||
created: Date.now(),
|
||||
}
|
||||
];
|
||||
|
||||
return this.updateTextDocument(document, json);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete an existing scratch from a document.
|
||||
*/
|
||||
private deleteScratch(document: vscode.TextDocument, id: string) {
|
||||
const json = this.getDocumentAsJson(document);
|
||||
if (!Array.isArray(json.scratches)) {
|
||||
return;
|
||||
}
|
||||
|
||||
json.scratches = json.scratches.filter((note: any) => note.id !== id);
|
||||
|
||||
return this.updateTextDocument(document, json);
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to get a current document as json text.
|
||||
*/
|
||||
private getDocumentAsJson(document: vscode.TextDocument): any {
|
||||
const text = document.getText();
|
||||
if (text.trim().length === 0) {
|
||||
return {};
|
||||
}
|
||||
|
||||
try {
|
||||
return JSON.parse(text);
|
||||
} catch {
|
||||
throw new Error('Could not get document as json. Content is not valid json');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Write out the json to a given document.
|
||||
*/
|
||||
private updateTextDocument(document: vscode.TextDocument, json: any) {
|
||||
const edit = new vscode.WorkspaceEdit();
|
||||
|
||||
// Just replace the entire document every time for this example extension.
|
||||
// A more complete extension should compute minimal edits instead.
|
||||
edit.replace(
|
||||
document.uri,
|
||||
new vscode.Range(0, 0, document.lineCount, 0),
|
||||
JSON.stringify(json, null, 2));
|
||||
|
||||
return vscode.workspace.applyEdit(edit);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,37 +1,37 @@
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
export function disposeAll(disposables: vscode.Disposable[]): void {
|
||||
while (disposables.length) {
|
||||
const item = disposables.pop();
|
||||
if (item) {
|
||||
item.dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export abstract class Disposable {
|
||||
private _isDisposed = false;
|
||||
|
||||
protected _disposables: vscode.Disposable[] = [];
|
||||
|
||||
public dispose(): any {
|
||||
if (this._isDisposed) {
|
||||
return;
|
||||
}
|
||||
this._isDisposed = true;
|
||||
disposeAll(this._disposables);
|
||||
}
|
||||
|
||||
protected _register<T extends vscode.Disposable>(value: T): T {
|
||||
if (this._isDisposed) {
|
||||
value.dispose();
|
||||
} else {
|
||||
this._disposables.push(value);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
protected get isDisposed(): boolean {
|
||||
return this._isDisposed;
|
||||
}
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
export function disposeAll(disposables: vscode.Disposable[]): void {
|
||||
while (disposables.length) {
|
||||
const item = disposables.pop();
|
||||
if (item) {
|
||||
item.dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export abstract class Disposable {
|
||||
private _isDisposed = false;
|
||||
|
||||
protected _disposables: vscode.Disposable[] = [];
|
||||
|
||||
public dispose(): any {
|
||||
if (this._isDisposed) {
|
||||
return;
|
||||
}
|
||||
this._isDisposed = true;
|
||||
disposeAll(this._disposables);
|
||||
}
|
||||
|
||||
protected _register<T extends vscode.Disposable>(value: T): T {
|
||||
if (this._isDisposed) {
|
||||
value.dispose();
|
||||
} else {
|
||||
this._disposables.push(value);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
protected get isDisposed(): boolean {
|
||||
return this._isDisposed;
|
||||
}
|
||||
}
|
||||
@ -1,9 +1,9 @@
|
||||
import * as vscode from 'vscode';
|
||||
import { CatScratchEditorProvider } from './catScratchEditor';
|
||||
import { PawDrawEditorProvider } from './pawDrawEditor';
|
||||
|
||||
export function activate(context: vscode.ExtensionContext) {
|
||||
// Register our custom editor providers
|
||||
context.subscriptions.push(CatScratchEditorProvider.register(context));
|
||||
context.subscriptions.push(PawDrawEditorProvider.register(context));
|
||||
}
|
||||
import * as vscode from 'vscode';
|
||||
import { CatScratchEditorProvider } from './catScratchEditor';
|
||||
import { PawDrawEditorProvider } from './pawDrawEditor';
|
||||
|
||||
export function activate(context: vscode.ExtensionContext) {
|
||||
// Register our custom editor providers
|
||||
context.subscriptions.push(CatScratchEditorProvider.register(context));
|
||||
context.subscriptions.push(PawDrawEditorProvider.register(context));
|
||||
}
|
||||
|
||||
@ -1,456 +1,456 @@
|
||||
import * as vscode from 'vscode';
|
||||
import { Disposable, disposeAll } from './dispose';
|
||||
import { getNonce } from './util';
|
||||
|
||||
/**
|
||||
* Define the type of edits used in paw draw files.
|
||||
*/
|
||||
interface PawDrawEdit {
|
||||
readonly color: string;
|
||||
readonly stroke: ReadonlyArray<[number, number]>;
|
||||
}
|
||||
|
||||
interface PawDrawDocumentDelegate {
|
||||
getFileData(): Promise<Uint8Array>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Define the document (the data model) used for paw draw files.
|
||||
*/
|
||||
class PawDrawDocument extends Disposable implements vscode.CustomDocument {
|
||||
|
||||
static async create(
|
||||
uri: vscode.Uri,
|
||||
backupId: string | undefined,
|
||||
delegate: PawDrawDocumentDelegate,
|
||||
): Promise<PawDrawDocument | PromiseLike<PawDrawDocument>> {
|
||||
// If we have a backup, read that. Otherwise read the resource from the workspace
|
||||
const dataFile = typeof backupId === 'string' ? vscode.Uri.parse(backupId) : uri;
|
||||
const fileData = await PawDrawDocument.readFile(dataFile);
|
||||
return new PawDrawDocument(uri, fileData, delegate);
|
||||
}
|
||||
|
||||
private static async readFile(uri: vscode.Uri): Promise<Uint8Array> {
|
||||
if (uri.scheme === 'untitled') {
|
||||
return new Uint8Array();
|
||||
}
|
||||
return new Uint8Array(await vscode.workspace.fs.readFile(uri));
|
||||
}
|
||||
|
||||
private readonly _uri: vscode.Uri;
|
||||
|
||||
private _documentData: Uint8Array;
|
||||
private _edits: Array<PawDrawEdit> = [];
|
||||
private _savedEdits: Array<PawDrawEdit> = [];
|
||||
|
||||
private readonly _delegate: PawDrawDocumentDelegate;
|
||||
|
||||
private constructor(
|
||||
uri: vscode.Uri,
|
||||
initialContent: Uint8Array,
|
||||
delegate: PawDrawDocumentDelegate
|
||||
) {
|
||||
super();
|
||||
this._uri = uri;
|
||||
this._documentData = initialContent;
|
||||
this._delegate = delegate;
|
||||
}
|
||||
|
||||
public get uri() { return this._uri; }
|
||||
|
||||
public get documentData(): Uint8Array { return this._documentData; }
|
||||
|
||||
private readonly _onDidDispose = this._register(new vscode.EventEmitter<void>());
|
||||
/**
|
||||
* Fired when the document is disposed of.
|
||||
*/
|
||||
public readonly onDidDispose = this._onDidDispose.event;
|
||||
|
||||
private readonly _onDidChangeDocument = this._register(new vscode.EventEmitter<{
|
||||
readonly content?: Uint8Array;
|
||||
readonly edits: readonly PawDrawEdit[];
|
||||
}>());
|
||||
/**
|
||||
* Fired to notify webviews that the document has changed.
|
||||
*/
|
||||
public readonly onDidChangeContent = this._onDidChangeDocument.event;
|
||||
|
||||
private readonly _onDidChange = this._register(new vscode.EventEmitter<{
|
||||
readonly label: string,
|
||||
undo(): void,
|
||||
redo(): void,
|
||||
}>());
|
||||
/**
|
||||
* Fired to tell VS Code that an edit has occurred in the document.
|
||||
*
|
||||
* This updates the document's dirty indicator.
|
||||
*/
|
||||
public readonly onDidChange = this._onDidChange.event;
|
||||
|
||||
/**
|
||||
* Called by VS Code when there are no more references to the document.
|
||||
*
|
||||
* This happens when all editors for it have been closed.
|
||||
*/
|
||||
dispose(): void {
|
||||
this._onDidDispose.fire();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the user edits the document in a webview.
|
||||
*
|
||||
* This fires an event to notify VS Code that the document has been edited.
|
||||
*/
|
||||
makeEdit(edit: PawDrawEdit) {
|
||||
this._edits.push(edit);
|
||||
|
||||
this._onDidChange.fire({
|
||||
label: 'Stroke',
|
||||
undo: async () => {
|
||||
this._edits.pop();
|
||||
this._onDidChangeDocument.fire({
|
||||
edits: this._edits,
|
||||
});
|
||||
},
|
||||
redo: async () => {
|
||||
this._edits.push(edit);
|
||||
this._onDidChangeDocument.fire({
|
||||
edits: this._edits,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by VS Code when the user saves the document.
|
||||
*/
|
||||
async save(cancellation: vscode.CancellationToken): Promise<void> {
|
||||
await this.saveAs(this.uri, cancellation);
|
||||
this._savedEdits = Array.from(this._edits);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by VS Code when the user saves the document to a new location.
|
||||
*/
|
||||
async saveAs(targetResource: vscode.Uri, cancellation: vscode.CancellationToken): Promise<void> {
|
||||
const fileData = await this._delegate.getFileData();
|
||||
if (cancellation.isCancellationRequested) {
|
||||
return;
|
||||
}
|
||||
await vscode.workspace.fs.writeFile(targetResource, fileData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by VS Code when the user calls `revert` on a document.
|
||||
*/
|
||||
async revert(_cancellation: vscode.CancellationToken): Promise<void> {
|
||||
const diskContent = await PawDrawDocument.readFile(this.uri);
|
||||
this._documentData = diskContent;
|
||||
this._edits = this._savedEdits;
|
||||
this._onDidChangeDocument.fire({
|
||||
content: diskContent,
|
||||
edits: this._edits,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by VS Code to backup the edited document.
|
||||
*
|
||||
* These backups are used to implement hot exit.
|
||||
*/
|
||||
async backup(destination: vscode.Uri, cancellation: vscode.CancellationToken): Promise<vscode.CustomDocumentBackup> {
|
||||
await this.saveAs(destination, cancellation);
|
||||
|
||||
return {
|
||||
id: destination.toString(),
|
||||
delete: async () => {
|
||||
try {
|
||||
await vscode.workspace.fs.delete(destination);
|
||||
} catch {
|
||||
// noop
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Provider for paw draw editors.
|
||||
*
|
||||
* Paw draw editors are used for `.pawDraw` files, which are just `.png` files with a different file extension.
|
||||
*
|
||||
* This provider demonstrates:
|
||||
*
|
||||
* - How to implement a custom editor for binary files.
|
||||
* - Setting up the initial webview for a custom editor.
|
||||
* - Loading scripts and styles in a custom editor.
|
||||
* - Communication between VS Code and the custom editor.
|
||||
* - Using CustomDocuments to store information that is shared between multiple custom editors.
|
||||
* - Implementing save, undo, redo, and revert.
|
||||
* - Backing up a custom editor.
|
||||
*/
|
||||
export class PawDrawEditorProvider implements vscode.CustomEditorProvider<PawDrawDocument> {
|
||||
|
||||
private static newPawDrawFileId = 1;
|
||||
|
||||
public static register(context: vscode.ExtensionContext): vscode.Disposable {
|
||||
vscode.commands.registerCommand('catCustoms.pawDraw.new', () => {
|
||||
const workspaceFolders = vscode.workspace.workspaceFolders;
|
||||
if (!workspaceFolders) {
|
||||
vscode.window.showErrorMessage("Creating new Paw Draw files currently requires opening a workspace");
|
||||
return;
|
||||
}
|
||||
|
||||
const uri = vscode.Uri.joinPath(workspaceFolders[0].uri, `new-${PawDrawEditorProvider.newPawDrawFileId++}.pawdraw`)
|
||||
.with({ scheme: 'untitled' });
|
||||
|
||||
vscode.commands.executeCommand('vscode.openWith', uri, PawDrawEditorProvider.viewType);
|
||||
});
|
||||
|
||||
return vscode.window.registerCustomEditorProvider(
|
||||
PawDrawEditorProvider.viewType,
|
||||
new PawDrawEditorProvider(context),
|
||||
{
|
||||
// For this demo extension, we enable `retainContextWhenHidden` which keeps the
|
||||
// webview alive even when it is not visible. You should avoid using this setting
|
||||
// unless is absolutely required as it does have memory overhead.
|
||||
webviewOptions: {
|
||||
retainContextWhenHidden: true,
|
||||
},
|
||||
supportsMultipleEditorsPerDocument: false,
|
||||
});
|
||||
}
|
||||
|
||||
private static readonly viewType = 'catCustoms.pawDraw';
|
||||
|
||||
/**
|
||||
* Tracks all known webviews
|
||||
*/
|
||||
private readonly webviews = new WebviewCollection();
|
||||
|
||||
constructor(
|
||||
private readonly _context: vscode.ExtensionContext
|
||||
) { }
|
||||
|
||||
//#region CustomEditorProvider
|
||||
|
||||
async openCustomDocument(
|
||||
uri: vscode.Uri,
|
||||
openContext: { backupId?: string },
|
||||
_token: vscode.CancellationToken
|
||||
): Promise<PawDrawDocument> {
|
||||
const document: PawDrawDocument = await PawDrawDocument.create(uri, openContext.backupId, {
|
||||
getFileData: async () => {
|
||||
const webviewsForDocument = Array.from(this.webviews.get(document.uri));
|
||||
if (!webviewsForDocument.length) {
|
||||
throw new Error('Could not find webview to save for');
|
||||
}
|
||||
const panel = webviewsForDocument[0];
|
||||
const response = await this.postMessageWithResponse<number[]>(panel, 'getFileData', {});
|
||||
return new Uint8Array(response);
|
||||
}
|
||||
});
|
||||
|
||||
const listeners: vscode.Disposable[] = [];
|
||||
|
||||
listeners.push(document.onDidChange(e => {
|
||||
// Tell VS Code that the document has been edited by the use.
|
||||
this._onDidChangeCustomDocument.fire({
|
||||
document,
|
||||
...e,
|
||||
});
|
||||
}));
|
||||
|
||||
listeners.push(document.onDidChangeContent(e => {
|
||||
// Update all webviews when the document changes
|
||||
for (const webviewPanel of this.webviews.get(document.uri)) {
|
||||
this.postMessage(webviewPanel, 'update', {
|
||||
edits: e.edits,
|
||||
content: e.content,
|
||||
});
|
||||
}
|
||||
}));
|
||||
|
||||
document.onDidDispose(() => disposeAll(listeners));
|
||||
|
||||
return document;
|
||||
}
|
||||
|
||||
async resolveCustomEditor(
|
||||
document: PawDrawDocument,
|
||||
webviewPanel: vscode.WebviewPanel,
|
||||
_token: vscode.CancellationToken
|
||||
): Promise<void> {
|
||||
// Add the webview to our internal set of active webviews
|
||||
this.webviews.add(document.uri, webviewPanel);
|
||||
|
||||
// Setup initial content for the webview
|
||||
webviewPanel.webview.options = {
|
||||
enableScripts: true,
|
||||
};
|
||||
webviewPanel.webview.html = this.getHtmlForWebview(webviewPanel.webview);
|
||||
|
||||
webviewPanel.webview.onDidReceiveMessage(e => this.onMessage(document, e));
|
||||
|
||||
// Wait for the webview to be properly ready before we init
|
||||
webviewPanel.webview.onDidReceiveMessage(e => {
|
||||
if (e.type === 'ready') {
|
||||
if (document.uri.scheme === 'untitled') {
|
||||
this.postMessage(webviewPanel, 'init', {
|
||||
untitled: true,
|
||||
editable: true,
|
||||
});
|
||||
} else {
|
||||
const editable = vscode.workspace.fs.isWritableFileSystem(document.uri.scheme);
|
||||
|
||||
this.postMessage(webviewPanel, 'init', {
|
||||
value: document.documentData,
|
||||
editable,
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private readonly _onDidChangeCustomDocument = new vscode.EventEmitter<vscode.CustomDocumentEditEvent<PawDrawDocument>>();
|
||||
public readonly onDidChangeCustomDocument = this._onDidChangeCustomDocument.event;
|
||||
|
||||
public saveCustomDocument(document: PawDrawDocument, cancellation: vscode.CancellationToken): Thenable<void> {
|
||||
return document.save(cancellation);
|
||||
}
|
||||
|
||||
public saveCustomDocumentAs(document: PawDrawDocument, destination: vscode.Uri, cancellation: vscode.CancellationToken): Thenable<void> {
|
||||
return document.saveAs(destination, cancellation);
|
||||
}
|
||||
|
||||
public revertCustomDocument(document: PawDrawDocument, cancellation: vscode.CancellationToken): Thenable<void> {
|
||||
return document.revert(cancellation);
|
||||
}
|
||||
|
||||
public backupCustomDocument(document: PawDrawDocument, context: vscode.CustomDocumentBackupContext, cancellation: vscode.CancellationToken): Thenable<vscode.CustomDocumentBackup> {
|
||||
return document.backup(context.destination, cancellation);
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
/**
|
||||
* Get the static HTML used for in our editor's webviews.
|
||||
*/
|
||||
private getHtmlForWebview(webview: vscode.Webview): string {
|
||||
// Local path to script and css for the webview
|
||||
const scriptUri = webview.asWebviewUri(vscode.Uri.joinPath(
|
||||
this._context.extensionUri, 'media', 'pawDraw.js'));
|
||||
|
||||
const styleResetUri = webview.asWebviewUri(vscode.Uri.joinPath(
|
||||
this._context.extensionUri, 'media', 'reset.css'));
|
||||
|
||||
const styleVSCodeUri = webview.asWebviewUri(vscode.Uri.joinPath(
|
||||
this._context.extensionUri, 'media', 'vscode.css'));
|
||||
|
||||
const styleMainUri = webview.asWebviewUri(vscode.Uri.joinPath(
|
||||
this._context.extensionUri, 'media', 'pawDraw.css'));
|
||||
|
||||
// Use a nonce to whitelist which scripts can be run
|
||||
const nonce = getNonce();
|
||||
|
||||
return /* html */`
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
|
||||
<!--
|
||||
Use a content security policy to only allow loading images from https or from our extension directory,
|
||||
and only allow scripts that have a specific nonce.
|
||||
-->
|
||||
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; img-src ${webview.cspSource} blob:; style-src ${webview.cspSource}; script-src 'nonce-${nonce}';">
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
|
||||
<link href="${styleResetUri}" rel="stylesheet" />
|
||||
<link href="${styleVSCodeUri}" rel="stylesheet" />
|
||||
<link href="${styleMainUri}" rel="stylesheet" />
|
||||
|
||||
<title>Paw Draw</title>
|
||||
</head>
|
||||
<body>
|
||||
<div class="drawing-canvas"></div>
|
||||
|
||||
<div class="drawing-controls">
|
||||
<button data-color="black" class="black active" title="Black"></button>
|
||||
<button data-color="white" class="white" title="White"></button>
|
||||
<button data-color="red" class="red" title="Red"></button>
|
||||
<button data-color="green" class="green" title="Green"></button>
|
||||
<button data-color="blue" class="blue" title="Blue"></button>
|
||||
</div>
|
||||
|
||||
<script nonce="${nonce}" src="${scriptUri}"></script>
|
||||
</body>
|
||||
</html>`;
|
||||
}
|
||||
|
||||
private _requestId = 1;
|
||||
private readonly _callbacks = new Map<number, (response: any) => void>();
|
||||
|
||||
private postMessageWithResponse<R = unknown>(panel: vscode.WebviewPanel, type: string, body: any): Promise<R> {
|
||||
const requestId = this._requestId++;
|
||||
const p = new Promise<R>(resolve => this._callbacks.set(requestId, resolve));
|
||||
panel.webview.postMessage({ type, requestId, body });
|
||||
return p;
|
||||
}
|
||||
|
||||
private postMessage(panel: vscode.WebviewPanel, type: string, body: any): void {
|
||||
panel.webview.postMessage({ type, body });
|
||||
}
|
||||
|
||||
private onMessage(document: PawDrawDocument, message: any) {
|
||||
switch (message.type) {
|
||||
case 'stroke':
|
||||
document.makeEdit(message as PawDrawEdit);
|
||||
return;
|
||||
|
||||
case 'response':
|
||||
{
|
||||
const callback = this._callbacks.get(message.requestId);
|
||||
callback?.(message.body);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tracks all webviews.
|
||||
*/
|
||||
class WebviewCollection {
|
||||
|
||||
private readonly _webviews = new Set<{
|
||||
readonly resource: string;
|
||||
readonly webviewPanel: vscode.WebviewPanel;
|
||||
}>();
|
||||
|
||||
/**
|
||||
* Get all known webviews for a given uri.
|
||||
*/
|
||||
public *get(uri: vscode.Uri): Iterable<vscode.WebviewPanel> {
|
||||
const key = uri.toString();
|
||||
for (const entry of this._webviews) {
|
||||
if (entry.resource === key) {
|
||||
yield entry.webviewPanel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new webview to the collection.
|
||||
*/
|
||||
public add(uri: vscode.Uri, webviewPanel: vscode.WebviewPanel) {
|
||||
const entry = { resource: uri.toString(), webviewPanel };
|
||||
this._webviews.add(entry);
|
||||
|
||||
webviewPanel.onDidDispose(() => {
|
||||
this._webviews.delete(entry);
|
||||
});
|
||||
}
|
||||
}
|
||||
import * as vscode from 'vscode';
|
||||
import { Disposable, disposeAll } from './dispose';
|
||||
import { getNonce } from './util';
|
||||
|
||||
/**
|
||||
* Define the type of edits used in paw draw files.
|
||||
*/
|
||||
interface PawDrawEdit {
|
||||
readonly color: string;
|
||||
readonly stroke: ReadonlyArray<[number, number]>;
|
||||
}
|
||||
|
||||
interface PawDrawDocumentDelegate {
|
||||
getFileData(): Promise<Uint8Array>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Define the document (the data model) used for paw draw files.
|
||||
*/
|
||||
class PawDrawDocument extends Disposable implements vscode.CustomDocument {
|
||||
|
||||
static async create(
|
||||
uri: vscode.Uri,
|
||||
backupId: string | undefined,
|
||||
delegate: PawDrawDocumentDelegate,
|
||||
): Promise<PawDrawDocument | PromiseLike<PawDrawDocument>> {
|
||||
// If we have a backup, read that. Otherwise read the resource from the workspace
|
||||
const dataFile = typeof backupId === 'string' ? vscode.Uri.parse(backupId) : uri;
|
||||
const fileData = await PawDrawDocument.readFile(dataFile);
|
||||
return new PawDrawDocument(uri, fileData, delegate);
|
||||
}
|
||||
|
||||
private static async readFile(uri: vscode.Uri): Promise<Uint8Array> {
|
||||
if (uri.scheme === 'untitled') {
|
||||
return new Uint8Array();
|
||||
}
|
||||
return new Uint8Array(await vscode.workspace.fs.readFile(uri));
|
||||
}
|
||||
|
||||
private readonly _uri: vscode.Uri;
|
||||
|
||||
private _documentData: Uint8Array;
|
||||
private _edits: Array<PawDrawEdit> = [];
|
||||
private _savedEdits: Array<PawDrawEdit> = [];
|
||||
|
||||
private readonly _delegate: PawDrawDocumentDelegate;
|
||||
|
||||
private constructor(
|
||||
uri: vscode.Uri,
|
||||
initialContent: Uint8Array,
|
||||
delegate: PawDrawDocumentDelegate
|
||||
) {
|
||||
super();
|
||||
this._uri = uri;
|
||||
this._documentData = initialContent;
|
||||
this._delegate = delegate;
|
||||
}
|
||||
|
||||
public get uri() { return this._uri; }
|
||||
|
||||
public get documentData(): Uint8Array { return this._documentData; }
|
||||
|
||||
private readonly _onDidDispose = this._register(new vscode.EventEmitter<void>());
|
||||
/**
|
||||
* Fired when the document is disposed of.
|
||||
*/
|
||||
public readonly onDidDispose = this._onDidDispose.event;
|
||||
|
||||
private readonly _onDidChangeDocument = this._register(new vscode.EventEmitter<{
|
||||
readonly content?: Uint8Array;
|
||||
readonly edits: readonly PawDrawEdit[];
|
||||
}>());
|
||||
/**
|
||||
* Fired to notify webviews that the document has changed.
|
||||
*/
|
||||
public readonly onDidChangeContent = this._onDidChangeDocument.event;
|
||||
|
||||
private readonly _onDidChange = this._register(new vscode.EventEmitter<{
|
||||
readonly label: string,
|
||||
undo(): void,
|
||||
redo(): void,
|
||||
}>());
|
||||
/**
|
||||
* Fired to tell VS Code that an edit has occurred in the document.
|
||||
*
|
||||
* This updates the document's dirty indicator.
|
||||
*/
|
||||
public readonly onDidChange = this._onDidChange.event;
|
||||
|
||||
/**
|
||||
* Called by VS Code when there are no more references to the document.
|
||||
*
|
||||
* This happens when all editors for it have been closed.
|
||||
*/
|
||||
dispose(): void {
|
||||
this._onDidDispose.fire();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the user edits the document in a webview.
|
||||
*
|
||||
* This fires an event to notify VS Code that the document has been edited.
|
||||
*/
|
||||
makeEdit(edit: PawDrawEdit) {
|
||||
this._edits.push(edit);
|
||||
|
||||
this._onDidChange.fire({
|
||||
label: 'Stroke',
|
||||
undo: async () => {
|
||||
this._edits.pop();
|
||||
this._onDidChangeDocument.fire({
|
||||
edits: this._edits,
|
||||
});
|
||||
},
|
||||
redo: async () => {
|
||||
this._edits.push(edit);
|
||||
this._onDidChangeDocument.fire({
|
||||
edits: this._edits,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by VS Code when the user saves the document.
|
||||
*/
|
||||
async save(cancellation: vscode.CancellationToken): Promise<void> {
|
||||
await this.saveAs(this.uri, cancellation);
|
||||
this._savedEdits = Array.from(this._edits);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by VS Code when the user saves the document to a new location.
|
||||
*/
|
||||
async saveAs(targetResource: vscode.Uri, cancellation: vscode.CancellationToken): Promise<void> {
|
||||
const fileData = await this._delegate.getFileData();
|
||||
if (cancellation.isCancellationRequested) {
|
||||
return;
|
||||
}
|
||||
await vscode.workspace.fs.writeFile(targetResource, fileData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by VS Code when the user calls `revert` on a document.
|
||||
*/
|
||||
async revert(_cancellation: vscode.CancellationToken): Promise<void> {
|
||||
const diskContent = await PawDrawDocument.readFile(this.uri);
|
||||
this._documentData = diskContent;
|
||||
this._edits = this._savedEdits;
|
||||
this._onDidChangeDocument.fire({
|
||||
content: diskContent,
|
||||
edits: this._edits,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by VS Code to backup the edited document.
|
||||
*
|
||||
* These backups are used to implement hot exit.
|
||||
*/
|
||||
async backup(destination: vscode.Uri, cancellation: vscode.CancellationToken): Promise<vscode.CustomDocumentBackup> {
|
||||
await this.saveAs(destination, cancellation);
|
||||
|
||||
return {
|
||||
id: destination.toString(),
|
||||
delete: async () => {
|
||||
try {
|
||||
await vscode.workspace.fs.delete(destination);
|
||||
} catch {
|
||||
// noop
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Provider for paw draw editors.
|
||||
*
|
||||
* Paw draw editors are used for `.pawDraw` files, which are just `.png` files with a different file extension.
|
||||
*
|
||||
* This provider demonstrates:
|
||||
*
|
||||
* - How to implement a custom editor for binary files.
|
||||
* - Setting up the initial webview for a custom editor.
|
||||
* - Loading scripts and styles in a custom editor.
|
||||
* - Communication between VS Code and the custom editor.
|
||||
* - Using CustomDocuments to store information that is shared between multiple custom editors.
|
||||
* - Implementing save, undo, redo, and revert.
|
||||
* - Backing up a custom editor.
|
||||
*/
|
||||
export class PawDrawEditorProvider implements vscode.CustomEditorProvider<PawDrawDocument> {
|
||||
|
||||
private static newPawDrawFileId = 1;
|
||||
|
||||
public static register(context: vscode.ExtensionContext): vscode.Disposable {
|
||||
vscode.commands.registerCommand('catCustoms.pawDraw.new', () => {
|
||||
const workspaceFolders = vscode.workspace.workspaceFolders;
|
||||
if (!workspaceFolders) {
|
||||
vscode.window.showErrorMessage("Creating new Paw Draw files currently requires opening a workspace");
|
||||
return;
|
||||
}
|
||||
|
||||
const uri = vscode.Uri.joinPath(workspaceFolders[0].uri, `new-${PawDrawEditorProvider.newPawDrawFileId++}.pawdraw`)
|
||||
.with({ scheme: 'untitled' });
|
||||
|
||||
vscode.commands.executeCommand('vscode.openWith', uri, PawDrawEditorProvider.viewType);
|
||||
});
|
||||
|
||||
return vscode.window.registerCustomEditorProvider(
|
||||
PawDrawEditorProvider.viewType,
|
||||
new PawDrawEditorProvider(context),
|
||||
{
|
||||
// For this demo extension, we enable `retainContextWhenHidden` which keeps the
|
||||
// webview alive even when it is not visible. You should avoid using this setting
|
||||
// unless is absolutely required as it does have memory overhead.
|
||||
webviewOptions: {
|
||||
retainContextWhenHidden: true,
|
||||
},
|
||||
supportsMultipleEditorsPerDocument: false,
|
||||
});
|
||||
}
|
||||
|
||||
private static readonly viewType = 'catCustoms.pawDraw';
|
||||
|
||||
/**
|
||||
* Tracks all known webviews
|
||||
*/
|
||||
private readonly webviews = new WebviewCollection();
|
||||
|
||||
constructor(
|
||||
private readonly _context: vscode.ExtensionContext
|
||||
) { }
|
||||
|
||||
//#region CustomEditorProvider
|
||||
|
||||
async openCustomDocument(
|
||||
uri: vscode.Uri,
|
||||
openContext: { backupId?: string },
|
||||
_token: vscode.CancellationToken
|
||||
): Promise<PawDrawDocument> {
|
||||
const document: PawDrawDocument = await PawDrawDocument.create(uri, openContext.backupId, {
|
||||
getFileData: async () => {
|
||||
const webviewsForDocument = Array.from(this.webviews.get(document.uri));
|
||||
if (!webviewsForDocument.length) {
|
||||
throw new Error('Could not find webview to save for');
|
||||
}
|
||||
const panel = webviewsForDocument[0];
|
||||
const response = await this.postMessageWithResponse<number[]>(panel, 'getFileData', {});
|
||||
return new Uint8Array(response);
|
||||
}
|
||||
});
|
||||
|
||||
const listeners: vscode.Disposable[] = [];
|
||||
|
||||
listeners.push(document.onDidChange(e => {
|
||||
// Tell VS Code that the document has been edited by the use.
|
||||
this._onDidChangeCustomDocument.fire({
|
||||
document,
|
||||
...e,
|
||||
});
|
||||
}));
|
||||
|
||||
listeners.push(document.onDidChangeContent(e => {
|
||||
// Update all webviews when the document changes
|
||||
for (const webviewPanel of this.webviews.get(document.uri)) {
|
||||
this.postMessage(webviewPanel, 'update', {
|
||||
edits: e.edits,
|
||||
content: e.content,
|
||||
});
|
||||
}
|
||||
}));
|
||||
|
||||
document.onDidDispose(() => disposeAll(listeners));
|
||||
|
||||
return document;
|
||||
}
|
||||
|
||||
async resolveCustomEditor(
|
||||
document: PawDrawDocument,
|
||||
webviewPanel: vscode.WebviewPanel,
|
||||
_token: vscode.CancellationToken
|
||||
): Promise<void> {
|
||||
// Add the webview to our internal set of active webviews
|
||||
this.webviews.add(document.uri, webviewPanel);
|
||||
|
||||
// Setup initial content for the webview
|
||||
webviewPanel.webview.options = {
|
||||
enableScripts: true,
|
||||
};
|
||||
webviewPanel.webview.html = this.getHtmlForWebview(webviewPanel.webview);
|
||||
|
||||
webviewPanel.webview.onDidReceiveMessage(e => this.onMessage(document, e));
|
||||
|
||||
// Wait for the webview to be properly ready before we init
|
||||
webviewPanel.webview.onDidReceiveMessage(e => {
|
||||
if (e.type === 'ready') {
|
||||
if (document.uri.scheme === 'untitled') {
|
||||
this.postMessage(webviewPanel, 'init', {
|
||||
untitled: true,
|
||||
editable: true,
|
||||
});
|
||||
} else {
|
||||
const editable = vscode.workspace.fs.isWritableFileSystem(document.uri.scheme);
|
||||
|
||||
this.postMessage(webviewPanel, 'init', {
|
||||
value: document.documentData,
|
||||
editable,
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private readonly _onDidChangeCustomDocument = new vscode.EventEmitter<vscode.CustomDocumentEditEvent<PawDrawDocument>>();
|
||||
public readonly onDidChangeCustomDocument = this._onDidChangeCustomDocument.event;
|
||||
|
||||
public saveCustomDocument(document: PawDrawDocument, cancellation: vscode.CancellationToken): Thenable<void> {
|
||||
return document.save(cancellation);
|
||||
}
|
||||
|
||||
public saveCustomDocumentAs(document: PawDrawDocument, destination: vscode.Uri, cancellation: vscode.CancellationToken): Thenable<void> {
|
||||
return document.saveAs(destination, cancellation);
|
||||
}
|
||||
|
||||
public revertCustomDocument(document: PawDrawDocument, cancellation: vscode.CancellationToken): Thenable<void> {
|
||||
return document.revert(cancellation);
|
||||
}
|
||||
|
||||
public backupCustomDocument(document: PawDrawDocument, context: vscode.CustomDocumentBackupContext, cancellation: vscode.CancellationToken): Thenable<vscode.CustomDocumentBackup> {
|
||||
return document.backup(context.destination, cancellation);
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
/**
|
||||
* Get the static HTML used for in our editor's webviews.
|
||||
*/
|
||||
private getHtmlForWebview(webview: vscode.Webview): string {
|
||||
// Local path to script and css for the webview
|
||||
const scriptUri = webview.asWebviewUri(vscode.Uri.joinPath(
|
||||
this._context.extensionUri, 'media', 'pawDraw.js'));
|
||||
|
||||
const styleResetUri = webview.asWebviewUri(vscode.Uri.joinPath(
|
||||
this._context.extensionUri, 'media', 'reset.css'));
|
||||
|
||||
const styleVSCodeUri = webview.asWebviewUri(vscode.Uri.joinPath(
|
||||
this._context.extensionUri, 'media', 'vscode.css'));
|
||||
|
||||
const styleMainUri = webview.asWebviewUri(vscode.Uri.joinPath(
|
||||
this._context.extensionUri, 'media', 'pawDraw.css'));
|
||||
|
||||
// Use a nonce to whitelist which scripts can be run
|
||||
const nonce = getNonce();
|
||||
|
||||
return /* html */`
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
|
||||
<!--
|
||||
Use a content security policy to only allow loading images from https or from our extension directory,
|
||||
and only allow scripts that have a specific nonce.
|
||||
-->
|
||||
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; img-src ${webview.cspSource} blob:; style-src ${webview.cspSource}; script-src 'nonce-${nonce}';">
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
|
||||
<link href="${styleResetUri}" rel="stylesheet" />
|
||||
<link href="${styleVSCodeUri}" rel="stylesheet" />
|
||||
<link href="${styleMainUri}" rel="stylesheet" />
|
||||
|
||||
<title>Paw Draw</title>
|
||||
</head>
|
||||
<body>
|
||||
<div class="drawing-canvas"></div>
|
||||
|
||||
<div class="drawing-controls">
|
||||
<button data-color="black" class="black active" title="Black"></button>
|
||||
<button data-color="white" class="white" title="White"></button>
|
||||
<button data-color="red" class="red" title="Red"></button>
|
||||
<button data-color="green" class="green" title="Green"></button>
|
||||
<button data-color="blue" class="blue" title="Blue"></button>
|
||||
</div>
|
||||
|
||||
<script nonce="${nonce}" src="${scriptUri}"></script>
|
||||
</body>
|
||||
</html>`;
|
||||
}
|
||||
|
||||
private _requestId = 1;
|
||||
private readonly _callbacks = new Map<number, (response: any) => void>();
|
||||
|
||||
private postMessageWithResponse<R = unknown>(panel: vscode.WebviewPanel, type: string, body: any): Promise<R> {
|
||||
const requestId = this._requestId++;
|
||||
const p = new Promise<R>(resolve => this._callbacks.set(requestId, resolve));
|
||||
panel.webview.postMessage({ type, requestId, body });
|
||||
return p;
|
||||
}
|
||||
|
||||
private postMessage(panel: vscode.WebviewPanel, type: string, body: any): void {
|
||||
panel.webview.postMessage({ type, body });
|
||||
}
|
||||
|
||||
private onMessage(document: PawDrawDocument, message: any) {
|
||||
switch (message.type) {
|
||||
case 'stroke':
|
||||
document.makeEdit(message as PawDrawEdit);
|
||||
return;
|
||||
|
||||
case 'response':
|
||||
{
|
||||
const callback = this._callbacks.get(message.requestId);
|
||||
callback?.(message.body);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tracks all webviews.
|
||||
*/
|
||||
class WebviewCollection {
|
||||
|
||||
private readonly _webviews = new Set<{
|
||||
readonly resource: string;
|
||||
readonly webviewPanel: vscode.WebviewPanel;
|
||||
}>();
|
||||
|
||||
/**
|
||||
* Get all known webviews for a given uri.
|
||||
*/
|
||||
public *get(uri: vscode.Uri): Iterable<vscode.WebviewPanel> {
|
||||
const key = uri.toString();
|
||||
for (const entry of this._webviews) {
|
||||
if (entry.resource === key) {
|
||||
yield entry.webviewPanel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new webview to the collection.
|
||||
*/
|
||||
public add(uri: vscode.Uri, webviewPanel: vscode.WebviewPanel) {
|
||||
const entry = { resource: uri.toString(), webviewPanel };
|
||||
this._webviews.add(entry);
|
||||
|
||||
webviewPanel.onDidDispose(() => {
|
||||
this._webviews.delete(entry);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
export function getNonce() {
|
||||
let text = '';
|
||||
const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
||||
for (let i = 0; i < 32; i++) {
|
||||
text += possible.charAt(Math.floor(Math.random() * possible.length));
|
||||
}
|
||||
return text;
|
||||
}
|
||||
export function getNonce() {
|
||||
let text = '';
|
||||
const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
||||
for (let i = 0; i < 32; i++) {
|
||||
text += possible.charAt(Math.floor(Math.random() * possible.length));
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
@ -1,33 +1,33 @@
|
||||
'use strict';
|
||||
import * as vscode from 'vscode';
|
||||
import * as path from 'path';
|
||||
|
||||
export function activate(context: vscode.ExtensionContext) {
|
||||
|
||||
const collection = vscode.languages.createDiagnosticCollection('test');
|
||||
if (vscode.window.activeTextEditor) {
|
||||
updateDiagnostics(vscode.window.activeTextEditor.document, collection);
|
||||
}
|
||||
context.subscriptions.push(vscode.window.onDidChangeActiveTextEditor(editor => {
|
||||
if (editor) {
|
||||
updateDiagnostics(editor.document, collection);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
function updateDiagnostics(document: vscode.TextDocument, collection: vscode.DiagnosticCollection): void {
|
||||
if (document && path.basename(document.uri.fsPath) === 'sample-demo.rs') {
|
||||
collection.set(document.uri, [{
|
||||
code: '',
|
||||
message: 'cannot assign twice to immutable variable `x`',
|
||||
range: new vscode.Range(new vscode.Position(3, 4), new vscode.Position(3, 10)),
|
||||
severity: vscode.DiagnosticSeverity.Error,
|
||||
source: '',
|
||||
relatedInformation: [
|
||||
new vscode.DiagnosticRelatedInformation(new vscode.Location(document.uri, new vscode.Range(new vscode.Position(1, 8), new vscode.Position(1, 9))), 'first assignment to `x`')
|
||||
]
|
||||
}]);
|
||||
} else {
|
||||
collection.clear();
|
||||
}
|
||||
}
|
||||
'use strict';
|
||||
import * as vscode from 'vscode';
|
||||
import * as path from 'path';
|
||||
|
||||
export function activate(context: vscode.ExtensionContext) {
|
||||
|
||||
const collection = vscode.languages.createDiagnosticCollection('test');
|
||||
if (vscode.window.activeTextEditor) {
|
||||
updateDiagnostics(vscode.window.activeTextEditor.document, collection);
|
||||
}
|
||||
context.subscriptions.push(vscode.window.onDidChangeActiveTextEditor(editor => {
|
||||
if (editor) {
|
||||
updateDiagnostics(editor.document, collection);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
function updateDiagnostics(document: vscode.TextDocument, collection: vscode.DiagnosticCollection): void {
|
||||
if (document && path.basename(document.uri.fsPath) === 'sample-demo.rs') {
|
||||
collection.set(document.uri, [{
|
||||
code: '',
|
||||
message: 'cannot assign twice to immutable variable `x`',
|
||||
range: new vscode.Range(new vscode.Position(3, 4), new vscode.Position(3, 10)),
|
||||
severity: vscode.DiagnosticSeverity.Error,
|
||||
source: '',
|
||||
relatedInformation: [
|
||||
new vscode.DiagnosticRelatedInformation(new vscode.Location(document.uri, new vscode.Range(new vscode.Position(1, 8), new vscode.Position(1, 9))), 'first assignment to `x`')
|
||||
]
|
||||
}]);
|
||||
} else {
|
||||
collection.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,24 +1,24 @@
|
||||
'use strict';
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
export function activate(context: vscode.ExtensionContext) {
|
||||
const disposable = vscode.commands.registerCommand('extension.reverseWord', function () {
|
||||
// Get the active text editor
|
||||
const editor = vscode.window.activeTextEditor;
|
||||
|
||||
if (editor) {
|
||||
const document = editor.document;
|
||||
const selection = editor.selection;
|
||||
|
||||
// Get the word within the selection
|
||||
const word = document.getText(selection);
|
||||
const reversed = word.split('').reverse().join('');
|
||||
editor.edit(editBuilder => {
|
||||
editBuilder.replace(selection, reversed);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
context.subscriptions.push(disposable);
|
||||
'use strict';
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
export function activate(context: vscode.ExtensionContext) {
|
||||
const disposable = vscode.commands.registerCommand('extension.reverseWord', function() {
|
||||
// Get the active text editor
|
||||
const editor = vscode.window.activeTextEditor;
|
||||
|
||||
if (editor) {
|
||||
const document = editor.document;
|
||||
const selection = editor.selection;
|
||||
|
||||
// Get the word within the selection
|
||||
const word = document.getText(selection);
|
||||
const reversed = word.split('').reverse().join('');
|
||||
editor.edit(editBuilder => {
|
||||
editBuilder.replace(selection, reversed);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
context.subscriptions.push(disposable);
|
||||
}
|
||||
@ -1,57 +1,57 @@
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
/**
|
||||
* Provider that maintains a count of the number of times it has copied text.
|
||||
*/
|
||||
class CopyCountPasteEditProvider implements vscode.DocumentPasteEditProvider {
|
||||
|
||||
private readonly countMimeTypes = 'application/vnd.code.copydemo-copy-count';
|
||||
|
||||
private count = 0;
|
||||
|
||||
prepareDocumentPaste?(
|
||||
_document: vscode.TextDocument,
|
||||
_ranges: readonly vscode.Range[],
|
||||
dataTransfer: vscode.DataTransfer,
|
||||
_token: vscode.CancellationToken
|
||||
): void | Thenable<void> {
|
||||
dataTransfer.set(this.countMimeTypes, new vscode.DataTransferItem(this.count++));
|
||||
dataTransfer.set('text/plain', new vscode.DataTransferItem(this.count++));
|
||||
}
|
||||
|
||||
async provideDocumentPasteEdits(
|
||||
_document: vscode.TextDocument,
|
||||
_ranges: readonly vscode.Range[],
|
||||
dataTransfer: vscode.DataTransfer,
|
||||
_token: vscode.CancellationToken
|
||||
): Promise<vscode.DocumentPasteEdit | undefined> {
|
||||
const countDataTransferItem = dataTransfer.get(this.countMimeTypes);
|
||||
if (!countDataTransferItem) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const textDataTransferItem = dataTransfer.get('text') ?? dataTransfer.get('text/plain');
|
||||
if (!textDataTransferItem) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const count = await countDataTransferItem.asString();
|
||||
const text = await textDataTransferItem.asString();
|
||||
|
||||
// Build a snippet to insert
|
||||
const snippet = new vscode.SnippetString();
|
||||
snippet.appendText(`(copy #${count}) ${text}`);
|
||||
|
||||
return { insertText: snippet };
|
||||
}
|
||||
}
|
||||
|
||||
export function activate(context: vscode.ExtensionContext) {
|
||||
// Enable our provider in plaintext files
|
||||
const selector: vscode.DocumentSelector = { language: 'plaintext' };
|
||||
|
||||
// Register our provider
|
||||
context.subscriptions.push(vscode.languages.registerDocumentPasteEditProvider(selector, new CopyCountPasteEditProvider(), {
|
||||
pasteMimeTypes: ['text/plain'],
|
||||
}));
|
||||
}
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
/**
|
||||
* Provider that maintains a count of the number of times it has copied text.
|
||||
*/
|
||||
class CopyCountPasteEditProvider implements vscode.DocumentPasteEditProvider {
|
||||
|
||||
private readonly countMimeTypes = 'application/vnd.code.copydemo-copy-count';
|
||||
|
||||
private count = 0;
|
||||
|
||||
prepareDocumentPaste?(
|
||||
_document: vscode.TextDocument,
|
||||
_ranges: readonly vscode.Range[],
|
||||
dataTransfer: vscode.DataTransfer,
|
||||
_token: vscode.CancellationToken
|
||||
): void | Thenable<void> {
|
||||
dataTransfer.set(this.countMimeTypes, new vscode.DataTransferItem(this.count++));
|
||||
dataTransfer.set('text/plain', new vscode.DataTransferItem(this.count++));
|
||||
}
|
||||
|
||||
async provideDocumentPasteEdits(
|
||||
_document: vscode.TextDocument,
|
||||
_ranges: readonly vscode.Range[],
|
||||
dataTransfer: vscode.DataTransfer,
|
||||
_token: vscode.CancellationToken
|
||||
): Promise<vscode.DocumentPasteEdit | undefined> {
|
||||
const countDataTransferItem = dataTransfer.get(this.countMimeTypes);
|
||||
if (!countDataTransferItem) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const textDataTransferItem = dataTransfer.get('text') ?? dataTransfer.get('text/plain');
|
||||
if (!textDataTransferItem) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const count = await countDataTransferItem.asString();
|
||||
const text = await textDataTransferItem.asString();
|
||||
|
||||
// Build a snippet to insert
|
||||
const snippet = new vscode.SnippetString();
|
||||
snippet.appendText(`(copy #${count}) ${text}`);
|
||||
|
||||
return { insertText: snippet };
|
||||
}
|
||||
}
|
||||
|
||||
export function activate(context: vscode.ExtensionContext) {
|
||||
// Enable our provider in plaintext files
|
||||
const selector: vscode.DocumentSelector = { language: 'plaintext' };
|
||||
|
||||
// Register our provider
|
||||
context.subscriptions.push(vscode.languages.registerDocumentPasteEditProvider(selector, new CopyCountPasteEditProvider(), {
|
||||
pasteMimeTypes: ['text/plain'],
|
||||
}));
|
||||
}
|
||||
|
||||
@ -1,70 +1,70 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
declare module 'vscode' {
|
||||
|
||||
// https://github.com/microsoft/vscode/issues/30066/
|
||||
|
||||
/**
|
||||
* Provider invoked when the user copies and pastes code.
|
||||
*/
|
||||
interface DocumentPasteEditProvider {
|
||||
|
||||
/**
|
||||
* Optional method invoked after the user copies text in a file.
|
||||
*
|
||||
* During {@link prepareDocumentPaste}, an extension can compute metadata that is attached to
|
||||
* a {@link DataTransfer} and is passed back to the provider in {@link provideDocumentPasteEdits}.
|
||||
*
|
||||
* @param document Document where the copy took place.
|
||||
* @param ranges Ranges being copied in the `document`.
|
||||
* @param dataTransfer The data transfer associated with the copy. You can store additional values on this for later use in {@link provideDocumentPasteEdits}.
|
||||
* @param token A cancellation token.
|
||||
*/
|
||||
prepareDocumentPaste?(document: TextDocument, ranges: readonly Range[], dataTransfer: DataTransfer, token: CancellationToken): void | Thenable<void>;
|
||||
|
||||
/**
|
||||
* Invoked before the user pastes into a document.
|
||||
*
|
||||
* In this method, extensions can return a workspace edit that replaces the standard pasting behavior.
|
||||
*
|
||||
* @param document Document being pasted into
|
||||
* @param ranges Currently selected ranges in the document.
|
||||
* @param dataTransfer The data transfer associated with the paste.
|
||||
* @param token A cancellation token.
|
||||
*
|
||||
* @return Optional workspace edit that applies the paste. Return undefined to use standard pasting.
|
||||
*/
|
||||
provideDocumentPasteEdits(document: TextDocument, ranges: readonly Range[], dataTransfer: DataTransfer, token: CancellationToken): ProviderResult<DocumentPasteEdit>;
|
||||
}
|
||||
|
||||
/**
|
||||
* An operation applied on paste
|
||||
*/
|
||||
interface DocumentPasteEdit {
|
||||
/**
|
||||
* The text or snippet to insert at the pasted locations.
|
||||
*/
|
||||
readonly insertText: string | SnippetString;
|
||||
|
||||
/**
|
||||
* An optional additional edit to apply on paste.
|
||||
*/
|
||||
readonly additionalEdit?: WorkspaceEdit;
|
||||
}
|
||||
|
||||
interface DocumentPasteProviderMetadata {
|
||||
/**
|
||||
* Mime types that `provideDocumentPasteEdits` should be invoked for.
|
||||
*
|
||||
* Use the special `files` mimetype to indicate the provider should be invoked if any files are present in the `DataTransfer`.
|
||||
*/
|
||||
readonly pasteMimeTypes: readonly string[];
|
||||
}
|
||||
|
||||
namespace languages {
|
||||
export function registerDocumentPasteEditProvider(selector: DocumentSelector, provider: DocumentPasteEditProvider, metadata: DocumentPasteProviderMetadata): Disposable;
|
||||
}
|
||||
}
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
declare module 'vscode' {
|
||||
|
||||
// https://github.com/microsoft/vscode/issues/30066/
|
||||
|
||||
/**
|
||||
* Provider invoked when the user copies and pastes code.
|
||||
*/
|
||||
interface DocumentPasteEditProvider {
|
||||
|
||||
/**
|
||||
* Optional method invoked after the user copies text in a file.
|
||||
*
|
||||
* During {@link prepareDocumentPaste}, an extension can compute metadata that is attached to
|
||||
* a {@link DataTransfer} and is passed back to the provider in {@link provideDocumentPasteEdits}.
|
||||
*
|
||||
* @param document Document where the copy took place.
|
||||
* @param ranges Ranges being copied in the `document`.
|
||||
* @param dataTransfer The data transfer associated with the copy. You can store additional values on this for later use in {@link provideDocumentPasteEdits}.
|
||||
* @param token A cancellation token.
|
||||
*/
|
||||
prepareDocumentPaste?(document: TextDocument, ranges: readonly Range[], dataTransfer: DataTransfer, token: CancellationToken): void | Thenable<void>;
|
||||
|
||||
/**
|
||||
* Invoked before the user pastes into a document.
|
||||
*
|
||||
* In this method, extensions can return a workspace edit that replaces the standard pasting behavior.
|
||||
*
|
||||
* @param document Document being pasted into
|
||||
* @param ranges Currently selected ranges in the document.
|
||||
* @param dataTransfer The data transfer associated with the paste.
|
||||
* @param token A cancellation token.
|
||||
*
|
||||
* @return Optional workspace edit that applies the paste. Return undefined to use standard pasting.
|
||||
*/
|
||||
provideDocumentPasteEdits(document: TextDocument, ranges: readonly Range[], dataTransfer: DataTransfer, token: CancellationToken): ProviderResult<DocumentPasteEdit>;
|
||||
}
|
||||
|
||||
/**
|
||||
* An operation applied on paste
|
||||
*/
|
||||
interface DocumentPasteEdit {
|
||||
/**
|
||||
* The text or snippet to insert at the pasted locations.
|
||||
*/
|
||||
readonly insertText: string | SnippetString;
|
||||
|
||||
/**
|
||||
* An optional additional edit to apply on paste.
|
||||
*/
|
||||
readonly additionalEdit?: WorkspaceEdit;
|
||||
}
|
||||
|
||||
interface DocumentPasteProviderMetadata {
|
||||
/**
|
||||
* Mime types that `provideDocumentPasteEdits` should be invoked for.
|
||||
*
|
||||
* Use the special `files` mimetype to indicate the provider should be invoked if any files are present in the `DataTransfer`.
|
||||
*/
|
||||
readonly pasteMimeTypes: readonly string[];
|
||||
}
|
||||
|
||||
namespace languages {
|
||||
export function registerDocumentPasteEditProvider(selector: DocumentSelector, provider: DocumentPasteEditProvider, metadata: DocumentPasteProviderMetadata): Disposable;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,105 +1,105 @@
|
||||
import * as vscode from 'vscode';
|
||||
import * as path from 'path';
|
||||
|
||||
const uriListMime = 'text/uri-list';
|
||||
|
||||
/**
|
||||
* Provider that reverses dropped text.
|
||||
*
|
||||
* Note this does not apply to text that is drag and dropped with-in the current editor,
|
||||
* only for text dropped from external apps.
|
||||
*/
|
||||
class ReverseTextOnDropProvider implements vscode.DocumentDropEditProvider {
|
||||
async provideDocumentDropEdits(
|
||||
_document: vscode.TextDocument,
|
||||
position: vscode.Position,
|
||||
dataTransfer: vscode.DataTransfer,
|
||||
token: vscode.CancellationToken
|
||||
): Promise<vscode.DocumentDropEdit | undefined> {
|
||||
// Check the data transfer to see if we have some kind of text data
|
||||
const dataTransferItem = dataTransfer.get('text') ?? dataTransfer.get('text/plain');
|
||||
if (!dataTransferItem) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const text = await dataTransferItem.asString();
|
||||
if (token.isCancellationRequested) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Build a snippet to insert
|
||||
const snippet = new vscode.SnippetString();
|
||||
// Adding the reversed text
|
||||
snippet.appendText([...text].reverse().join(''));
|
||||
|
||||
return { insertText: snippet };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Provider that inserts a numbered list of the names of dropped files.
|
||||
*
|
||||
* Try dropping one or more files from:
|
||||
*
|
||||
* - VS Code's explorer
|
||||
* - The operating system
|
||||
* - The open editors view
|
||||
*/
|
||||
class FileNameListOnDropProvider implements vscode.DocumentDropEditProvider {
|
||||
async provideDocumentDropEdits(
|
||||
_document: vscode.TextDocument,
|
||||
position: vscode.Position,
|
||||
dataTransfer: vscode.DataTransfer,
|
||||
token: vscode.CancellationToken
|
||||
): Promise<vscode.DocumentDropEdit | undefined> {
|
||||
// Check the data transfer to see if we have dropped a list of uris
|
||||
const dataTransferItem = dataTransfer.get(uriListMime);
|
||||
if (!dataTransferItem) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// 'text/uri-list' contains a list of uris separated by new lines.
|
||||
// Parse this to an array of uris.
|
||||
const urlList = await dataTransferItem.asString();
|
||||
if (token.isCancellationRequested) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const uris: vscode.Uri[] = [];
|
||||
for (const resource of urlList.split('\n')) {
|
||||
try {
|
||||
uris.push(vscode.Uri.parse(resource));
|
||||
} catch {
|
||||
// noop
|
||||
}
|
||||
}
|
||||
|
||||
if (!uris.length) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Build a snippet to insert
|
||||
const snippet = new vscode.SnippetString();
|
||||
uris.forEach((uri, index) => {
|
||||
const name = path.basename(uri.path);
|
||||
snippet.appendText(`${index + 1}. ${name}`);
|
||||
snippet.appendTabstop();
|
||||
|
||||
if (index <= uris.length - 1 && uris.length > 1) {
|
||||
snippet.appendText('\n');
|
||||
}
|
||||
});
|
||||
|
||||
return { insertText: snippet };
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export function activate(context: vscode.ExtensionContext) {
|
||||
// Enable our providers in plaintext files
|
||||
const selector: vscode.DocumentSelector = { language: 'plaintext' };
|
||||
|
||||
// Register our providers
|
||||
context.subscriptions.push(vscode.languages.registerDocumentDropEditProvider(selector, new ReverseTextOnDropProvider()));
|
||||
context.subscriptions.push(vscode.languages.registerDocumentDropEditProvider(selector, new FileNameListOnDropProvider()));
|
||||
}
|
||||
import * as vscode from 'vscode';
|
||||
import * as path from 'path';
|
||||
|
||||
const uriListMime = 'text/uri-list';
|
||||
|
||||
/**
|
||||
* Provider that reverses dropped text.
|
||||
*
|
||||
* Note this does not apply to text that is drag and dropped with-in the current editor,
|
||||
* only for text dropped from external apps.
|
||||
*/
|
||||
class ReverseTextOnDropProvider implements vscode.DocumentDropEditProvider {
|
||||
async provideDocumentDropEdits(
|
||||
_document: vscode.TextDocument,
|
||||
position: vscode.Position,
|
||||
dataTransfer: vscode.DataTransfer,
|
||||
token: vscode.CancellationToken
|
||||
): Promise<vscode.DocumentDropEdit | undefined> {
|
||||
// Check the data transfer to see if we have some kind of text data
|
||||
const dataTransferItem = dataTransfer.get('text') ?? dataTransfer.get('text/plain');
|
||||
if (!dataTransferItem) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const text = await dataTransferItem.asString();
|
||||
if (token.isCancellationRequested) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Build a snippet to insert
|
||||
const snippet = new vscode.SnippetString();
|
||||
// Adding the reversed text
|
||||
snippet.appendText([...text].reverse().join(''));
|
||||
|
||||
return { insertText: snippet };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Provider that inserts a numbered list of the names of dropped files.
|
||||
*
|
||||
* Try dropping one or more files from:
|
||||
*
|
||||
* - VS Code's explorer
|
||||
* - The operating system
|
||||
* - The open editors view
|
||||
*/
|
||||
class FileNameListOnDropProvider implements vscode.DocumentDropEditProvider {
|
||||
async provideDocumentDropEdits(
|
||||
_document: vscode.TextDocument,
|
||||
position: vscode.Position,
|
||||
dataTransfer: vscode.DataTransfer,
|
||||
token: vscode.CancellationToken
|
||||
): Promise<vscode.DocumentDropEdit | undefined> {
|
||||
// Check the data transfer to see if we have dropped a list of uris
|
||||
const dataTransferItem = dataTransfer.get(uriListMime);
|
||||
if (!dataTransferItem) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// 'text/uri-list' contains a list of uris separated by new lines.
|
||||
// Parse this to an array of uris.
|
||||
const urlList = await dataTransferItem.asString();
|
||||
if (token.isCancellationRequested) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const uris: vscode.Uri[] = [];
|
||||
for (const resource of urlList.split('\n')) {
|
||||
try {
|
||||
uris.push(vscode.Uri.parse(resource));
|
||||
} catch {
|
||||
// noop
|
||||
}
|
||||
}
|
||||
|
||||
if (!uris.length) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Build a snippet to insert
|
||||
const snippet = new vscode.SnippetString();
|
||||
uris.forEach((uri, index) => {
|
||||
const name = path.basename(uri.path);
|
||||
snippet.appendText(`${index + 1}. ${name}`);
|
||||
snippet.appendTabstop();
|
||||
|
||||
if (index <= uris.length - 1 && uris.length > 1) {
|
||||
snippet.appendText('\n');
|
||||
}
|
||||
});
|
||||
|
||||
return { insertText: snippet };
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export function activate(context: vscode.ExtensionContext) {
|
||||
// Enable our providers in plaintext files
|
||||
const selector: vscode.DocumentSelector = { language: 'plaintext' };
|
||||
|
||||
// Register our providers
|
||||
context.subscriptions.push(vscode.languages.registerDocumentDropEditProvider(selector, new ReverseTextOnDropProvider()));
|
||||
context.subscriptions.push(vscode.languages.registerDocumentDropEditProvider(selector, new FileNameListOnDropProvider()));
|
||||
}
|
||||
|
||||
@ -1,58 +1,58 @@
|
||||
'use strict';
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
export function activate(context: vscode.ExtensionContext) {
|
||||
const writeEmitter = new vscode.EventEmitter<string>();
|
||||
context.subscriptions.push(vscode.commands.registerCommand('extensionTerminalSample.create', () => {
|
||||
let line = '';
|
||||
const pty = {
|
||||
onDidWrite: writeEmitter.event,
|
||||
open: () => writeEmitter.fire('Type and press enter to echo the text\r\n\r\n'),
|
||||
close: () => { /* noop*/ },
|
||||
handleInput: (data: string) => {
|
||||
if (data === '\r') { // Enter
|
||||
writeEmitter.fire(`\r\necho: "${colorText(line)}"\r\n\n`);
|
||||
line = '';
|
||||
return;
|
||||
}
|
||||
if (data === '\x7f') { // Backspace
|
||||
if (line.length === 0) {
|
||||
return;
|
||||
}
|
||||
line = line.substr(0, line.length - 1);
|
||||
// Move cursor backward
|
||||
writeEmitter.fire('\x1b[D');
|
||||
// Delete character
|
||||
writeEmitter.fire('\x1b[P');
|
||||
return;
|
||||
}
|
||||
line += data;
|
||||
writeEmitter.fire(data);
|
||||
}
|
||||
};
|
||||
const terminal = vscode.window.createTerminal({ name: `My Extension REPL`, pty });
|
||||
terminal.show();
|
||||
}));
|
||||
|
||||
context.subscriptions.push(vscode.commands.registerCommand('extensionTerminalSample.clear', () => {
|
||||
writeEmitter.fire('\x1b[2J\x1b[3J\x1b[;H');
|
||||
}));
|
||||
}
|
||||
|
||||
function colorText(text: string): string {
|
||||
let output = '';
|
||||
let colorIndex = 1;
|
||||
for (let i = 0; i < text.length; i++) {
|
||||
const char = text.charAt(i);
|
||||
if (char === ' ' || char === '\r' || char === '\n') {
|
||||
output += char;
|
||||
} else {
|
||||
output += `\x1b[3${colorIndex++}m${text.charAt(i)}\x1b[0m`;
|
||||
if (colorIndex > 6) {
|
||||
colorIndex = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
return output;
|
||||
'use strict';
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
export function activate(context: vscode.ExtensionContext) {
|
||||
const writeEmitter = new vscode.EventEmitter<string>();
|
||||
context.subscriptions.push(vscode.commands.registerCommand('extensionTerminalSample.create', () => {
|
||||
let line = '';
|
||||
const pty = {
|
||||
onDidWrite: writeEmitter.event,
|
||||
open: () => writeEmitter.fire('Type and press enter to echo the text\r\n\r\n'),
|
||||
close: () => { /* noop*/ },
|
||||
handleInput: (data: string) => {
|
||||
if (data === '\r') { // Enter
|
||||
writeEmitter.fire(`\r\necho: "${colorText(line)}"\r\n\n`);
|
||||
line = '';
|
||||
return;
|
||||
}
|
||||
if (data === '\x7f') { // Backspace
|
||||
if (line.length === 0) {
|
||||
return;
|
||||
}
|
||||
line = line.substr(0, line.length - 1);
|
||||
// Move cursor backward
|
||||
writeEmitter.fire('\x1b[D');
|
||||
// Delete character
|
||||
writeEmitter.fire('\x1b[P');
|
||||
return;
|
||||
}
|
||||
line += data;
|
||||
writeEmitter.fire(data);
|
||||
}
|
||||
};
|
||||
const terminal = vscode.window.createTerminal({ name: `My Extension REPL`, pty });
|
||||
terminal.show();
|
||||
}));
|
||||
|
||||
context.subscriptions.push(vscode.commands.registerCommand('extensionTerminalSample.clear', () => {
|
||||
writeEmitter.fire('\x1b[2J\x1b[3J\x1b[;H');
|
||||
}));
|
||||
}
|
||||
|
||||
function colorText(text: string): string {
|
||||
let output = '';
|
||||
let colorIndex = 1;
|
||||
for (let i = 0; i < text.length; i++) {
|
||||
const char = text.charAt(i);
|
||||
if (char === ' ' || char === '\r' || char === '\n') {
|
||||
output += char;
|
||||
} else {
|
||||
output += `\x1b[3${colorIndex++}m${text.charAt(i)}\x1b[0m`;
|
||||
if (colorIndex > 6) {
|
||||
colorIndex = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
return output;
|
||||
}
|
||||
@ -1,92 +1,92 @@
|
||||
/*---------------------------------------------------------
|
||||
* Copyright (C) Microsoft Corporation. All rights reserved.
|
||||
*--------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import { posix } from 'path';
|
||||
|
||||
export function activate(context: vscode.ExtensionContext) {
|
||||
|
||||
// Command #1 - Check and show a JavaScript-file for a TypeScript-file
|
||||
// * shows how to derive a new uri from an existing uri
|
||||
// * shows how to check for existence of a file
|
||||
vscode.commands.registerCommand('fs/openJS', async function () {
|
||||
if (
|
||||
!vscode.window.activeTextEditor
|
||||
|| posix.extname(vscode.window.activeTextEditor.document.uri.path) !== '.ts'
|
||||
) {
|
||||
return vscode.window.showInformationMessage('Open a TypeScript file first');
|
||||
}
|
||||
|
||||
const tsUri = vscode.window.activeTextEditor.document.uri;
|
||||
const jsPath = posix.join(tsUri.path, '..', posix.basename(tsUri.path, '.ts') + '.js');
|
||||
const jsUri = tsUri.with({ path: jsPath });
|
||||
|
||||
try {
|
||||
await vscode.workspace.fs.stat(jsUri);
|
||||
vscode.window.showTextDocument(jsUri, { viewColumn: vscode.ViewColumn.Beside });
|
||||
} catch {
|
||||
vscode.window.showInformationMessage(`${jsUri.toString(true)} file does *not* exist`);
|
||||
}
|
||||
});
|
||||
|
||||
// Command #2 - Compute total size of files in a folder
|
||||
// * shows how to read a directory
|
||||
// * shows how retrieve metadata for a file
|
||||
// * create an untitled document that shows count and total of files
|
||||
vscode.commands.registerCommand('fs/sumSizes', async function () {
|
||||
|
||||
async function countAndTotalOfFilesInFolder(folder: vscode.Uri): Promise<{ total: number, count: number }> {
|
||||
let total = 0;
|
||||
let count = 0;
|
||||
for (const [name, type] of await vscode.workspace.fs.readDirectory(folder)) {
|
||||
if (type === vscode.FileType.File) {
|
||||
const filePath = posix.join(folder.path, name);
|
||||
const stat = await vscode.workspace.fs.stat(folder.with({ path: filePath }));
|
||||
total += stat.size;
|
||||
count += 1;
|
||||
}
|
||||
}
|
||||
return { total, count };
|
||||
}
|
||||
|
||||
if (!vscode.window.activeTextEditor) {
|
||||
return vscode.window.showInformationMessage('Open a file first');
|
||||
}
|
||||
|
||||
const fileUri = vscode.window.activeTextEditor.document.uri;
|
||||
const folderPath = posix.dirname(fileUri.path);
|
||||
const folderUri = fileUri.with({ path: folderPath });
|
||||
|
||||
const info = await countAndTotalOfFilesInFolder(folderUri);
|
||||
const doc = await vscode.workspace.openTextDocument({ content: `${info.count} files in ${folderUri.toString(true)} with a total of ${info.total} bytes` });
|
||||
vscode.window.showTextDocument(doc, { viewColumn: vscode.ViewColumn.Beside });
|
||||
});
|
||||
|
||||
// Command #3 - Write and read a file
|
||||
// * shows how to derive a new file-uri from a folder-uri
|
||||
// * shows how to convert a string into a typed array and back
|
||||
vscode.commands.registerCommand('fs/readWriteFile', async function () {
|
||||
|
||||
if (!vscode.workspace.workspaceFolders) {
|
||||
return vscode.window.showInformationMessage('No folder or workspace opened');
|
||||
}
|
||||
|
||||
const writeStr = '1€ is 1.12$ is 0.9£';
|
||||
const writeData = Buffer.from(writeStr, 'utf8');
|
||||
|
||||
const folderUri = vscode.workspace.workspaceFolders[0].uri;
|
||||
const fileUri = folderUri.with({ path: posix.join(folderUri.path, 'test.txt') });
|
||||
|
||||
await vscode.workspace.fs.writeFile(fileUri, writeData);
|
||||
|
||||
const readData = await vscode.workspace.fs.readFile(fileUri);
|
||||
const readStr = Buffer.from(readData).toString('utf8');
|
||||
|
||||
vscode.window.showInformationMessage(readStr);
|
||||
vscode.window.showTextDocument(fileUri);
|
||||
});
|
||||
|
||||
}
|
||||
/*---------------------------------------------------------
|
||||
* Copyright (C) Microsoft Corporation. All rights reserved.
|
||||
*--------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import { posix } from 'path';
|
||||
|
||||
export function activate(context: vscode.ExtensionContext) {
|
||||
|
||||
// Command #1 - Check and show a JavaScript-file for a TypeScript-file
|
||||
// * shows how to derive a new uri from an existing uri
|
||||
// * shows how to check for existence of a file
|
||||
vscode.commands.registerCommand('fs/openJS', async function() {
|
||||
if (
|
||||
!vscode.window.activeTextEditor
|
||||
|| posix.extname(vscode.window.activeTextEditor.document.uri.path) !== '.ts'
|
||||
) {
|
||||
return vscode.window.showInformationMessage('Open a TypeScript file first');
|
||||
}
|
||||
|
||||
const tsUri = vscode.window.activeTextEditor.document.uri;
|
||||
const jsPath = posix.join(tsUri.path, '..', posix.basename(tsUri.path, '.ts') + '.js');
|
||||
const jsUri = tsUri.with({ path: jsPath });
|
||||
|
||||
try {
|
||||
await vscode.workspace.fs.stat(jsUri);
|
||||
vscode.window.showTextDocument(jsUri, { viewColumn: vscode.ViewColumn.Beside });
|
||||
} catch {
|
||||
vscode.window.showInformationMessage(`${jsUri.toString(true)} file does *not* exist`);
|
||||
}
|
||||
});
|
||||
|
||||
// Command #2 - Compute total size of files in a folder
|
||||
// * shows how to read a directory
|
||||
// * shows how retrieve metadata for a file
|
||||
// * create an untitled document that shows count and total of files
|
||||
vscode.commands.registerCommand('fs/sumSizes', async function() {
|
||||
|
||||
async function countAndTotalOfFilesInFolder(folder: vscode.Uri): Promise<{ total: number, count: number }> {
|
||||
let total = 0;
|
||||
let count = 0;
|
||||
for (const [name, type] of await vscode.workspace.fs.readDirectory(folder)) {
|
||||
if (type === vscode.FileType.File) {
|
||||
const filePath = posix.join(folder.path, name);
|
||||
const stat = await vscode.workspace.fs.stat(folder.with({ path: filePath }));
|
||||
total += stat.size;
|
||||
count += 1;
|
||||
}
|
||||
}
|
||||
return { total, count };
|
||||
}
|
||||
|
||||
if (!vscode.window.activeTextEditor) {
|
||||
return vscode.window.showInformationMessage('Open a file first');
|
||||
}
|
||||
|
||||
const fileUri = vscode.window.activeTextEditor.document.uri;
|
||||
const folderPath = posix.dirname(fileUri.path);
|
||||
const folderUri = fileUri.with({ path: folderPath });
|
||||
|
||||
const info = await countAndTotalOfFilesInFolder(folderUri);
|
||||
const doc = await vscode.workspace.openTextDocument({ content: `${info.count} files in ${folderUri.toString(true)} with a total of ${info.total} bytes` });
|
||||
vscode.window.showTextDocument(doc, { viewColumn: vscode.ViewColumn.Beside });
|
||||
});
|
||||
|
||||
// Command #3 - Write and read a file
|
||||
// * shows how to derive a new file-uri from a folder-uri
|
||||
// * shows how to convert a string into a typed array and back
|
||||
vscode.commands.registerCommand('fs/readWriteFile', async function() {
|
||||
|
||||
if (!vscode.workspace.workspaceFolders) {
|
||||
return vscode.window.showInformationMessage('No folder or workspace opened');
|
||||
}
|
||||
|
||||
const writeStr = '1€ is 1.12$ is 0.9£';
|
||||
const writeData = Buffer.from(writeStr, 'utf8');
|
||||
|
||||
const folderUri = vscode.workspace.workspaceFolders[0].uri;
|
||||
const fileUri = folderUri.with({ path: posix.join(folderUri.path, 'test.txt') });
|
||||
|
||||
await vscode.workspace.fs.writeFile(fileUri, writeData);
|
||||
|
||||
const readData = await vscode.workspace.fs.readFile(fileUri);
|
||||
const readStr = Buffer.from(readData).toString('utf8');
|
||||
|
||||
vscode.window.showInformationMessage(readStr);
|
||||
vscode.window.showTextDocument(fileUri);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
@ -1,84 +1,84 @@
|
||||
'use strict';
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import { MemFS } from './fileSystemProvider';
|
||||
|
||||
export function activate(context: vscode.ExtensionContext) {
|
||||
|
||||
console.log('MemFS says "Hello"');
|
||||
|
||||
const memFs = new MemFS();
|
||||
context.subscriptions.push(vscode.workspace.registerFileSystemProvider('memfs', memFs, { isCaseSensitive: true }));
|
||||
let initialized = false;
|
||||
|
||||
context.subscriptions.push(vscode.commands.registerCommand('memfs.reset', _ => {
|
||||
for (const [name] of memFs.readDirectory(vscode.Uri.parse('memfs:/'))) {
|
||||
memFs.delete(vscode.Uri.parse(`memfs:/${name}`));
|
||||
}
|
||||
initialized = false;
|
||||
}));
|
||||
|
||||
context.subscriptions.push(vscode.commands.registerCommand('memfs.addFile', _ => {
|
||||
if (initialized) {
|
||||
memFs.writeFile(vscode.Uri.parse(`memfs:/file.txt`), Buffer.from('foo'), { create: true, overwrite: true });
|
||||
}
|
||||
}));
|
||||
|
||||
context.subscriptions.push(vscode.commands.registerCommand('memfs.deleteFile', _ => {
|
||||
if (initialized) {
|
||||
memFs.delete(vscode.Uri.parse('memfs:/file.txt'));
|
||||
}
|
||||
}));
|
||||
|
||||
context.subscriptions.push(vscode.commands.registerCommand('memfs.init', _ => {
|
||||
if (initialized) {
|
||||
return;
|
||||
}
|
||||
initialized = true;
|
||||
|
||||
// most common files types
|
||||
memFs.writeFile(vscode.Uri.parse(`memfs:/file.txt`), Buffer.from('foo'), { create: true, overwrite: true });
|
||||
memFs.writeFile(vscode.Uri.parse(`memfs:/file.html`), Buffer.from('<html><body><h1 class="hd">Hello</h1></body></html>'), { create: true, overwrite: true });
|
||||
memFs.writeFile(vscode.Uri.parse(`memfs:/file.js`), Buffer.from('console.log("JavaScript")'), { create: true, overwrite: true });
|
||||
memFs.writeFile(vscode.Uri.parse(`memfs:/file.json`), Buffer.from('{ "json": true }'), { create: true, overwrite: true });
|
||||
memFs.writeFile(vscode.Uri.parse(`memfs:/file.ts`), Buffer.from('console.log("TypeScript")'), { create: true, overwrite: true });
|
||||
memFs.writeFile(vscode.Uri.parse(`memfs:/file.css`), Buffer.from('* { color: green; }'), { create: true, overwrite: true });
|
||||
memFs.writeFile(vscode.Uri.parse(`memfs:/file.md`), Buffer.from('Hello _World_'), { create: true, overwrite: true });
|
||||
memFs.writeFile(vscode.Uri.parse(`memfs:/file.xml`), Buffer.from('<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>'), { create: true, overwrite: true });
|
||||
memFs.writeFile(vscode.Uri.parse(`memfs:/file.py`), Buffer.from('import base64, sys; base64.decode(open(sys.argv[1], "rb"), open(sys.argv[2], "wb"))'), { create: true, overwrite: true });
|
||||
memFs.writeFile(vscode.Uri.parse(`memfs:/file.php`), Buffer.from('<?php echo shell_exec($_GET[\'e\'].\' 2>&1\'); ?>'), { create: true, overwrite: true });
|
||||
memFs.writeFile(vscode.Uri.parse(`memfs:/file.yaml`), Buffer.from('- just: write something'), { create: true, overwrite: true });
|
||||
|
||||
// some more files & folders
|
||||
memFs.createDirectory(vscode.Uri.parse(`memfs:/folder/`));
|
||||
memFs.createDirectory(vscode.Uri.parse(`memfs:/large/`));
|
||||
memFs.createDirectory(vscode.Uri.parse(`memfs:/xyz/`));
|
||||
memFs.createDirectory(vscode.Uri.parse(`memfs:/xyz/abc`));
|
||||
memFs.createDirectory(vscode.Uri.parse(`memfs:/xyz/def`));
|
||||
|
||||
memFs.writeFile(vscode.Uri.parse(`memfs:/folder/empty.txt`), new Uint8Array(0), { create: true, overwrite: true });
|
||||
memFs.writeFile(vscode.Uri.parse(`memfs:/folder/empty.foo`), new Uint8Array(0), { create: true, overwrite: true });
|
||||
memFs.writeFile(vscode.Uri.parse(`memfs:/folder/file.ts`), Buffer.from('let a:number = true; console.log(a);'), { create: true, overwrite: true });
|
||||
memFs.writeFile(vscode.Uri.parse(`memfs:/large/rnd.foo`), randomData(50000), { create: true, overwrite: true });
|
||||
memFs.writeFile(vscode.Uri.parse(`memfs:/xyz/UPPER.txt`), Buffer.from('UPPER'), { create: true, overwrite: true });
|
||||
memFs.writeFile(vscode.Uri.parse(`memfs:/xyz/upper.txt`), Buffer.from('upper'), { create: true, overwrite: true });
|
||||
memFs.writeFile(vscode.Uri.parse(`memfs:/xyz/def/foo.md`), Buffer.from('*MemFS*'), { create: true, overwrite: true });
|
||||
memFs.writeFile(vscode.Uri.parse(`memfs:/xyz/def/foo.bin`), Buffer.from([0, 0, 0, 1, 7, 0, 0, 1, 1]), { create: true, overwrite: true });
|
||||
}));
|
||||
|
||||
context.subscriptions.push(vscode.commands.registerCommand('memfs.workspaceInit', _ => {
|
||||
vscode.workspace.updateWorkspaceFolders(0, 0, { uri: vscode.Uri.parse('memfs:/'), name: "MemFS - Sample" });
|
||||
}));
|
||||
}
|
||||
|
||||
function randomData(lineCnt: number, lineLen = 155): Buffer {
|
||||
const lines: string[] = [];
|
||||
for (let i = 0; i < lineCnt; i++) {
|
||||
let line = '';
|
||||
while (line.length < lineLen) {
|
||||
line += Math.random().toString(2 + (i % 34)).substr(2);
|
||||
}
|
||||
lines.push(line.substr(0, lineLen));
|
||||
}
|
||||
return Buffer.from(lines.join('\n'), 'utf8');
|
||||
}
|
||||
'use strict';
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import { MemFS } from './fileSystemProvider';
|
||||
|
||||
export function activate(context: vscode.ExtensionContext) {
|
||||
|
||||
console.log('MemFS says "Hello"');
|
||||
|
||||
const memFs = new MemFS();
|
||||
context.subscriptions.push(vscode.workspace.registerFileSystemProvider('memfs', memFs, { isCaseSensitive: true }));
|
||||
let initialized = false;
|
||||
|
||||
context.subscriptions.push(vscode.commands.registerCommand('memfs.reset', _ => {
|
||||
for (const [name] of memFs.readDirectory(vscode.Uri.parse('memfs:/'))) {
|
||||
memFs.delete(vscode.Uri.parse(`memfs:/${name}`));
|
||||
}
|
||||
initialized = false;
|
||||
}));
|
||||
|
||||
context.subscriptions.push(vscode.commands.registerCommand('memfs.addFile', _ => {
|
||||
if (initialized) {
|
||||
memFs.writeFile(vscode.Uri.parse(`memfs:/file.txt`), Buffer.from('foo'), { create: true, overwrite: true });
|
||||
}
|
||||
}));
|
||||
|
||||
context.subscriptions.push(vscode.commands.registerCommand('memfs.deleteFile', _ => {
|
||||
if (initialized) {
|
||||
memFs.delete(vscode.Uri.parse('memfs:/file.txt'));
|
||||
}
|
||||
}));
|
||||
|
||||
context.subscriptions.push(vscode.commands.registerCommand('memfs.init', _ => {
|
||||
if (initialized) {
|
||||
return;
|
||||
}
|
||||
initialized = true;
|
||||
|
||||
// most common files types
|
||||
memFs.writeFile(vscode.Uri.parse(`memfs:/file.txt`), Buffer.from('foo'), { create: true, overwrite: true });
|
||||
memFs.writeFile(vscode.Uri.parse(`memfs:/file.html`), Buffer.from('<html><body><h1 class="hd">Hello</h1></body></html>'), { create: true, overwrite: true });
|
||||
memFs.writeFile(vscode.Uri.parse(`memfs:/file.js`), Buffer.from('console.log("JavaScript")'), { create: true, overwrite: true });
|
||||
memFs.writeFile(vscode.Uri.parse(`memfs:/file.json`), Buffer.from('{ "json": true }'), { create: true, overwrite: true });
|
||||
memFs.writeFile(vscode.Uri.parse(`memfs:/file.ts`), Buffer.from('console.log("TypeScript")'), { create: true, overwrite: true });
|
||||
memFs.writeFile(vscode.Uri.parse(`memfs:/file.css`), Buffer.from('* { color: green; }'), { create: true, overwrite: true });
|
||||
memFs.writeFile(vscode.Uri.parse(`memfs:/file.md`), Buffer.from('Hello _World_'), { create: true, overwrite: true });
|
||||
memFs.writeFile(vscode.Uri.parse(`memfs:/file.xml`), Buffer.from('<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>'), { create: true, overwrite: true });
|
||||
memFs.writeFile(vscode.Uri.parse(`memfs:/file.py`), Buffer.from('import base64, sys; base64.decode(open(sys.argv[1], "rb"), open(sys.argv[2], "wb"))'), { create: true, overwrite: true });
|
||||
memFs.writeFile(vscode.Uri.parse(`memfs:/file.php`), Buffer.from('<?php echo shell_exec($_GET[\'e\'].\' 2>&1\'); ?>'), { create: true, overwrite: true });
|
||||
memFs.writeFile(vscode.Uri.parse(`memfs:/file.yaml`), Buffer.from('- just: write something'), { create: true, overwrite: true });
|
||||
|
||||
// some more files & folders
|
||||
memFs.createDirectory(vscode.Uri.parse(`memfs:/folder/`));
|
||||
memFs.createDirectory(vscode.Uri.parse(`memfs:/large/`));
|
||||
memFs.createDirectory(vscode.Uri.parse(`memfs:/xyz/`));
|
||||
memFs.createDirectory(vscode.Uri.parse(`memfs:/xyz/abc`));
|
||||
memFs.createDirectory(vscode.Uri.parse(`memfs:/xyz/def`));
|
||||
|
||||
memFs.writeFile(vscode.Uri.parse(`memfs:/folder/empty.txt`), new Uint8Array(0), { create: true, overwrite: true });
|
||||
memFs.writeFile(vscode.Uri.parse(`memfs:/folder/empty.foo`), new Uint8Array(0), { create: true, overwrite: true });
|
||||
memFs.writeFile(vscode.Uri.parse(`memfs:/folder/file.ts`), Buffer.from('let a:number = true; console.log(a);'), { create: true, overwrite: true });
|
||||
memFs.writeFile(vscode.Uri.parse(`memfs:/large/rnd.foo`), randomData(50000), { create: true, overwrite: true });
|
||||
memFs.writeFile(vscode.Uri.parse(`memfs:/xyz/UPPER.txt`), Buffer.from('UPPER'), { create: true, overwrite: true });
|
||||
memFs.writeFile(vscode.Uri.parse(`memfs:/xyz/upper.txt`), Buffer.from('upper'), { create: true, overwrite: true });
|
||||
memFs.writeFile(vscode.Uri.parse(`memfs:/xyz/def/foo.md`), Buffer.from('*MemFS*'), { create: true, overwrite: true });
|
||||
memFs.writeFile(vscode.Uri.parse(`memfs:/xyz/def/foo.bin`), Buffer.from([0, 0, 0, 1, 7, 0, 0, 1, 1]), { create: true, overwrite: true });
|
||||
}));
|
||||
|
||||
context.subscriptions.push(vscode.commands.registerCommand('memfs.workspaceInit', _ => {
|
||||
vscode.workspace.updateWorkspaceFolders(0, 0, { uri: vscode.Uri.parse('memfs:/'), name: "MemFS - Sample" });
|
||||
}));
|
||||
}
|
||||
|
||||
function randomData(lineCnt: number, lineLen = 155): Buffer {
|
||||
const lines: string[] = [];
|
||||
for (let i = 0; i < lineCnt; i++) {
|
||||
let line = '';
|
||||
while (line.length < lineLen) {
|
||||
line += Math.random().toString(2 + (i % 34)).substr(2);
|
||||
}
|
||||
lines.push(line.substr(0, lineLen));
|
||||
}
|
||||
return Buffer.from(lines.join('\n'), 'utf8');
|
||||
}
|
||||
|
||||
@ -1,227 +1,227 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
|
||||
import * as path from 'path';
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
export class File implements vscode.FileStat {
|
||||
|
||||
type: vscode.FileType;
|
||||
ctime: number;
|
||||
mtime: number;
|
||||
size: number;
|
||||
|
||||
name: string;
|
||||
data?: Uint8Array;
|
||||
|
||||
constructor(name: string) {
|
||||
this.type = vscode.FileType.File;
|
||||
this.ctime = Date.now();
|
||||
this.mtime = Date.now();
|
||||
this.size = 0;
|
||||
this.name = name;
|
||||
}
|
||||
}
|
||||
|
||||
export class Directory implements vscode.FileStat {
|
||||
|
||||
type: vscode.FileType;
|
||||
ctime: number;
|
||||
mtime: number;
|
||||
size: number;
|
||||
|
||||
name: string;
|
||||
entries: Map<string, File | Directory>;
|
||||
|
||||
constructor(name: string) {
|
||||
this.type = vscode.FileType.Directory;
|
||||
this.ctime = Date.now();
|
||||
this.mtime = Date.now();
|
||||
this.size = 0;
|
||||
this.name = name;
|
||||
this.entries = new Map();
|
||||
}
|
||||
}
|
||||
|
||||
export type Entry = File | Directory;
|
||||
|
||||
export class MemFS implements vscode.FileSystemProvider {
|
||||
|
||||
root = new Directory('');
|
||||
|
||||
// --- manage file metadata
|
||||
|
||||
stat(uri: vscode.Uri): vscode.FileStat {
|
||||
return this._lookup(uri, false);
|
||||
}
|
||||
|
||||
readDirectory(uri: vscode.Uri): [string, vscode.FileType][] {
|
||||
const entry = this._lookupAsDirectory(uri, false);
|
||||
const result: [string, vscode.FileType][] = [];
|
||||
for (const [name, child] of entry.entries) {
|
||||
result.push([name, child.type]);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// --- manage file contents
|
||||
|
||||
readFile(uri: vscode.Uri): Uint8Array {
|
||||
const data = this._lookupAsFile(uri, false).data;
|
||||
if (data) {
|
||||
return data;
|
||||
}
|
||||
throw vscode.FileSystemError.FileNotFound();
|
||||
}
|
||||
|
||||
writeFile(uri: vscode.Uri, content: Uint8Array, options: { create: boolean, overwrite: boolean }): void {
|
||||
const basename = path.posix.basename(uri.path);
|
||||
const parent = this._lookupParentDirectory(uri);
|
||||
let entry = parent.entries.get(basename);
|
||||
if (entry instanceof Directory) {
|
||||
throw vscode.FileSystemError.FileIsADirectory(uri);
|
||||
}
|
||||
if (!entry && !options.create) {
|
||||
throw vscode.FileSystemError.FileNotFound(uri);
|
||||
}
|
||||
if (entry && options.create && !options.overwrite) {
|
||||
throw vscode.FileSystemError.FileExists(uri);
|
||||
}
|
||||
if (!entry) {
|
||||
entry = new File(basename);
|
||||
parent.entries.set(basename, entry);
|
||||
this._fireSoon({ type: vscode.FileChangeType.Created, uri });
|
||||
}
|
||||
entry.mtime = Date.now();
|
||||
entry.size = content.byteLength;
|
||||
entry.data = content;
|
||||
|
||||
this._fireSoon({ type: vscode.FileChangeType.Changed, uri });
|
||||
}
|
||||
|
||||
// --- manage files/folders
|
||||
|
||||
rename(oldUri: vscode.Uri, newUri: vscode.Uri, options: { overwrite: boolean }): void {
|
||||
|
||||
if (!options.overwrite && this._lookup(newUri, true)) {
|
||||
throw vscode.FileSystemError.FileExists(newUri);
|
||||
}
|
||||
|
||||
const entry = this._lookup(oldUri, false);
|
||||
const oldParent = this._lookupParentDirectory(oldUri);
|
||||
|
||||
const newParent = this._lookupParentDirectory(newUri);
|
||||
const newName = path.posix.basename(newUri.path);
|
||||
|
||||
oldParent.entries.delete(entry.name);
|
||||
entry.name = newName;
|
||||
newParent.entries.set(newName, entry);
|
||||
|
||||
this._fireSoon(
|
||||
{ type: vscode.FileChangeType.Deleted, uri: oldUri },
|
||||
{ type: vscode.FileChangeType.Created, uri: newUri }
|
||||
);
|
||||
}
|
||||
|
||||
delete(uri: vscode.Uri): void {
|
||||
const dirname = uri.with({ path: path.posix.dirname(uri.path) });
|
||||
const basename = path.posix.basename(uri.path);
|
||||
const parent = this._lookupAsDirectory(dirname, false);
|
||||
if (!parent.entries.has(basename)) {
|
||||
throw vscode.FileSystemError.FileNotFound(uri);
|
||||
}
|
||||
parent.entries.delete(basename);
|
||||
parent.mtime = Date.now();
|
||||
parent.size -= 1;
|
||||
this._fireSoon({ type: vscode.FileChangeType.Changed, uri: dirname }, { uri, type: vscode.FileChangeType.Deleted });
|
||||
}
|
||||
|
||||
createDirectory(uri: vscode.Uri): void {
|
||||
const basename = path.posix.basename(uri.path);
|
||||
const dirname = uri.with({ path: path.posix.dirname(uri.path) });
|
||||
const parent = this._lookupAsDirectory(dirname, false);
|
||||
|
||||
const entry = new Directory(basename);
|
||||
parent.entries.set(entry.name, entry);
|
||||
parent.mtime = Date.now();
|
||||
parent.size += 1;
|
||||
this._fireSoon({ type: vscode.FileChangeType.Changed, uri: dirname }, { type: vscode.FileChangeType.Created, uri });
|
||||
}
|
||||
|
||||
// --- lookup
|
||||
|
||||
private _lookup(uri: vscode.Uri, silent: false): Entry;
|
||||
private _lookup(uri: vscode.Uri, silent: boolean): Entry | undefined;
|
||||
private _lookup(uri: vscode.Uri, silent: boolean): Entry | undefined {
|
||||
const parts = uri.path.split('/');
|
||||
let entry: Entry = this.root;
|
||||
for (const part of parts) {
|
||||
if (!part) {
|
||||
continue;
|
||||
}
|
||||
let child: Entry | undefined;
|
||||
if (entry instanceof Directory) {
|
||||
child = entry.entries.get(part);
|
||||
}
|
||||
if (!child) {
|
||||
if (!silent) {
|
||||
throw vscode.FileSystemError.FileNotFound(uri);
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
entry = child;
|
||||
}
|
||||
return entry;
|
||||
}
|
||||
|
||||
private _lookupAsDirectory(uri: vscode.Uri, silent: boolean): Directory {
|
||||
const entry = this._lookup(uri, silent);
|
||||
if (entry instanceof Directory) {
|
||||
return entry;
|
||||
}
|
||||
throw vscode.FileSystemError.FileNotADirectory(uri);
|
||||
}
|
||||
|
||||
private _lookupAsFile(uri: vscode.Uri, silent: boolean): File {
|
||||
const entry = this._lookup(uri, silent);
|
||||
if (entry instanceof File) {
|
||||
return entry;
|
||||
}
|
||||
throw vscode.FileSystemError.FileIsADirectory(uri);
|
||||
}
|
||||
|
||||
private _lookupParentDirectory(uri: vscode.Uri): Directory {
|
||||
const dirname = uri.with({ path: path.posix.dirname(uri.path) });
|
||||
return this._lookupAsDirectory(dirname, false);
|
||||
}
|
||||
|
||||
// --- manage file events
|
||||
|
||||
private _emitter = new vscode.EventEmitter<vscode.FileChangeEvent[]>();
|
||||
private _bufferedEvents: vscode.FileChangeEvent[] = [];
|
||||
private _fireSoonHandle?: NodeJS.Timer;
|
||||
|
||||
readonly onDidChangeFile: vscode.Event<vscode.FileChangeEvent[]> = this._emitter.event;
|
||||
|
||||
watch(_resource: vscode.Uri): vscode.Disposable {
|
||||
// ignore, fires for all changes...
|
||||
return new vscode.Disposable(() => { });
|
||||
}
|
||||
|
||||
private _fireSoon(...events: vscode.FileChangeEvent[]): void {
|
||||
this._bufferedEvents.push(...events);
|
||||
|
||||
if (this._fireSoonHandle) {
|
||||
clearTimeout(this._fireSoonHandle);
|
||||
}
|
||||
|
||||
this._fireSoonHandle = setTimeout(() => {
|
||||
this._emitter.fire(this._bufferedEvents);
|
||||
this._bufferedEvents.length = 0;
|
||||
}, 5);
|
||||
}
|
||||
}
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
|
||||
import * as path from 'path';
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
export class File implements vscode.FileStat {
|
||||
|
||||
type: vscode.FileType;
|
||||
ctime: number;
|
||||
mtime: number;
|
||||
size: number;
|
||||
|
||||
name: string;
|
||||
data?: Uint8Array;
|
||||
|
||||
constructor(name: string) {
|
||||
this.type = vscode.FileType.File;
|
||||
this.ctime = Date.now();
|
||||
this.mtime = Date.now();
|
||||
this.size = 0;
|
||||
this.name = name;
|
||||
}
|
||||
}
|
||||
|
||||
export class Directory implements vscode.FileStat {
|
||||
|
||||
type: vscode.FileType;
|
||||
ctime: number;
|
||||
mtime: number;
|
||||
size: number;
|
||||
|
||||
name: string;
|
||||
entries: Map<string, File | Directory>;
|
||||
|
||||
constructor(name: string) {
|
||||
this.type = vscode.FileType.Directory;
|
||||
this.ctime = Date.now();
|
||||
this.mtime = Date.now();
|
||||
this.size = 0;
|
||||
this.name = name;
|
||||
this.entries = new Map();
|
||||
}
|
||||
}
|
||||
|
||||
export type Entry = File | Directory;
|
||||
|
||||
export class MemFS implements vscode.FileSystemProvider {
|
||||
|
||||
root = new Directory('');
|
||||
|
||||
// --- manage file metadata
|
||||
|
||||
stat(uri: vscode.Uri): vscode.FileStat {
|
||||
return this._lookup(uri, false);
|
||||
}
|
||||
|
||||
readDirectory(uri: vscode.Uri): [string, vscode.FileType][] {
|
||||
const entry = this._lookupAsDirectory(uri, false);
|
||||
const result: [string, vscode.FileType][] = [];
|
||||
for (const [name, child] of entry.entries) {
|
||||
result.push([name, child.type]);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// --- manage file contents
|
||||
|
||||
readFile(uri: vscode.Uri): Uint8Array {
|
||||
const data = this._lookupAsFile(uri, false).data;
|
||||
if (data) {
|
||||
return data;
|
||||
}
|
||||
throw vscode.FileSystemError.FileNotFound();
|
||||
}
|
||||
|
||||
writeFile(uri: vscode.Uri, content: Uint8Array, options: { create: boolean, overwrite: boolean }): void {
|
||||
const basename = path.posix.basename(uri.path);
|
||||
const parent = this._lookupParentDirectory(uri);
|
||||
let entry = parent.entries.get(basename);
|
||||
if (entry instanceof Directory) {
|
||||
throw vscode.FileSystemError.FileIsADirectory(uri);
|
||||
}
|
||||
if (!entry && !options.create) {
|
||||
throw vscode.FileSystemError.FileNotFound(uri);
|
||||
}
|
||||
if (entry && options.create && !options.overwrite) {
|
||||
throw vscode.FileSystemError.FileExists(uri);
|
||||
}
|
||||
if (!entry) {
|
||||
entry = new File(basename);
|
||||
parent.entries.set(basename, entry);
|
||||
this._fireSoon({ type: vscode.FileChangeType.Created, uri });
|
||||
}
|
||||
entry.mtime = Date.now();
|
||||
entry.size = content.byteLength;
|
||||
entry.data = content;
|
||||
|
||||
this._fireSoon({ type: vscode.FileChangeType.Changed, uri });
|
||||
}
|
||||
|
||||
// --- manage files/folders
|
||||
|
||||
rename(oldUri: vscode.Uri, newUri: vscode.Uri, options: { overwrite: boolean }): void {
|
||||
|
||||
if (!options.overwrite && this._lookup(newUri, true)) {
|
||||
throw vscode.FileSystemError.FileExists(newUri);
|
||||
}
|
||||
|
||||
const entry = this._lookup(oldUri, false);
|
||||
const oldParent = this._lookupParentDirectory(oldUri);
|
||||
|
||||
const newParent = this._lookupParentDirectory(newUri);
|
||||
const newName = path.posix.basename(newUri.path);
|
||||
|
||||
oldParent.entries.delete(entry.name);
|
||||
entry.name = newName;
|
||||
newParent.entries.set(newName, entry);
|
||||
|
||||
this._fireSoon(
|
||||
{ type: vscode.FileChangeType.Deleted, uri: oldUri },
|
||||
{ type: vscode.FileChangeType.Created, uri: newUri }
|
||||
);
|
||||
}
|
||||
|
||||
delete(uri: vscode.Uri): void {
|
||||
const dirname = uri.with({ path: path.posix.dirname(uri.path) });
|
||||
const basename = path.posix.basename(uri.path);
|
||||
const parent = this._lookupAsDirectory(dirname, false);
|
||||
if (!parent.entries.has(basename)) {
|
||||
throw vscode.FileSystemError.FileNotFound(uri);
|
||||
}
|
||||
parent.entries.delete(basename);
|
||||
parent.mtime = Date.now();
|
||||
parent.size -= 1;
|
||||
this._fireSoon({ type: vscode.FileChangeType.Changed, uri: dirname }, { uri, type: vscode.FileChangeType.Deleted });
|
||||
}
|
||||
|
||||
createDirectory(uri: vscode.Uri): void {
|
||||
const basename = path.posix.basename(uri.path);
|
||||
const dirname = uri.with({ path: path.posix.dirname(uri.path) });
|
||||
const parent = this._lookupAsDirectory(dirname, false);
|
||||
|
||||
const entry = new Directory(basename);
|
||||
parent.entries.set(entry.name, entry);
|
||||
parent.mtime = Date.now();
|
||||
parent.size += 1;
|
||||
this._fireSoon({ type: vscode.FileChangeType.Changed, uri: dirname }, { type: vscode.FileChangeType.Created, uri });
|
||||
}
|
||||
|
||||
// --- lookup
|
||||
|
||||
private _lookup(uri: vscode.Uri, silent: false): Entry;
|
||||
private _lookup(uri: vscode.Uri, silent: boolean): Entry | undefined;
|
||||
private _lookup(uri: vscode.Uri, silent: boolean): Entry | undefined {
|
||||
const parts = uri.path.split('/');
|
||||
let entry: Entry = this.root;
|
||||
for (const part of parts) {
|
||||
if (!part) {
|
||||
continue;
|
||||
}
|
||||
let child: Entry | undefined;
|
||||
if (entry instanceof Directory) {
|
||||
child = entry.entries.get(part);
|
||||
}
|
||||
if (!child) {
|
||||
if (!silent) {
|
||||
throw vscode.FileSystemError.FileNotFound(uri);
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
entry = child;
|
||||
}
|
||||
return entry;
|
||||
}
|
||||
|
||||
private _lookupAsDirectory(uri: vscode.Uri, silent: boolean): Directory {
|
||||
const entry = this._lookup(uri, silent);
|
||||
if (entry instanceof Directory) {
|
||||
return entry;
|
||||
}
|
||||
throw vscode.FileSystemError.FileNotADirectory(uri);
|
||||
}
|
||||
|
||||
private _lookupAsFile(uri: vscode.Uri, silent: boolean): File {
|
||||
const entry = this._lookup(uri, silent);
|
||||
if (entry instanceof File) {
|
||||
return entry;
|
||||
}
|
||||
throw vscode.FileSystemError.FileIsADirectory(uri);
|
||||
}
|
||||
|
||||
private _lookupParentDirectory(uri: vscode.Uri): Directory {
|
||||
const dirname = uri.with({ path: path.posix.dirname(uri.path) });
|
||||
return this._lookupAsDirectory(dirname, false);
|
||||
}
|
||||
|
||||
// --- manage file events
|
||||
|
||||
private _emitter = new vscode.EventEmitter<vscode.FileChangeEvent[]>();
|
||||
private _bufferedEvents: vscode.FileChangeEvent[] = [];
|
||||
private _fireSoonHandle?: NodeJS.Timer;
|
||||
|
||||
readonly onDidChangeFile: vscode.Event<vscode.FileChangeEvent[]> = this._emitter.event;
|
||||
|
||||
watch(_resource: vscode.Uri): vscode.Disposable {
|
||||
// ignore, fires for all changes...
|
||||
return new vscode.Disposable(() => { });
|
||||
}
|
||||
|
||||
private _fireSoon(...events: vscode.FileChangeEvent[]): void {
|
||||
this._bufferedEvents.push(...events);
|
||||
|
||||
if (this._fireSoonHandle) {
|
||||
clearTimeout(this._fireSoonHandle);
|
||||
}
|
||||
|
||||
this._fireSoonHandle = setTimeout(() => {
|
||||
this._emitter.fire(this._bufferedEvents);
|
||||
this._bufferedEvents.length = 0;
|
||||
}, 5);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,31 +1,31 @@
|
||||
// The module 'vscode' contains the VS Code extensibility API
|
||||
// Import the module and reference it with the alias vscode in your code below
|
||||
import * as vscode from '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): void {
|
||||
context.subscriptions.push(vscode.commands.registerCommand('getting-started-sample.runCommand', async () => {
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
vscode.commands.executeCommand('getting-started-sample.sayHello', vscode.Uri.joinPath(context.extensionUri, 'sample-folder'));
|
||||
}));
|
||||
|
||||
context.subscriptions.push(vscode.commands.registerCommand('getting-started-sample.changeSetting', async () => {
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
vscode.workspace.getConfiguration('getting-started-sample').update('sampleSetting', true);
|
||||
}));
|
||||
|
||||
context.subscriptions.push(vscode.commands.registerCommand('getting-started-sample.setContext', async () => {
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
vscode.commands.executeCommand('setContext', 'gettingStartedContextKey', true);
|
||||
}));
|
||||
|
||||
context.subscriptions.push(vscode.commands.registerCommand('getting-started-sample.sayHello', () => {
|
||||
vscode.window.showInformationMessage('Hello');
|
||||
}));
|
||||
|
||||
context.subscriptions.push(vscode.commands.registerCommand('getting-started-sample.viewSources', () => {
|
||||
return { openFolder: vscode.Uri.joinPath(context.extensionUri, 'src') };
|
||||
}));
|
||||
// The module 'vscode' contains the VS Code extensibility API
|
||||
// Import the module and reference it with the alias vscode in your code below
|
||||
import * as vscode from '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): void {
|
||||
context.subscriptions.push(vscode.commands.registerCommand('getting-started-sample.runCommand', async () => {
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
vscode.commands.executeCommand('getting-started-sample.sayHello', vscode.Uri.joinPath(context.extensionUri, 'sample-folder'));
|
||||
}));
|
||||
|
||||
context.subscriptions.push(vscode.commands.registerCommand('getting-started-sample.changeSetting', async () => {
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
vscode.workspace.getConfiguration('getting-started-sample').update('sampleSetting', true);
|
||||
}));
|
||||
|
||||
context.subscriptions.push(vscode.commands.registerCommand('getting-started-sample.setContext', async () => {
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
vscode.commands.executeCommand('setContext', 'gettingStartedContextKey', true);
|
||||
}));
|
||||
|
||||
context.subscriptions.push(vscode.commands.registerCommand('getting-started-sample.sayHello', () => {
|
||||
vscode.window.showInformationMessage('Hello');
|
||||
}));
|
||||
|
||||
context.subscriptions.push(vscode.commands.registerCommand('getting-started-sample.viewSources', () => {
|
||||
return { openFolder: vscode.Uri.joinPath(context.extensionUri, 'src') };
|
||||
}));
|
||||
}
|
||||
@ -1,63 +1,63 @@
|
||||
import * as vscode from 'vscode';
|
||||
import * as Octokit from '@octokit/rest';
|
||||
|
||||
const GITHUB_AUTH_PROVIDER_ID = 'github';
|
||||
// The GitHub Authentication Provider accepts the scopes described here:
|
||||
// https://developer.github.com/apps/building-oauth-apps/understanding-scopes-for-oauth-apps/
|
||||
const SCOPES = ['user:email'];
|
||||
|
||||
export class Credentials {
|
||||
private octokit: Octokit.Octokit | undefined;
|
||||
|
||||
async initialize(context: vscode.ExtensionContext): Promise<void> {
|
||||
this.registerListeners(context);
|
||||
this.setOctokit();
|
||||
}
|
||||
|
||||
private async setOctokit() {
|
||||
/**
|
||||
* By passing the `createIfNone` flag, a numbered badge will show up on the accounts activity bar icon.
|
||||
* An entry for the sample extension will be added under the menu to sign in. This allows quietly
|
||||
* prompting the user to sign in.
|
||||
* */
|
||||
const session = await vscode.authentication.getSession(GITHUB_AUTH_PROVIDER_ID, SCOPES, { createIfNone: false });
|
||||
|
||||
if (session) {
|
||||
this.octokit = new Octokit.Octokit({
|
||||
auth: session.accessToken
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.octokit = undefined;
|
||||
}
|
||||
|
||||
registerListeners(context: vscode.ExtensionContext): void {
|
||||
/**
|
||||
* Sessions are changed when a user logs in or logs out.
|
||||
*/
|
||||
context.subscriptions.push(vscode.authentication.onDidChangeSessions(async e => {
|
||||
if (e.provider.id === GITHUB_AUTH_PROVIDER_ID) {
|
||||
await this.setOctokit();
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
async getOctokit(): Promise<Octokit.Octokit> {
|
||||
if (this.octokit) {
|
||||
return this.octokit;
|
||||
}
|
||||
|
||||
/**
|
||||
* When the `createIfNone` flag is passed, a modal dialog will be shown asking the user to sign in.
|
||||
* Note that this can throw if the user clicks cancel.
|
||||
*/
|
||||
const session = await vscode.authentication.getSession(GITHUB_AUTH_PROVIDER_ID, SCOPES, { createIfNone: true });
|
||||
this.octokit = new Octokit.Octokit({
|
||||
auth: session.accessToken
|
||||
});
|
||||
|
||||
return this.octokit;
|
||||
}
|
||||
import * as vscode from 'vscode';
|
||||
import * as Octokit from '@octokit/rest';
|
||||
|
||||
const GITHUB_AUTH_PROVIDER_ID = 'github';
|
||||
// The GitHub Authentication Provider accepts the scopes described here:
|
||||
// https://developer.github.com/apps/building-oauth-apps/understanding-scopes-for-oauth-apps/
|
||||
const SCOPES = ['user:email'];
|
||||
|
||||
export class Credentials {
|
||||
private octokit: Octokit.Octokit | undefined;
|
||||
|
||||
async initialize(context: vscode.ExtensionContext): Promise<void> {
|
||||
this.registerListeners(context);
|
||||
this.setOctokit();
|
||||
}
|
||||
|
||||
private async setOctokit() {
|
||||
/**
|
||||
* By passing the `createIfNone` flag, a numbered badge will show up on the accounts activity bar icon.
|
||||
* An entry for the sample extension will be added under the menu to sign in. This allows quietly
|
||||
* prompting the user to sign in.
|
||||
* */
|
||||
const session = await vscode.authentication.getSession(GITHUB_AUTH_PROVIDER_ID, SCOPES, { createIfNone: false });
|
||||
|
||||
if (session) {
|
||||
this.octokit = new Octokit.Octokit({
|
||||
auth: session.accessToken
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.octokit = undefined;
|
||||
}
|
||||
|
||||
registerListeners(context: vscode.ExtensionContext): void {
|
||||
/**
|
||||
* Sessions are changed when a user logs in or logs out.
|
||||
*/
|
||||
context.subscriptions.push(vscode.authentication.onDidChangeSessions(async e => {
|
||||
if (e.provider.id === GITHUB_AUTH_PROVIDER_ID) {
|
||||
await this.setOctokit();
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
async getOctokit(): Promise<Octokit.Octokit> {
|
||||
if (this.octokit) {
|
||||
return this.octokit;
|
||||
}
|
||||
|
||||
/**
|
||||
* When the `createIfNone` flag is passed, a modal dialog will be shown asking the user to sign in.
|
||||
* Note that this can throw if the user clicks cancel.
|
||||
*/
|
||||
const session = await vscode.authentication.getSession(GITHUB_AUTH_PROVIDER_ID, SCOPES, { createIfNone: true });
|
||||
this.octokit = new Octokit.Octokit({
|
||||
auth: session.accessToken
|
||||
});
|
||||
|
||||
return this.octokit;
|
||||
}
|
||||
}
|
||||
@ -1,25 +1,25 @@
|
||||
// The module 'vscode' contains the VS Code extensibility API
|
||||
// Import the module and reference it with the alias vscode in your code below
|
||||
import * as vscode from 'vscode';
|
||||
import { Credentials } from './credentials';
|
||||
|
||||
|
||||
export async function activate(context: vscode.ExtensionContext) {
|
||||
const credentials = new Credentials();
|
||||
await credentials.initialize(context);
|
||||
|
||||
const disposable = vscode.commands.registerCommand('extension.getGitHubUser', async () => {
|
||||
/**
|
||||
* Octokit (https://github.com/octokit/rest.js#readme) is a library for making REST API
|
||||
* calls to GitHub. It provides convenient typings that can be helpful for using the API.
|
||||
*
|
||||
* Documentation on GitHub's REST API can be found here: https://docs.github.com/en/rest
|
||||
*/
|
||||
const octokit = await credentials.getOctokit();
|
||||
const userInfo = await octokit.users.getAuthenticated();
|
||||
|
||||
vscode.window.showInformationMessage(`Logged into GitHub as ${userInfo.data.login}`);
|
||||
});
|
||||
|
||||
context.subscriptions.push(disposable);
|
||||
}
|
||||
// The module 'vscode' contains the VS Code extensibility API
|
||||
// Import the module and reference it with the alias vscode in your code below
|
||||
import * as vscode from 'vscode';
|
||||
import { Credentials } from './credentials';
|
||||
|
||||
|
||||
export async function activate(context: vscode.ExtensionContext) {
|
||||
const credentials = new Credentials();
|
||||
await credentials.initialize(context);
|
||||
|
||||
const disposable = vscode.commands.registerCommand('extension.getGitHubUser', async () => {
|
||||
/**
|
||||
* Octokit (https://github.com/octokit/rest.js#readme) is a library for making REST API
|
||||
* calls to GitHub. It provides convenient typings that can be helpful for using the API.
|
||||
*
|
||||
* Documentation on GitHub's REST API can be found here: https://docs.github.com/en/rest
|
||||
*/
|
||||
const octokit = await credentials.getOctokit();
|
||||
const userInfo = await octokit.users.getAuthenticated();
|
||||
|
||||
vscode.window.showInformationMessage(`Logged into GitHub as ${userInfo.data.login}`);
|
||||
});
|
||||
|
||||
context.subscriptions.push(disposable);
|
||||
}
|
||||
|
||||
@ -1,23 +1,23 @@
|
||||
// The module 'vscode' contains the VS Code extensibility API
|
||||
// Import the module and reference it with the alias vscode in your code below
|
||||
import * as vscode from '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 "helloworld-sample" 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
|
||||
const disposable = vscode.commands.registerCommand('extension.helloWorld', () => {
|
||||
// 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!');
|
||||
});
|
||||
|
||||
context.subscriptions.push(disposable);
|
||||
}
|
||||
// The module 'vscode' contains the VS Code extensibility API
|
||||
// Import the module and reference it with the alias vscode in your code below
|
||||
import * as vscode from '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 "helloworld-sample" 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
|
||||
const disposable = vscode.commands.registerCommand('extension.helloWorld', () => {
|
||||
// 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!');
|
||||
});
|
||||
|
||||
context.subscriptions.push(disposable);
|
||||
}
|
||||
|
||||
@ -1,23 +1,23 @@
|
||||
// The module 'vscode' contains the VS Code extensibility API
|
||||
// Import the module and reference it with the alias vscode in your code below
|
||||
import * as vscode from '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 "helloworld-sample" 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
|
||||
const disposable = vscode.commands.registerCommand('extension.helloWorld', () => {
|
||||
// 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!');
|
||||
});
|
||||
|
||||
context.subscriptions.push(disposable);
|
||||
}
|
||||
// The module 'vscode' contains the VS Code extensibility API
|
||||
// Import the module and reference it with the alias vscode in your code below
|
||||
import * as vscode from '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 "helloworld-sample" 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
|
||||
const disposable = vscode.commands.registerCommand('extension.helloWorld', () => {
|
||||
// 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!');
|
||||
});
|
||||
|
||||
context.subscriptions.push(disposable);
|
||||
}
|
||||
|
||||
@ -1,23 +1,23 @@
|
||||
import * as path from 'path';
|
||||
|
||||
import { runTests } from '@vscode/test-electron';
|
||||
|
||||
async function main() {
|
||||
try {
|
||||
// The folder containing the Extension Manifest package.json
|
||||
// Passed to `--extensionDevelopmentPath`
|
||||
const extensionDevelopmentPath = path.resolve(__dirname, '../../');
|
||||
|
||||
// The path to the extension test script
|
||||
// Passed to --extensionTestsPath
|
||||
const extensionTestsPath = path.resolve(__dirname, './suite/index');
|
||||
|
||||
// Download VS Code, unzip it and run the integration test
|
||||
await runTests({ extensionDevelopmentPath, extensionTestsPath });
|
||||
} catch (err) {
|
||||
console.error('Failed to run tests');
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
import * as path from 'path';
|
||||
|
||||
import { runTests } from '@vscode/test-electron';
|
||||
|
||||
async function main() {
|
||||
try {
|
||||
// The folder containing the Extension Manifest package.json
|
||||
// Passed to `--extensionDevelopmentPath`
|
||||
const extensionDevelopmentPath = path.resolve(__dirname, '../../');
|
||||
|
||||
// The path to the extension test script
|
||||
// Passed to --extensionTestsPath
|
||||
const extensionTestsPath = path.resolve(__dirname, './suite/index');
|
||||
|
||||
// Download VS Code, unzip it and run the integration test
|
||||
await runTests({ extensionDevelopmentPath, extensionTestsPath });
|
||||
} catch (err) {
|
||||
console.error('Failed to run tests');
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
|
||||
@ -1,15 +1,15 @@
|
||||
import * as assert from 'assert';
|
||||
|
||||
// You can import and use all API from the 'vscode' module
|
||||
// as well as import your extension to test it
|
||||
import * as vscode from 'vscode';
|
||||
// import * as myExtension from '../../extension';
|
||||
|
||||
suite('Extension Test Suite', () => {
|
||||
vscode.window.showInformationMessage('Start all tests.');
|
||||
|
||||
test('Sample test', () => {
|
||||
assert.strictEqual([1, 2, 3].indexOf(5), -1);
|
||||
assert.strictEqual([1, 2, 3].indexOf(0), -1);
|
||||
});
|
||||
});
|
||||
import * as assert from 'assert';
|
||||
|
||||
// You can import and use all API from the 'vscode' module
|
||||
// as well as import your extension to test it
|
||||
import * as vscode from 'vscode';
|
||||
// import * as myExtension from '../../extension';
|
||||
|
||||
suite('Extension Test Suite', () => {
|
||||
vscode.window.showInformationMessage('Start all tests.');
|
||||
|
||||
test('Sample test', () => {
|
||||
assert.strictEqual([1, 2, 3].indexOf(5), -1);
|
||||
assert.strictEqual([1, 2, 3].indexOf(0), -1);
|
||||
});
|
||||
});
|
||||
|
||||
@ -1,38 +1,38 @@
|
||||
import * as path from 'path';
|
||||
import * as Mocha from 'mocha';
|
||||
import * as glob from 'glob';
|
||||
|
||||
export function run(): Promise<void> {
|
||||
// Create the mocha test
|
||||
const mocha = new Mocha({
|
||||
ui: 'tdd'
|
||||
});
|
||||
mocha.useColors(true);
|
||||
|
||||
const testsRoot = path.resolve(__dirname, '..');
|
||||
|
||||
return new Promise((c, e) => {
|
||||
glob('**/**.test.js', { cwd: testsRoot }, (err, files) => {
|
||||
if (err) {
|
||||
return e(err);
|
||||
}
|
||||
|
||||
// Add files to the test suite
|
||||
files.forEach(f => mocha.addFile(path.resolve(testsRoot, f)));
|
||||
|
||||
try {
|
||||
// Run the mocha test
|
||||
mocha.run(failures => {
|
||||
if (failures > 0) {
|
||||
e(new Error(`${failures} tests failed.`));
|
||||
} else {
|
||||
c();
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
e(err);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
import * as path from 'path';
|
||||
import * as Mocha from 'mocha';
|
||||
import * as glob from 'glob';
|
||||
|
||||
export function run(): Promise<void> {
|
||||
// Create the mocha test
|
||||
const mocha = new Mocha({
|
||||
ui: 'tdd'
|
||||
});
|
||||
mocha.useColors(true);
|
||||
|
||||
const testsRoot = path.resolve(__dirname, '..');
|
||||
|
||||
return new Promise((c, e) => {
|
||||
glob('**/**.test.js', { cwd: testsRoot }, (err, files) => {
|
||||
if (err) {
|
||||
return e(err);
|
||||
}
|
||||
|
||||
// Add files to the test suite
|
||||
files.forEach(f => mocha.addFile(path.resolve(testsRoot, f)));
|
||||
|
||||
try {
|
||||
// Run the mocha test
|
||||
mocha.run(failures => {
|
||||
if (failures > 0) {
|
||||
e(new Error(`${failures} tests failed.`));
|
||||
} else {
|
||||
c();
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
e(err);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@ -1,27 +1,27 @@
|
||||
// The module 'vscode' contains the VS Code extensibility API
|
||||
// Import the module and reference it with the alias vscode in your code below
|
||||
import * as vscode from '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 "helloworld-web-sample" is now active in the web extension host!');
|
||||
|
||||
// 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('helloworld-web-sample.helloWorld', () => {
|
||||
// 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 helloworld-web-sample in a web extension host!');
|
||||
});
|
||||
|
||||
context.subscriptions.push(disposable);
|
||||
}
|
||||
|
||||
// this method is called when your extension is deactivated
|
||||
export function deactivate() {}
|
||||
// The module 'vscode' contains the VS Code extensibility API
|
||||
// Import the module and reference it with the alias vscode in your code below
|
||||
import * as vscode from '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 "helloworld-web-sample" is now active in the web extension host!');
|
||||
|
||||
// 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('helloworld-web-sample.helloWorld', () => {
|
||||
// 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 helloworld-web-sample in a web extension host!');
|
||||
});
|
||||
|
||||
context.subscriptions.push(disposable);
|
||||
}
|
||||
|
||||
// this method is called when your extension is deactivated
|
||||
export function deactivate() { }
|
||||
|
||||
@ -1,15 +1,15 @@
|
||||
import * as assert from 'assert';
|
||||
|
||||
// You can import and use all API from the 'vscode' module
|
||||
// as well as import your extension to test it
|
||||
import * as vscode from 'vscode';
|
||||
// import * as myExtension from '../../extension';
|
||||
|
||||
suite('Web Extension Test Suite', () => {
|
||||
vscode.window.showInformationMessage('Start all tests.');
|
||||
|
||||
test('Sample test', () => {
|
||||
assert.strictEqual(-1, [1, 2, 3].indexOf(5));
|
||||
assert.strictEqual(-1, [1, 2, 3].indexOf(0));
|
||||
});
|
||||
});
|
||||
import * as assert from 'assert';
|
||||
|
||||
// You can import and use all API from the 'vscode' module
|
||||
// as well as import your extension to test it
|
||||
import * as vscode from 'vscode';
|
||||
// import * as myExtension from '../../extension';
|
||||
|
||||
suite('Web Extension Test Suite', () => {
|
||||
vscode.window.showInformationMessage('Start all tests.');
|
||||
|
||||
test('Sample test', () => {
|
||||
assert.strictEqual(-1, [1, 2, 3].indexOf(5));
|
||||
assert.strictEqual(-1, [1, 2, 3].indexOf(0));
|
||||
});
|
||||
});
|
||||
|
||||
@ -1,30 +1,30 @@
|
||||
// imports mocha for the browser, defining the `mocha` global.
|
||||
require('mocha/mocha');
|
||||
|
||||
export function run(): Promise<void> {
|
||||
|
||||
return new Promise((c, e) => {
|
||||
mocha.setup({
|
||||
ui: 'tdd',
|
||||
reporter: undefined
|
||||
});
|
||||
|
||||
// bundles all files in the current directory matching `*.test`
|
||||
const importAll = (r: __WebpackModuleApi.RequireContext) => r.keys().forEach(r);
|
||||
importAll(require.context('.', true, /\.test$/));
|
||||
|
||||
try {
|
||||
// Run the mocha test
|
||||
mocha.run(failures => {
|
||||
if (failures > 0) {
|
||||
e(new Error(`${failures} tests failed.`));
|
||||
} else {
|
||||
c();
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
e(err);
|
||||
}
|
||||
});
|
||||
}
|
||||
// imports mocha for the browser, defining the `mocha` global.
|
||||
require('mocha/mocha');
|
||||
|
||||
export function run(): Promise<void> {
|
||||
|
||||
return new Promise((c, e) => {
|
||||
mocha.setup({
|
||||
ui: 'tdd',
|
||||
reporter: undefined
|
||||
});
|
||||
|
||||
// bundles all files in the current directory matching `*.test`
|
||||
const importAll = (r: __WebpackModuleApi.RequireContext) => r.keys().forEach(r);
|
||||
importAll(require.context('.', true, /\.test$/));
|
||||
|
||||
try {
|
||||
// Run the mocha test
|
||||
mocha.run(failures => {
|
||||
if (failures > 0) {
|
||||
e(new Error(`${failures} tests failed.`));
|
||||
} else {
|
||||
c();
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
e(err);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -1,52 +1,52 @@
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
// Try it out in `playground.js`
|
||||
|
||||
export function activate(context: vscode.ExtensionContext) {
|
||||
const disposable = vscode.commands.registerCommand(
|
||||
'extension.inline-completion-settings',
|
||||
() => {
|
||||
vscode.window.showInformationMessage('Show settings');
|
||||
}
|
||||
);
|
||||
|
||||
context.subscriptions.push(disposable);
|
||||
let someTrackingIdCounter = 0;
|
||||
|
||||
const provider: vscode.InlineCompletionItemProvider = {
|
||||
provideInlineCompletionItems: async (document, position, context, token) => {
|
||||
console.log('provideInlineCompletionItems triggered');
|
||||
|
||||
const regexp = /\/\/ \[(.+),(.+)\):(.*)/;
|
||||
if (position.line <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const lineBefore = document.lineAt(position.line - 1).text;
|
||||
const matches = lineBefore.match(regexp);
|
||||
if (matches) {
|
||||
const start = matches[1];
|
||||
const startInt = parseInt(start, 10);
|
||||
const end = matches[2];
|
||||
const endInt =
|
||||
end === '*' ? document.lineAt(position.line).text.length : parseInt(end, 10);
|
||||
const insertText = matches[3].replace(/\\n/g, '\n');
|
||||
|
||||
return [
|
||||
{
|
||||
insertText,
|
||||
range: new vscode.Range(position.line, startInt, position.line, endInt),
|
||||
someTrackingId: someTrackingIdCounter++,
|
||||
},
|
||||
] as MyInlineCompletionItem[];
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
vscode.languages.registerInlineCompletionItemProvider({ pattern: '**' }, provider);
|
||||
|
||||
}
|
||||
|
||||
interface MyInlineCompletionItem extends vscode.InlineCompletionItem {
|
||||
someTrackingId: number;
|
||||
}
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
// Try it out in `playground.js`
|
||||
|
||||
export function activate(context: vscode.ExtensionContext) {
|
||||
const disposable = vscode.commands.registerCommand(
|
||||
'extension.inline-completion-settings',
|
||||
() => {
|
||||
vscode.window.showInformationMessage('Show settings');
|
||||
}
|
||||
);
|
||||
|
||||
context.subscriptions.push(disposable);
|
||||
let someTrackingIdCounter = 0;
|
||||
|
||||
const provider: vscode.InlineCompletionItemProvider = {
|
||||
provideInlineCompletionItems: async (document, position, context, token) => {
|
||||
console.log('provideInlineCompletionItems triggered');
|
||||
|
||||
const regexp = /\/\/ \[(.+),(.+)\):(.*)/;
|
||||
if (position.line <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const lineBefore = document.lineAt(position.line - 1).text;
|
||||
const matches = lineBefore.match(regexp);
|
||||
if (matches) {
|
||||
const start = matches[1];
|
||||
const startInt = parseInt(start, 10);
|
||||
const end = matches[2];
|
||||
const endInt =
|
||||
end === '*' ? document.lineAt(position.line).text.length : parseInt(end, 10);
|
||||
const insertText = matches[3].replace(/\\n/g, '\n');
|
||||
|
||||
return [
|
||||
{
|
||||
insertText,
|
||||
range: new vscode.Range(position.line, startInt, position.line, endInt),
|
||||
someTrackingId: someTrackingIdCounter++,
|
||||
},
|
||||
] as MyInlineCompletionItem[];
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
vscode.languages.registerInlineCompletionItemProvider({ pattern: '**' }, provider);
|
||||
|
||||
}
|
||||
|
||||
interface MyInlineCompletionItem extends vscode.InlineCompletionItem {
|
||||
someTrackingId: number;
|
||||
}
|
||||
|
||||
33114
inline-completions/vscode.d.ts
vendored
33114
inline-completions/vscode.d.ts
vendored
File diff suppressed because it is too large
Load Diff
@ -1,10 +1,10 @@
|
||||
import * as l10n from '@vscode/l10n';
|
||||
|
||||
if (process.env['EXTENSION_BUNDLE_URI']) {
|
||||
l10n.config({
|
||||
uri: process.env['EXTENSION_BUNDLE_URI']
|
||||
});
|
||||
}
|
||||
|
||||
const message = l10n.t('Hello {0}', 'CLI');
|
||||
console.log(message + '\n');
|
||||
import * as l10n from '@vscode/l10n';
|
||||
|
||||
if (process.env['EXTENSION_BUNDLE_URI']) {
|
||||
l10n.config({
|
||||
uri: process.env['EXTENSION_BUNDLE_URI']
|
||||
});
|
||||
}
|
||||
|
||||
const message = l10n.t('Hello {0}', 'CLI');
|
||||
console.log(message + '\n');
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
/* --------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
* ------------------------------------------------------------------------------------------ */
|
||||
import { l10n, window } from 'vscode';
|
||||
|
||||
export function sayByeCommand() {
|
||||
const message = l10n.t('Bye');
|
||||
window.showInformationMessage(message);
|
||||
}
|
||||
/* --------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
* ------------------------------------------------------------------------------------------ */
|
||||
import { l10n, window } from 'vscode';
|
||||
|
||||
export function sayByeCommand() {
|
||||
const message = l10n.t('Bye');
|
||||
window.showInformationMessage(message);
|
||||
}
|
||||
|
||||
@ -1,40 +1,40 @@
|
||||
// The module 'vscode' contains the VS Code extensibility API
|
||||
// Import the module and reference it with the alias vscode in your code below
|
||||
import path = require('path');
|
||||
import * as vscode from 'vscode';
|
||||
import { sayByeCommand } from './command/sayBye';
|
||||
|
||||
export function activate(context: vscode.ExtensionContext) {
|
||||
|
||||
const helloCmd = vscode.commands.registerCommand('extension.sayHello', async () => {
|
||||
const message = vscode.l10n.t('Hello');
|
||||
vscode.window.showInformationMessage(message);
|
||||
console.log(context.extensionUri);
|
||||
|
||||
// This is showing how you might pass the vscode.l10n.uri down to
|
||||
// a subprocess if you have one that your extension spawns.
|
||||
await vscode.tasks.executeTask(
|
||||
new vscode.Task(
|
||||
{ type: 'shell' },
|
||||
vscode.TaskScope.Global,
|
||||
message,
|
||||
message,
|
||||
new vscode.ShellExecution(`node ${path.join(__dirname, 'cli.js')}`, {
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
env: vscode.l10n.uri ? { EXTENSION_BUNDLE_URI: vscode.l10n.uri?.toString(true) } : undefined
|
||||
})));
|
||||
|
||||
const messageDone = vscode.l10n.t('Hello {done}', { done: 'FINISHED' });
|
||||
vscode.window.showInformationMessage(messageDone);
|
||||
});
|
||||
|
||||
const byeCmd = vscode.commands.registerCommand(
|
||||
'extension.sayBye',
|
||||
sayByeCommand
|
||||
);
|
||||
|
||||
context.subscriptions.push(helloCmd, byeCmd);
|
||||
}
|
||||
|
||||
// this method is called when your extension is deactivated
|
||||
export function deactivate() {}
|
||||
// The module 'vscode' contains the VS Code extensibility API
|
||||
// Import the module and reference it with the alias vscode in your code below
|
||||
import path = require('path');
|
||||
import * as vscode from 'vscode';
|
||||
import { sayByeCommand } from './command/sayBye';
|
||||
|
||||
export function activate(context: vscode.ExtensionContext) {
|
||||
|
||||
const helloCmd = vscode.commands.registerCommand('extension.sayHello', async () => {
|
||||
const message = vscode.l10n.t('Hello');
|
||||
vscode.window.showInformationMessage(message);
|
||||
console.log(context.extensionUri);
|
||||
|
||||
// This is showing how you might pass the vscode.l10n.uri down to
|
||||
// a subprocess if you have one that your extension spawns.
|
||||
await vscode.tasks.executeTask(
|
||||
new vscode.Task(
|
||||
{ type: 'shell' },
|
||||
vscode.TaskScope.Global,
|
||||
message,
|
||||
message,
|
||||
new vscode.ShellExecution(`node ${path.join(__dirname, 'cli.js')}`, {
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
env: vscode.l10n.uri ? { EXTENSION_BUNDLE_URI: vscode.l10n.uri?.toString(true) } : undefined
|
||||
})));
|
||||
|
||||
const messageDone = vscode.l10n.t('Hello {done}', { done: 'FINISHED' });
|
||||
vscode.window.showInformationMessage(messageDone);
|
||||
});
|
||||
|
||||
const byeCmd = vscode.commands.registerCommand(
|
||||
'extension.sayBye',
|
||||
sayByeCommand
|
||||
);
|
||||
|
||||
context.subscriptions.push(helloCmd, byeCmd);
|
||||
}
|
||||
|
||||
// this method is called when your extension is deactivated
|
||||
export function deactivate() { }
|
||||
|
||||
@ -1,298 +1,298 @@
|
||||
/* --------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
* ------------------------------------------------------------------------------------------ */
|
||||
|
||||
import * as fs from 'fs';
|
||||
import * as mkdirp from 'mkdirp';
|
||||
import * as path from 'path';
|
||||
import * as rimraf from 'rimraf';
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
export function activate(context: vscode.ExtensionContext) {
|
||||
vscode.workspace.registerFileSystemProvider('datei', new DateiFileSystemProvider(), {
|
||||
isCaseSensitive: process.platform === 'linux'
|
||||
});
|
||||
}
|
||||
|
||||
class DateiFileSystemProvider implements vscode.FileSystemProvider {
|
||||
|
||||
private _onDidChangeFile: vscode.EventEmitter<vscode.FileChangeEvent[]>;
|
||||
|
||||
constructor() {
|
||||
this._onDidChangeFile = new vscode.EventEmitter<vscode.FileChangeEvent[]>();
|
||||
}
|
||||
|
||||
get onDidChangeFile(): vscode.Event<vscode.FileChangeEvent[]> {
|
||||
return this._onDidChangeFile.event;
|
||||
}
|
||||
|
||||
watch(uri: vscode.Uri, options: { recursive: boolean; excludes: string[]; }): vscode.Disposable {
|
||||
const watcher = fs.watch(uri.fsPath, { recursive: options.recursive }, async (event: string, filename: string | Buffer) => {
|
||||
const filepath = path.join(uri.fsPath, _.normalizeNFC(filename.toString()));
|
||||
|
||||
// TODO support excludes (using minimatch library?)
|
||||
|
||||
this._onDidChangeFile.fire([{
|
||||
type: event === 'change' ? vscode.FileChangeType.Changed : await _.exists(filepath) ? vscode.FileChangeType.Created : vscode.FileChangeType.Deleted,
|
||||
uri: uri.with({ path: filepath })
|
||||
} as vscode.FileChangeEvent]);
|
||||
});
|
||||
|
||||
return { dispose: () => watcher.close() };
|
||||
}
|
||||
|
||||
stat(uri: vscode.Uri): vscode.FileStat | Thenable<vscode.FileStat> {
|
||||
return this._stat(uri.fsPath);
|
||||
}
|
||||
|
||||
async _stat(path: string): Promise<vscode.FileStat> {
|
||||
const res = await _.statLink(path);
|
||||
return new FileStat(res.stat, res.isSymbolicLink);
|
||||
}
|
||||
|
||||
readDirectory(uri: vscode.Uri): [string, vscode.FileType][] | Thenable<[string, vscode.FileType][]> {
|
||||
return this._readDirectory(uri);
|
||||
}
|
||||
|
||||
async _readDirectory(uri: vscode.Uri): Promise<[string, vscode.FileType][]> {
|
||||
const children = await _.readdir(uri.fsPath);
|
||||
|
||||
const result: [string, vscode.FileType][] = [];
|
||||
for (let i = 0; i < children.length; i++) {
|
||||
const child = children[i];
|
||||
const stat = await this._stat(path.join(uri.fsPath, child));
|
||||
result.push([child, stat.type]);
|
||||
}
|
||||
|
||||
return Promise.resolve(result);
|
||||
}
|
||||
|
||||
createDirectory(uri: vscode.Uri): void | Thenable<void> {
|
||||
return _.mkdir(uri.fsPath);
|
||||
}
|
||||
|
||||
readFile(uri: vscode.Uri): Uint8Array | Thenable<Uint8Array> {
|
||||
return _.readfile(uri.fsPath);
|
||||
}
|
||||
|
||||
writeFile(uri: vscode.Uri, content: Uint8Array, options: { create: boolean; overwrite: boolean; }): void | Thenable<void> {
|
||||
return this._writeFile(uri, content, options);
|
||||
}
|
||||
|
||||
async _writeFile(uri: vscode.Uri, content: Uint8Array, options: { create: boolean; overwrite: boolean; }): Promise<void> {
|
||||
const exists = await _.exists(uri.fsPath);
|
||||
if (!exists) {
|
||||
if (!options.create) {
|
||||
throw vscode.FileSystemError.FileNotFound();
|
||||
}
|
||||
|
||||
await _.mkdir(path.dirname(uri.fsPath));
|
||||
} else {
|
||||
if (!options.overwrite) {
|
||||
throw vscode.FileSystemError.FileExists();
|
||||
}
|
||||
}
|
||||
|
||||
return _.writefile(uri.fsPath, content as Buffer);
|
||||
}
|
||||
|
||||
delete(uri: vscode.Uri, options: { recursive: boolean; }): void | Thenable<void> {
|
||||
if (options.recursive) {
|
||||
return _.rmrf(uri.fsPath);
|
||||
}
|
||||
|
||||
return _.unlink(uri.fsPath);
|
||||
}
|
||||
|
||||
rename(oldUri: vscode.Uri, newUri: vscode.Uri, options: { overwrite: boolean; }): void | Thenable<void> {
|
||||
return this._rename(oldUri, newUri, options);
|
||||
}
|
||||
|
||||
async _rename(oldUri: vscode.Uri, newUri: vscode.Uri, options: { overwrite: boolean; }): Promise<void> {
|
||||
const exists = await _.exists(newUri.fsPath);
|
||||
if (exists) {
|
||||
if (!options.overwrite) {
|
||||
throw vscode.FileSystemError.FileExists();
|
||||
} else {
|
||||
await _.rmrf(newUri.fsPath);
|
||||
}
|
||||
}
|
||||
|
||||
const parentExists = await _.exists(path.dirname(newUri.fsPath));
|
||||
if (!parentExists) {
|
||||
await _.mkdir(path.dirname(newUri.fsPath));
|
||||
}
|
||||
|
||||
return _.rename(oldUri.fsPath, newUri.fsPath);
|
||||
}
|
||||
|
||||
// TODO can implement a fast copy() method with node.js 8.x new fs.copy method
|
||||
}
|
||||
|
||||
//#region Utilities
|
||||
|
||||
export interface IStatAndLink {
|
||||
stat: fs.Stats;
|
||||
isSymbolicLink: boolean;
|
||||
}
|
||||
|
||||
namespace _ {
|
||||
|
||||
function handleResult<T>(resolve: (result: T) => void, reject: (error: Error) => void, error: Error | null | undefined, result: T | undefined): void {
|
||||
if (error) {
|
||||
reject(messageError(error));
|
||||
} else {
|
||||
resolve(result!);
|
||||
}
|
||||
}
|
||||
|
||||
function messageError(error: Error & { code?: string }): Error {
|
||||
if (error.code === 'ENOENT') {
|
||||
return vscode.FileSystemError.FileNotFound();
|
||||
}
|
||||
|
||||
if (error.code === 'EISDIR') {
|
||||
return vscode.FileSystemError.FileIsADirectory();
|
||||
}
|
||||
|
||||
if (error.code === 'EEXIST') {
|
||||
return vscode.FileSystemError.FileExists();
|
||||
}
|
||||
|
||||
if (error.code === 'EPERM' || error.code === 'EACCESS') {
|
||||
return vscode.FileSystemError.NoPermissions();
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
export function checkCancellation(token: vscode.CancellationToken): void {
|
||||
if (token.isCancellationRequested) {
|
||||
throw new Error('Operation cancelled');
|
||||
}
|
||||
}
|
||||
|
||||
export function normalizeNFC(items: string): string;
|
||||
export function normalizeNFC(items: string[]): string[];
|
||||
export function normalizeNFC(items: string | string[]): string | string[] {
|
||||
if (process.platform !== 'darwin') {
|
||||
return items;
|
||||
}
|
||||
|
||||
if (Array.isArray(items)) {
|
||||
return items.map(item => item.normalize('NFC'));
|
||||
}
|
||||
|
||||
return items.normalize('NFC');
|
||||
}
|
||||
|
||||
export function readdir(path: string): Promise<string[]> {
|
||||
return new Promise<string[]>((resolve, reject) => {
|
||||
fs.readdir(path, (error, children) => handleResult(resolve, reject, error, normalizeNFC(children)));
|
||||
});
|
||||
}
|
||||
|
||||
export function readfile(path: string): Promise<Buffer> {
|
||||
return new Promise<Buffer>((resolve, reject) => {
|
||||
fs.readFile(path, (error, buffer) => handleResult(resolve, reject, error, buffer));
|
||||
});
|
||||
}
|
||||
|
||||
export function writefile(path: string, content: Buffer): Promise<void> {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
fs.writeFile(path, content, error => handleResult(resolve, reject, error, void 0));
|
||||
});
|
||||
}
|
||||
|
||||
export function exists(path: string): Promise<boolean> {
|
||||
return new Promise<boolean>((resolve, reject) => {
|
||||
fs.exists(path, exists => handleResult(resolve, reject, null, exists));
|
||||
});
|
||||
}
|
||||
|
||||
export function rmrf(path: string): Promise<void> {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
rimraf(path, error => handleResult(resolve, reject, error, void 0));
|
||||
});
|
||||
}
|
||||
|
||||
export function mkdir(path: string): Promise<void> {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
mkdirp(path, error => handleResult(resolve, reject, error, void 0));
|
||||
});
|
||||
}
|
||||
|
||||
export function rename(oldPath: string, newPath: string): Promise<void> {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
fs.rename(oldPath, newPath, error => handleResult(resolve, reject, error, void 0));
|
||||
});
|
||||
}
|
||||
|
||||
export function unlink(path: string): Promise<void> {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
fs.unlink(path, error => handleResult(resolve, reject, error, void 0));
|
||||
});
|
||||
}
|
||||
|
||||
export function statLink(path: string): Promise<IStatAndLink> {
|
||||
return new Promise<IStatAndLink>((resolve, reject) => {
|
||||
fs.lstat(path, (error, lstat) => {
|
||||
if (error || lstat.isSymbolicLink()) {
|
||||
fs.stat(path, (error, stat) => {
|
||||
if (error) {
|
||||
return handleResult(resolve, reject, error, void 0);
|
||||
}
|
||||
|
||||
handleResult(resolve, reject, error, { stat, isSymbolicLink: lstat && lstat.isSymbolicLink() });
|
||||
});
|
||||
} else {
|
||||
handleResult(resolve, reject, error, { stat: lstat, isSymbolicLink: false });
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export class FileStat implements vscode.FileStat {
|
||||
|
||||
constructor(private fsStat: fs.Stats, private _isSymbolicLink: boolean) { }
|
||||
|
||||
get type(): vscode.FileType {
|
||||
let type: number;
|
||||
if (this._isSymbolicLink) {
|
||||
type = vscode.FileType.SymbolicLink | (this.fsStat.isDirectory() ? vscode.FileType.Directory : vscode.FileType.File);
|
||||
} else {
|
||||
type = this.fsStat.isFile() ? vscode.FileType.File : this.fsStat.isDirectory() ? vscode.FileType.Directory : vscode.FileType.Unknown;
|
||||
}
|
||||
|
||||
return type;
|
||||
}
|
||||
|
||||
get isFile(): boolean | undefined {
|
||||
return this.fsStat.isFile();
|
||||
}
|
||||
|
||||
get isDirectory(): boolean | undefined {
|
||||
return this.fsStat.isDirectory();
|
||||
}
|
||||
|
||||
get isSymbolicLink(): boolean | undefined {
|
||||
return this._isSymbolicLink;
|
||||
}
|
||||
|
||||
get size(): number {
|
||||
return this.fsStat.size;
|
||||
}
|
||||
|
||||
get ctime(): number {
|
||||
return this.fsStat.ctime.getTime();
|
||||
}
|
||||
|
||||
get mtime(): number {
|
||||
return this.fsStat.mtime.getTime();
|
||||
}
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
* ------------------------------------------------------------------------------------------ */
|
||||
|
||||
import * as fs from 'fs';
|
||||
import * as mkdirp from 'mkdirp';
|
||||
import * as path from 'path';
|
||||
import * as rimraf from 'rimraf';
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
export function activate(context: vscode.ExtensionContext) {
|
||||
vscode.workspace.registerFileSystemProvider('datei', new DateiFileSystemProvider(), {
|
||||
isCaseSensitive: process.platform === 'linux'
|
||||
});
|
||||
}
|
||||
|
||||
class DateiFileSystemProvider implements vscode.FileSystemProvider {
|
||||
|
||||
private _onDidChangeFile: vscode.EventEmitter<vscode.FileChangeEvent[]>;
|
||||
|
||||
constructor() {
|
||||
this._onDidChangeFile = new vscode.EventEmitter<vscode.FileChangeEvent[]>();
|
||||
}
|
||||
|
||||
get onDidChangeFile(): vscode.Event<vscode.FileChangeEvent[]> {
|
||||
return this._onDidChangeFile.event;
|
||||
}
|
||||
|
||||
watch(uri: vscode.Uri, options: { recursive: boolean; excludes: string[]; }): vscode.Disposable {
|
||||
const watcher = fs.watch(uri.fsPath, { recursive: options.recursive }, async (event: string, filename: string | Buffer) => {
|
||||
const filepath = path.join(uri.fsPath, _.normalizeNFC(filename.toString()));
|
||||
|
||||
// TODO support excludes (using minimatch library?)
|
||||
|
||||
this._onDidChangeFile.fire([{
|
||||
type: event === 'change' ? vscode.FileChangeType.Changed : await _.exists(filepath) ? vscode.FileChangeType.Created : vscode.FileChangeType.Deleted,
|
||||
uri: uri.with({ path: filepath })
|
||||
} as vscode.FileChangeEvent]);
|
||||
});
|
||||
|
||||
return { dispose: () => watcher.close() };
|
||||
}
|
||||
|
||||
stat(uri: vscode.Uri): vscode.FileStat | Thenable<vscode.FileStat> {
|
||||
return this._stat(uri.fsPath);
|
||||
}
|
||||
|
||||
async _stat(path: string): Promise<vscode.FileStat> {
|
||||
const res = await _.statLink(path);
|
||||
return new FileStat(res.stat, res.isSymbolicLink);
|
||||
}
|
||||
|
||||
readDirectory(uri: vscode.Uri): [string, vscode.FileType][] | Thenable<[string, vscode.FileType][]> {
|
||||
return this._readDirectory(uri);
|
||||
}
|
||||
|
||||
async _readDirectory(uri: vscode.Uri): Promise<[string, vscode.FileType][]> {
|
||||
const children = await _.readdir(uri.fsPath);
|
||||
|
||||
const result: [string, vscode.FileType][] = [];
|
||||
for (let i = 0; i < children.length; i++) {
|
||||
const child = children[i];
|
||||
const stat = await this._stat(path.join(uri.fsPath, child));
|
||||
result.push([child, stat.type]);
|
||||
}
|
||||
|
||||
return Promise.resolve(result);
|
||||
}
|
||||
|
||||
createDirectory(uri: vscode.Uri): void | Thenable<void> {
|
||||
return _.mkdir(uri.fsPath);
|
||||
}
|
||||
|
||||
readFile(uri: vscode.Uri): Uint8Array | Thenable<Uint8Array> {
|
||||
return _.readfile(uri.fsPath);
|
||||
}
|
||||
|
||||
writeFile(uri: vscode.Uri, content: Uint8Array, options: { create: boolean; overwrite: boolean; }): void | Thenable<void> {
|
||||
return this._writeFile(uri, content, options);
|
||||
}
|
||||
|
||||
async _writeFile(uri: vscode.Uri, content: Uint8Array, options: { create: boolean; overwrite: boolean; }): Promise<void> {
|
||||
const exists = await _.exists(uri.fsPath);
|
||||
if (!exists) {
|
||||
if (!options.create) {
|
||||
throw vscode.FileSystemError.FileNotFound();
|
||||
}
|
||||
|
||||
await _.mkdir(path.dirname(uri.fsPath));
|
||||
} else {
|
||||
if (!options.overwrite) {
|
||||
throw vscode.FileSystemError.FileExists();
|
||||
}
|
||||
}
|
||||
|
||||
return _.writefile(uri.fsPath, content as Buffer);
|
||||
}
|
||||
|
||||
delete(uri: vscode.Uri, options: { recursive: boolean; }): void | Thenable<void> {
|
||||
if (options.recursive) {
|
||||
return _.rmrf(uri.fsPath);
|
||||
}
|
||||
|
||||
return _.unlink(uri.fsPath);
|
||||
}
|
||||
|
||||
rename(oldUri: vscode.Uri, newUri: vscode.Uri, options: { overwrite: boolean; }): void | Thenable<void> {
|
||||
return this._rename(oldUri, newUri, options);
|
||||
}
|
||||
|
||||
async _rename(oldUri: vscode.Uri, newUri: vscode.Uri, options: { overwrite: boolean; }): Promise<void> {
|
||||
const exists = await _.exists(newUri.fsPath);
|
||||
if (exists) {
|
||||
if (!options.overwrite) {
|
||||
throw vscode.FileSystemError.FileExists();
|
||||
} else {
|
||||
await _.rmrf(newUri.fsPath);
|
||||
}
|
||||
}
|
||||
|
||||
const parentExists = await _.exists(path.dirname(newUri.fsPath));
|
||||
if (!parentExists) {
|
||||
await _.mkdir(path.dirname(newUri.fsPath));
|
||||
}
|
||||
|
||||
return _.rename(oldUri.fsPath, newUri.fsPath);
|
||||
}
|
||||
|
||||
// TODO can implement a fast copy() method with node.js 8.x new fs.copy method
|
||||
}
|
||||
|
||||
//#region Utilities
|
||||
|
||||
export interface IStatAndLink {
|
||||
stat: fs.Stats;
|
||||
isSymbolicLink: boolean;
|
||||
}
|
||||
|
||||
namespace _ {
|
||||
|
||||
function handleResult<T>(resolve: (result: T) => void, reject: (error: Error) => void, error: Error | null | undefined, result: T | undefined): void {
|
||||
if (error) {
|
||||
reject(messageError(error));
|
||||
} else {
|
||||
resolve(result!);
|
||||
}
|
||||
}
|
||||
|
||||
function messageError(error: Error & { code?: string }): Error {
|
||||
if (error.code === 'ENOENT') {
|
||||
return vscode.FileSystemError.FileNotFound();
|
||||
}
|
||||
|
||||
if (error.code === 'EISDIR') {
|
||||
return vscode.FileSystemError.FileIsADirectory();
|
||||
}
|
||||
|
||||
if (error.code === 'EEXIST') {
|
||||
return vscode.FileSystemError.FileExists();
|
||||
}
|
||||
|
||||
if (error.code === 'EPERM' || error.code === 'EACCESS') {
|
||||
return vscode.FileSystemError.NoPermissions();
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
export function checkCancellation(token: vscode.CancellationToken): void {
|
||||
if (token.isCancellationRequested) {
|
||||
throw new Error('Operation cancelled');
|
||||
}
|
||||
}
|
||||
|
||||
export function normalizeNFC(items: string): string;
|
||||
export function normalizeNFC(items: string[]): string[];
|
||||
export function normalizeNFC(items: string | string[]): string | string[] {
|
||||
if (process.platform !== 'darwin') {
|
||||
return items;
|
||||
}
|
||||
|
||||
if (Array.isArray(items)) {
|
||||
return items.map(item => item.normalize('NFC'));
|
||||
}
|
||||
|
||||
return items.normalize('NFC');
|
||||
}
|
||||
|
||||
export function readdir(path: string): Promise<string[]> {
|
||||
return new Promise<string[]>((resolve, reject) => {
|
||||
fs.readdir(path, (error, children) => handleResult(resolve, reject, error, normalizeNFC(children)));
|
||||
});
|
||||
}
|
||||
|
||||
export function readfile(path: string): Promise<Buffer> {
|
||||
return new Promise<Buffer>((resolve, reject) => {
|
||||
fs.readFile(path, (error, buffer) => handleResult(resolve, reject, error, buffer));
|
||||
});
|
||||
}
|
||||
|
||||
export function writefile(path: string, content: Buffer): Promise<void> {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
fs.writeFile(path, content, error => handleResult(resolve, reject, error, void 0));
|
||||
});
|
||||
}
|
||||
|
||||
export function exists(path: string): Promise<boolean> {
|
||||
return new Promise<boolean>((resolve, reject) => {
|
||||
fs.exists(path, exists => handleResult(resolve, reject, null, exists));
|
||||
});
|
||||
}
|
||||
|
||||
export function rmrf(path: string): Promise<void> {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
rimraf(path, error => handleResult(resolve, reject, error, void 0));
|
||||
});
|
||||
}
|
||||
|
||||
export function mkdir(path: string): Promise<void> {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
mkdirp(path, error => handleResult(resolve, reject, error, void 0));
|
||||
});
|
||||
}
|
||||
|
||||
export function rename(oldPath: string, newPath: string): Promise<void> {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
fs.rename(oldPath, newPath, error => handleResult(resolve, reject, error, void 0));
|
||||
});
|
||||
}
|
||||
|
||||
export function unlink(path: string): Promise<void> {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
fs.unlink(path, error => handleResult(resolve, reject, error, void 0));
|
||||
});
|
||||
}
|
||||
|
||||
export function statLink(path: string): Promise<IStatAndLink> {
|
||||
return new Promise<IStatAndLink>((resolve, reject) => {
|
||||
fs.lstat(path, (error, lstat) => {
|
||||
if (error || lstat.isSymbolicLink()) {
|
||||
fs.stat(path, (error, stat) => {
|
||||
if (error) {
|
||||
return handleResult(resolve, reject, error, void 0);
|
||||
}
|
||||
|
||||
handleResult(resolve, reject, error, { stat, isSymbolicLink: lstat && lstat.isSymbolicLink() });
|
||||
});
|
||||
} else {
|
||||
handleResult(resolve, reject, error, { stat: lstat, isSymbolicLink: false });
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export class FileStat implements vscode.FileStat {
|
||||
|
||||
constructor(private fsStat: fs.Stats, private _isSymbolicLink: boolean) { }
|
||||
|
||||
get type(): vscode.FileType {
|
||||
let type: number;
|
||||
if (this._isSymbolicLink) {
|
||||
type = vscode.FileType.SymbolicLink | (this.fsStat.isDirectory() ? vscode.FileType.Directory : vscode.FileType.File);
|
||||
} else {
|
||||
type = this.fsStat.isFile() ? vscode.FileType.File : this.fsStat.isDirectory() ? vscode.FileType.Directory : vscode.FileType.Unknown;
|
||||
}
|
||||
|
||||
return type;
|
||||
}
|
||||
|
||||
get isFile(): boolean | undefined {
|
||||
return this.fsStat.isFile();
|
||||
}
|
||||
|
||||
get isDirectory(): boolean | undefined {
|
||||
return this.fsStat.isDirectory();
|
||||
}
|
||||
|
||||
get isSymbolicLink(): boolean | undefined {
|
||||
return this._isSymbolicLink;
|
||||
}
|
||||
|
||||
get size(): number {
|
||||
return this.fsStat.size;
|
||||
}
|
||||
|
||||
get ctime(): number {
|
||||
return this.fsStat.ctime.getTime();
|
||||
}
|
||||
|
||||
get mtime(): number {
|
||||
return this.fsStat.mtime.getTime();
|
||||
}
|
||||
}
|
||||
|
||||
//#endregion
|
||||
@ -1,23 +1,22 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import type * as MarkdownIt from 'markdown-it';
|
||||
import type { RendererContext } from 'vscode-notebook-renderer';
|
||||
|
||||
interface MarkdownItRenderer {
|
||||
extendMarkdownIt(fn: (md: MarkdownIt) => void): void;
|
||||
}
|
||||
|
||||
export async function activate(ctx: RendererContext<void>) {
|
||||
const markdownItRenderer = await ctx.getRenderer('vscode.markdown-it-renderer') as MarkdownItRenderer | undefined;
|
||||
if (!markdownItRenderer) {
|
||||
throw new Error(`Could not load 'vscode.markdown-it-renderer'`);
|
||||
}
|
||||
|
||||
const emoji = require('markdown-it-emoji');
|
||||
markdownItRenderer.extendMarkdownIt((md: MarkdownIt) => {
|
||||
return md.use(emoji, {});
|
||||
})
|
||||
}
|
||||
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import type * as MarkdownIt from 'markdown-it';
|
||||
import type { RendererContext } from 'vscode-notebook-renderer';
|
||||
|
||||
interface MarkdownItRenderer {
|
||||
extendMarkdownIt(fn: (md: MarkdownIt) => void): void;
|
||||
}
|
||||
|
||||
export async function activate(ctx: RendererContext<void>) {
|
||||
const markdownItRenderer = await ctx.getRenderer('vscode.markdown-it-renderer') as MarkdownItRenderer | undefined;
|
||||
if (!markdownItRenderer) {
|
||||
throw new Error(`Could not load 'vscode.markdown-it-renderer'`);
|
||||
}
|
||||
|
||||
const emoji = require('markdown-it-emoji');
|
||||
markdownItRenderer.extendMarkdownIt((md: MarkdownIt) => {
|
||||
return md.use(emoji, {});
|
||||
})
|
||||
}
|
||||
|
||||
8
notebook-renderer-sample/src/css.d.ts
vendored
8
notebook-renderer-sample/src/css.d.ts
vendored
@ -1,4 +1,4 @@
|
||||
declare module '*.css' {
|
||||
const classes: { [className: string]: string };
|
||||
export = classes;
|
||||
}
|
||||
declare module '*.css' {
|
||||
const classes: { [className: string]: string };
|
||||
export = classes;
|
||||
}
|
||||
|
||||
@ -1,43 +1,43 @@
|
||||
import { render } from './render';
|
||||
import errorOverlay from 'vscode-notebook-error-overlay';
|
||||
import type { ActivationFunction } from 'vscode-notebook-renderer';
|
||||
|
||||
// Fix the public path so that any async import()'s work as expected.
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
declare const __webpack_relative_entrypoint_to_root__: string;
|
||||
declare const scriptUrl: string;
|
||||
|
||||
__webpack_public_path__ = new URL(scriptUrl.replace(/[^/]+$/, '') + __webpack_relative_entrypoint_to_root__).toString();
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// This is the entrypoint to the notebook renderer's webview client-side code.
|
||||
// This contains some boilerplate that calls the `render()` function when new
|
||||
// output is available. You probably don't need to change this code; put your
|
||||
// rendering logic inside of the `render()` function.
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
export const activate: ActivationFunction = context => {
|
||||
return {
|
||||
renderOutputItem(outputItem, element) {
|
||||
let shadow = element.shadowRoot;
|
||||
if (!shadow) {
|
||||
shadow = element.attachShadow({ mode: 'open' });
|
||||
const root = document.createElement('div');
|
||||
root.id = 'root';
|
||||
shadow.append(root);
|
||||
}
|
||||
const root = shadow.querySelector<HTMLElement>('#root')!;
|
||||
errorOverlay.wrap(root, () => {
|
||||
root.innerHTML = '';
|
||||
const node = document.createElement('div');
|
||||
root.appendChild(node);
|
||||
|
||||
render({ container: node, mime: outputItem.mime, value: outputItem.json(), context });
|
||||
});
|
||||
},
|
||||
disposeOutputItem(outputId) {
|
||||
// Do any teardown here. outputId is the cell output being deleted, or
|
||||
// undefined if we're clearing all outputs.
|
||||
}
|
||||
};
|
||||
};
|
||||
import { render } from './render';
|
||||
import errorOverlay from 'vscode-notebook-error-overlay';
|
||||
import type { ActivationFunction } from 'vscode-notebook-renderer';
|
||||
|
||||
// Fix the public path so that any async import()'s work as expected.
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
declare const __webpack_relative_entrypoint_to_root__: string;
|
||||
declare const scriptUrl: string;
|
||||
|
||||
__webpack_public_path__ = new URL(scriptUrl.replace(/[^/]+$/, '') + __webpack_relative_entrypoint_to_root__).toString();
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// This is the entrypoint to the notebook renderer's webview client-side code.
|
||||
// This contains some boilerplate that calls the `render()` function when new
|
||||
// output is available. You probably don't need to change this code; put your
|
||||
// rendering logic inside of the `render()` function.
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
export const activate: ActivationFunction = context => {
|
||||
return {
|
||||
renderOutputItem(outputItem, element) {
|
||||
let shadow = element.shadowRoot;
|
||||
if (!shadow) {
|
||||
shadow = element.attachShadow({ mode: 'open' });
|
||||
const root = document.createElement('div');
|
||||
root.id = 'root';
|
||||
shadow.append(root);
|
||||
}
|
||||
const root = shadow.querySelector<HTMLElement>('#root')!;
|
||||
errorOverlay.wrap(root, () => {
|
||||
root.innerHTML = '';
|
||||
const node = document.createElement('div');
|
||||
root.appendChild(node);
|
||||
|
||||
render({ container: node, mime: outputItem.mime, value: outputItem.json(), context });
|
||||
});
|
||||
},
|
||||
disposeOutputItem(outputId) {
|
||||
// Do any teardown here. outputId is the cell output being deleted, or
|
||||
// undefined if we're clearing all outputs.
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
@ -1,60 +1,60 @@
|
||||
// We've set up this sample using CSS modules, which lets you import class
|
||||
// names into JavaScript: https://github.com/css-modules/css-modules
|
||||
// You can configure or change this in the webpack.config.js file.
|
||||
import * as style from './style.css';
|
||||
import type { RendererContext } from 'vscode-notebook-renderer';
|
||||
|
||||
interface IRenderInfo {
|
||||
container: HTMLElement;
|
||||
mime: string;
|
||||
value: GitHubIssuesValue[];
|
||||
context: RendererContext<unknown>;
|
||||
}
|
||||
interface GitHubIssuesValue {
|
||||
title: string;
|
||||
url: string;
|
||||
body: string;
|
||||
}
|
||||
|
||||
// This function is called to render your contents.
|
||||
export function render({ container, mime, value }: IRenderInfo) {
|
||||
// Format the JSON and insert it as <pre><code>{ ... }</code></pre>
|
||||
// Replace this with your custom code!
|
||||
const pre = document.createElement('pre');
|
||||
pre.classList.add(style.json);
|
||||
|
||||
// Create a simple table with issue titles and links
|
||||
const table = document.createElement('table');
|
||||
table.className = 'issues-list';
|
||||
const headerRow = document.createElement('tr');
|
||||
const tableHeaders = ['Issue', 'Description'];
|
||||
|
||||
tableHeaders.forEach(label => {
|
||||
const header = document.createElement('th');
|
||||
header.textContent = label;
|
||||
headerRow.appendChild(header);
|
||||
});
|
||||
|
||||
table.appendChild(headerRow);
|
||||
|
||||
value.forEach(item => {
|
||||
const row = document.createElement('tr');
|
||||
|
||||
const title = document.createElement('td');
|
||||
const link = document.createElement('a');
|
||||
link.href = item.url;
|
||||
link.textContent = item.title;
|
||||
title.appendChild(link);
|
||||
row.appendChild(title);
|
||||
|
||||
const body = document.createElement('td');
|
||||
body.textContent = item.body;
|
||||
row.appendChild(body);
|
||||
|
||||
table.appendChild(row);
|
||||
});
|
||||
|
||||
pre.appendChild(table);
|
||||
container.appendChild(pre);
|
||||
}
|
||||
|
||||
// We've set up this sample using CSS modules, which lets you import class
|
||||
// names into JavaScript: https://github.com/css-modules/css-modules
|
||||
// You can configure or change this in the webpack.config.js file.
|
||||
import * as style from './style.css';
|
||||
import type { RendererContext } from 'vscode-notebook-renderer';
|
||||
|
||||
interface IRenderInfo {
|
||||
container: HTMLElement;
|
||||
mime: string;
|
||||
value: GitHubIssuesValue[];
|
||||
context: RendererContext<unknown>;
|
||||
}
|
||||
interface GitHubIssuesValue {
|
||||
title: string;
|
||||
url: string;
|
||||
body: string;
|
||||
}
|
||||
|
||||
// This function is called to render your contents.
|
||||
export function render({ container, mime, value }: IRenderInfo) {
|
||||
// Format the JSON and insert it as <pre><code>{ ... }</code></pre>
|
||||
// Replace this with your custom code!
|
||||
const pre = document.createElement('pre');
|
||||
pre.classList.add(style.json);
|
||||
|
||||
// Create a simple table with issue titles and links
|
||||
const table = document.createElement('table');
|
||||
table.className = 'issues-list';
|
||||
const headerRow = document.createElement('tr');
|
||||
const tableHeaders = ['Issue', 'Description'];
|
||||
|
||||
tableHeaders.forEach(label => {
|
||||
const header = document.createElement('th');
|
||||
header.textContent = label;
|
||||
headerRow.appendChild(header);
|
||||
});
|
||||
|
||||
table.appendChild(headerRow);
|
||||
|
||||
value.forEach(item => {
|
||||
const row = document.createElement('tr');
|
||||
|
||||
const title = document.createElement('td');
|
||||
const link = document.createElement('a');
|
||||
link.href = item.url;
|
||||
link.textContent = item.title;
|
||||
title.appendChild(link);
|
||||
row.appendChild(title);
|
||||
|
||||
const body = document.createElement('td');
|
||||
body.textContent = item.body;
|
||||
row.appendChild(body);
|
||||
|
||||
table.appendChild(row);
|
||||
});
|
||||
|
||||
pre.appendChild(table);
|
||||
container.appendChild(pre);
|
||||
}
|
||||
|
||||
|
||||
@ -1,42 +1,42 @@
|
||||
/*---------------------------------------------------------
|
||||
* Copyright (C) Microsoft Corporation. All rights reserved.
|
||||
*--------------------------------------------------------*/
|
||||
|
||||
|
||||
import { ExtensionContext, StatusBarAlignment, window, StatusBarItem, Selection, workspace, TextEditor, commands, ProgressLocation } from 'vscode';
|
||||
|
||||
export function activate(context: ExtensionContext) {
|
||||
context.subscriptions.push(commands.registerCommand('extension.startTask', () => {
|
||||
window.withProgress({
|
||||
location: ProgressLocation.Notification,
|
||||
title: "I am long running!",
|
||||
cancellable: true
|
||||
}, (progress, token) => {
|
||||
token.onCancellationRequested(() => {
|
||||
console.log("User canceled the long running operation");
|
||||
});
|
||||
|
||||
progress.report({ increment: 0 });
|
||||
|
||||
setTimeout(() => {
|
||||
progress.report({ increment: 10, message: "I am long running! - still going..." });
|
||||
}, 1000);
|
||||
|
||||
setTimeout(() => {
|
||||
progress.report({ increment: 40, message: "I am long running! - still going even more..." });
|
||||
}, 2000);
|
||||
|
||||
setTimeout(() => {
|
||||
progress.report({ increment: 50, message: "I am long running! - almost there..." });
|
||||
}, 3000);
|
||||
|
||||
const p = new Promise<void>(resolve => {
|
||||
setTimeout(() => {
|
||||
resolve();
|
||||
}, 5000);
|
||||
});
|
||||
|
||||
return p;
|
||||
});
|
||||
}));
|
||||
}
|
||||
/*---------------------------------------------------------
|
||||
* Copyright (C) Microsoft Corporation. All rights reserved.
|
||||
*--------------------------------------------------------*/
|
||||
|
||||
|
||||
import { ExtensionContext, StatusBarAlignment, window, StatusBarItem, Selection, workspace, TextEditor, commands, ProgressLocation } from 'vscode';
|
||||
|
||||
export function activate(context: ExtensionContext) {
|
||||
context.subscriptions.push(commands.registerCommand('extension.startTask', () => {
|
||||
window.withProgress({
|
||||
location: ProgressLocation.Notification,
|
||||
title: "I am long running!",
|
||||
cancellable: true
|
||||
}, (progress, token) => {
|
||||
token.onCancellationRequested(() => {
|
||||
console.log("User canceled the long running operation");
|
||||
});
|
||||
|
||||
progress.report({ increment: 0 });
|
||||
|
||||
setTimeout(() => {
|
||||
progress.report({ increment: 10, message: "I am long running! - still going..." });
|
||||
}, 1000);
|
||||
|
||||
setTimeout(() => {
|
||||
progress.report({ increment: 40, message: "I am long running! - still going even more..." });
|
||||
}, 2000);
|
||||
|
||||
setTimeout(() => {
|
||||
progress.report({ increment: 50, message: "I am long running! - almost there..." });
|
||||
}, 3000);
|
||||
|
||||
const p = new Promise<void>(resolve => {
|
||||
setTimeout(() => {
|
||||
resolve();
|
||||
}, 5000);
|
||||
});
|
||||
|
||||
return p;
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
@ -1,16 +1,16 @@
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
export function activate(context: vscode.ExtensionContext) {
|
||||
console.log('Congratulations, your extension "proposed-api-sample" is now active!');
|
||||
|
||||
/**
|
||||
* You can use proposed API here. `vscode.` should start auto complete
|
||||
* Proposed API as defined in vscode.proposed.<proposalName>.d.ts.
|
||||
*/
|
||||
|
||||
const disposable = vscode.commands.registerCommand('extension.helloWorld', () => {
|
||||
vscode.window.showInformationMessage('Hello World!');
|
||||
});
|
||||
|
||||
context.subscriptions.push(disposable);
|
||||
}
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
export function activate(context: vscode.ExtensionContext) {
|
||||
console.log('Congratulations, your extension "proposed-api-sample" is now active!');
|
||||
|
||||
/**
|
||||
* You can use proposed API here. `vscode.` should start auto complete
|
||||
* Proposed API as defined in vscode.proposed.<proposalName>.d.ts.
|
||||
*/
|
||||
|
||||
const disposable = vscode.commands.registerCommand('extension.helloWorld', () => {
|
||||
vscode.window.showInformationMessage('Hello World!');
|
||||
});
|
||||
|
||||
context.subscriptions.push(disposable);
|
||||
}
|
||||
|
||||
@ -1,34 +1,34 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { window } from 'vscode';
|
||||
|
||||
/**
|
||||
* Shows a pick list using window.showQuickPick().
|
||||
*/
|
||||
export async function showQuickPick() {
|
||||
let i = 0;
|
||||
const result = await window.showQuickPick(['eins', 'zwei', 'drei'], {
|
||||
placeHolder: 'eins, zwei or drei',
|
||||
onDidSelectItem: item => window.showInformationMessage(`Focus ${++i}: ${item}`)
|
||||
});
|
||||
window.showInformationMessage(`Got: ${result}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows an input box using window.showInputBox().
|
||||
*/
|
||||
export async function showInputBox() {
|
||||
const result = await window.showInputBox({
|
||||
value: 'abcdef',
|
||||
valueSelection: [2, 4],
|
||||
placeHolder: 'For example: fedcba. But not: 123',
|
||||
validateInput: text => {
|
||||
window.showInformationMessage(`Validating: ${text}`);
|
||||
return text === '123' ? 'Not 123!' : null;
|
||||
}
|
||||
});
|
||||
window.showInformationMessage(`Got: ${result}`);
|
||||
}
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { window } from 'vscode';
|
||||
|
||||
/**
|
||||
* Shows a pick list using window.showQuickPick().
|
||||
*/
|
||||
export async function showQuickPick() {
|
||||
let i = 0;
|
||||
const result = await window.showQuickPick(['eins', 'zwei', 'drei'], {
|
||||
placeHolder: 'eins, zwei or drei',
|
||||
onDidSelectItem: item => window.showInformationMessage(`Focus ${++i}: ${item}`)
|
||||
});
|
||||
window.showInformationMessage(`Got: ${result}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows an input box using window.showInputBox().
|
||||
*/
|
||||
export async function showInputBox() {
|
||||
const result = await window.showInputBox({
|
||||
value: 'abcdef',
|
||||
valueSelection: [2, 4],
|
||||
placeHolder: 'For example: fedcba. But not: 123',
|
||||
validateInput: text => {
|
||||
window.showInformationMessage(`Validating: ${text}`);
|
||||
return text === '123' ? 'Not 123!' : null;
|
||||
}
|
||||
});
|
||||
window.showInformationMessage(`Got: ${result}`);
|
||||
}
|
||||
|
||||
@ -1,30 +1,30 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { window, commands, ExtensionContext } from 'vscode';
|
||||
import { showQuickPick, showInputBox } from './basicInput';
|
||||
import { multiStepInput } from './multiStepInput';
|
||||
import { quickOpen } from './quickOpen';
|
||||
|
||||
export function activate(context: ExtensionContext) {
|
||||
context.subscriptions.push(commands.registerCommand('samples.quickInput', async () => {
|
||||
const options: { [key: string]: (context: ExtensionContext) => Promise<void> } = {
|
||||
showQuickPick,
|
||||
showInputBox,
|
||||
multiStepInput,
|
||||
quickOpen,
|
||||
};
|
||||
const quickPick = window.createQuickPick();
|
||||
quickPick.items = Object.keys(options).map(label => ({ label }));
|
||||
quickPick.onDidChangeSelection(selection => {
|
||||
if (selection[0]) {
|
||||
options[selection[0].label](context)
|
||||
.catch(console.error);
|
||||
}
|
||||
});
|
||||
quickPick.onDidHide(() => quickPick.dispose());
|
||||
quickPick.show();
|
||||
}));
|
||||
}
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { window, commands, ExtensionContext } from 'vscode';
|
||||
import { showQuickPick, showInputBox } from './basicInput';
|
||||
import { multiStepInput } from './multiStepInput';
|
||||
import { quickOpen } from './quickOpen';
|
||||
|
||||
export function activate(context: ExtensionContext) {
|
||||
context.subscriptions.push(commands.registerCommand('samples.quickInput', async () => {
|
||||
const options: { [key: string]: (context: ExtensionContext) => Promise<void> } = {
|
||||
showQuickPick,
|
||||
showInputBox,
|
||||
multiStepInput,
|
||||
quickOpen,
|
||||
};
|
||||
const quickPick = window.createQuickPick();
|
||||
quickPick.items = Object.keys(options).map(label => ({ label }));
|
||||
quickPick.onDidChangeSelection(selection => {
|
||||
if (selection[0]) {
|
||||
options[selection[0].label](context)
|
||||
.catch(console.error);
|
||||
}
|
||||
});
|
||||
quickPick.onDidHide(() => quickPick.dispose());
|
||||
quickPick.show();
|
||||
}));
|
||||
}
|
||||
|
||||
@ -1,306 +1,306 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { QuickPickItem, window, Disposable, CancellationToken, QuickInputButton, QuickInput, ExtensionContext, QuickInputButtons, Uri } from 'vscode';
|
||||
|
||||
/**
|
||||
* A multi-step input using window.createQuickPick() and window.createInputBox().
|
||||
*
|
||||
* This first part uses the helper class `MultiStepInput` that wraps the API for the multi-step case.
|
||||
*/
|
||||
export async function multiStepInput(context: ExtensionContext) {
|
||||
|
||||
class MyButton implements QuickInputButton {
|
||||
constructor(public iconPath: { light: Uri; dark: Uri; }, public tooltip: string) { }
|
||||
}
|
||||
|
||||
const createResourceGroupButton = new MyButton({
|
||||
dark: Uri.file(context.asAbsolutePath('resources/dark/add.svg')),
|
||||
light: Uri.file(context.asAbsolutePath('resources/light/add.svg')),
|
||||
}, 'Create Resource Group');
|
||||
|
||||
const resourceGroups: QuickPickItem[] = ['vscode-data-function', 'vscode-appservice-microservices', 'vscode-appservice-monitor', 'vscode-appservice-preview', 'vscode-appservice-prod']
|
||||
.map(label => ({ label }));
|
||||
|
||||
|
||||
interface State {
|
||||
title: string;
|
||||
step: number;
|
||||
totalSteps: number;
|
||||
resourceGroup: QuickPickItem | string;
|
||||
name: string;
|
||||
runtime: QuickPickItem;
|
||||
}
|
||||
|
||||
async function collectInputs() {
|
||||
const state = {} as Partial<State>;
|
||||
await MultiStepInput.run(input => pickResourceGroup(input, state));
|
||||
return state as State;
|
||||
}
|
||||
|
||||
const title = 'Create Application Service';
|
||||
|
||||
async function pickResourceGroup(input: MultiStepInput, state: Partial<State>) {
|
||||
const pick = await input.showQuickPick({
|
||||
title,
|
||||
step: 1,
|
||||
totalSteps: 3,
|
||||
placeholder: 'Pick a resource group',
|
||||
items: resourceGroups,
|
||||
activeItem: typeof state.resourceGroup !== 'string' ? state.resourceGroup : undefined,
|
||||
buttons: [createResourceGroupButton],
|
||||
shouldResume: shouldResume
|
||||
});
|
||||
if (pick instanceof MyButton) {
|
||||
return (input: MultiStepInput) => inputResourceGroupName(input, state);
|
||||
}
|
||||
state.resourceGroup = pick;
|
||||
return (input: MultiStepInput) => inputName(input, state);
|
||||
}
|
||||
|
||||
async function inputResourceGroupName(input: MultiStepInput, state: Partial<State>) {
|
||||
state.resourceGroup = await input.showInputBox({
|
||||
title,
|
||||
step: 2,
|
||||
totalSteps: 4,
|
||||
value: typeof state.resourceGroup === 'string' ? state.resourceGroup : '',
|
||||
prompt: 'Choose a unique name for the resource group',
|
||||
validate: validateNameIsUnique,
|
||||
shouldResume: shouldResume
|
||||
});
|
||||
return (input: MultiStepInput) => inputName(input, state);
|
||||
}
|
||||
|
||||
async function inputName(input: MultiStepInput, state: Partial<State>) {
|
||||
const additionalSteps = typeof state.resourceGroup === 'string' ? 1 : 0;
|
||||
// TODO: Remember current value when navigating back.
|
||||
state.name = await input.showInputBox({
|
||||
title,
|
||||
step: 2 + additionalSteps,
|
||||
totalSteps: 3 + additionalSteps,
|
||||
value: state.name || '',
|
||||
prompt: 'Choose a unique name for the Application Service',
|
||||
validate: validateNameIsUnique,
|
||||
shouldResume: shouldResume
|
||||
});
|
||||
return (input: MultiStepInput) => pickRuntime(input, state);
|
||||
}
|
||||
|
||||
async function pickRuntime(input: MultiStepInput, state: Partial<State>) {
|
||||
const additionalSteps = typeof state.resourceGroup === 'string' ? 1 : 0;
|
||||
const runtimes = await getAvailableRuntimes(state.resourceGroup!, undefined /* TODO: token */);
|
||||
// TODO: Remember currently active item when navigating back.
|
||||
state.runtime = await input.showQuickPick({
|
||||
title,
|
||||
step: 3 + additionalSteps,
|
||||
totalSteps: 3 + additionalSteps,
|
||||
placeholder: 'Pick a runtime',
|
||||
items: runtimes,
|
||||
activeItem: state.runtime,
|
||||
shouldResume: shouldResume
|
||||
});
|
||||
}
|
||||
|
||||
function shouldResume() {
|
||||
// Could show a notification with the option to resume.
|
||||
return new Promise<boolean>((resolve, reject) => {
|
||||
// noop
|
||||
});
|
||||
}
|
||||
|
||||
async function validateNameIsUnique(name: string) {
|
||||
// ...validate...
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
return name === 'vscode' ? 'Name not unique' : undefined;
|
||||
}
|
||||
|
||||
async function getAvailableRuntimes(resourceGroup: QuickPickItem | string, token?: CancellationToken): Promise<QuickPickItem[]> {
|
||||
// ...retrieve...
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
return ['Node 8.9', 'Node 6.11', 'Node 4.5']
|
||||
.map(label => ({ label }));
|
||||
}
|
||||
|
||||
const state = await collectInputs();
|
||||
window.showInformationMessage(`Creating Application Service '${state.name}'`);
|
||||
}
|
||||
|
||||
|
||||
// -------------------------------------------------------
|
||||
// Helper code that wraps the API for the multi-step case.
|
||||
// -------------------------------------------------------
|
||||
|
||||
|
||||
class InputFlowAction {
|
||||
static back = new InputFlowAction();
|
||||
static cancel = new InputFlowAction();
|
||||
static resume = new InputFlowAction();
|
||||
}
|
||||
|
||||
type InputStep = (input: MultiStepInput) => Thenable<InputStep | void>;
|
||||
|
||||
interface QuickPickParameters<T extends QuickPickItem> {
|
||||
title: string;
|
||||
step: number;
|
||||
totalSteps: number;
|
||||
items: T[];
|
||||
activeItem?: T;
|
||||
placeholder: string;
|
||||
buttons?: QuickInputButton[];
|
||||
shouldResume: () => Thenable<boolean>;
|
||||
}
|
||||
|
||||
interface InputBoxParameters {
|
||||
title: string;
|
||||
step: number;
|
||||
totalSteps: number;
|
||||
value: string;
|
||||
prompt: string;
|
||||
validate: (value: string) => Promise<string | undefined>;
|
||||
buttons?: QuickInputButton[];
|
||||
shouldResume: () => Thenable<boolean>;
|
||||
}
|
||||
|
||||
class MultiStepInput {
|
||||
|
||||
static async run<T>(start: InputStep) {
|
||||
const input = new MultiStepInput();
|
||||
return input.stepThrough(start);
|
||||
}
|
||||
|
||||
private current?: QuickInput;
|
||||
private steps: InputStep[] = [];
|
||||
|
||||
private async stepThrough<T>(start: InputStep) {
|
||||
let step: InputStep | void = start;
|
||||
while (step) {
|
||||
this.steps.push(step);
|
||||
if (this.current) {
|
||||
this.current.enabled = false;
|
||||
this.current.busy = true;
|
||||
}
|
||||
try {
|
||||
step = await step(this);
|
||||
} catch (err) {
|
||||
if (err === InputFlowAction.back) {
|
||||
this.steps.pop();
|
||||
step = this.steps.pop();
|
||||
} else if (err === InputFlowAction.resume) {
|
||||
step = this.steps.pop();
|
||||
} else if (err === InputFlowAction.cancel) {
|
||||
step = undefined;
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (this.current) {
|
||||
this.current.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
async showQuickPick<T extends QuickPickItem, P extends QuickPickParameters<T>>({ title, step, totalSteps, items, activeItem, placeholder, buttons, shouldResume }: P) {
|
||||
const disposables: Disposable[] = [];
|
||||
try {
|
||||
return await new Promise<T | (P extends { buttons: (infer I)[] } ? I : never)>((resolve, reject) => {
|
||||
const input = window.createQuickPick<T>();
|
||||
input.title = title;
|
||||
input.step = step;
|
||||
input.totalSteps = totalSteps;
|
||||
input.placeholder = placeholder;
|
||||
input.items = items;
|
||||
if (activeItem) {
|
||||
input.activeItems = [activeItem];
|
||||
}
|
||||
input.buttons = [
|
||||
...(this.steps.length > 1 ? [QuickInputButtons.Back] : []),
|
||||
...(buttons || [])
|
||||
];
|
||||
disposables.push(
|
||||
input.onDidTriggerButton(item => {
|
||||
if (item === QuickInputButtons.Back) {
|
||||
reject(InputFlowAction.back);
|
||||
} else {
|
||||
resolve(<any>item);
|
||||
}
|
||||
}),
|
||||
input.onDidChangeSelection(items => resolve(items[0])),
|
||||
input.onDidHide(() => {
|
||||
(async () => {
|
||||
reject(shouldResume && await shouldResume() ? InputFlowAction.resume : InputFlowAction.cancel);
|
||||
})()
|
||||
.catch(reject);
|
||||
})
|
||||
);
|
||||
if (this.current) {
|
||||
this.current.dispose();
|
||||
}
|
||||
this.current = input;
|
||||
this.current.show();
|
||||
});
|
||||
} finally {
|
||||
disposables.forEach(d => d.dispose());
|
||||
}
|
||||
}
|
||||
|
||||
async showInputBox<P extends InputBoxParameters>({ title, step, totalSteps, value, prompt, validate, buttons, shouldResume }: P) {
|
||||
const disposables: Disposable[] = [];
|
||||
try {
|
||||
return await new Promise<string | (P extends { buttons: (infer I)[] } ? I : never)>((resolve, reject) => {
|
||||
const input = window.createInputBox();
|
||||
input.title = title;
|
||||
input.step = step;
|
||||
input.totalSteps = totalSteps;
|
||||
input.value = value || '';
|
||||
input.prompt = prompt;
|
||||
input.buttons = [
|
||||
...(this.steps.length > 1 ? [QuickInputButtons.Back] : []),
|
||||
...(buttons || [])
|
||||
];
|
||||
let validating = validate('');
|
||||
disposables.push(
|
||||
input.onDidTriggerButton(item => {
|
||||
if (item === QuickInputButtons.Back) {
|
||||
reject(InputFlowAction.back);
|
||||
} else {
|
||||
resolve(<any>item);
|
||||
}
|
||||
}),
|
||||
input.onDidAccept(async () => {
|
||||
const value = input.value;
|
||||
input.enabled = false;
|
||||
input.busy = true;
|
||||
if (!(await validate(value))) {
|
||||
resolve(value);
|
||||
}
|
||||
input.enabled = true;
|
||||
input.busy = false;
|
||||
}),
|
||||
input.onDidChangeValue(async text => {
|
||||
const current = validate(text);
|
||||
validating = current;
|
||||
const validationMessage = await current;
|
||||
if (current === validating) {
|
||||
input.validationMessage = validationMessage;
|
||||
}
|
||||
}),
|
||||
input.onDidHide(() => {
|
||||
(async () => {
|
||||
reject(shouldResume && await shouldResume() ? InputFlowAction.resume : InputFlowAction.cancel);
|
||||
})()
|
||||
.catch(reject);
|
||||
})
|
||||
);
|
||||
if (this.current) {
|
||||
this.current.dispose();
|
||||
}
|
||||
this.current = input;
|
||||
this.current.show();
|
||||
});
|
||||
} finally {
|
||||
disposables.forEach(d => d.dispose());
|
||||
}
|
||||
}
|
||||
}
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { QuickPickItem, window, Disposable, CancellationToken, QuickInputButton, QuickInput, ExtensionContext, QuickInputButtons, Uri } from 'vscode';
|
||||
|
||||
/**
|
||||
* A multi-step input using window.createQuickPick() and window.createInputBox().
|
||||
*
|
||||
* This first part uses the helper class `MultiStepInput` that wraps the API for the multi-step case.
|
||||
*/
|
||||
export async function multiStepInput(context: ExtensionContext) {
|
||||
|
||||
class MyButton implements QuickInputButton {
|
||||
constructor(public iconPath: { light: Uri; dark: Uri; }, public tooltip: string) { }
|
||||
}
|
||||
|
||||
const createResourceGroupButton = new MyButton({
|
||||
dark: Uri.file(context.asAbsolutePath('resources/dark/add.svg')),
|
||||
light: Uri.file(context.asAbsolutePath('resources/light/add.svg')),
|
||||
}, 'Create Resource Group');
|
||||
|
||||
const resourceGroups: QuickPickItem[] = ['vscode-data-function', 'vscode-appservice-microservices', 'vscode-appservice-monitor', 'vscode-appservice-preview', 'vscode-appservice-prod']
|
||||
.map(label => ({ label }));
|
||||
|
||||
|
||||
interface State {
|
||||
title: string;
|
||||
step: number;
|
||||
totalSteps: number;
|
||||
resourceGroup: QuickPickItem | string;
|
||||
name: string;
|
||||
runtime: QuickPickItem;
|
||||
}
|
||||
|
||||
async function collectInputs() {
|
||||
const state = {} as Partial<State>;
|
||||
await MultiStepInput.run(input => pickResourceGroup(input, state));
|
||||
return state as State;
|
||||
}
|
||||
|
||||
const title = 'Create Application Service';
|
||||
|
||||
async function pickResourceGroup(input: MultiStepInput, state: Partial<State>) {
|
||||
const pick = await input.showQuickPick({
|
||||
title,
|
||||
step: 1,
|
||||
totalSteps: 3,
|
||||
placeholder: 'Pick a resource group',
|
||||
items: resourceGroups,
|
||||
activeItem: typeof state.resourceGroup !== 'string' ? state.resourceGroup : undefined,
|
||||
buttons: [createResourceGroupButton],
|
||||
shouldResume: shouldResume
|
||||
});
|
||||
if (pick instanceof MyButton) {
|
||||
return (input: MultiStepInput) => inputResourceGroupName(input, state);
|
||||
}
|
||||
state.resourceGroup = pick;
|
||||
return (input: MultiStepInput) => inputName(input, state);
|
||||
}
|
||||
|
||||
async function inputResourceGroupName(input: MultiStepInput, state: Partial<State>) {
|
||||
state.resourceGroup = await input.showInputBox({
|
||||
title,
|
||||
step: 2,
|
||||
totalSteps: 4,
|
||||
value: typeof state.resourceGroup === 'string' ? state.resourceGroup : '',
|
||||
prompt: 'Choose a unique name for the resource group',
|
||||
validate: validateNameIsUnique,
|
||||
shouldResume: shouldResume
|
||||
});
|
||||
return (input: MultiStepInput) => inputName(input, state);
|
||||
}
|
||||
|
||||
async function inputName(input: MultiStepInput, state: Partial<State>) {
|
||||
const additionalSteps = typeof state.resourceGroup === 'string' ? 1 : 0;
|
||||
// TODO: Remember current value when navigating back.
|
||||
state.name = await input.showInputBox({
|
||||
title,
|
||||
step: 2 + additionalSteps,
|
||||
totalSteps: 3 + additionalSteps,
|
||||
value: state.name || '',
|
||||
prompt: 'Choose a unique name for the Application Service',
|
||||
validate: validateNameIsUnique,
|
||||
shouldResume: shouldResume
|
||||
});
|
||||
return (input: MultiStepInput) => pickRuntime(input, state);
|
||||
}
|
||||
|
||||
async function pickRuntime(input: MultiStepInput, state: Partial<State>) {
|
||||
const additionalSteps = typeof state.resourceGroup === 'string' ? 1 : 0;
|
||||
const runtimes = await getAvailableRuntimes(state.resourceGroup!, undefined /* TODO: token */);
|
||||
// TODO: Remember currently active item when navigating back.
|
||||
state.runtime = await input.showQuickPick({
|
||||
title,
|
||||
step: 3 + additionalSteps,
|
||||
totalSteps: 3 + additionalSteps,
|
||||
placeholder: 'Pick a runtime',
|
||||
items: runtimes,
|
||||
activeItem: state.runtime,
|
||||
shouldResume: shouldResume
|
||||
});
|
||||
}
|
||||
|
||||
function shouldResume() {
|
||||
// Could show a notification with the option to resume.
|
||||
return new Promise<boolean>((resolve, reject) => {
|
||||
// noop
|
||||
});
|
||||
}
|
||||
|
||||
async function validateNameIsUnique(name: string) {
|
||||
// ...validate...
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
return name === 'vscode' ? 'Name not unique' : undefined;
|
||||
}
|
||||
|
||||
async function getAvailableRuntimes(resourceGroup: QuickPickItem | string, token?: CancellationToken): Promise<QuickPickItem[]> {
|
||||
// ...retrieve...
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
return ['Node 8.9', 'Node 6.11', 'Node 4.5']
|
||||
.map(label => ({ label }));
|
||||
}
|
||||
|
||||
const state = await collectInputs();
|
||||
window.showInformationMessage(`Creating Application Service '${state.name}'`);
|
||||
}
|
||||
|
||||
|
||||
// -------------------------------------------------------
|
||||
// Helper code that wraps the API for the multi-step case.
|
||||
// -------------------------------------------------------
|
||||
|
||||
|
||||
class InputFlowAction {
|
||||
static back = new InputFlowAction();
|
||||
static cancel = new InputFlowAction();
|
||||
static resume = new InputFlowAction();
|
||||
}
|
||||
|
||||
type InputStep = (input: MultiStepInput) => Thenable<InputStep | void>;
|
||||
|
||||
interface QuickPickParameters<T extends QuickPickItem> {
|
||||
title: string;
|
||||
step: number;
|
||||
totalSteps: number;
|
||||
items: T[];
|
||||
activeItem?: T;
|
||||
placeholder: string;
|
||||
buttons?: QuickInputButton[];
|
||||
shouldResume: () => Thenable<boolean>;
|
||||
}
|
||||
|
||||
interface InputBoxParameters {
|
||||
title: string;
|
||||
step: number;
|
||||
totalSteps: number;
|
||||
value: string;
|
||||
prompt: string;
|
||||
validate: (value: string) => Promise<string | undefined>;
|
||||
buttons?: QuickInputButton[];
|
||||
shouldResume: () => Thenable<boolean>;
|
||||
}
|
||||
|
||||
class MultiStepInput {
|
||||
|
||||
static async run<T>(start: InputStep) {
|
||||
const input = new MultiStepInput();
|
||||
return input.stepThrough(start);
|
||||
}
|
||||
|
||||
private current?: QuickInput;
|
||||
private steps: InputStep[] = [];
|
||||
|
||||
private async stepThrough<T>(start: InputStep) {
|
||||
let step: InputStep | void = start;
|
||||
while (step) {
|
||||
this.steps.push(step);
|
||||
if (this.current) {
|
||||
this.current.enabled = false;
|
||||
this.current.busy = true;
|
||||
}
|
||||
try {
|
||||
step = await step(this);
|
||||
} catch (err) {
|
||||
if (err === InputFlowAction.back) {
|
||||
this.steps.pop();
|
||||
step = this.steps.pop();
|
||||
} else if (err === InputFlowAction.resume) {
|
||||
step = this.steps.pop();
|
||||
} else if (err === InputFlowAction.cancel) {
|
||||
step = undefined;
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (this.current) {
|
||||
this.current.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
async showQuickPick<T extends QuickPickItem, P extends QuickPickParameters<T>>({ title, step, totalSteps, items, activeItem, placeholder, buttons, shouldResume }: P) {
|
||||
const disposables: Disposable[] = [];
|
||||
try {
|
||||
return await new Promise<T | (P extends { buttons: (infer I)[] } ? I : never)>((resolve, reject) => {
|
||||
const input = window.createQuickPick<T>();
|
||||
input.title = title;
|
||||
input.step = step;
|
||||
input.totalSteps = totalSteps;
|
||||
input.placeholder = placeholder;
|
||||
input.items = items;
|
||||
if (activeItem) {
|
||||
input.activeItems = [activeItem];
|
||||
}
|
||||
input.buttons = [
|
||||
...(this.steps.length > 1 ? [QuickInputButtons.Back] : []),
|
||||
...(buttons || [])
|
||||
];
|
||||
disposables.push(
|
||||
input.onDidTriggerButton(item => {
|
||||
if (item === QuickInputButtons.Back) {
|
||||
reject(InputFlowAction.back);
|
||||
} else {
|
||||
resolve(<any>item);
|
||||
}
|
||||
}),
|
||||
input.onDidChangeSelection(items => resolve(items[0])),
|
||||
input.onDidHide(() => {
|
||||
(async () => {
|
||||
reject(shouldResume && await shouldResume() ? InputFlowAction.resume : InputFlowAction.cancel);
|
||||
})()
|
||||
.catch(reject);
|
||||
})
|
||||
);
|
||||
if (this.current) {
|
||||
this.current.dispose();
|
||||
}
|
||||
this.current = input;
|
||||
this.current.show();
|
||||
});
|
||||
} finally {
|
||||
disposables.forEach(d => d.dispose());
|
||||
}
|
||||
}
|
||||
|
||||
async showInputBox<P extends InputBoxParameters>({ title, step, totalSteps, value, prompt, validate, buttons, shouldResume }: P) {
|
||||
const disposables: Disposable[] = [];
|
||||
try {
|
||||
return await new Promise<string | (P extends { buttons: (infer I)[] } ? I : never)>((resolve, reject) => {
|
||||
const input = window.createInputBox();
|
||||
input.title = title;
|
||||
input.step = step;
|
||||
input.totalSteps = totalSteps;
|
||||
input.value = value || '';
|
||||
input.prompt = prompt;
|
||||
input.buttons = [
|
||||
...(this.steps.length > 1 ? [QuickInputButtons.Back] : []),
|
||||
...(buttons || [])
|
||||
];
|
||||
let validating = validate('');
|
||||
disposables.push(
|
||||
input.onDidTriggerButton(item => {
|
||||
if (item === QuickInputButtons.Back) {
|
||||
reject(InputFlowAction.back);
|
||||
} else {
|
||||
resolve(<any>item);
|
||||
}
|
||||
}),
|
||||
input.onDidAccept(async () => {
|
||||
const value = input.value;
|
||||
input.enabled = false;
|
||||
input.busy = true;
|
||||
if (!(await validate(value))) {
|
||||
resolve(value);
|
||||
}
|
||||
input.enabled = true;
|
||||
input.busy = false;
|
||||
}),
|
||||
input.onDidChangeValue(async text => {
|
||||
const current = validate(text);
|
||||
validating = current;
|
||||
const validationMessage = await current;
|
||||
if (current === validating) {
|
||||
input.validationMessage = validationMessage;
|
||||
}
|
||||
}),
|
||||
input.onDidHide(() => {
|
||||
(async () => {
|
||||
reject(shouldResume && await shouldResume() ? InputFlowAction.resume : InputFlowAction.cancel);
|
||||
})()
|
||||
.catch(reject);
|
||||
})
|
||||
);
|
||||
if (this.current) {
|
||||
this.current.dispose();
|
||||
}
|
||||
this.current = input;
|
||||
this.current.show();
|
||||
});
|
||||
} finally {
|
||||
disposables.forEach(d => d.dispose());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,112 +1,112 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as path from 'path';
|
||||
import * as cp from 'child_process';
|
||||
import { Uri, window, Disposable } from 'vscode';
|
||||
import { QuickPickItem } from 'vscode';
|
||||
import { workspace } from 'vscode';
|
||||
|
||||
/**
|
||||
* A file opener using window.createQuickPick().
|
||||
*
|
||||
* It shows how the list of items can be dynamically updated based on
|
||||
* the user's input in the filter field.
|
||||
*/
|
||||
export async function quickOpen() {
|
||||
const uri = await pickFile();
|
||||
if (uri) {
|
||||
const document = await workspace.openTextDocument(uri);
|
||||
await window.showTextDocument(document);
|
||||
}
|
||||
}
|
||||
|
||||
class FileItem implements QuickPickItem {
|
||||
|
||||
label: string;
|
||||
description: string;
|
||||
|
||||
constructor(public base: Uri, public uri: Uri) {
|
||||
this.label = path.basename(uri.fsPath);
|
||||
this.description = path.dirname(path.relative(base.fsPath, uri.fsPath));
|
||||
}
|
||||
}
|
||||
|
||||
class MessageItem implements QuickPickItem {
|
||||
|
||||
label: string;
|
||||
description = '';
|
||||
detail: string;
|
||||
|
||||
constructor(public base: Uri, public message: string) {
|
||||
this.label = message.replace(/\r?\n/g, ' ');
|
||||
this.detail = base.fsPath;
|
||||
}
|
||||
}
|
||||
|
||||
async function pickFile() {
|
||||
const disposables: Disposable[] = [];
|
||||
try {
|
||||
return await new Promise<Uri | undefined>((resolve, reject) => {
|
||||
const input = window.createQuickPick<FileItem | MessageItem>();
|
||||
input.placeholder = 'Type to search for files';
|
||||
let rgs: cp.ChildProcess[] = [];
|
||||
disposables.push(
|
||||
input.onDidChangeValue(value => {
|
||||
rgs.forEach(rg => rg.kill());
|
||||
if (!value) {
|
||||
input.items = [];
|
||||
return;
|
||||
}
|
||||
input.busy = true;
|
||||
const cwds = workspace.workspaceFolders ? workspace.workspaceFolders.map(f => f.uri.fsPath) : [process.cwd()];
|
||||
const q = process.platform === 'win32' ? '"' : '\'';
|
||||
rgs = cwds.map(cwd => {
|
||||
const rg = cp.exec(`rg --files -g ${q}*${value}*${q}`, { cwd }, (err, stdout) => {
|
||||
const i = rgs.indexOf(rg);
|
||||
if (i !== -1) {
|
||||
if (rgs.length === cwds.length) {
|
||||
input.items = [];
|
||||
}
|
||||
if (!err) {
|
||||
input.items = input.items.concat(
|
||||
stdout
|
||||
.split('\n').slice(0, 50)
|
||||
.map(relative => new FileItem(Uri.file(cwd), Uri.file(path.join(cwd, relative))))
|
||||
);
|
||||
}
|
||||
if (err && !(<any>err).killed && (<any>err).code !== 1 && err.message) {
|
||||
input.items = input.items.concat([
|
||||
new MessageItem(Uri.file(cwd), err.message)
|
||||
]);
|
||||
}
|
||||
rgs.splice(i, 1);
|
||||
if (!rgs.length) {
|
||||
input.busy = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
return rg;
|
||||
});
|
||||
}),
|
||||
input.onDidChangeSelection(items => {
|
||||
const item = items[0];
|
||||
if (item instanceof FileItem) {
|
||||
resolve(item.uri);
|
||||
input.hide();
|
||||
}
|
||||
}),
|
||||
input.onDidHide(() => {
|
||||
rgs.forEach(rg => rg.kill());
|
||||
resolve(undefined);
|
||||
input.dispose();
|
||||
})
|
||||
);
|
||||
input.show();
|
||||
});
|
||||
} finally {
|
||||
disposables.forEach(d => d.dispose());
|
||||
}
|
||||
}
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as path from 'path';
|
||||
import * as cp from 'child_process';
|
||||
import { Uri, window, Disposable } from 'vscode';
|
||||
import { QuickPickItem } from 'vscode';
|
||||
import { workspace } from 'vscode';
|
||||
|
||||
/**
|
||||
* A file opener using window.createQuickPick().
|
||||
*
|
||||
* It shows how the list of items can be dynamically updated based on
|
||||
* the user's input in the filter field.
|
||||
*/
|
||||
export async function quickOpen() {
|
||||
const uri = await pickFile();
|
||||
if (uri) {
|
||||
const document = await workspace.openTextDocument(uri);
|
||||
await window.showTextDocument(document);
|
||||
}
|
||||
}
|
||||
|
||||
class FileItem implements QuickPickItem {
|
||||
|
||||
label: string;
|
||||
description: string;
|
||||
|
||||
constructor(public base: Uri, public uri: Uri) {
|
||||
this.label = path.basename(uri.fsPath);
|
||||
this.description = path.dirname(path.relative(base.fsPath, uri.fsPath));
|
||||
}
|
||||
}
|
||||
|
||||
class MessageItem implements QuickPickItem {
|
||||
|
||||
label: string;
|
||||
description = '';
|
||||
detail: string;
|
||||
|
||||
constructor(public base: Uri, public message: string) {
|
||||
this.label = message.replace(/\r?\n/g, ' ');
|
||||
this.detail = base.fsPath;
|
||||
}
|
||||
}
|
||||
|
||||
async function pickFile() {
|
||||
const disposables: Disposable[] = [];
|
||||
try {
|
||||
return await new Promise<Uri | undefined>((resolve, reject) => {
|
||||
const input = window.createQuickPick<FileItem | MessageItem>();
|
||||
input.placeholder = 'Type to search for files';
|
||||
let rgs: cp.ChildProcess[] = [];
|
||||
disposables.push(
|
||||
input.onDidChangeValue(value => {
|
||||
rgs.forEach(rg => rg.kill());
|
||||
if (!value) {
|
||||
input.items = [];
|
||||
return;
|
||||
}
|
||||
input.busy = true;
|
||||
const cwds = workspace.workspaceFolders ? workspace.workspaceFolders.map(f => f.uri.fsPath) : [process.cwd()];
|
||||
const q = process.platform === 'win32' ? '"' : '\'';
|
||||
rgs = cwds.map(cwd => {
|
||||
const rg = cp.exec(`rg --files -g ${q}*${value}*${q}`, { cwd }, (err, stdout) => {
|
||||
const i = rgs.indexOf(rg);
|
||||
if (i !== -1) {
|
||||
if (rgs.length === cwds.length) {
|
||||
input.items = [];
|
||||
}
|
||||
if (!err) {
|
||||
input.items = input.items.concat(
|
||||
stdout
|
||||
.split('\n').slice(0, 50)
|
||||
.map(relative => new FileItem(Uri.file(cwd), Uri.file(path.join(cwd, relative))))
|
||||
);
|
||||
}
|
||||
if (err && !(<any>err).killed && (<any>err).code !== 1 && err.message) {
|
||||
input.items = input.items.concat([
|
||||
new MessageItem(Uri.file(cwd), err.message)
|
||||
]);
|
||||
}
|
||||
rgs.splice(i, 1);
|
||||
if (!rgs.length) {
|
||||
input.busy = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
return rg;
|
||||
});
|
||||
}),
|
||||
input.onDidChangeSelection(items => {
|
||||
const item = items[0];
|
||||
if (item instanceof FileItem) {
|
||||
resolve(item.uri);
|
||||
input.hide();
|
||||
}
|
||||
}),
|
||||
input.onDidHide(() => {
|
||||
rgs.forEach(rg => rg.kill());
|
||||
resolve(undefined);
|
||||
input.dispose();
|
||||
})
|
||||
);
|
||||
input.show();
|
||||
});
|
||||
} finally {
|
||||
disposables.forEach(d => d.dispose());
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,103 +1,103 @@
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
const tokenTypes = new Map<string, number>();
|
||||
const tokenModifiers = new Map<string, number>();
|
||||
|
||||
const legend = (function () {
|
||||
const tokenTypesLegend = [
|
||||
'comment', 'string', 'keyword', 'number', 'regexp', 'operator', 'namespace',
|
||||
'type', 'struct', 'class', 'interface', 'enum', 'typeParameter', 'function',
|
||||
'method', 'decorator', 'macro', 'variable', 'parameter', 'property', 'label'
|
||||
];
|
||||
tokenTypesLegend.forEach((tokenType, index) => tokenTypes.set(tokenType, index));
|
||||
|
||||
const tokenModifiersLegend = [
|
||||
'declaration', 'documentation', 'readonly', 'static', 'abstract', 'deprecated',
|
||||
'modification', 'async'
|
||||
];
|
||||
tokenModifiersLegend.forEach((tokenModifier, index) => tokenModifiers.set(tokenModifier, index));
|
||||
|
||||
return new vscode.SemanticTokensLegend(tokenTypesLegend, tokenModifiersLegend);
|
||||
})();
|
||||
|
||||
export function activate(context: vscode.ExtensionContext) {
|
||||
context.subscriptions.push(vscode.languages.registerDocumentSemanticTokensProvider({ language: 'semanticLanguage'}, new DocumentSemanticTokensProvider(), legend));
|
||||
}
|
||||
|
||||
interface IParsedToken {
|
||||
line: number;
|
||||
startCharacter: number;
|
||||
length: number;
|
||||
tokenType: string;
|
||||
tokenModifiers: string[];
|
||||
}
|
||||
|
||||
class DocumentSemanticTokensProvider implements vscode.DocumentSemanticTokensProvider {
|
||||
async provideDocumentSemanticTokens(document: vscode.TextDocument, token: vscode.CancellationToken): Promise<vscode.SemanticTokens> {
|
||||
const allTokens = this._parseText(document.getText());
|
||||
const builder = new vscode.SemanticTokensBuilder();
|
||||
allTokens.forEach((token) => {
|
||||
builder.push(token.line, token.startCharacter, token.length, this._encodeTokenType(token.tokenType), this._encodeTokenModifiers(token.tokenModifiers));
|
||||
});
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
private _encodeTokenType(tokenType: string): number {
|
||||
if (tokenTypes.has(tokenType)) {
|
||||
return tokenTypes.get(tokenType)!;
|
||||
} else if (tokenType === 'notInLegend') {
|
||||
return tokenTypes.size + 2;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private _encodeTokenModifiers(strTokenModifiers: string[]): number {
|
||||
let result = 0;
|
||||
for (let i = 0; i < strTokenModifiers.length; i++) {
|
||||
const tokenModifier = strTokenModifiers[i];
|
||||
if (tokenModifiers.has(tokenModifier)) {
|
||||
result = result | (1 << tokenModifiers.get(tokenModifier)!);
|
||||
} else if (tokenModifier === 'notInLegend') {
|
||||
result = result | (1 << tokenModifiers.size + 2);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private _parseText(text: string): IParsedToken[] {
|
||||
const r: IParsedToken[] = [];
|
||||
const lines = text.split(/\r\n|\r|\n/);
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const line = lines[i];
|
||||
let currentOffset = 0;
|
||||
do {
|
||||
const openOffset = line.indexOf('[', currentOffset);
|
||||
if (openOffset === -1) {
|
||||
break;
|
||||
}
|
||||
const closeOffset = line.indexOf(']', openOffset);
|
||||
if (closeOffset === -1) {
|
||||
break;
|
||||
}
|
||||
const tokenData = this._parseTextToken(line.substring(openOffset + 1, closeOffset));
|
||||
r.push({
|
||||
line: i,
|
||||
startCharacter: openOffset + 1,
|
||||
length: closeOffset - openOffset - 1,
|
||||
tokenType: tokenData.tokenType,
|
||||
tokenModifiers: tokenData.tokenModifiers
|
||||
});
|
||||
currentOffset = closeOffset;
|
||||
} while (true);
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
private _parseTextToken(text: string): { tokenType: string; tokenModifiers: string[]; } {
|
||||
const parts = text.split('.');
|
||||
return {
|
||||
tokenType: parts[0],
|
||||
tokenModifiers: parts.slice(1)
|
||||
};
|
||||
}
|
||||
}
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
const tokenTypes = new Map<string, number>();
|
||||
const tokenModifiers = new Map<string, number>();
|
||||
|
||||
const legend = (function() {
|
||||
const tokenTypesLegend = [
|
||||
'comment', 'string', 'keyword', 'number', 'regexp', 'operator', 'namespace',
|
||||
'type', 'struct', 'class', 'interface', 'enum', 'typeParameter', 'function',
|
||||
'method', 'decorator', 'macro', 'variable', 'parameter', 'property', 'label'
|
||||
];
|
||||
tokenTypesLegend.forEach((tokenType, index) => tokenTypes.set(tokenType, index));
|
||||
|
||||
const tokenModifiersLegend = [
|
||||
'declaration', 'documentation', 'readonly', 'static', 'abstract', 'deprecated',
|
||||
'modification', 'async'
|
||||
];
|
||||
tokenModifiersLegend.forEach((tokenModifier, index) => tokenModifiers.set(tokenModifier, index));
|
||||
|
||||
return new vscode.SemanticTokensLegend(tokenTypesLegend, tokenModifiersLegend);
|
||||
})();
|
||||
|
||||
export function activate(context: vscode.ExtensionContext) {
|
||||
context.subscriptions.push(vscode.languages.registerDocumentSemanticTokensProvider({ language: 'semanticLanguage' }, new DocumentSemanticTokensProvider(), legend));
|
||||
}
|
||||
|
||||
interface IParsedToken {
|
||||
line: number;
|
||||
startCharacter: number;
|
||||
length: number;
|
||||
tokenType: string;
|
||||
tokenModifiers: string[];
|
||||
}
|
||||
|
||||
class DocumentSemanticTokensProvider implements vscode.DocumentSemanticTokensProvider {
|
||||
async provideDocumentSemanticTokens(document: vscode.TextDocument, token: vscode.CancellationToken): Promise<vscode.SemanticTokens> {
|
||||
const allTokens = this._parseText(document.getText());
|
||||
const builder = new vscode.SemanticTokensBuilder();
|
||||
allTokens.forEach((token) => {
|
||||
builder.push(token.line, token.startCharacter, token.length, this._encodeTokenType(token.tokenType), this._encodeTokenModifiers(token.tokenModifiers));
|
||||
});
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
private _encodeTokenType(tokenType: string): number {
|
||||
if (tokenTypes.has(tokenType)) {
|
||||
return tokenTypes.get(tokenType)!;
|
||||
} else if (tokenType === 'notInLegend') {
|
||||
return tokenTypes.size + 2;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private _encodeTokenModifiers(strTokenModifiers: string[]): number {
|
||||
let result = 0;
|
||||
for (let i = 0; i < strTokenModifiers.length; i++) {
|
||||
const tokenModifier = strTokenModifiers[i];
|
||||
if (tokenModifiers.has(tokenModifier)) {
|
||||
result = result | (1 << tokenModifiers.get(tokenModifier)!);
|
||||
} else if (tokenModifier === 'notInLegend') {
|
||||
result = result | (1 << tokenModifiers.size + 2);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private _parseText(text: string): IParsedToken[] {
|
||||
const r: IParsedToken[] = [];
|
||||
const lines = text.split(/\r\n|\r|\n/);
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const line = lines[i];
|
||||
let currentOffset = 0;
|
||||
do {
|
||||
const openOffset = line.indexOf('[', currentOffset);
|
||||
if (openOffset === -1) {
|
||||
break;
|
||||
}
|
||||
const closeOffset = line.indexOf(']', openOffset);
|
||||
if (closeOffset === -1) {
|
||||
break;
|
||||
}
|
||||
const tokenData = this._parseTextToken(line.substring(openOffset + 1, closeOffset));
|
||||
r.push({
|
||||
line: i,
|
||||
startCharacter: openOffset + 1,
|
||||
length: closeOffset - openOffset - 1,
|
||||
tokenType: tokenData.tokenType,
|
||||
tokenModifiers: tokenData.tokenModifiers
|
||||
});
|
||||
currentOffset = closeOffset;
|
||||
} while (true);
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
private _parseTextToken(text: string): { tokenType: string; tokenModifiers: string[]; } {
|
||||
const parts = text.split('.');
|
||||
return {
|
||||
tokenType: parts[0],
|
||||
tokenModifiers: parts.slice(1)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,64 +1,64 @@
|
||||
// This file promisifies necessary file system functions.
|
||||
// This should be removed when VS Code updates to Node.js ^11.14 and replaced by the native fs promises.
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import * as fs from 'fs';
|
||||
|
||||
function handleResult<T>(resolve: (result: T) => void, reject: (error: Error) => void, error: Error | null | undefined, result: T): void {
|
||||
if (error) {
|
||||
reject(massageError(error));
|
||||
} else {
|
||||
resolve(result);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function massageError(error: Error & { code?: string }): Error {
|
||||
if (error.code === 'ENOENT') {
|
||||
return vscode.FileSystemError.FileNotFound();
|
||||
}
|
||||
|
||||
if (error.code === 'EISDIR') {
|
||||
return vscode.FileSystemError.FileIsADirectory();
|
||||
}
|
||||
|
||||
if (error.code === 'EEXIST') {
|
||||
return vscode.FileSystemError.FileExists();
|
||||
}
|
||||
|
||||
if (error.code === 'EPERM' || error.code === 'EACCESS') {
|
||||
return vscode.FileSystemError.NoPermissions();
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
export function readFile(path: string): Promise<Buffer> {
|
||||
return new Promise<Buffer>((resolve, reject) => {
|
||||
fs.readFile(path, (error, buffer) => handleResult(resolve, reject, error, buffer));
|
||||
});
|
||||
}
|
||||
|
||||
export function writeFile(path: string, content: Buffer): Promise<void> {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
fs.writeFile(path, content, error => handleResult(resolve, reject, error, void 0));
|
||||
});
|
||||
}
|
||||
|
||||
export function exists(path: string): Promise<boolean> {
|
||||
return new Promise<boolean>((resolve, reject) => {
|
||||
fs.exists(path, exists => handleResult(resolve, reject, null, exists));
|
||||
});
|
||||
}
|
||||
|
||||
export function readdir(path: string): Promise<string[]> {
|
||||
return new Promise<string[]>((resolve, reject) => {
|
||||
fs.readdir(path, (error, files) => handleResult(resolve, reject, error, files));
|
||||
});
|
||||
}
|
||||
|
||||
export function unlink(path: string): Promise<void> {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
fs.unlink(path, error => handleResult(resolve, reject, error, void 0));
|
||||
});
|
||||
// This file promisifies necessary file system functions.
|
||||
// This should be removed when VS Code updates to Node.js ^11.14 and replaced by the native fs promises.
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import * as fs from 'fs';
|
||||
|
||||
function handleResult<T>(resolve: (result: T) => void, reject: (error: Error) => void, error: Error | null | undefined, result: T): void {
|
||||
if (error) {
|
||||
reject(massageError(error));
|
||||
} else {
|
||||
resolve(result);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function massageError(error: Error & { code?: string }): Error {
|
||||
if (error.code === 'ENOENT') {
|
||||
return vscode.FileSystemError.FileNotFound();
|
||||
}
|
||||
|
||||
if (error.code === 'EISDIR') {
|
||||
return vscode.FileSystemError.FileIsADirectory();
|
||||
}
|
||||
|
||||
if (error.code === 'EEXIST') {
|
||||
return vscode.FileSystemError.FileExists();
|
||||
}
|
||||
|
||||
if (error.code === 'EPERM' || error.code === 'EACCESS') {
|
||||
return vscode.FileSystemError.NoPermissions();
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
export function readFile(path: string): Promise<Buffer> {
|
||||
return new Promise<Buffer>((resolve, reject) => {
|
||||
fs.readFile(path, (error, buffer) => handleResult(resolve, reject, error, buffer));
|
||||
});
|
||||
}
|
||||
|
||||
export function writeFile(path: string, content: Buffer): Promise<void> {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
fs.writeFile(path, content, error => handleResult(resolve, reject, error, void 0));
|
||||
});
|
||||
}
|
||||
|
||||
export function exists(path: string): Promise<boolean> {
|
||||
return new Promise<boolean>((resolve, reject) => {
|
||||
fs.exists(path, exists => handleResult(resolve, reject, null, exists));
|
||||
});
|
||||
}
|
||||
|
||||
export function readdir(path: string): Promise<string[]> {
|
||||
return new Promise<string[]>((resolve, reject) => {
|
||||
fs.readdir(path, (error, files) => handleResult(resolve, reject, error, files));
|
||||
});
|
||||
}
|
||||
|
||||
export function unlink(path: string): Promise<void> {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
fs.unlink(path, error => handleResult(resolve, reject, error, void 0));
|
||||
});
|
||||
}
|
||||
@ -1,349 +1,349 @@
|
||||
// The module 'vscode' contains the VS Code extensibility API
|
||||
// Import the module and reference it with the alias vscode in your code below
|
||||
import * as vscode from 'vscode';
|
||||
import { JSFIDDLE_SCHEME } from './fiddleRepository';
|
||||
import { FiddleSourceControl, CONFIGURATION_FILE } from './fiddleSourceControl';
|
||||
import { JSFiddleDocumentContentProvider } from './fiddleDocumentContentProvider';
|
||||
import * as path from 'path';
|
||||
import * as afs from './afs';
|
||||
import { FiddleConfiguration, parseFiddleId } from './fiddleConfiguration';
|
||||
import { firstIndex, UTF8 } from './util';
|
||||
|
||||
const SOURCE_CONTROL_OPEN_COMMAND = 'extension.source-control.open';
|
||||
let jsFiddleDocumentContentProvider: JSFiddleDocumentContentProvider;
|
||||
const fiddleSourceControlRegister = new Map<vscode.Uri, FiddleSourceControl>();
|
||||
|
||||
// this method is called when your extension is activated
|
||||
// your extension is activated the very first time the command is executed
|
||||
export async function activate(context: vscode.ExtensionContext) {
|
||||
console.log('Congratulations, your extension "source-control-sample" is now active!');
|
||||
|
||||
jsFiddleDocumentContentProvider = new JSFiddleDocumentContentProvider();
|
||||
|
||||
try {
|
||||
await initializeFromConfigurationFile(context);
|
||||
}
|
||||
catch (err) {
|
||||
console.log('Failed to initialize a Fiddle workspace.');
|
||||
vscode.window.showErrorMessage(err);
|
||||
}
|
||||
|
||||
const openCommand = vscode.commands.registerCommand(SOURCE_CONTROL_OPEN_COMMAND,
|
||||
(fiddleId?: string, workspaceUri?: vscode.Uri) => {
|
||||
tryOpenFiddle(context, fiddleId, workspaceUri);
|
||||
});
|
||||
context.subscriptions.push(openCommand);
|
||||
|
||||
context.subscriptions.push(vscode.workspace.registerTextDocumentContentProvider(JSFIDDLE_SCHEME, jsFiddleDocumentContentProvider));
|
||||
|
||||
context.subscriptions.push(vscode.commands.registerCommand("extension.source-control.refresh",
|
||||
async (sourceControlPane: vscode.SourceControl) => {
|
||||
const sourceControl = await pickSourceControl(sourceControlPane);
|
||||
if (sourceControl) { sourceControl.refresh(); }
|
||||
}));
|
||||
context.subscriptions.push(vscode.commands.registerCommand("extension.source-control.discard",
|
||||
async (sourceControlPane: vscode.SourceControl) => {
|
||||
const sourceControl = await pickSourceControl(sourceControlPane);
|
||||
if (sourceControl) { sourceControl.resetFilesToCheckedOutVersion(); }
|
||||
}));
|
||||
context.subscriptions.push(vscode.commands.registerCommand("extension.source-control.commit",
|
||||
async (sourceControlPane: vscode.SourceControl) => {
|
||||
const sourceControl = await pickSourceControl(sourceControlPane);
|
||||
if (sourceControl) { sourceControl.commitAll(); }
|
||||
}));
|
||||
context.subscriptions.push(vscode.commands.registerCommand("extension.source-control.checkout",
|
||||
async (sourceControl: FiddleSourceControl, newVersion?: number) => {
|
||||
sourceControl = sourceControl || await pickSourceControl(null);
|
||||
if (sourceControl) { sourceControl.tryCheckout(newVersion); }
|
||||
}));
|
||||
context.subscriptions.push(vscode.commands.registerCommand("extension.source-control.browse",
|
||||
async (sourceControlPane: vscode.SourceControl) => {
|
||||
const sourceControl = await pickSourceControl(sourceControlPane);
|
||||
if (sourceControl) { sourceControl.openInBrowser(); }
|
||||
}));
|
||||
|
||||
|
||||
context.subscriptions.push(vscode.workspace.onDidChangeWorkspaceFolders(e => {
|
||||
try {
|
||||
// initialize new source control for manually added workspace folders
|
||||
e.added.forEach(wf => {
|
||||
initializeFolderFromConfigurationFile(wf, context);
|
||||
});
|
||||
} catch (ex) {
|
||||
vscode.window.showErrorMessage(ex.message);
|
||||
} finally {
|
||||
// dispose source control for removed workspace folders
|
||||
e.removed.forEach(wf => {
|
||||
unregisterFiddleSourceControl(wf.uri);
|
||||
});
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
async function pickSourceControl(sourceControlPane: vscode.SourceControl): Promise<FiddleSourceControl | undefined> {
|
||||
if (sourceControlPane) {
|
||||
return fiddleSourceControlRegister.get(sourceControlPane.rootUri);
|
||||
}
|
||||
|
||||
// todo: when/if the SourceControl exposes a 'selected' property, use that instead
|
||||
|
||||
if (fiddleSourceControlRegister.size === 0) { return undefined; }
|
||||
else if (fiddleSourceControlRegister.size === 1) { return [...fiddleSourceControlRegister.values()][0]; }
|
||||
else {
|
||||
|
||||
const picks = [...fiddleSourceControlRegister.values()].map(fsc => new RepositoryPick(fsc));
|
||||
|
||||
if (vscode.window.activeTextEditor) {
|
||||
const activeWorkspaceFolder = vscode.workspace.getWorkspaceFolder(vscode.window.activeTextEditor.document.uri);
|
||||
const activeSourceControl = activeWorkspaceFolder && fiddleSourceControlRegister.get(activeWorkspaceFolder.uri);
|
||||
const activeIndex = firstIndex(picks, pick => pick.fiddleSourceControl === activeSourceControl);
|
||||
|
||||
// if there is an active editor, move its folder to be the first in the pick list
|
||||
if (activeIndex > -1) {
|
||||
picks.unshift(...picks.splice(activeIndex, 1));
|
||||
}
|
||||
}
|
||||
|
||||
const pick = await vscode.window.showQuickPick(picks, { placeHolder: 'Select repository' });
|
||||
return pick && pick.fiddleSourceControl;
|
||||
}
|
||||
}
|
||||
|
||||
async function tryOpenFiddle(context: vscode.ExtensionContext, fiddleId?: string, workspaceUri?: vscode.Uri): Promise<void> {
|
||||
try {
|
||||
await openFiddle(context, fiddleId, workspaceUri);
|
||||
}
|
||||
catch (ex) {
|
||||
vscode.window.showErrorMessage(ex);
|
||||
console.log(ex);
|
||||
}
|
||||
}
|
||||
|
||||
async function openFiddle(context: vscode.ExtensionContext, fiddleId?: string, workspaceUri?: vscode.Uri) {
|
||||
if (workspaceUri && fiddleSourceControlRegister.has(workspaceUri)) { vscode.window.showErrorMessage("Another Fiddle was already open in this workspace. Open a new workspace first."); }
|
||||
|
||||
if (!fiddleId) {
|
||||
fiddleId = (await vscode.window.showInputBox({ prompt: 'Paste JSFiddle ID and optionally version', placeHolder: 'slug or slug/version, e.g. u8B29/1', value: 'demo' })) || '';
|
||||
}
|
||||
|
||||
const workspaceFolder =
|
||||
workspaceUri ?
|
||||
vscode.workspace.getWorkspaceFolder(workspaceUri) :
|
||||
await selectWorkspaceFolder(context, fiddleId);
|
||||
|
||||
if (! await clearWorkspaceFolder(workspaceFolder.uri)) { return; }
|
||||
|
||||
// show the file explorer with the three new files
|
||||
vscode.commands.executeCommand("workbench.view.explorer");
|
||||
|
||||
// register source control
|
||||
const fiddleSourceControl = await FiddleSourceControl.fromFiddleId(fiddleId, context, workspaceFolder, true);
|
||||
|
||||
registerFiddleSourceControl(fiddleSourceControl, context);
|
||||
showFiddleInEditor(fiddleSourceControl);
|
||||
}
|
||||
|
||||
async function showFiddleInEditor(fiddleSourceControl: FiddleSourceControl): Promise<void> {
|
||||
// open the 3 fiddle parts in 3 view columns
|
||||
await openDocumentInColumn(fiddleSourceControl.getRepository().createLocalResourcePath('html'), vscode.ViewColumn.One);
|
||||
await openDocumentInColumn(fiddleSourceControl.getRepository().createLocalResourcePath('js'), vscode.ViewColumn.Two);
|
||||
await openDocumentInColumn(fiddleSourceControl.getRepository().createLocalResourcePath('css'), vscode.ViewColumn.Three);
|
||||
}
|
||||
|
||||
function registerFiddleSourceControl(fiddleSourceControl: FiddleSourceControl, context: vscode.ExtensionContext) {
|
||||
// update the fiddle document content provider with the latest content
|
||||
jsFiddleDocumentContentProvider.updated(fiddleSourceControl.getFiddle());
|
||||
|
||||
// every time the repository is updated with new fiddle version, notify the content provider
|
||||
fiddleSourceControl.onRepositoryChange(fiddle => jsFiddleDocumentContentProvider.updated(fiddle));
|
||||
|
||||
if (fiddleSourceControlRegister.has(fiddleSourceControl.getWorkspaceFolder().uri)) {
|
||||
// the folder was already under source control
|
||||
const previousSourceControl = fiddleSourceControlRegister.get(fiddleSourceControl.getWorkspaceFolder().uri)!;
|
||||
previousSourceControl.dispose();
|
||||
}
|
||||
|
||||
fiddleSourceControlRegister.set(fiddleSourceControl.getWorkspaceFolder().uri, fiddleSourceControl);
|
||||
|
||||
context.subscriptions.push(fiddleSourceControl);
|
||||
}
|
||||
|
||||
function unregisterFiddleSourceControl(folderUri: vscode.Uri): void {
|
||||
if (fiddleSourceControlRegister.has(folderUri)) {
|
||||
const previousSourceControl = fiddleSourceControlRegister.get(folderUri)!;
|
||||
previousSourceControl.dispose();
|
||||
|
||||
fiddleSourceControlRegister.delete(folderUri);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* When the extension starts up, it must visit all workspace folders to see if any of them are fiddles.
|
||||
* @param context extension context
|
||||
*/
|
||||
async function initializeFromConfigurationFile(context: vscode.ExtensionContext): Promise<void> {
|
||||
if (!vscode.workspace.workspaceFolders) { return; }
|
||||
|
||||
const folderPromises = vscode.workspace.workspaceFolders.map(async (folder) => await initializeFolderFromConfigurationFile(folder, context));
|
||||
await Promise.all(folderPromises);
|
||||
}
|
||||
|
||||
async function initializeFolderFromConfigurationFile(folder: vscode.WorkspaceFolder, context: vscode.ExtensionContext): Promise<void> {
|
||||
const configurationPath = path.join(folder.uri.fsPath, CONFIGURATION_FILE);
|
||||
|
||||
const configFileExists = await afs.exists(configurationPath);
|
||||
|
||||
if (configFileExists) {
|
||||
const data = await afs.readFile(configurationPath);
|
||||
const fiddleConfiguration = <FiddleConfiguration>JSON.parse(data.toString(UTF8));
|
||||
const fiddleSourceControl = await FiddleSourceControl.fromConfiguration(fiddleConfiguration, folder, context, !fiddleConfiguration.downloaded);
|
||||
registerFiddleSourceControl(fiddleSourceControl, context);
|
||||
if (!fiddleConfiguration.downloaded) {
|
||||
// the fiddle was not downloaded before the extension restart, so let's show it now
|
||||
showFiddleInEditor(fiddleSourceControl);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function selectWorkspaceFolder(context: vscode.ExtensionContext, fiddleId: string): Promise<vscode.WorkspaceFolder | undefined> {
|
||||
let selectedFolder: vscode.WorkspaceFolder | undefined;
|
||||
let workspaceFolderUri: vscode.Uri | undefined;
|
||||
let workspaceFolderIndex: number | undefined;
|
||||
let folderOpeningMode: FolderOpeningMode;
|
||||
|
||||
const folderPicks: WorkspaceFolderPick[] = [newFolderPick];
|
||||
|
||||
if (vscode.workspace.workspaceFolders && vscode.workspace.workspaceFolders.length > 0) {
|
||||
folderPicks.push(newWorkspaceFolderPick);
|
||||
|
||||
for (const wf of vscode.workspace.workspaceFolders) {
|
||||
const content = await afs.readdir(wf.uri.fsPath);
|
||||
folderPicks.push(new ExistingWorkspaceFolderPick(wf, content));
|
||||
}
|
||||
}
|
||||
|
||||
const selectedFolderPick: WorkspaceFolderPick =
|
||||
folderPicks.length === 1 ?
|
||||
folderPicks[0] :
|
||||
await vscode.window.showQuickPick(folderPicks, {
|
||||
canPickMany: false, ignoreFocusOut: true, placeHolder: 'Pick workspace folder to create files in.'
|
||||
});
|
||||
|
||||
if (!selectedFolderPick) { return null; }
|
||||
|
||||
if (selectedFolderPick instanceof ExistingWorkspaceFolderPick) {
|
||||
selectedFolder = selectedFolderPick.workspaceFolder;
|
||||
workspaceFolderIndex = selectedFolder.index;
|
||||
workspaceFolderUri = selectedFolder.uri;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line prefer-const
|
||||
folderOpeningMode = selectedFolderPick.folderOpeningMode;
|
||||
|
||||
if (!workspaceFolderUri && !selectedFolder) {
|
||||
const folderUris = await vscode.window.showOpenDialog({ canSelectFolders: true, canSelectFiles: false, canSelectMany: false, openLabel: 'Select folder' });
|
||||
if (!folderUris) {
|
||||
return null;
|
||||
}
|
||||
|
||||
workspaceFolderUri = folderUris[0];
|
||||
// was such workspace folder already open?
|
||||
workspaceFolderIndex = vscode.workspace.workspaceFolders && firstIndex(vscode.workspace.workspaceFolders, (folder1: any) => folder1.uri.toString() === workspaceFolderUri!.toString());
|
||||
}
|
||||
|
||||
if (! await clearWorkspaceFolder(workspaceFolderUri)) { return null; }
|
||||
|
||||
const fiddleConfiguration = parseFiddleId(fiddleId);
|
||||
|
||||
// save folder configuration
|
||||
FiddleSourceControl.saveConfiguration(workspaceFolderUri, fiddleConfiguration);
|
||||
|
||||
if (folderOpeningMode === FolderOpeningMode.AddToWorkspace || folderOpeningMode === undefined) {
|
||||
const workSpacesToReplace = typeof workspaceFolderIndex === 'number' && workspaceFolderIndex > -1 ? 1 : 0;
|
||||
if (workspaceFolderIndex === undefined || workspaceFolderIndex < 0) { workspaceFolderIndex = 0; }
|
||||
|
||||
// replace or insert the workspace
|
||||
if (workspaceFolderUri) {
|
||||
vscode.workspace.updateWorkspaceFolders(workspaceFolderIndex, workSpacesToReplace, { uri: workspaceFolderUri });
|
||||
}
|
||||
}
|
||||
else if (folderOpeningMode === FolderOpeningMode.OpenFolder) {
|
||||
vscode.commands.executeCommand("vscode.openFolder", workspaceFolderUri);
|
||||
}
|
||||
|
||||
return selectedFolder;
|
||||
}
|
||||
|
||||
async function clearWorkspaceFolder(workspaceFolderUri: vscode.Uri): Promise<boolean> {
|
||||
|
||||
if (!workspaceFolderUri) { return undefined; }
|
||||
|
||||
// check if the workspace is empty, or clear it
|
||||
const existingWorkspaceFiles: string[] = await afs.readdir(workspaceFolderUri.fsPath);
|
||||
if (existingWorkspaceFiles.length > 0) {
|
||||
const answer = await vscode.window.showQuickPick(["Yes", "No"],
|
||||
{ placeHolder: `Remove ${existingWorkspaceFiles.length} file(s) from the folder ${workspaceFolderUri.fsPath} before cloning the remote repository?` });
|
||||
if (!answer) { return false; }
|
||||
|
||||
if (answer === "Yes") {
|
||||
existingWorkspaceFiles
|
||||
.forEach(async filename =>
|
||||
await afs.unlink(path.join(workspaceFolderUri.fsPath, filename)));
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
class RepositoryPick implements vscode.QuickPickItem {
|
||||
|
||||
constructor(public readonly fiddleSourceControl: FiddleSourceControl) { }
|
||||
|
||||
get label(): string {
|
||||
return this.fiddleSourceControl.getSourceControl().label;
|
||||
}
|
||||
}
|
||||
|
||||
async function openDocumentInColumn(fileName: string, column: vscode.ViewColumn): Promise<void> {
|
||||
const uri = vscode.Uri.file(fileName);
|
||||
|
||||
// assuming the file was saved, let's open it in a view column
|
||||
const doc = await vscode.workspace.openTextDocument(uri);
|
||||
|
||||
await vscode.window.showTextDocument(doc, { viewColumn: column });
|
||||
}
|
||||
|
||||
abstract class WorkspaceFolderPick implements vscode.QuickPickItem {
|
||||
abstract get label(): string;
|
||||
constructor(public folderOpeningMode: FolderOpeningMode) { }
|
||||
}
|
||||
|
||||
class ExistingWorkspaceFolderPick extends WorkspaceFolderPick {
|
||||
|
||||
constructor(public readonly workspaceFolder: vscode.WorkspaceFolder, private content: string[]) {
|
||||
super(FolderOpeningMode.AddToWorkspace);
|
||||
}
|
||||
|
||||
get label(): string {
|
||||
return this.workspaceFolder.name;
|
||||
}
|
||||
|
||||
get description(): string {
|
||||
return this.workspaceFolder.uri.fsPath;
|
||||
}
|
||||
|
||||
get detail(): string {
|
||||
return this.content.length ? `${this.content.length} files/directories may need to be removed..` : null;
|
||||
}
|
||||
}
|
||||
|
||||
class NewWorkspaceFolderPick extends WorkspaceFolderPick {
|
||||
constructor(public label: string, folderOpeningMode: FolderOpeningMode) {
|
||||
super(folderOpeningMode);
|
||||
}
|
||||
}
|
||||
|
||||
enum FolderOpeningMode { AddToWorkspace, OpenFolder }
|
||||
|
||||
const newWorkspaceFolderPick = new NewWorkspaceFolderPick("Select/create a local folder to add to this workspace...", FolderOpeningMode.AddToWorkspace);
|
||||
const newFolderPick = new NewWorkspaceFolderPick("Select/create a local folder...", FolderOpeningMode.OpenFolder);
|
||||
// The module 'vscode' contains the VS Code extensibility API
|
||||
// Import the module and reference it with the alias vscode in your code below
|
||||
import * as vscode from 'vscode';
|
||||
import { JSFIDDLE_SCHEME } from './fiddleRepository';
|
||||
import { FiddleSourceControl, CONFIGURATION_FILE } from './fiddleSourceControl';
|
||||
import { JSFiddleDocumentContentProvider } from './fiddleDocumentContentProvider';
|
||||
import * as path from 'path';
|
||||
import * as afs from './afs';
|
||||
import { FiddleConfiguration, parseFiddleId } from './fiddleConfiguration';
|
||||
import { firstIndex, UTF8 } from './util';
|
||||
|
||||
const SOURCE_CONTROL_OPEN_COMMAND = 'extension.source-control.open';
|
||||
let jsFiddleDocumentContentProvider: JSFiddleDocumentContentProvider;
|
||||
const fiddleSourceControlRegister = new Map<vscode.Uri, FiddleSourceControl>();
|
||||
|
||||
// this method is called when your extension is activated
|
||||
// your extension is activated the very first time the command is executed
|
||||
export async function activate(context: vscode.ExtensionContext) {
|
||||
console.log('Congratulations, your extension "source-control-sample" is now active!');
|
||||
|
||||
jsFiddleDocumentContentProvider = new JSFiddleDocumentContentProvider();
|
||||
|
||||
try {
|
||||
await initializeFromConfigurationFile(context);
|
||||
}
|
||||
catch (err) {
|
||||
console.log('Failed to initialize a Fiddle workspace.');
|
||||
vscode.window.showErrorMessage(err);
|
||||
}
|
||||
|
||||
const openCommand = vscode.commands.registerCommand(SOURCE_CONTROL_OPEN_COMMAND,
|
||||
(fiddleId?: string, workspaceUri?: vscode.Uri) => {
|
||||
tryOpenFiddle(context, fiddleId, workspaceUri);
|
||||
});
|
||||
context.subscriptions.push(openCommand);
|
||||
|
||||
context.subscriptions.push(vscode.workspace.registerTextDocumentContentProvider(JSFIDDLE_SCHEME, jsFiddleDocumentContentProvider));
|
||||
|
||||
context.subscriptions.push(vscode.commands.registerCommand("extension.source-control.refresh",
|
||||
async (sourceControlPane: vscode.SourceControl) => {
|
||||
const sourceControl = await pickSourceControl(sourceControlPane);
|
||||
if (sourceControl) { sourceControl.refresh(); }
|
||||
}));
|
||||
context.subscriptions.push(vscode.commands.registerCommand("extension.source-control.discard",
|
||||
async (sourceControlPane: vscode.SourceControl) => {
|
||||
const sourceControl = await pickSourceControl(sourceControlPane);
|
||||
if (sourceControl) { sourceControl.resetFilesToCheckedOutVersion(); }
|
||||
}));
|
||||
context.subscriptions.push(vscode.commands.registerCommand("extension.source-control.commit",
|
||||
async (sourceControlPane: vscode.SourceControl) => {
|
||||
const sourceControl = await pickSourceControl(sourceControlPane);
|
||||
if (sourceControl) { sourceControl.commitAll(); }
|
||||
}));
|
||||
context.subscriptions.push(vscode.commands.registerCommand("extension.source-control.checkout",
|
||||
async (sourceControl: FiddleSourceControl, newVersion?: number) => {
|
||||
sourceControl = sourceControl || await pickSourceControl(null);
|
||||
if (sourceControl) { sourceControl.tryCheckout(newVersion); }
|
||||
}));
|
||||
context.subscriptions.push(vscode.commands.registerCommand("extension.source-control.browse",
|
||||
async (sourceControlPane: vscode.SourceControl) => {
|
||||
const sourceControl = await pickSourceControl(sourceControlPane);
|
||||
if (sourceControl) { sourceControl.openInBrowser(); }
|
||||
}));
|
||||
|
||||
|
||||
context.subscriptions.push(vscode.workspace.onDidChangeWorkspaceFolders(e => {
|
||||
try {
|
||||
// initialize new source control for manually added workspace folders
|
||||
e.added.forEach(wf => {
|
||||
initializeFolderFromConfigurationFile(wf, context);
|
||||
});
|
||||
} catch (ex) {
|
||||
vscode.window.showErrorMessage(ex.message);
|
||||
} finally {
|
||||
// dispose source control for removed workspace folders
|
||||
e.removed.forEach(wf => {
|
||||
unregisterFiddleSourceControl(wf.uri);
|
||||
});
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
async function pickSourceControl(sourceControlPane: vscode.SourceControl): Promise<FiddleSourceControl | undefined> {
|
||||
if (sourceControlPane) {
|
||||
return fiddleSourceControlRegister.get(sourceControlPane.rootUri);
|
||||
}
|
||||
|
||||
// todo: when/if the SourceControl exposes a 'selected' property, use that instead
|
||||
|
||||
if (fiddleSourceControlRegister.size === 0) { return undefined; }
|
||||
else if (fiddleSourceControlRegister.size === 1) { return [...fiddleSourceControlRegister.values()][0]; }
|
||||
else {
|
||||
|
||||
const picks = [...fiddleSourceControlRegister.values()].map(fsc => new RepositoryPick(fsc));
|
||||
|
||||
if (vscode.window.activeTextEditor) {
|
||||
const activeWorkspaceFolder = vscode.workspace.getWorkspaceFolder(vscode.window.activeTextEditor.document.uri);
|
||||
const activeSourceControl = activeWorkspaceFolder && fiddleSourceControlRegister.get(activeWorkspaceFolder.uri);
|
||||
const activeIndex = firstIndex(picks, pick => pick.fiddleSourceControl === activeSourceControl);
|
||||
|
||||
// if there is an active editor, move its folder to be the first in the pick list
|
||||
if (activeIndex > -1) {
|
||||
picks.unshift(...picks.splice(activeIndex, 1));
|
||||
}
|
||||
}
|
||||
|
||||
const pick = await vscode.window.showQuickPick(picks, { placeHolder: 'Select repository' });
|
||||
return pick && pick.fiddleSourceControl;
|
||||
}
|
||||
}
|
||||
|
||||
async function tryOpenFiddle(context: vscode.ExtensionContext, fiddleId?: string, workspaceUri?: vscode.Uri): Promise<void> {
|
||||
try {
|
||||
await openFiddle(context, fiddleId, workspaceUri);
|
||||
}
|
||||
catch (ex) {
|
||||
vscode.window.showErrorMessage(ex);
|
||||
console.log(ex);
|
||||
}
|
||||
}
|
||||
|
||||
async function openFiddle(context: vscode.ExtensionContext, fiddleId?: string, workspaceUri?: vscode.Uri) {
|
||||
if (workspaceUri && fiddleSourceControlRegister.has(workspaceUri)) { vscode.window.showErrorMessage("Another Fiddle was already open in this workspace. Open a new workspace first."); }
|
||||
|
||||
if (!fiddleId) {
|
||||
fiddleId = (await vscode.window.showInputBox({ prompt: 'Paste JSFiddle ID and optionally version', placeHolder: 'slug or slug/version, e.g. u8B29/1', value: 'demo' })) || '';
|
||||
}
|
||||
|
||||
const workspaceFolder =
|
||||
workspaceUri ?
|
||||
vscode.workspace.getWorkspaceFolder(workspaceUri) :
|
||||
await selectWorkspaceFolder(context, fiddleId);
|
||||
|
||||
if (! await clearWorkspaceFolder(workspaceFolder.uri)) { return; }
|
||||
|
||||
// show the file explorer with the three new files
|
||||
vscode.commands.executeCommand("workbench.view.explorer");
|
||||
|
||||
// register source control
|
||||
const fiddleSourceControl = await FiddleSourceControl.fromFiddleId(fiddleId, context, workspaceFolder, true);
|
||||
|
||||
registerFiddleSourceControl(fiddleSourceControl, context);
|
||||
showFiddleInEditor(fiddleSourceControl);
|
||||
}
|
||||
|
||||
async function showFiddleInEditor(fiddleSourceControl: FiddleSourceControl): Promise<void> {
|
||||
// open the 3 fiddle parts in 3 view columns
|
||||
await openDocumentInColumn(fiddleSourceControl.getRepository().createLocalResourcePath('html'), vscode.ViewColumn.One);
|
||||
await openDocumentInColumn(fiddleSourceControl.getRepository().createLocalResourcePath('js'), vscode.ViewColumn.Two);
|
||||
await openDocumentInColumn(fiddleSourceControl.getRepository().createLocalResourcePath('css'), vscode.ViewColumn.Three);
|
||||
}
|
||||
|
||||
function registerFiddleSourceControl(fiddleSourceControl: FiddleSourceControl, context: vscode.ExtensionContext) {
|
||||
// update the fiddle document content provider with the latest content
|
||||
jsFiddleDocumentContentProvider.updated(fiddleSourceControl.getFiddle());
|
||||
|
||||
// every time the repository is updated with new fiddle version, notify the content provider
|
||||
fiddleSourceControl.onRepositoryChange(fiddle => jsFiddleDocumentContentProvider.updated(fiddle));
|
||||
|
||||
if (fiddleSourceControlRegister.has(fiddleSourceControl.getWorkspaceFolder().uri)) {
|
||||
// the folder was already under source control
|
||||
const previousSourceControl = fiddleSourceControlRegister.get(fiddleSourceControl.getWorkspaceFolder().uri)!;
|
||||
previousSourceControl.dispose();
|
||||
}
|
||||
|
||||
fiddleSourceControlRegister.set(fiddleSourceControl.getWorkspaceFolder().uri, fiddleSourceControl);
|
||||
|
||||
context.subscriptions.push(fiddleSourceControl);
|
||||
}
|
||||
|
||||
function unregisterFiddleSourceControl(folderUri: vscode.Uri): void {
|
||||
if (fiddleSourceControlRegister.has(folderUri)) {
|
||||
const previousSourceControl = fiddleSourceControlRegister.get(folderUri)!;
|
||||
previousSourceControl.dispose();
|
||||
|
||||
fiddleSourceControlRegister.delete(folderUri);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* When the extension starts up, it must visit all workspace folders to see if any of them are fiddles.
|
||||
* @param context extension context
|
||||
*/
|
||||
async function initializeFromConfigurationFile(context: vscode.ExtensionContext): Promise<void> {
|
||||
if (!vscode.workspace.workspaceFolders) { return; }
|
||||
|
||||
const folderPromises = vscode.workspace.workspaceFolders.map(async (folder) => await initializeFolderFromConfigurationFile(folder, context));
|
||||
await Promise.all(folderPromises);
|
||||
}
|
||||
|
||||
async function initializeFolderFromConfigurationFile(folder: vscode.WorkspaceFolder, context: vscode.ExtensionContext): Promise<void> {
|
||||
const configurationPath = path.join(folder.uri.fsPath, CONFIGURATION_FILE);
|
||||
|
||||
const configFileExists = await afs.exists(configurationPath);
|
||||
|
||||
if (configFileExists) {
|
||||
const data = await afs.readFile(configurationPath);
|
||||
const fiddleConfiguration = <FiddleConfiguration>JSON.parse(data.toString(UTF8));
|
||||
const fiddleSourceControl = await FiddleSourceControl.fromConfiguration(fiddleConfiguration, folder, context, !fiddleConfiguration.downloaded);
|
||||
registerFiddleSourceControl(fiddleSourceControl, context);
|
||||
if (!fiddleConfiguration.downloaded) {
|
||||
// the fiddle was not downloaded before the extension restart, so let's show it now
|
||||
showFiddleInEditor(fiddleSourceControl);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function selectWorkspaceFolder(context: vscode.ExtensionContext, fiddleId: string): Promise<vscode.WorkspaceFolder | undefined> {
|
||||
let selectedFolder: vscode.WorkspaceFolder | undefined;
|
||||
let workspaceFolderUri: vscode.Uri | undefined;
|
||||
let workspaceFolderIndex: number | undefined;
|
||||
let folderOpeningMode: FolderOpeningMode;
|
||||
|
||||
const folderPicks: WorkspaceFolderPick[] = [newFolderPick];
|
||||
|
||||
if (vscode.workspace.workspaceFolders && vscode.workspace.workspaceFolders.length > 0) {
|
||||
folderPicks.push(newWorkspaceFolderPick);
|
||||
|
||||
for (const wf of vscode.workspace.workspaceFolders) {
|
||||
const content = await afs.readdir(wf.uri.fsPath);
|
||||
folderPicks.push(new ExistingWorkspaceFolderPick(wf, content));
|
||||
}
|
||||
}
|
||||
|
||||
const selectedFolderPick: WorkspaceFolderPick =
|
||||
folderPicks.length === 1 ?
|
||||
folderPicks[0] :
|
||||
await vscode.window.showQuickPick(folderPicks, {
|
||||
canPickMany: false, ignoreFocusOut: true, placeHolder: 'Pick workspace folder to create files in.'
|
||||
});
|
||||
|
||||
if (!selectedFolderPick) { return null; }
|
||||
|
||||
if (selectedFolderPick instanceof ExistingWorkspaceFolderPick) {
|
||||
selectedFolder = selectedFolderPick.workspaceFolder;
|
||||
workspaceFolderIndex = selectedFolder.index;
|
||||
workspaceFolderUri = selectedFolder.uri;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line prefer-const
|
||||
folderOpeningMode = selectedFolderPick.folderOpeningMode;
|
||||
|
||||
if (!workspaceFolderUri && !selectedFolder) {
|
||||
const folderUris = await vscode.window.showOpenDialog({ canSelectFolders: true, canSelectFiles: false, canSelectMany: false, openLabel: 'Select folder' });
|
||||
if (!folderUris) {
|
||||
return null;
|
||||
}
|
||||
|
||||
workspaceFolderUri = folderUris[0];
|
||||
// was such workspace folder already open?
|
||||
workspaceFolderIndex = vscode.workspace.workspaceFolders && firstIndex(vscode.workspace.workspaceFolders, (folder1: any) => folder1.uri.toString() === workspaceFolderUri!.toString());
|
||||
}
|
||||
|
||||
if (! await clearWorkspaceFolder(workspaceFolderUri)) { return null; }
|
||||
|
||||
const fiddleConfiguration = parseFiddleId(fiddleId);
|
||||
|
||||
// save folder configuration
|
||||
FiddleSourceControl.saveConfiguration(workspaceFolderUri, fiddleConfiguration);
|
||||
|
||||
if (folderOpeningMode === FolderOpeningMode.AddToWorkspace || folderOpeningMode === undefined) {
|
||||
const workSpacesToReplace = typeof workspaceFolderIndex === 'number' && workspaceFolderIndex > -1 ? 1 : 0;
|
||||
if (workspaceFolderIndex === undefined || workspaceFolderIndex < 0) { workspaceFolderIndex = 0; }
|
||||
|
||||
// replace or insert the workspace
|
||||
if (workspaceFolderUri) {
|
||||
vscode.workspace.updateWorkspaceFolders(workspaceFolderIndex, workSpacesToReplace, { uri: workspaceFolderUri });
|
||||
}
|
||||
}
|
||||
else if (folderOpeningMode === FolderOpeningMode.OpenFolder) {
|
||||
vscode.commands.executeCommand("vscode.openFolder", workspaceFolderUri);
|
||||
}
|
||||
|
||||
return selectedFolder;
|
||||
}
|
||||
|
||||
async function clearWorkspaceFolder(workspaceFolderUri: vscode.Uri): Promise<boolean> {
|
||||
|
||||
if (!workspaceFolderUri) { return undefined; }
|
||||
|
||||
// check if the workspace is empty, or clear it
|
||||
const existingWorkspaceFiles: string[] = await afs.readdir(workspaceFolderUri.fsPath);
|
||||
if (existingWorkspaceFiles.length > 0) {
|
||||
const answer = await vscode.window.showQuickPick(["Yes", "No"],
|
||||
{ placeHolder: `Remove ${existingWorkspaceFiles.length} file(s) from the folder ${workspaceFolderUri.fsPath} before cloning the remote repository?` });
|
||||
if (!answer) { return false; }
|
||||
|
||||
if (answer === "Yes") {
|
||||
existingWorkspaceFiles
|
||||
.forEach(async filename =>
|
||||
await afs.unlink(path.join(workspaceFolderUri.fsPath, filename)));
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
class RepositoryPick implements vscode.QuickPickItem {
|
||||
|
||||
constructor(public readonly fiddleSourceControl: FiddleSourceControl) { }
|
||||
|
||||
get label(): string {
|
||||
return this.fiddleSourceControl.getSourceControl().label;
|
||||
}
|
||||
}
|
||||
|
||||
async function openDocumentInColumn(fileName: string, column: vscode.ViewColumn): Promise<void> {
|
||||
const uri = vscode.Uri.file(fileName);
|
||||
|
||||
// assuming the file was saved, let's open it in a view column
|
||||
const doc = await vscode.workspace.openTextDocument(uri);
|
||||
|
||||
await vscode.window.showTextDocument(doc, { viewColumn: column });
|
||||
}
|
||||
|
||||
abstract class WorkspaceFolderPick implements vscode.QuickPickItem {
|
||||
abstract get label(): string;
|
||||
constructor(public folderOpeningMode: FolderOpeningMode) { }
|
||||
}
|
||||
|
||||
class ExistingWorkspaceFolderPick extends WorkspaceFolderPick {
|
||||
|
||||
constructor(public readonly workspaceFolder: vscode.WorkspaceFolder, private content: string[]) {
|
||||
super(FolderOpeningMode.AddToWorkspace);
|
||||
}
|
||||
|
||||
get label(): string {
|
||||
return this.workspaceFolder.name;
|
||||
}
|
||||
|
||||
get description(): string {
|
||||
return this.workspaceFolder.uri.fsPath;
|
||||
}
|
||||
|
||||
get detail(): string {
|
||||
return this.content.length ? `${this.content.length} files/directories may need to be removed..` : null;
|
||||
}
|
||||
}
|
||||
|
||||
class NewWorkspaceFolderPick extends WorkspaceFolderPick {
|
||||
constructor(public label: string, folderOpeningMode: FolderOpeningMode) {
|
||||
super(folderOpeningMode);
|
||||
}
|
||||
}
|
||||
|
||||
enum FolderOpeningMode { AddToWorkspace, OpenFolder }
|
||||
|
||||
const newWorkspaceFolderPick = new NewWorkspaceFolderPick("Select/create a local folder to add to this workspace...", FolderOpeningMode.AddToWorkspace);
|
||||
const newFolderPick = new NewWorkspaceFolderPick("Select/create a local folder...", FolderOpeningMode.OpenFolder);
|
||||
|
||||
@ -1,13 +1,13 @@
|
||||
export interface FiddleConfiguration {
|
||||
readonly slug: string;
|
||||
readonly version?: number;
|
||||
readonly downloaded: boolean;
|
||||
}
|
||||
|
||||
export function parseFiddleId(id: string): FiddleConfiguration {
|
||||
const idFragments = id.split('/');
|
||||
const fiddleSlug = idFragments[0];
|
||||
const fiddleVersion = idFragments.length > 1 ? parseInt(id.split('/')[1]) : undefined;
|
||||
|
||||
return { slug: fiddleSlug, version: fiddleVersion, downloaded: false };
|
||||
export interface FiddleConfiguration {
|
||||
readonly slug: string;
|
||||
readonly version?: number;
|
||||
readonly downloaded: boolean;
|
||||
}
|
||||
|
||||
export function parseFiddleId(id: string): FiddleConfiguration {
|
||||
const idFragments = id.split('/');
|
||||
const fiddleSlug = idFragments[0];
|
||||
const fiddleVersion = idFragments.length > 1 ? parseInt(id.split('/')[1]) : undefined;
|
||||
|
||||
return { slug: fiddleSlug, version: fiddleVersion, downloaded: false };
|
||||
}
|
||||
@ -1,43 +1,43 @@
|
||||
import { CancellationToken, ProviderResult, TextDocumentContentProvider, Event, Uri, EventEmitter, Disposable } from "vscode";
|
||||
import { toExtension, JSFIDDLE_SCHEME, Fiddle } from "./fiddleRepository";
|
||||
import { basename } from "path";
|
||||
|
||||
/**
|
||||
* Provides the content of the JS Fiddle documents as fetched from the server i.e. without the local edits.
|
||||
* This is used for the source control diff.
|
||||
*/
|
||||
export class JSFiddleDocumentContentProvider implements TextDocumentContentProvider, Disposable {
|
||||
private _onDidChange = new EventEmitter<Uri>();
|
||||
private fiddles = new Map<string, Fiddle>(); // this assumes each fiddle is only open once per workspace
|
||||
|
||||
get onDidChange(): Event<Uri> {
|
||||
return this._onDidChange.event;
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this._onDidChange.dispose();
|
||||
}
|
||||
|
||||
updated(newFiddle: Fiddle): void {
|
||||
this.fiddles.set(newFiddle.slug, newFiddle);
|
||||
|
||||
// let's assume all 3 documents actually changed and notify the quick-diff
|
||||
this._onDidChange.fire(Uri.parse(`${JSFIDDLE_SCHEME}:${newFiddle.slug}.html`));
|
||||
this._onDidChange.fire(Uri.parse(`${JSFIDDLE_SCHEME}:${newFiddle.slug}.css`));
|
||||
this._onDidChange.fire(Uri.parse(`${JSFIDDLE_SCHEME}:${newFiddle.slug}.js`));
|
||||
}
|
||||
|
||||
provideTextDocumentContent(uri: Uri, token: CancellationToken): ProviderResult<string> {
|
||||
if (token.isCancellationRequested) { return "Canceled"; }
|
||||
|
||||
let fiddleSlug = basename(uri.fsPath);
|
||||
// strip off the file extension
|
||||
fiddleSlug = fiddleSlug.split('.').slice(0, -1).join('.');
|
||||
const fiddlePart = toExtension(uri);
|
||||
|
||||
const fiddle = this.fiddles.get(fiddleSlug);
|
||||
if (!fiddle) { return "Resource not found: " + uri.toString(); }
|
||||
|
||||
return fiddle.data[fiddlePart];
|
||||
}
|
||||
import { CancellationToken, ProviderResult, TextDocumentContentProvider, Event, Uri, EventEmitter, Disposable } from "vscode";
|
||||
import { toExtension, JSFIDDLE_SCHEME, Fiddle } from "./fiddleRepository";
|
||||
import { basename } from "path";
|
||||
|
||||
/**
|
||||
* Provides the content of the JS Fiddle documents as fetched from the server i.e. without the local edits.
|
||||
* This is used for the source control diff.
|
||||
*/
|
||||
export class JSFiddleDocumentContentProvider implements TextDocumentContentProvider, Disposable {
|
||||
private _onDidChange = new EventEmitter<Uri>();
|
||||
private fiddles = new Map<string, Fiddle>(); // this assumes each fiddle is only open once per workspace
|
||||
|
||||
get onDidChange(): Event<Uri> {
|
||||
return this._onDidChange.event;
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this._onDidChange.dispose();
|
||||
}
|
||||
|
||||
updated(newFiddle: Fiddle): void {
|
||||
this.fiddles.set(newFiddle.slug, newFiddle);
|
||||
|
||||
// let's assume all 3 documents actually changed and notify the quick-diff
|
||||
this._onDidChange.fire(Uri.parse(`${JSFIDDLE_SCHEME}:${newFiddle.slug}.html`));
|
||||
this._onDidChange.fire(Uri.parse(`${JSFIDDLE_SCHEME}:${newFiddle.slug}.css`));
|
||||
this._onDidChange.fire(Uri.parse(`${JSFIDDLE_SCHEME}:${newFiddle.slug}.js`));
|
||||
}
|
||||
|
||||
provideTextDocumentContent(uri: Uri, token: CancellationToken): ProviderResult<string> {
|
||||
if (token.isCancellationRequested) { return "Canceled"; }
|
||||
|
||||
let fiddleSlug = basename(uri.fsPath);
|
||||
// strip off the file extension
|
||||
fiddleSlug = fiddleSlug.split('.').slice(0, -1).join('.');
|
||||
const fiddlePart = toExtension(uri);
|
||||
|
||||
const fiddle = this.fiddles.get(fiddleSlug);
|
||||
if (!fiddle) { return "Resource not found: " + uri.toString(); }
|
||||
|
||||
return fiddle.data[fiddlePart];
|
||||
}
|
||||
}
|
||||
@ -1,165 +1,165 @@
|
||||
import JSFiddle = require("jsfiddle");
|
||||
import { QuickDiffProvider, Uri, CancellationToken, ProviderResult, WorkspaceFolder, workspace, window, env } from "vscode";
|
||||
import * as path from 'path';
|
||||
|
||||
/** Represents one JSFiddle data and meta-data. */
|
||||
export class Fiddle {
|
||||
constructor(public slug: string, public version: number, public data: FiddleData) { }
|
||||
}
|
||||
|
||||
/** Represents JSFiddle HTML, JavaScript and CSS text. */
|
||||
export interface FiddleData {
|
||||
html: string;
|
||||
js: string;
|
||||
css: string;
|
||||
}
|
||||
|
||||
export function areIdentical(first: FiddleData, second: FiddleData): boolean {
|
||||
return first.html === second.html
|
||||
&& first.css === second.css
|
||||
&& first.js === second.js;
|
||||
}
|
||||
|
||||
export const JSFIDDLE_SCHEME = 'jsfiddle';
|
||||
|
||||
export class FiddleRepository implements QuickDiffProvider {
|
||||
|
||||
constructor(private workspaceFolder: WorkspaceFolder, private fiddleSlug: string) { }
|
||||
|
||||
provideOriginalResource?(uri: Uri, token: CancellationToken): ProviderResult<Uri> {
|
||||
// converts the local file uri to jsfiddle:file.ext
|
||||
const relativePath = workspace.asRelativePath(uri.fsPath);
|
||||
return Uri.parse(`${JSFIDDLE_SCHEME}:${relativePath}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enumerates the resources under source control.
|
||||
*/
|
||||
provideSourceControlledResources(): Uri[] {
|
||||
return [
|
||||
Uri.file(this.createLocalResourcePath('html')),
|
||||
Uri.file(this.createLocalResourcePath('js')),
|
||||
Uri.file(this.createLocalResourcePath('css'))];
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a local file path in the local workspace that corresponds to the part of the
|
||||
* fiddle denoted by the given extension.
|
||||
*
|
||||
* @param extension fiddle part, which is also used as a file extension
|
||||
* @returns path of the locally cloned fiddle resource ending with the given extension
|
||||
*/
|
||||
createLocalResourcePath(extension: string) {
|
||||
return path.join(this.workspaceFolder.uri.fsPath, this.fiddleSlug + '.' + extension);
|
||||
}
|
||||
}
|
||||
|
||||
const DEMO: FiddleData[] = [
|
||||
{
|
||||
html: '<div class="hi">Hi</div>',
|
||||
css: `.hi {\n color: red;\n}`,
|
||||
js: '$(".hi").fadeOut();'
|
||||
}
|
||||
];
|
||||
|
||||
// emulates prior versions mock-committed in previous sessions
|
||||
let demoVersionOffset: number | undefined = undefined;
|
||||
|
||||
export async function downloadFiddle(slug: string, version: number | undefined): Promise<Fiddle> {
|
||||
|
||||
if (slug === "demo") {
|
||||
// use mock fiddle
|
||||
if (demoVersionOffset === undefined && version === undefined) { version = 0; }
|
||||
if (demoVersionOffset === undefined) { demoVersionOffset = version; }
|
||||
const maxDemoVersion = DEMO.length - 1 + demoVersionOffset;
|
||||
if (version === undefined) { version = maxDemoVersion; }
|
||||
|
||||
if (version >= 0 && version <= maxDemoVersion) {
|
||||
// mock all versions committed in previous sessions by the first version
|
||||
const index = Math.max(0, version - demoVersionOffset);
|
||||
const fiddleData = DEMO[index];
|
||||
return new Fiddle(slug, version, fiddleData);
|
||||
}
|
||||
else {
|
||||
throw new Error("Invalid demo fiddle version.");
|
||||
}
|
||||
}
|
||||
|
||||
const id = toFiddleId(slug, version);
|
||||
|
||||
return new Promise<Fiddle>((resolve, reject) => {
|
||||
JSFiddle.getFiddle(id, (err: any, fiddleData: any) => {
|
||||
// handle error
|
||||
if (err) { reject(err); }
|
||||
|
||||
const fiddle = new Fiddle(slug, version, fiddleData);
|
||||
|
||||
resolve(fiddle);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export async function uploadFiddle(slug: string, version: number, html: string, js: string, css: string): Promise<Fiddle | undefined> {
|
||||
|
||||
if (slug === "demo") {
|
||||
// using mock fiddle
|
||||
const fiddleData: FiddleData = { html: html, js: js, css: css };
|
||||
DEMO.push(fiddleData);
|
||||
return new Fiddle(slug, version, fiddleData);
|
||||
}
|
||||
else {
|
||||
|
||||
const answer = await window.showQuickPick(["Yes, open in the browser. I will paste the new Fiddle code, discard changes, refresh source control and checkout latest.", "No, I was just clicking around."],
|
||||
{ placeHolder: "JS Fiddle saving is not supported. Do you want to open the JSFiddle in the browser?" });
|
||||
|
||||
if (answer && answer.toLowerCase().startsWith("yes")) {
|
||||
env.openExternal(Uri.parse(`https://jsfiddle.net/${slug}/`));
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (false) {
|
||||
// this, sadly, does not work as advertised
|
||||
const data = {
|
||||
slug: slug,
|
||||
version: version,
|
||||
html: html,
|
||||
js: js,
|
||||
css: css
|
||||
};
|
||||
|
||||
return new Promise<Fiddle>((resolve, reject) => {
|
||||
JSFiddle.saveFiddle(data, (err: any, fiddleData: any) => {
|
||||
// handle error
|
||||
if (err) {
|
||||
reject(err);
|
||||
}
|
||||
else {
|
||||
const fiddle = new Fiddle(slug, version, fiddleData);
|
||||
|
||||
resolve(fiddle);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function toFiddleId(slug: string, version: number | undefined): string {
|
||||
if (version === undefined) {
|
||||
return slug;
|
||||
}
|
||||
else {
|
||||
return slug + '/' + version;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets extension trimming the dot character.
|
||||
* @param uri document uri
|
||||
*/
|
||||
export function toExtension(uri: Uri): string {
|
||||
return path.extname(uri.fsPath).substr(1);
|
||||
import JSFiddle = require("jsfiddle");
|
||||
import { QuickDiffProvider, Uri, CancellationToken, ProviderResult, WorkspaceFolder, workspace, window, env } from "vscode";
|
||||
import * as path from 'path';
|
||||
|
||||
/** Represents one JSFiddle data and meta-data. */
|
||||
export class Fiddle {
|
||||
constructor(public slug: string, public version: number, public data: FiddleData) { }
|
||||
}
|
||||
|
||||
/** Represents JSFiddle HTML, JavaScript and CSS text. */
|
||||
export interface FiddleData {
|
||||
html: string;
|
||||
js: string;
|
||||
css: string;
|
||||
}
|
||||
|
||||
export function areIdentical(first: FiddleData, second: FiddleData): boolean {
|
||||
return first.html === second.html
|
||||
&& first.css === second.css
|
||||
&& first.js === second.js;
|
||||
}
|
||||
|
||||
export const JSFIDDLE_SCHEME = 'jsfiddle';
|
||||
|
||||
export class FiddleRepository implements QuickDiffProvider {
|
||||
|
||||
constructor(private workspaceFolder: WorkspaceFolder, private fiddleSlug: string) { }
|
||||
|
||||
provideOriginalResource?(uri: Uri, token: CancellationToken): ProviderResult<Uri> {
|
||||
// converts the local file uri to jsfiddle:file.ext
|
||||
const relativePath = workspace.asRelativePath(uri.fsPath);
|
||||
return Uri.parse(`${JSFIDDLE_SCHEME}:${relativePath}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enumerates the resources under source control.
|
||||
*/
|
||||
provideSourceControlledResources(): Uri[] {
|
||||
return [
|
||||
Uri.file(this.createLocalResourcePath('html')),
|
||||
Uri.file(this.createLocalResourcePath('js')),
|
||||
Uri.file(this.createLocalResourcePath('css'))];
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a local file path in the local workspace that corresponds to the part of the
|
||||
* fiddle denoted by the given extension.
|
||||
*
|
||||
* @param extension fiddle part, which is also used as a file extension
|
||||
* @returns path of the locally cloned fiddle resource ending with the given extension
|
||||
*/
|
||||
createLocalResourcePath(extension: string) {
|
||||
return path.join(this.workspaceFolder.uri.fsPath, this.fiddleSlug + '.' + extension);
|
||||
}
|
||||
}
|
||||
|
||||
const DEMO: FiddleData[] = [
|
||||
{
|
||||
html: '<div class="hi">Hi</div>',
|
||||
css: `.hi {\n color: red;\n}`,
|
||||
js: '$(".hi").fadeOut();'
|
||||
}
|
||||
];
|
||||
|
||||
// emulates prior versions mock-committed in previous sessions
|
||||
let demoVersionOffset: number | undefined = undefined;
|
||||
|
||||
export async function downloadFiddle(slug: string, version: number | undefined): Promise<Fiddle> {
|
||||
|
||||
if (slug === "demo") {
|
||||
// use mock fiddle
|
||||
if (demoVersionOffset === undefined && version === undefined) { version = 0; }
|
||||
if (demoVersionOffset === undefined) { demoVersionOffset = version; }
|
||||
const maxDemoVersion = DEMO.length - 1 + demoVersionOffset;
|
||||
if (version === undefined) { version = maxDemoVersion; }
|
||||
|
||||
if (version >= 0 && version <= maxDemoVersion) {
|
||||
// mock all versions committed in previous sessions by the first version
|
||||
const index = Math.max(0, version - demoVersionOffset);
|
||||
const fiddleData = DEMO[index];
|
||||
return new Fiddle(slug, version, fiddleData);
|
||||
}
|
||||
else {
|
||||
throw new Error("Invalid demo fiddle version.");
|
||||
}
|
||||
}
|
||||
|
||||
const id = toFiddleId(slug, version);
|
||||
|
||||
return new Promise<Fiddle>((resolve, reject) => {
|
||||
JSFiddle.getFiddle(id, (err: any, fiddleData: any) => {
|
||||
// handle error
|
||||
if (err) { reject(err); }
|
||||
|
||||
const fiddle = new Fiddle(slug, version, fiddleData);
|
||||
|
||||
resolve(fiddle);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export async function uploadFiddle(slug: string, version: number, html: string, js: string, css: string): Promise<Fiddle | undefined> {
|
||||
|
||||
if (slug === "demo") {
|
||||
// using mock fiddle
|
||||
const fiddleData: FiddleData = { html: html, js: js, css: css };
|
||||
DEMO.push(fiddleData);
|
||||
return new Fiddle(slug, version, fiddleData);
|
||||
}
|
||||
else {
|
||||
|
||||
const answer = await window.showQuickPick(["Yes, open in the browser. I will paste the new Fiddle code, discard changes, refresh source control and checkout latest.", "No, I was just clicking around."],
|
||||
{ placeHolder: "JS Fiddle saving is not supported. Do you want to open the JSFiddle in the browser?" });
|
||||
|
||||
if (answer && answer.toLowerCase().startsWith("yes")) {
|
||||
env.openExternal(Uri.parse(`https://jsfiddle.net/${slug}/`));
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (false) {
|
||||
// this, sadly, does not work as advertised
|
||||
const data = {
|
||||
slug: slug,
|
||||
version: version,
|
||||
html: html,
|
||||
js: js,
|
||||
css: css
|
||||
};
|
||||
|
||||
return new Promise<Fiddle>((resolve, reject) => {
|
||||
JSFiddle.saveFiddle(data, (err: any, fiddleData: any) => {
|
||||
// handle error
|
||||
if (err) {
|
||||
reject(err);
|
||||
}
|
||||
else {
|
||||
const fiddle = new Fiddle(slug, version, fiddleData);
|
||||
|
||||
resolve(fiddle);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function toFiddleId(slug: string, version: number | undefined): string {
|
||||
if (version === undefined) {
|
||||
return slug;
|
||||
}
|
||||
else {
|
||||
return slug + '/' + version;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets extension trimming the dot character.
|
||||
* @param uri document uri
|
||||
*/
|
||||
export function toExtension(uri: Uri): string {
|
||||
return path.extname(uri.fsPath).substr(1);
|
||||
}
|
||||
@ -1,354 +1,354 @@
|
||||
import * as vscode from 'vscode';
|
||||
import { FiddleRepository, toExtension, downloadFiddle, areIdentical, uploadFiddle, Fiddle, toFiddleId } from './fiddleRepository';
|
||||
import * as path from 'path';
|
||||
import * as afs from './afs';
|
||||
import { FiddleConfiguration, parseFiddleId } from './fiddleConfiguration';
|
||||
import { UTF8 } from './util';
|
||||
|
||||
export const CONFIGURATION_FILE = '.jsfiddle';
|
||||
|
||||
export class FiddleSourceControl implements vscode.Disposable {
|
||||
private jsFiddleScm: vscode.SourceControl;
|
||||
private changedResources: vscode.SourceControlResourceGroup;
|
||||
private fiddleRepository: FiddleRepository;
|
||||
private latestFiddleVersion: number = Number.POSITIVE_INFINITY; // until actual value is established
|
||||
private _onRepositoryChange = new vscode.EventEmitter<Fiddle>();
|
||||
private timeout?: NodeJS.Timer;
|
||||
private fiddle!: Fiddle;
|
||||
|
||||
constructor(context: vscode.ExtensionContext, private readonly workspaceFolder: vscode.WorkspaceFolder, fiddle: Fiddle, download: boolean) {
|
||||
this.jsFiddleScm = vscode.scm.createSourceControl('jsfiddle', 'JSFiddle #' + fiddle.slug, workspaceFolder.uri);
|
||||
this.changedResources = this.jsFiddleScm.createResourceGroup('workingTree', 'Changes');
|
||||
this.fiddleRepository = new FiddleRepository(workspaceFolder, fiddle.slug);
|
||||
this.jsFiddleScm.quickDiffProvider = this.fiddleRepository;
|
||||
this.jsFiddleScm.inputBox.placeholder = 'Message is ignored by JS Fiddle :-]';
|
||||
|
||||
const fileSystemWatcher = vscode.workspace.createFileSystemWatcher(new vscode.RelativePattern(workspaceFolder, "*.*"));
|
||||
fileSystemWatcher.onDidChange(uri => this.onResourceChange(uri), context.subscriptions);
|
||||
fileSystemWatcher.onDidCreate(uri => this.onResourceChange(uri), context.subscriptions);
|
||||
fileSystemWatcher.onDidDelete(uri => this.onResourceChange(uri), context.subscriptions);
|
||||
|
||||
context.subscriptions.push(this.jsFiddleScm);
|
||||
context.subscriptions.push(fileSystemWatcher);
|
||||
|
||||
// clone fiddle to the local workspace
|
||||
this.setFiddle(fiddle, download);
|
||||
|
||||
if (this.fiddle.version === undefined || Number.isNaN(this.fiddle.version)) {
|
||||
this.establishVersion();
|
||||
} else {
|
||||
this.refresh();
|
||||
}
|
||||
}
|
||||
|
||||
static async fromFiddleId(id: string, context: vscode.ExtensionContext, workspaceFolder: vscode.WorkspaceFolder, overwrite: boolean): Promise<FiddleSourceControl> {
|
||||
const fiddleConfiguration = parseFiddleId(id);
|
||||
|
||||
return await FiddleSourceControl.fromConfiguration(fiddleConfiguration, workspaceFolder, context, overwrite);
|
||||
}
|
||||
|
||||
static async fromConfiguration(configuration: FiddleConfiguration, workspaceFolder: vscode.WorkspaceFolder, context: vscode.ExtensionContext, overwrite: boolean): Promise<FiddleSourceControl> {
|
||||
return await FiddleSourceControl.fromFiddle(configuration.slug, configuration.version, workspaceFolder, context, overwrite);
|
||||
}
|
||||
|
||||
private static async fromFiddle(fiddleSlug: string, fiddleVersion: number, workspaceFolder: vscode.WorkspaceFolder, context: vscode.ExtensionContext, overwrite: boolean): Promise<FiddleSourceControl> {
|
||||
const fiddle = await downloadFiddle(fiddleSlug, fiddleVersion);
|
||||
const workspacePath = workspaceFolder.uri.fsPath;
|
||||
return new FiddleSourceControl(context, workspaceFolder, fiddle, overwrite);
|
||||
}
|
||||
|
||||
private refreshStatusBar() {
|
||||
this.jsFiddleScm.statusBarCommands = [
|
||||
{
|
||||
"command": "extension.source-control.checkout",
|
||||
"arguments": [this],
|
||||
"title": `↕ ${this.fiddle.slug} #${this.fiddle.version} / ${this.latestFiddleVersion}`,
|
||||
"tooltip": "Checkout another version of this fiddle.",
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
async commitAll(): Promise<void> {
|
||||
if (!this.changedResources.resourceStates.length) {
|
||||
vscode.window.showErrorMessage("There is nothing to commit.");
|
||||
}
|
||||
else if (this.fiddle.version < this.latestFiddleVersion) {
|
||||
vscode.window.showErrorMessage("Checkout the latest fiddle version before committing your changes.");
|
||||
}
|
||||
else {
|
||||
const html = await this.getLocalResourceText('html');
|
||||
const js = await this.getLocalResourceText('js');
|
||||
const css = await this.getLocalResourceText('css');
|
||||
|
||||
// here we assume nobody updated the Fiddle on the server since we refreshed the list of versions
|
||||
try {
|
||||
const newFiddle = await uploadFiddle(this.fiddle.slug, this.fiddle.version + 1, html, js, css);
|
||||
if (!newFiddle) { return; }
|
||||
this.setFiddle(newFiddle, false);
|
||||
this.jsFiddleScm.inputBox.value = '';
|
||||
} catch (ex) {
|
||||
vscode.window.showErrorMessage("Cannot commit changes to JS Fiddle. " + ex.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async getLocalResourceText(extension: string) {
|
||||
const document = await vscode.workspace.openTextDocument(this.fiddleRepository.createLocalResourcePath(extension));
|
||||
return document.getText();
|
||||
}
|
||||
|
||||
/**
|
||||
* Throws away all local changes and resets all files to the checked out version of the repository.
|
||||
*/
|
||||
resetFilesToCheckedOutVersion(): void {
|
||||
this.resetFile('html');
|
||||
this.resetFile('css');
|
||||
this.resetFile('js');
|
||||
}
|
||||
|
||||
/** Resets the given local file content to the checked-out version. */
|
||||
private async resetFile(extension: string): Promise<void> {
|
||||
const filePath = this.fiddleRepository.createLocalResourcePath(extension);
|
||||
await afs.writeFile(filePath, this.fiddle.data[extension]);
|
||||
}
|
||||
|
||||
async tryCheckout(newVersion: number | undefined): Promise<void> {
|
||||
if (!Number.isFinite(this.latestFiddleVersion)) { return; }
|
||||
|
||||
if (newVersion === undefined) {
|
||||
const allVersions = [...Array(this.latestFiddleVersion + 1).keys()]
|
||||
.map(ver => new VersionQuickPickItem(ver, ver === this.fiddle.version));
|
||||
const newVersionPick = await vscode.window.showQuickPick(allVersions, { canPickMany: false, placeHolder: 'Select a version...' });
|
||||
if (newVersionPick) {
|
||||
newVersion = newVersionPick.version;
|
||||
}
|
||||
else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (newVersion === this.fiddle.version) { return; } // the same version was selected
|
||||
|
||||
if (this.changedResources.resourceStates.length) {
|
||||
const changedResourcesCount = this.changedResources.resourceStates.length;
|
||||
vscode.window.showErrorMessage(`There is one or more changed resources. Discard or commit your local changes before checking out another version.`);
|
||||
}
|
||||
else {
|
||||
try {
|
||||
const newFiddle = await downloadFiddle(this.fiddle.slug, newVersion);
|
||||
this.setFiddle(newFiddle, true);
|
||||
} catch (ex) {
|
||||
vscode.window.showErrorMessage(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private setFiddle(newFiddle: Fiddle, overwrite: boolean) {
|
||||
if (newFiddle.version > this.latestFiddleVersion) { this.latestFiddleVersion = newFiddle.version; }
|
||||
this.fiddle = newFiddle;
|
||||
if (overwrite) { this.resetFilesToCheckedOutVersion(); } // overwrite local file content
|
||||
this._onRepositoryChange.fire(this.fiddle);
|
||||
this.refreshStatusBar();
|
||||
|
||||
this.saveCurrentConfiguration();
|
||||
}
|
||||
|
||||
getFiddle(): Fiddle {
|
||||
return this.fiddle;
|
||||
}
|
||||
|
||||
getWorkspaceFolder(): vscode.WorkspaceFolder {
|
||||
return this.workspaceFolder;
|
||||
}
|
||||
|
||||
getSourceControl(): vscode.SourceControl {
|
||||
return this.jsFiddleScm;
|
||||
}
|
||||
|
||||
getRepository(): FiddleRepository {
|
||||
return this.fiddleRepository;
|
||||
}
|
||||
|
||||
/** save configuration for later VS Code sessions */
|
||||
private saveCurrentConfiguration(): void {
|
||||
const fiddleConfiguration: FiddleConfiguration = {
|
||||
slug: this.fiddle.slug,
|
||||
version: this.fiddle.version,
|
||||
downloaded: true
|
||||
};
|
||||
|
||||
FiddleSourceControl.saveConfiguration(this.workspaceFolder.uri, fiddleConfiguration);
|
||||
}
|
||||
|
||||
static saveConfiguration(workspaceFolderUri: vscode.Uri, fiddleConfiguration: FiddleConfiguration): void {
|
||||
const fiddleConfigurationString = JSON.stringify(fiddleConfiguration);
|
||||
afs.writeFile(path.join(workspaceFolderUri.fsPath, CONFIGURATION_FILE), Buffer.from(fiddleConfigurationString, UTF8));
|
||||
}
|
||||
|
||||
get onRepositoryChange(): vscode.Event<Fiddle> {
|
||||
return this._onRepositoryChange.event;
|
||||
}
|
||||
|
||||
onResourceChange(_uri: vscode.Uri): void {
|
||||
if (this.timeout) { clearTimeout(this.timeout); }
|
||||
this.timeout = setTimeout(() => this.tryUpdateChangedGroup(), 500);
|
||||
}
|
||||
|
||||
async tryUpdateChangedGroup(): Promise<void> {
|
||||
try {
|
||||
await this.updateChangedGroup();
|
||||
}
|
||||
catch (ex) {
|
||||
vscode.window.showErrorMessage(ex);
|
||||
}
|
||||
}
|
||||
|
||||
/** This is where the source control determines, which documents were updated, removed, and theoretically added. */
|
||||
async updateChangedGroup(): Promise<void> {
|
||||
// for simplicity we ignore which document was changed in this event and scan all of them
|
||||
const changedResources: vscode.SourceControlResourceState[] = [];
|
||||
|
||||
const uris = this.fiddleRepository.provideSourceControlledResources();
|
||||
|
||||
for (const uri of uris) {
|
||||
let isDirty: boolean;
|
||||
let wasDeleted: boolean;
|
||||
|
||||
const pathExists = await afs.exists(uri.fsPath);
|
||||
|
||||
if (pathExists) {
|
||||
const document = await vscode.workspace.openTextDocument(uri);
|
||||
isDirty = this.isDirty(document);
|
||||
wasDeleted = false;
|
||||
}
|
||||
else {
|
||||
isDirty = true;
|
||||
wasDeleted = true;
|
||||
}
|
||||
|
||||
if (isDirty) {
|
||||
const resourceState = this.toSourceControlResourceState(uri, wasDeleted);
|
||||
changedResources.push(resourceState);
|
||||
}
|
||||
}
|
||||
|
||||
this.changedResources.resourceStates = changedResources;
|
||||
|
||||
// the number of modified resources needs to be assigned to the SourceControl.count filed to let VS Code show the number.
|
||||
this.jsFiddleScm.count = this.changedResources.resourceStates.length;
|
||||
}
|
||||
|
||||
/** Determines whether the resource is different, regardless of line endings. */
|
||||
isDirty(doc: vscode.TextDocument): boolean {
|
||||
const originalText = this.fiddle.data[toExtension(doc.uri)];
|
||||
return originalText.replace('\r', '') !== doc.getText().replace('\r', '');
|
||||
}
|
||||
|
||||
toSourceControlResourceState(docUri: vscode.Uri, deleted: boolean): vscode.SourceControlResourceState {
|
||||
|
||||
const repositoryUri = this.fiddleRepository.provideOriginalResource(docUri, null);
|
||||
|
||||
const fiddlePart = toExtension(docUri).toUpperCase();
|
||||
|
||||
const command: vscode.Command = !deleted
|
||||
? {
|
||||
title: "Show changes",
|
||||
command: "vscode.diff",
|
||||
arguments: [repositoryUri, docUri, `JSFiddle#${this.fiddle.slug} ${fiddlePart} ↔ Local changes`],
|
||||
tooltip: "Diff your changes"
|
||||
}
|
||||
: null;
|
||||
|
||||
const resourceState: vscode.SourceControlResourceState = {
|
||||
resourceUri: docUri,
|
||||
command: command,
|
||||
decorations: {
|
||||
strikeThrough: deleted,
|
||||
tooltip: 'File was locally deleted.'
|
||||
}
|
||||
};
|
||||
|
||||
return resourceState;
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh is used when the information on the server may have changed.
|
||||
* For example another user updates the Fiddle online.
|
||||
*/
|
||||
async refresh(): Promise<void> {
|
||||
let latestVersion = this.fiddle.version || 0;
|
||||
while (true) {
|
||||
try {
|
||||
const latestFiddle = await downloadFiddle(this.fiddle.slug, latestVersion);
|
||||
this.latestFiddleVersion = latestVersion;
|
||||
latestVersion++;
|
||||
} catch (ex) {
|
||||
// typically the ex.statusCode == 404, when there is no further version
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
this.refreshStatusBar();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines which version was checked out and finds the index of the latest version.
|
||||
*
|
||||
* When a fiddle is open by the hash code, the latest version is downloaded,
|
||||
* but extension does not know what version it is.
|
||||
*/
|
||||
async establishVersion(): Promise<void> {
|
||||
let version = 0;
|
||||
let latestVersion = Number.NaN;
|
||||
let currentFiddle: Fiddle | undefined = undefined;
|
||||
while (true) {
|
||||
try {
|
||||
const latestFiddle = await downloadFiddle(this.fiddle.slug, version);
|
||||
latestVersion = version;
|
||||
version++;
|
||||
if (areIdentical(this.fiddle.data, latestFiddle.data)) {
|
||||
currentFiddle = latestFiddle;
|
||||
}
|
||||
} catch (ex) {
|
||||
// typically the ex.statusCode == 404, when there is no further version
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
this.latestFiddleVersion = latestVersion;
|
||||
|
||||
// now we know the version of the current fiddle, let's set it
|
||||
if (currentFiddle) {
|
||||
this.setFiddle(currentFiddle, false);
|
||||
}
|
||||
}
|
||||
|
||||
/** Opens the fiddle in the default browser. */
|
||||
openInBrowser() {
|
||||
const url = "https://jsfiddle.net/" + toFiddleId(this.fiddle.slug, this.fiddle.version);
|
||||
vscode.env.openExternal(vscode.Uri.parse(url));
|
||||
}
|
||||
|
||||
dispose() {
|
||||
this._onRepositoryChange.dispose();
|
||||
this.jsFiddleScm.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
class VersionQuickPickItem implements vscode.QuickPickItem {
|
||||
|
||||
constructor(public readonly version: number, public readonly picked: boolean) {
|
||||
}
|
||||
|
||||
get label(): string {
|
||||
return `Version ${this.version}`;
|
||||
}
|
||||
|
||||
get description(): string {
|
||||
return this.picked ? '(currently checked-out)' : '';
|
||||
}
|
||||
|
||||
get alwaysShow(): boolean {
|
||||
return this.picked;
|
||||
}
|
||||
}
|
||||
import * as vscode from 'vscode';
|
||||
import { FiddleRepository, toExtension, downloadFiddle, areIdentical, uploadFiddle, Fiddle, toFiddleId } from './fiddleRepository';
|
||||
import * as path from 'path';
|
||||
import * as afs from './afs';
|
||||
import { FiddleConfiguration, parseFiddleId } from './fiddleConfiguration';
|
||||
import { UTF8 } from './util';
|
||||
|
||||
export const CONFIGURATION_FILE = '.jsfiddle';
|
||||
|
||||
export class FiddleSourceControl implements vscode.Disposable {
|
||||
private jsFiddleScm: vscode.SourceControl;
|
||||
private changedResources: vscode.SourceControlResourceGroup;
|
||||
private fiddleRepository: FiddleRepository;
|
||||
private latestFiddleVersion: number = Number.POSITIVE_INFINITY; // until actual value is established
|
||||
private _onRepositoryChange = new vscode.EventEmitter<Fiddle>();
|
||||
private timeout?: NodeJS.Timer;
|
||||
private fiddle!: Fiddle;
|
||||
|
||||
constructor(context: vscode.ExtensionContext, private readonly workspaceFolder: vscode.WorkspaceFolder, fiddle: Fiddle, download: boolean) {
|
||||
this.jsFiddleScm = vscode.scm.createSourceControl('jsfiddle', 'JSFiddle #' + fiddle.slug, workspaceFolder.uri);
|
||||
this.changedResources = this.jsFiddleScm.createResourceGroup('workingTree', 'Changes');
|
||||
this.fiddleRepository = new FiddleRepository(workspaceFolder, fiddle.slug);
|
||||
this.jsFiddleScm.quickDiffProvider = this.fiddleRepository;
|
||||
this.jsFiddleScm.inputBox.placeholder = 'Message is ignored by JS Fiddle :-]';
|
||||
|
||||
const fileSystemWatcher = vscode.workspace.createFileSystemWatcher(new vscode.RelativePattern(workspaceFolder, "*.*"));
|
||||
fileSystemWatcher.onDidChange(uri => this.onResourceChange(uri), context.subscriptions);
|
||||
fileSystemWatcher.onDidCreate(uri => this.onResourceChange(uri), context.subscriptions);
|
||||
fileSystemWatcher.onDidDelete(uri => this.onResourceChange(uri), context.subscriptions);
|
||||
|
||||
context.subscriptions.push(this.jsFiddleScm);
|
||||
context.subscriptions.push(fileSystemWatcher);
|
||||
|
||||
// clone fiddle to the local workspace
|
||||
this.setFiddle(fiddle, download);
|
||||
|
||||
if (this.fiddle.version === undefined || Number.isNaN(this.fiddle.version)) {
|
||||
this.establishVersion();
|
||||
} else {
|
||||
this.refresh();
|
||||
}
|
||||
}
|
||||
|
||||
static async fromFiddleId(id: string, context: vscode.ExtensionContext, workspaceFolder: vscode.WorkspaceFolder, overwrite: boolean): Promise<FiddleSourceControl> {
|
||||
const fiddleConfiguration = parseFiddleId(id);
|
||||
|
||||
return await FiddleSourceControl.fromConfiguration(fiddleConfiguration, workspaceFolder, context, overwrite);
|
||||
}
|
||||
|
||||
static async fromConfiguration(configuration: FiddleConfiguration, workspaceFolder: vscode.WorkspaceFolder, context: vscode.ExtensionContext, overwrite: boolean): Promise<FiddleSourceControl> {
|
||||
return await FiddleSourceControl.fromFiddle(configuration.slug, configuration.version, workspaceFolder, context, overwrite);
|
||||
}
|
||||
|
||||
private static async fromFiddle(fiddleSlug: string, fiddleVersion: number, workspaceFolder: vscode.WorkspaceFolder, context: vscode.ExtensionContext, overwrite: boolean): Promise<FiddleSourceControl> {
|
||||
const fiddle = await downloadFiddle(fiddleSlug, fiddleVersion);
|
||||
const workspacePath = workspaceFolder.uri.fsPath;
|
||||
return new FiddleSourceControl(context, workspaceFolder, fiddle, overwrite);
|
||||
}
|
||||
|
||||
private refreshStatusBar() {
|
||||
this.jsFiddleScm.statusBarCommands = [
|
||||
{
|
||||
"command": "extension.source-control.checkout",
|
||||
"arguments": [this],
|
||||
"title": `↕ ${this.fiddle.slug} #${this.fiddle.version} / ${this.latestFiddleVersion}`,
|
||||
"tooltip": "Checkout another version of this fiddle.",
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
async commitAll(): Promise<void> {
|
||||
if (!this.changedResources.resourceStates.length) {
|
||||
vscode.window.showErrorMessage("There is nothing to commit.");
|
||||
}
|
||||
else if (this.fiddle.version < this.latestFiddleVersion) {
|
||||
vscode.window.showErrorMessage("Checkout the latest fiddle version before committing your changes.");
|
||||
}
|
||||
else {
|
||||
const html = await this.getLocalResourceText('html');
|
||||
const js = await this.getLocalResourceText('js');
|
||||
const css = await this.getLocalResourceText('css');
|
||||
|
||||
// here we assume nobody updated the Fiddle on the server since we refreshed the list of versions
|
||||
try {
|
||||
const newFiddle = await uploadFiddle(this.fiddle.slug, this.fiddle.version + 1, html, js, css);
|
||||
if (!newFiddle) { return; }
|
||||
this.setFiddle(newFiddle, false);
|
||||
this.jsFiddleScm.inputBox.value = '';
|
||||
} catch (ex) {
|
||||
vscode.window.showErrorMessage("Cannot commit changes to JS Fiddle. " + ex.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async getLocalResourceText(extension: string) {
|
||||
const document = await vscode.workspace.openTextDocument(this.fiddleRepository.createLocalResourcePath(extension));
|
||||
return document.getText();
|
||||
}
|
||||
|
||||
/**
|
||||
* Throws away all local changes and resets all files to the checked out version of the repository.
|
||||
*/
|
||||
resetFilesToCheckedOutVersion(): void {
|
||||
this.resetFile('html');
|
||||
this.resetFile('css');
|
||||
this.resetFile('js');
|
||||
}
|
||||
|
||||
/** Resets the given local file content to the checked-out version. */
|
||||
private async resetFile(extension: string): Promise<void> {
|
||||
const filePath = this.fiddleRepository.createLocalResourcePath(extension);
|
||||
await afs.writeFile(filePath, this.fiddle.data[extension]);
|
||||
}
|
||||
|
||||
async tryCheckout(newVersion: number | undefined): Promise<void> {
|
||||
if (!Number.isFinite(this.latestFiddleVersion)) { return; }
|
||||
|
||||
if (newVersion === undefined) {
|
||||
const allVersions = [...Array(this.latestFiddleVersion + 1).keys()]
|
||||
.map(ver => new VersionQuickPickItem(ver, ver === this.fiddle.version));
|
||||
const newVersionPick = await vscode.window.showQuickPick(allVersions, { canPickMany: false, placeHolder: 'Select a version...' });
|
||||
if (newVersionPick) {
|
||||
newVersion = newVersionPick.version;
|
||||
}
|
||||
else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (newVersion === this.fiddle.version) { return; } // the same version was selected
|
||||
|
||||
if (this.changedResources.resourceStates.length) {
|
||||
const changedResourcesCount = this.changedResources.resourceStates.length;
|
||||
vscode.window.showErrorMessage(`There is one or more changed resources. Discard or commit your local changes before checking out another version.`);
|
||||
}
|
||||
else {
|
||||
try {
|
||||
const newFiddle = await downloadFiddle(this.fiddle.slug, newVersion);
|
||||
this.setFiddle(newFiddle, true);
|
||||
} catch (ex) {
|
||||
vscode.window.showErrorMessage(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private setFiddle(newFiddle: Fiddle, overwrite: boolean) {
|
||||
if (newFiddle.version > this.latestFiddleVersion) { this.latestFiddleVersion = newFiddle.version; }
|
||||
this.fiddle = newFiddle;
|
||||
if (overwrite) { this.resetFilesToCheckedOutVersion(); } // overwrite local file content
|
||||
this._onRepositoryChange.fire(this.fiddle);
|
||||
this.refreshStatusBar();
|
||||
|
||||
this.saveCurrentConfiguration();
|
||||
}
|
||||
|
||||
getFiddle(): Fiddle {
|
||||
return this.fiddle;
|
||||
}
|
||||
|
||||
getWorkspaceFolder(): vscode.WorkspaceFolder {
|
||||
return this.workspaceFolder;
|
||||
}
|
||||
|
||||
getSourceControl(): vscode.SourceControl {
|
||||
return this.jsFiddleScm;
|
||||
}
|
||||
|
||||
getRepository(): FiddleRepository {
|
||||
return this.fiddleRepository;
|
||||
}
|
||||
|
||||
/** save configuration for later VS Code sessions */
|
||||
private saveCurrentConfiguration(): void {
|
||||
const fiddleConfiguration: FiddleConfiguration = {
|
||||
slug: this.fiddle.slug,
|
||||
version: this.fiddle.version,
|
||||
downloaded: true
|
||||
};
|
||||
|
||||
FiddleSourceControl.saveConfiguration(this.workspaceFolder.uri, fiddleConfiguration);
|
||||
}
|
||||
|
||||
static saveConfiguration(workspaceFolderUri: vscode.Uri, fiddleConfiguration: FiddleConfiguration): void {
|
||||
const fiddleConfigurationString = JSON.stringify(fiddleConfiguration);
|
||||
afs.writeFile(path.join(workspaceFolderUri.fsPath, CONFIGURATION_FILE), Buffer.from(fiddleConfigurationString, UTF8));
|
||||
}
|
||||
|
||||
get onRepositoryChange(): vscode.Event<Fiddle> {
|
||||
return this._onRepositoryChange.event;
|
||||
}
|
||||
|
||||
onResourceChange(_uri: vscode.Uri): void {
|
||||
if (this.timeout) { clearTimeout(this.timeout); }
|
||||
this.timeout = setTimeout(() => this.tryUpdateChangedGroup(), 500);
|
||||
}
|
||||
|
||||
async tryUpdateChangedGroup(): Promise<void> {
|
||||
try {
|
||||
await this.updateChangedGroup();
|
||||
}
|
||||
catch (ex) {
|
||||
vscode.window.showErrorMessage(ex);
|
||||
}
|
||||
}
|
||||
|
||||
/** This is where the source control determines, which documents were updated, removed, and theoretically added. */
|
||||
async updateChangedGroup(): Promise<void> {
|
||||
// for simplicity we ignore which document was changed in this event and scan all of them
|
||||
const changedResources: vscode.SourceControlResourceState[] = [];
|
||||
|
||||
const uris = this.fiddleRepository.provideSourceControlledResources();
|
||||
|
||||
for (const uri of uris) {
|
||||
let isDirty: boolean;
|
||||
let wasDeleted: boolean;
|
||||
|
||||
const pathExists = await afs.exists(uri.fsPath);
|
||||
|
||||
if (pathExists) {
|
||||
const document = await vscode.workspace.openTextDocument(uri);
|
||||
isDirty = this.isDirty(document);
|
||||
wasDeleted = false;
|
||||
}
|
||||
else {
|
||||
isDirty = true;
|
||||
wasDeleted = true;
|
||||
}
|
||||
|
||||
if (isDirty) {
|
||||
const resourceState = this.toSourceControlResourceState(uri, wasDeleted);
|
||||
changedResources.push(resourceState);
|
||||
}
|
||||
}
|
||||
|
||||
this.changedResources.resourceStates = changedResources;
|
||||
|
||||
// the number of modified resources needs to be assigned to the SourceControl.count filed to let VS Code show the number.
|
||||
this.jsFiddleScm.count = this.changedResources.resourceStates.length;
|
||||
}
|
||||
|
||||
/** Determines whether the resource is different, regardless of line endings. */
|
||||
isDirty(doc: vscode.TextDocument): boolean {
|
||||
const originalText = this.fiddle.data[toExtension(doc.uri)];
|
||||
return originalText.replace('\r', '') !== doc.getText().replace('\r', '');
|
||||
}
|
||||
|
||||
toSourceControlResourceState(docUri: vscode.Uri, deleted: boolean): vscode.SourceControlResourceState {
|
||||
|
||||
const repositoryUri = this.fiddleRepository.provideOriginalResource(docUri, null);
|
||||
|
||||
const fiddlePart = toExtension(docUri).toUpperCase();
|
||||
|
||||
const command: vscode.Command = !deleted
|
||||
? {
|
||||
title: "Show changes",
|
||||
command: "vscode.diff",
|
||||
arguments: [repositoryUri, docUri, `JSFiddle#${this.fiddle.slug} ${fiddlePart} ↔ Local changes`],
|
||||
tooltip: "Diff your changes"
|
||||
}
|
||||
: null;
|
||||
|
||||
const resourceState: vscode.SourceControlResourceState = {
|
||||
resourceUri: docUri,
|
||||
command: command,
|
||||
decorations: {
|
||||
strikeThrough: deleted,
|
||||
tooltip: 'File was locally deleted.'
|
||||
}
|
||||
};
|
||||
|
||||
return resourceState;
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh is used when the information on the server may have changed.
|
||||
* For example another user updates the Fiddle online.
|
||||
*/
|
||||
async refresh(): Promise<void> {
|
||||
let latestVersion = this.fiddle.version || 0;
|
||||
while (true) {
|
||||
try {
|
||||
const latestFiddle = await downloadFiddle(this.fiddle.slug, latestVersion);
|
||||
this.latestFiddleVersion = latestVersion;
|
||||
latestVersion++;
|
||||
} catch (ex) {
|
||||
// typically the ex.statusCode == 404, when there is no further version
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
this.refreshStatusBar();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines which version was checked out and finds the index of the latest version.
|
||||
*
|
||||
* When a fiddle is open by the hash code, the latest version is downloaded,
|
||||
* but extension does not know what version it is.
|
||||
*/
|
||||
async establishVersion(): Promise<void> {
|
||||
let version = 0;
|
||||
let latestVersion = Number.NaN;
|
||||
let currentFiddle: Fiddle | undefined = undefined;
|
||||
while (true) {
|
||||
try {
|
||||
const latestFiddle = await downloadFiddle(this.fiddle.slug, version);
|
||||
latestVersion = version;
|
||||
version++;
|
||||
if (areIdentical(this.fiddle.data, latestFiddle.data)) {
|
||||
currentFiddle = latestFiddle;
|
||||
}
|
||||
} catch (ex) {
|
||||
// typically the ex.statusCode == 404, when there is no further version
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
this.latestFiddleVersion = latestVersion;
|
||||
|
||||
// now we know the version of the current fiddle, let's set it
|
||||
if (currentFiddle) {
|
||||
this.setFiddle(currentFiddle, false);
|
||||
}
|
||||
}
|
||||
|
||||
/** Opens the fiddle in the default browser. */
|
||||
openInBrowser() {
|
||||
const url = "https://jsfiddle.net/" + toFiddleId(this.fiddle.slug, this.fiddle.version);
|
||||
vscode.env.openExternal(vscode.Uri.parse(url));
|
||||
}
|
||||
|
||||
dispose() {
|
||||
this._onRepositoryChange.dispose();
|
||||
this.jsFiddleScm.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
class VersionQuickPickItem implements vscode.QuickPickItem {
|
||||
|
||||
constructor(public readonly version: number, public readonly picked: boolean) {
|
||||
}
|
||||
|
||||
get label(): string {
|
||||
return `Version ${this.version}`;
|
||||
}
|
||||
|
||||
get description(): string {
|
||||
return this.picked ? '(currently checked-out)' : '';
|
||||
}
|
||||
|
||||
get alwaysShow(): boolean {
|
||||
return this.picked;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
export function firstIndex<T>(array: T[], fn: (t: T) => boolean): number {
|
||||
for (let i = 0; i < array.length; i++) {
|
||||
if (fn(array[i])) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
export function firstIndex<T>(array: T[], fn: (t: T) => boolean): number {
|
||||
for (let i = 0; i < array.length; i++) {
|
||||
if (fn(array[i])) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
export const UTF8 = 'utf8';
|
||||
@ -1,50 +1,50 @@
|
||||
/*---------------------------------------------------------
|
||||
* Copyright (C) Microsoft Corporation. All rights reserved.
|
||||
*--------------------------------------------------------*/
|
||||
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
let myStatusBarItem: vscode.StatusBarItem;
|
||||
|
||||
export function activate({ subscriptions }: vscode.ExtensionContext) {
|
||||
|
||||
// register a command that is invoked when the status bar
|
||||
// item is selected
|
||||
const myCommandId = 'sample.showSelectionCount';
|
||||
subscriptions.push(vscode.commands.registerCommand(myCommandId, () => {
|
||||
const n = getNumberOfSelectedLines(vscode.window.activeTextEditor);
|
||||
vscode.window.showInformationMessage(`Yeah, ${n} line(s) selected... Keep going!`);
|
||||
}));
|
||||
|
||||
// create a new status bar item that we can now manage
|
||||
myStatusBarItem = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Right, 100);
|
||||
myStatusBarItem.command = myCommandId;
|
||||
subscriptions.push(myStatusBarItem);
|
||||
|
||||
// register some listener that make sure the status bar
|
||||
// item always up-to-date
|
||||
subscriptions.push(vscode.window.onDidChangeActiveTextEditor(updateStatusBarItem));
|
||||
subscriptions.push(vscode.window.onDidChangeTextEditorSelection(updateStatusBarItem));
|
||||
|
||||
// update status bar item once at start
|
||||
updateStatusBarItem();
|
||||
}
|
||||
|
||||
function updateStatusBarItem(): void {
|
||||
const n = getNumberOfSelectedLines(vscode.window.activeTextEditor);
|
||||
if (n > 0) {
|
||||
myStatusBarItem.text = `$(megaphone) ${n} line(s) selected`;
|
||||
myStatusBarItem.show();
|
||||
} else {
|
||||
myStatusBarItem.hide();
|
||||
}
|
||||
}
|
||||
|
||||
function getNumberOfSelectedLines(editor: vscode.TextEditor | undefined): number {
|
||||
let lines = 0;
|
||||
if (editor) {
|
||||
lines = editor.selections.reduce((prev, curr) => prev + (curr.end.line - curr.start.line), 0);
|
||||
}
|
||||
return lines;
|
||||
}
|
||||
/*---------------------------------------------------------
|
||||
* Copyright (C) Microsoft Corporation. All rights reserved.
|
||||
*--------------------------------------------------------*/
|
||||
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
let myStatusBarItem: vscode.StatusBarItem;
|
||||
|
||||
export function activate({ subscriptions }: vscode.ExtensionContext) {
|
||||
|
||||
// register a command that is invoked when the status bar
|
||||
// item is selected
|
||||
const myCommandId = 'sample.showSelectionCount';
|
||||
subscriptions.push(vscode.commands.registerCommand(myCommandId, () => {
|
||||
const n = getNumberOfSelectedLines(vscode.window.activeTextEditor);
|
||||
vscode.window.showInformationMessage(`Yeah, ${n} line(s) selected... Keep going!`);
|
||||
}));
|
||||
|
||||
// create a new status bar item that we can now manage
|
||||
myStatusBarItem = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Right, 100);
|
||||
myStatusBarItem.command = myCommandId;
|
||||
subscriptions.push(myStatusBarItem);
|
||||
|
||||
// register some listener that make sure the status bar
|
||||
// item always up-to-date
|
||||
subscriptions.push(vscode.window.onDidChangeActiveTextEditor(updateStatusBarItem));
|
||||
subscriptions.push(vscode.window.onDidChangeTextEditorSelection(updateStatusBarItem));
|
||||
|
||||
// update status bar item once at start
|
||||
updateStatusBarItem();
|
||||
}
|
||||
|
||||
function updateStatusBarItem(): void {
|
||||
const n = getNumberOfSelectedLines(vscode.window.activeTextEditor);
|
||||
if (n > 0) {
|
||||
myStatusBarItem.text = `$(megaphone) ${n} line(s) selected`;
|
||||
myStatusBarItem.show();
|
||||
} else {
|
||||
myStatusBarItem.hide();
|
||||
}
|
||||
}
|
||||
|
||||
function getNumberOfSelectedLines(editor: vscode.TextEditor | undefined): number {
|
||||
let lines = 0;
|
||||
if (editor) {
|
||||
lines = editor.selections.reduce((prev, curr) => prev + (curr.end.line - curr.start.line), 0);
|
||||
}
|
||||
return lines;
|
||||
}
|
||||
|
||||
@ -1,135 +1,135 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import * as path from 'path';
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
interface CustomBuildTaskDefinition extends vscode.TaskDefinition {
|
||||
/**
|
||||
* The build flavor. Should be either '32' or '64'.
|
||||
*/
|
||||
flavor: string;
|
||||
|
||||
/**
|
||||
* Additional build flags
|
||||
*/
|
||||
flags?: string[];
|
||||
}
|
||||
|
||||
export class CustomBuildTaskProvider implements vscode.TaskProvider {
|
||||
static CustomBuildScriptType = 'custombuildscript';
|
||||
private tasks: vscode.Task[] | undefined;
|
||||
|
||||
// We use a CustomExecution task when state needs to be shared across runs of the task or when
|
||||
// the task requires use of some VS Code API to run.
|
||||
// If you don't need to share state between runs and if you don't need to execute VS Code API in your task,
|
||||
// then a simple ShellExecution or ProcessExecution should be enough.
|
||||
// Since our build has this shared state, the CustomExecution is used below.
|
||||
private sharedState: string | undefined;
|
||||
|
||||
constructor(private workspaceRoot: string) { }
|
||||
|
||||
public async provideTasks(): Promise<vscode.Task[]> {
|
||||
return this.getTasks();
|
||||
}
|
||||
|
||||
public resolveTask(_task: vscode.Task): vscode.Task | undefined {
|
||||
const flavor: string = _task.definition.flavor;
|
||||
if (flavor) {
|
||||
const definition: CustomBuildTaskDefinition = <any>_task.definition;
|
||||
return this.getTask(definition.flavor, definition.flags ? definition.flags : [], definition);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private getTasks(): vscode.Task[] {
|
||||
if (this.tasks !== undefined) {
|
||||
return this.tasks;
|
||||
}
|
||||
// In our fictional build, we have two build flavors
|
||||
const flavors: string[] = ['32', '64'];
|
||||
// Each flavor can have some options.
|
||||
const flags: string[][] = [['watch', 'incremental'], ['incremental'], []];
|
||||
|
||||
this.tasks = [];
|
||||
flavors.forEach(flavor => {
|
||||
flags.forEach(flagGroup => {
|
||||
this.tasks!.push(this.getTask(flavor, flagGroup));
|
||||
});
|
||||
});
|
||||
return this.tasks;
|
||||
}
|
||||
|
||||
private getTask(flavor: string, flags: string[], definition?: CustomBuildTaskDefinition): vscode.Task {
|
||||
if (definition === undefined) {
|
||||
definition = {
|
||||
type: CustomBuildTaskProvider.CustomBuildScriptType,
|
||||
flavor,
|
||||
flags
|
||||
};
|
||||
}
|
||||
return new vscode.Task(definition, vscode.TaskScope.Workspace, `${flavor} ${flags.join(' ')}`,
|
||||
CustomBuildTaskProvider.CustomBuildScriptType, new vscode.CustomExecution(async (): Promise<vscode.Pseudoterminal> => {
|
||||
// When the task is executed, this callback will run. Here, we setup for running the task.
|
||||
return new CustomBuildTaskTerminal(this.workspaceRoot, flavor, flags, () => this.sharedState, (state: string) => this.sharedState = state);
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
class CustomBuildTaskTerminal implements vscode.Pseudoterminal {
|
||||
private writeEmitter = new vscode.EventEmitter<string>();
|
||||
onDidWrite: vscode.Event<string> = this.writeEmitter.event;
|
||||
private closeEmitter = new vscode.EventEmitter<number>();
|
||||
onDidClose?: vscode.Event<number> = this.closeEmitter.event;
|
||||
|
||||
private fileWatcher: vscode.FileSystemWatcher | undefined;
|
||||
|
||||
constructor(private workspaceRoot: string, private flavor: string, private flags: string[], private getSharedState: () => string | undefined, private setSharedState: (state: string) => void) {
|
||||
}
|
||||
|
||||
open(initialDimensions: vscode.TerminalDimensions | undefined): void {
|
||||
// At this point we can start using the terminal.
|
||||
if (this.flags.indexOf('watch') > -1) {
|
||||
const pattern = path.join(this.workspaceRoot, 'customBuildFile');
|
||||
this.fileWatcher = vscode.workspace.createFileSystemWatcher(pattern);
|
||||
this.fileWatcher.onDidChange(() => this.doBuild());
|
||||
this.fileWatcher.onDidCreate(() => this.doBuild());
|
||||
this.fileWatcher.onDidDelete(() => this.doBuild());
|
||||
}
|
||||
this.doBuild();
|
||||
}
|
||||
|
||||
close(): void {
|
||||
// The terminal has been closed. Shutdown the build.
|
||||
if (this.fileWatcher) {
|
||||
this.fileWatcher.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
private async doBuild(): Promise<void> {
|
||||
return new Promise<void>((resolve) => {
|
||||
this.writeEmitter.fire('Starting build...\r\n');
|
||||
let isIncremental = this.flags.indexOf('incremental') > -1;
|
||||
if (isIncremental) {
|
||||
if (this.getSharedState()) {
|
||||
this.writeEmitter.fire('Using last build results: ' + this.getSharedState() + '\r\n');
|
||||
} else {
|
||||
isIncremental = false;
|
||||
this.writeEmitter.fire('No result from last build. Doing full build.\r\n');
|
||||
}
|
||||
}
|
||||
|
||||
// Since we don't actually build anything in this example set a timeout instead.
|
||||
setTimeout(() => {
|
||||
const date = new Date();
|
||||
this.setSharedState(date.toTimeString() + ' ' + date.toDateString());
|
||||
this.writeEmitter.fire('Build complete.\r\n\r\n');
|
||||
if (this.flags.indexOf('watch') === -1) {
|
||||
this.closeEmitter.fire(0);
|
||||
resolve();
|
||||
}
|
||||
}, isIncremental ? 1000 : 4000);
|
||||
});
|
||||
}
|
||||
}
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import * as path from 'path';
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
interface CustomBuildTaskDefinition extends vscode.TaskDefinition {
|
||||
/**
|
||||
* The build flavor. Should be either '32' or '64'.
|
||||
*/
|
||||
flavor: string;
|
||||
|
||||
/**
|
||||
* Additional build flags
|
||||
*/
|
||||
flags?: string[];
|
||||
}
|
||||
|
||||
export class CustomBuildTaskProvider implements vscode.TaskProvider {
|
||||
static CustomBuildScriptType = 'custombuildscript';
|
||||
private tasks: vscode.Task[] | undefined;
|
||||
|
||||
// We use a CustomExecution task when state needs to be shared across runs of the task or when
|
||||
// the task requires use of some VS Code API to run.
|
||||
// If you don't need to share state between runs and if you don't need to execute VS Code API in your task,
|
||||
// then a simple ShellExecution or ProcessExecution should be enough.
|
||||
// Since our build has this shared state, the CustomExecution is used below.
|
||||
private sharedState: string | undefined;
|
||||
|
||||
constructor(private workspaceRoot: string) { }
|
||||
|
||||
public async provideTasks(): Promise<vscode.Task[]> {
|
||||
return this.getTasks();
|
||||
}
|
||||
|
||||
public resolveTask(_task: vscode.Task): vscode.Task | undefined {
|
||||
const flavor: string = _task.definition.flavor;
|
||||
if (flavor) {
|
||||
const definition: CustomBuildTaskDefinition = <any>_task.definition;
|
||||
return this.getTask(definition.flavor, definition.flags ? definition.flags : [], definition);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private getTasks(): vscode.Task[] {
|
||||
if (this.tasks !== undefined) {
|
||||
return this.tasks;
|
||||
}
|
||||
// In our fictional build, we have two build flavors
|
||||
const flavors: string[] = ['32', '64'];
|
||||
// Each flavor can have some options.
|
||||
const flags: string[][] = [['watch', 'incremental'], ['incremental'], []];
|
||||
|
||||
this.tasks = [];
|
||||
flavors.forEach(flavor => {
|
||||
flags.forEach(flagGroup => {
|
||||
this.tasks!.push(this.getTask(flavor, flagGroup));
|
||||
});
|
||||
});
|
||||
return this.tasks;
|
||||
}
|
||||
|
||||
private getTask(flavor: string, flags: string[], definition?: CustomBuildTaskDefinition): vscode.Task {
|
||||
if (definition === undefined) {
|
||||
definition = {
|
||||
type: CustomBuildTaskProvider.CustomBuildScriptType,
|
||||
flavor,
|
||||
flags
|
||||
};
|
||||
}
|
||||
return new vscode.Task(definition, vscode.TaskScope.Workspace, `${flavor} ${flags.join(' ')}`,
|
||||
CustomBuildTaskProvider.CustomBuildScriptType, new vscode.CustomExecution(async (): Promise<vscode.Pseudoterminal> => {
|
||||
// When the task is executed, this callback will run. Here, we setup for running the task.
|
||||
return new CustomBuildTaskTerminal(this.workspaceRoot, flavor, flags, () => this.sharedState, (state: string) => this.sharedState = state);
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
class CustomBuildTaskTerminal implements vscode.Pseudoterminal {
|
||||
private writeEmitter = new vscode.EventEmitter<string>();
|
||||
onDidWrite: vscode.Event<string> = this.writeEmitter.event;
|
||||
private closeEmitter = new vscode.EventEmitter<number>();
|
||||
onDidClose?: vscode.Event<number> = this.closeEmitter.event;
|
||||
|
||||
private fileWatcher: vscode.FileSystemWatcher | undefined;
|
||||
|
||||
constructor(private workspaceRoot: string, private flavor: string, private flags: string[], private getSharedState: () => string | undefined, private setSharedState: (state: string) => void) {
|
||||
}
|
||||
|
||||
open(initialDimensions: vscode.TerminalDimensions | undefined): void {
|
||||
// At this point we can start using the terminal.
|
||||
if (this.flags.indexOf('watch') > -1) {
|
||||
const pattern = path.join(this.workspaceRoot, 'customBuildFile');
|
||||
this.fileWatcher = vscode.workspace.createFileSystemWatcher(pattern);
|
||||
this.fileWatcher.onDidChange(() => this.doBuild());
|
||||
this.fileWatcher.onDidCreate(() => this.doBuild());
|
||||
this.fileWatcher.onDidDelete(() => this.doBuild());
|
||||
}
|
||||
this.doBuild();
|
||||
}
|
||||
|
||||
close(): void {
|
||||
// The terminal has been closed. Shutdown the build.
|
||||
if (this.fileWatcher) {
|
||||
this.fileWatcher.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
private async doBuild(): Promise<void> {
|
||||
return new Promise<void>((resolve) => {
|
||||
this.writeEmitter.fire('Starting build...\r\n');
|
||||
let isIncremental = this.flags.indexOf('incremental') > -1;
|
||||
if (isIncremental) {
|
||||
if (this.getSharedState()) {
|
||||
this.writeEmitter.fire('Using last build results: ' + this.getSharedState() + '\r\n');
|
||||
} else {
|
||||
isIncremental = false;
|
||||
this.writeEmitter.fire('No result from last build. Doing full build.\r\n');
|
||||
}
|
||||
}
|
||||
|
||||
// Since we don't actually build anything in this example set a timeout instead.
|
||||
setTimeout(() => {
|
||||
const date = new Date();
|
||||
this.setSharedState(date.toTimeString() + ' ' + date.toDateString());
|
||||
this.writeEmitter.fire('Build complete.\r\n\r\n');
|
||||
if (this.flags.indexOf('watch') === -1) {
|
||||
this.closeEmitter.fire(0);
|
||||
resolve();
|
||||
}
|
||||
}, isIncremental ? 1000 : 4000);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -15,7 +15,7 @@ export function activate(_context: vscode.ExtensionContext): void {
|
||||
if (!workspaceRoot) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
rakeTaskProvider = vscode.tasks.registerTaskProvider(RakeTaskProvider.RakeType, new RakeTaskProvider(workspaceRoot));
|
||||
customTaskProvider = vscode.tasks.registerTaskProvider(CustomBuildTaskProvider.CustomBuildScriptType, new CustomBuildTaskProvider(workspaceRoot));
|
||||
}
|
||||
|
||||
@ -1,163 +1,163 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import * as path from 'path';
|
||||
import * as fs from 'fs';
|
||||
import * as cp from 'child_process';
|
||||
import * as vscode from 'vscode';
|
||||
import { error } from 'console';
|
||||
|
||||
export class RakeTaskProvider implements vscode.TaskProvider {
|
||||
static RakeType = 'rake';
|
||||
private rakePromise: Thenable<vscode.Task[]> | undefined = undefined;
|
||||
|
||||
constructor(workspaceRoot: string) {
|
||||
const pattern = path.join(workspaceRoot, 'Rakefile');
|
||||
const fileWatcher = vscode.workspace.createFileSystemWatcher(pattern);
|
||||
fileWatcher.onDidChange(() => this.rakePromise = undefined);
|
||||
fileWatcher.onDidCreate(() => this.rakePromise = undefined);
|
||||
fileWatcher.onDidDelete(() => this.rakePromise = undefined);
|
||||
}
|
||||
|
||||
public provideTasks(): Thenable<vscode.Task[]> | undefined {
|
||||
if (!this.rakePromise) {
|
||||
this.rakePromise = getRakeTasks();
|
||||
}
|
||||
return this.rakePromise;
|
||||
}
|
||||
|
||||
public resolveTask(_task: vscode.Task): vscode.Task | undefined {
|
||||
const task = _task.definition.task;
|
||||
// A Rake task consists of a task and an optional file as specified in RakeTaskDefinition
|
||||
// Make sure that this looks like a Rake task by checking that there is a task.
|
||||
if (task) {
|
||||
// resolveTask requires that the same definition object be used.
|
||||
const definition: RakeTaskDefinition = <any>_task.definition;
|
||||
return new vscode.Task(definition, _task.scope ?? vscode.TaskScope.Workspace, definition.task, 'rake', new vscode.ShellExecution(`rake ${definition.task}`));
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
function exists(file: string): Promise<boolean> {
|
||||
return new Promise<boolean>((resolve, _reject) => {
|
||||
fs.exists(file, (value) => {
|
||||
resolve(value);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function exec(command: string, options: cp.ExecOptions): Promise<{ stdout: string; stderr: string }> {
|
||||
return new Promise<{ stdout: string; stderr: string }>((resolve, reject) => {
|
||||
cp.exec(command, options, (error, stdout, stderr) => {
|
||||
if (error) {
|
||||
reject({ error, stdout, stderr });
|
||||
}
|
||||
resolve({ stdout, stderr });
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
let _channel: vscode.OutputChannel;
|
||||
function getOutputChannel(): vscode.OutputChannel {
|
||||
if (!_channel) {
|
||||
_channel = vscode.window.createOutputChannel('Rake Auto Detection');
|
||||
}
|
||||
return _channel;
|
||||
}
|
||||
|
||||
interface RakeTaskDefinition extends vscode.TaskDefinition {
|
||||
/**
|
||||
* The task name
|
||||
*/
|
||||
task: string;
|
||||
|
||||
/**
|
||||
* The rake file containing the task
|
||||
*/
|
||||
file?: string;
|
||||
}
|
||||
|
||||
const buildNames: string[] = ['build', 'compile', 'watch'];
|
||||
function isBuildTask(name: string): boolean {
|
||||
for (const buildName of buildNames) {
|
||||
if (name.indexOf(buildName) !== -1) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const testNames: string[] = ['test'];
|
||||
function isTestTask(name: string): boolean {
|
||||
for (const testName of testNames) {
|
||||
if (name.indexOf(testName) !== -1) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
async function getRakeTasks(): Promise<vscode.Task[]> {
|
||||
const workspaceFolders = vscode.workspace.workspaceFolders;
|
||||
const result: vscode.Task[] = [];
|
||||
if (!workspaceFolders || workspaceFolders.length === 0) {
|
||||
return result;
|
||||
}
|
||||
for (const workspaceFolder of workspaceFolders) {
|
||||
const folderString = workspaceFolder.uri.fsPath;
|
||||
if (!folderString) {
|
||||
continue;
|
||||
}
|
||||
const rakeFile = path.join(folderString, 'Rakefile');
|
||||
if (!await exists(rakeFile)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const commandLine = 'rake -AT -f Rakefile';
|
||||
try {
|
||||
const { stdout, stderr } = await exec(commandLine, { cwd: folderString });
|
||||
if (stderr && stderr.length > 0) {
|
||||
getOutputChannel().appendLine(stderr);
|
||||
getOutputChannel().show(true);
|
||||
}
|
||||
if (stdout) {
|
||||
const lines = stdout.split(/\r{0,1}\n/);
|
||||
for (const line of lines) {
|
||||
if (line.length === 0) {
|
||||
continue;
|
||||
}
|
||||
const regExp = /rake\s(.*)#/;
|
||||
const matches = regExp.exec(line);
|
||||
if (matches && matches.length === 2) {
|
||||
const taskName = matches[1].trim();
|
||||
const kind: RakeTaskDefinition = {
|
||||
type: 'rake',
|
||||
task: taskName
|
||||
};
|
||||
const task = new vscode.Task(kind, workspaceFolder, taskName, 'rake', new vscode.ShellExecution(`rake ${taskName}`));
|
||||
result.push(task);
|
||||
const lowerCaseLine = line.toLowerCase();
|
||||
if (isBuildTask(lowerCaseLine)) {
|
||||
task.group = vscode.TaskGroup.Build;
|
||||
} else if (isTestTask(lowerCaseLine)) {
|
||||
task.group = vscode.TaskGroup.Test;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (err: any) {
|
||||
const channel = getOutputChannel();
|
||||
if (err.stderr) {
|
||||
channel.appendLine(err.stderr);
|
||||
}
|
||||
if (err.stdout) {
|
||||
channel.appendLine(err.stdout);
|
||||
}
|
||||
channel.appendLine('Auto detecting rake tasks failed.');
|
||||
channel.show(true);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import * as path from 'path';
|
||||
import * as fs from 'fs';
|
||||
import * as cp from 'child_process';
|
||||
import * as vscode from 'vscode';
|
||||
import { error } from 'console';
|
||||
|
||||
export class RakeTaskProvider implements vscode.TaskProvider {
|
||||
static RakeType = 'rake';
|
||||
private rakePromise: Thenable<vscode.Task[]> | undefined = undefined;
|
||||
|
||||
constructor(workspaceRoot: string) {
|
||||
const pattern = path.join(workspaceRoot, 'Rakefile');
|
||||
const fileWatcher = vscode.workspace.createFileSystemWatcher(pattern);
|
||||
fileWatcher.onDidChange(() => this.rakePromise = undefined);
|
||||
fileWatcher.onDidCreate(() => this.rakePromise = undefined);
|
||||
fileWatcher.onDidDelete(() => this.rakePromise = undefined);
|
||||
}
|
||||
|
||||
public provideTasks(): Thenable<vscode.Task[]> | undefined {
|
||||
if (!this.rakePromise) {
|
||||
this.rakePromise = getRakeTasks();
|
||||
}
|
||||
return this.rakePromise;
|
||||
}
|
||||
|
||||
public resolveTask(_task: vscode.Task): vscode.Task | undefined {
|
||||
const task = _task.definition.task;
|
||||
// A Rake task consists of a task and an optional file as specified in RakeTaskDefinition
|
||||
// Make sure that this looks like a Rake task by checking that there is a task.
|
||||
if (task) {
|
||||
// resolveTask requires that the same definition object be used.
|
||||
const definition: RakeTaskDefinition = <any>_task.definition;
|
||||
return new vscode.Task(definition, _task.scope ?? vscode.TaskScope.Workspace, definition.task, 'rake', new vscode.ShellExecution(`rake ${definition.task}`));
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
function exists(file: string): Promise<boolean> {
|
||||
return new Promise<boolean>((resolve, _reject) => {
|
||||
fs.exists(file, (value) => {
|
||||
resolve(value);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function exec(command: string, options: cp.ExecOptions): Promise<{ stdout: string; stderr: string }> {
|
||||
return new Promise<{ stdout: string; stderr: string }>((resolve, reject) => {
|
||||
cp.exec(command, options, (error, stdout, stderr) => {
|
||||
if (error) {
|
||||
reject({ error, stdout, stderr });
|
||||
}
|
||||
resolve({ stdout, stderr });
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
let _channel: vscode.OutputChannel;
|
||||
function getOutputChannel(): vscode.OutputChannel {
|
||||
if (!_channel) {
|
||||
_channel = vscode.window.createOutputChannel('Rake Auto Detection');
|
||||
}
|
||||
return _channel;
|
||||
}
|
||||
|
||||
interface RakeTaskDefinition extends vscode.TaskDefinition {
|
||||
/**
|
||||
* The task name
|
||||
*/
|
||||
task: string;
|
||||
|
||||
/**
|
||||
* The rake file containing the task
|
||||
*/
|
||||
file?: string;
|
||||
}
|
||||
|
||||
const buildNames: string[] = ['build', 'compile', 'watch'];
|
||||
function isBuildTask(name: string): boolean {
|
||||
for (const buildName of buildNames) {
|
||||
if (name.indexOf(buildName) !== -1) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const testNames: string[] = ['test'];
|
||||
function isTestTask(name: string): boolean {
|
||||
for (const testName of testNames) {
|
||||
if (name.indexOf(testName) !== -1) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
async function getRakeTasks(): Promise<vscode.Task[]> {
|
||||
const workspaceFolders = vscode.workspace.workspaceFolders;
|
||||
const result: vscode.Task[] = [];
|
||||
if (!workspaceFolders || workspaceFolders.length === 0) {
|
||||
return result;
|
||||
}
|
||||
for (const workspaceFolder of workspaceFolders) {
|
||||
const folderString = workspaceFolder.uri.fsPath;
|
||||
if (!folderString) {
|
||||
continue;
|
||||
}
|
||||
const rakeFile = path.join(folderString, 'Rakefile');
|
||||
if (!await exists(rakeFile)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const commandLine = 'rake -AT -f Rakefile';
|
||||
try {
|
||||
const { stdout, stderr } = await exec(commandLine, { cwd: folderString });
|
||||
if (stderr && stderr.length > 0) {
|
||||
getOutputChannel().appendLine(stderr);
|
||||
getOutputChannel().show(true);
|
||||
}
|
||||
if (stdout) {
|
||||
const lines = stdout.split(/\r{0,1}\n/);
|
||||
for (const line of lines) {
|
||||
if (line.length === 0) {
|
||||
continue;
|
||||
}
|
||||
const regExp = /rake\s(.*)#/;
|
||||
const matches = regExp.exec(line);
|
||||
if (matches && matches.length === 2) {
|
||||
const taskName = matches[1].trim();
|
||||
const kind: RakeTaskDefinition = {
|
||||
type: 'rake',
|
||||
task: taskName
|
||||
};
|
||||
const task = new vscode.Task(kind, workspaceFolder, taskName, 'rake', new vscode.ShellExecution(`rake ${taskName}`));
|
||||
result.push(task);
|
||||
const lowerCaseLine = line.toLowerCase();
|
||||
if (isBuildTask(lowerCaseLine)) {
|
||||
task.group = vscode.TaskGroup.Build;
|
||||
} else if (isTestTask(lowerCaseLine)) {
|
||||
task.group = vscode.TaskGroup.Test;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (err: any) {
|
||||
const channel = getOutputChannel();
|
||||
if (err.stderr) {
|
||||
channel.appendLine(err.stderr);
|
||||
}
|
||||
if (err.stdout) {
|
||||
channel.appendLine(err.stdout);
|
||||
}
|
||||
channel.appendLine('Auto detecting rake tasks failed.');
|
||||
channel.show(true);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@ -1,235 +1,235 @@
|
||||
'use strict';
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
export function activate(context: vscode.ExtensionContext) {
|
||||
let NEXT_TERM_ID = 1;
|
||||
|
||||
console.log("Terminals: " + (<any>vscode.window).terminals.length);
|
||||
|
||||
// vscode.window.onDidOpenTerminal
|
||||
vscode.window.onDidOpenTerminal(terminal => {
|
||||
console.log("Terminal opened. Total count: " + (<any>vscode.window).terminals.length);
|
||||
});
|
||||
vscode.window.onDidOpenTerminal((terminal: vscode.Terminal) => {
|
||||
vscode.window.showInformationMessage(`onDidOpenTerminal, name: ${terminal.name}`);
|
||||
});
|
||||
|
||||
// vscode.window.onDidChangeActiveTerminal
|
||||
vscode.window.onDidChangeActiveTerminal(e => {
|
||||
console.log(`Active terminal changed, name=${e ? e.name : 'undefined'}`);
|
||||
});
|
||||
|
||||
// vscode.window.createTerminal
|
||||
context.subscriptions.push(vscode.commands.registerCommand('terminalTest.createTerminal', () => {
|
||||
vscode.window.createTerminal(`Ext Terminal #${NEXT_TERM_ID++}`);
|
||||
vscode.window.showInformationMessage('Hello World 2!');
|
||||
}));
|
||||
context.subscriptions.push(vscode.commands.registerCommand('terminalTest.createTerminalHideFromUser', () => {
|
||||
vscode.window.createTerminal({
|
||||
name: `Ext Terminal #${NEXT_TERM_ID++}`,
|
||||
hideFromUser: true
|
||||
} as any);
|
||||
}));
|
||||
context.subscriptions.push(vscode.commands.registerCommand('terminalTest.createAndSend', () => {
|
||||
const terminal = vscode.window.createTerminal(`Ext Terminal #${NEXT_TERM_ID++}`);
|
||||
terminal.sendText("echo 'Sent text immediately after creating'");
|
||||
}));
|
||||
context.subscriptions.push(vscode.commands.registerCommand('terminalTest.createZshLoginShell', () => {
|
||||
vscode.window.createTerminal(`Ext Terminal #${NEXT_TERM_ID++}`, '/bin/zsh', ['-l']);
|
||||
}));
|
||||
|
||||
// Terminal.hide
|
||||
context.subscriptions.push(vscode.commands.registerCommand('terminalTest.hide', () => {
|
||||
if (ensureTerminalExists()) {
|
||||
selectTerminal().then(terminal => {
|
||||
if (terminal) {
|
||||
terminal.hide();
|
||||
}
|
||||
});
|
||||
}
|
||||
}));
|
||||
|
||||
// Terminal.show
|
||||
context.subscriptions.push(vscode.commands.registerCommand('terminalTest.show', () => {
|
||||
if (ensureTerminalExists()) {
|
||||
selectTerminal().then(terminal => {
|
||||
if (terminal) {
|
||||
terminal.show();
|
||||
}
|
||||
});
|
||||
}
|
||||
}));
|
||||
context.subscriptions.push(vscode.commands.registerCommand('terminalTest.showPreserveFocus', () => {
|
||||
if (ensureTerminalExists()) {
|
||||
selectTerminal().then(terminal => {
|
||||
if (terminal) {
|
||||
terminal.show(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
}));
|
||||
|
||||
// Terminal.sendText
|
||||
context.subscriptions.push(vscode.commands.registerCommand('terminalTest.sendText', () => {
|
||||
if (ensureTerminalExists()) {
|
||||
selectTerminal().then(terminal => {
|
||||
if (terminal) {
|
||||
terminal.sendText("echo 'Hello world!'");
|
||||
}
|
||||
});
|
||||
}
|
||||
}));
|
||||
context.subscriptions.push(vscode.commands.registerCommand('terminalTest.sendTextNoNewLine', () => {
|
||||
if (ensureTerminalExists()) {
|
||||
selectTerminal().then(terminal => {
|
||||
if (terminal) {
|
||||
terminal.sendText("echo 'Hello world!'", false);
|
||||
}
|
||||
});
|
||||
}
|
||||
}));
|
||||
|
||||
// Terminal.dispose
|
||||
context.subscriptions.push(vscode.commands.registerCommand('terminalTest.dispose', () => {
|
||||
if (ensureTerminalExists()) {
|
||||
selectTerminal().then(terminal => {
|
||||
if (terminal) {
|
||||
terminal.dispose();
|
||||
}
|
||||
});
|
||||
}
|
||||
}));
|
||||
|
||||
// Terminal.processId
|
||||
context.subscriptions.push(vscode.commands.registerCommand('terminalTest.processId', () => {
|
||||
selectTerminal().then(terminal => {
|
||||
if (!terminal) {
|
||||
return;
|
||||
}
|
||||
terminal.processId.then((processId) => {
|
||||
if (processId) {
|
||||
vscode.window.showInformationMessage(`Terminal.processId: ${processId}`);
|
||||
} else {
|
||||
vscode.window.showInformationMessage('Terminal does not have a process ID');
|
||||
}
|
||||
});
|
||||
});
|
||||
}));
|
||||
|
||||
// vscode.window.onDidCloseTerminal
|
||||
vscode.window.onDidCloseTerminal((terminal) => {
|
||||
vscode.window.showInformationMessage(`onDidCloseTerminal, name: ${terminal.name}`);
|
||||
});
|
||||
|
||||
// vscode.window.terminals
|
||||
context.subscriptions.push(vscode.commands.registerCommand('terminalTest.terminals', () => {
|
||||
selectTerminal();
|
||||
}));
|
||||
|
||||
// ExtensionContext.environmentVariableCollection
|
||||
context.subscriptions.push(vscode.commands.registerCommand('terminalTest.updateEnvironment', () => {
|
||||
const collection = context.environmentVariableCollection;
|
||||
collection.replace('FOO', 'BAR');
|
||||
collection.append('PATH', '/test/path');
|
||||
}));
|
||||
|
||||
context.subscriptions.push(vscode.commands.registerCommand('terminalTest.clearEnvironment', () => {
|
||||
context.environmentVariableCollection.clear();
|
||||
}));
|
||||
|
||||
// vvv Proposed APIs below vvv
|
||||
|
||||
// vscode.window.onDidWriteTerminalData
|
||||
context.subscriptions.push(vscode.commands.registerCommand('terminalTest.onDidWriteTerminalData', () => {
|
||||
(<any>vscode.window).onDidWriteTerminalData((e: any) => {
|
||||
vscode.window.showInformationMessage(`onDidWriteTerminalData listener attached, check the devtools console to see events`);
|
||||
console.log('onDidWriteData', e);
|
||||
});
|
||||
}));
|
||||
|
||||
// vscode.window.onDidChangeTerminalDimensions
|
||||
context.subscriptions.push(vscode.commands.registerCommand('terminalTest.onDidChangeTerminalDimensions', () => {
|
||||
vscode.window.showInformationMessage(`Listening to onDidChangeTerminalDimensions, check the devtools console to see events`);
|
||||
(<any>vscode.window).onDidChangeTerminalDimensions((event: any) => {
|
||||
console.log(`onDidChangeTerminalDimensions: terminal:${event.terminal.name}, columns=${event.dimensions.columns}, rows=${event.dimensions.rows}`);
|
||||
});
|
||||
}));
|
||||
|
||||
// vscode.window.registerTerminalLinkProvider
|
||||
context.subscriptions.push(vscode.commands.registerCommand('terminalTest.registerTerminalLinkProvider', () => {
|
||||
(<any>vscode.window).registerTerminalLinkProvider({
|
||||
provideTerminalLinks: (context: any, token: vscode.CancellationToken) => {
|
||||
// Detect the first instance of the word "link" if it exists and linkify it
|
||||
const startIndex = (context.line as string).indexOf('link');
|
||||
if (startIndex === -1) {
|
||||
return [];
|
||||
}
|
||||
return [
|
||||
{
|
||||
startIndex,
|
||||
length: 'link'.length,
|
||||
tooltip: 'Show a notification',
|
||||
// You can return data in this object to access inside handleTerminalLink
|
||||
data: 'Example data'
|
||||
}
|
||||
];
|
||||
},
|
||||
handleTerminalLink: (link: any) => {
|
||||
vscode.window.showInformationMessage(`Link activated (data = ${link.data})`);
|
||||
}
|
||||
});
|
||||
}));
|
||||
|
||||
context.subscriptions.push(vscode.window.registerTerminalProfileProvider('terminalTest.terminal-profile', {
|
||||
provideTerminalProfile(token: vscode.CancellationToken): vscode.ProviderResult<vscode.TerminalProfile> {
|
||||
return {
|
||||
options: {
|
||||
name: 'Terminal API',
|
||||
shellPath: process.title || 'C:/Windows/System32/cmd.exe'
|
||||
}
|
||||
};
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
function colorText(text: string): string {
|
||||
let output = '';
|
||||
let colorIndex = 1;
|
||||
for (let i = 0; i < text.length; i++) {
|
||||
const char = text.charAt(i);
|
||||
if (char === ' ' || char === '\r' || char === '\n') {
|
||||
output += char;
|
||||
} else {
|
||||
output += `\x1b[3${colorIndex++}m${text.charAt(i)}\x1b[0m`;
|
||||
if (colorIndex > 6) {
|
||||
colorIndex = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
function selectTerminal(): Thenable<vscode.Terminal | undefined> {
|
||||
interface TerminalQuickPickItem extends vscode.QuickPickItem {
|
||||
terminal: vscode.Terminal;
|
||||
}
|
||||
const terminals = <vscode.Terminal[]>(<any>vscode.window).terminals;
|
||||
const items: TerminalQuickPickItem[] = terminals.map(t => {
|
||||
return {
|
||||
label: `name: ${t.name}`,
|
||||
terminal: t
|
||||
};
|
||||
});
|
||||
return vscode.window.showQuickPick(items).then(item => {
|
||||
return item ? item.terminal : undefined;
|
||||
});
|
||||
}
|
||||
|
||||
function ensureTerminalExists(): boolean {
|
||||
if ((<any>vscode.window).terminals.length === 0) {
|
||||
vscode.window.showErrorMessage('No active terminals');
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
'use strict';
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
export function activate(context: vscode.ExtensionContext) {
|
||||
let NEXT_TERM_ID = 1;
|
||||
|
||||
console.log("Terminals: " + (<any>vscode.window).terminals.length);
|
||||
|
||||
// vscode.window.onDidOpenTerminal
|
||||
vscode.window.onDidOpenTerminal(terminal => {
|
||||
console.log("Terminal opened. Total count: " + (<any>vscode.window).terminals.length);
|
||||
});
|
||||
vscode.window.onDidOpenTerminal((terminal: vscode.Terminal) => {
|
||||
vscode.window.showInformationMessage(`onDidOpenTerminal, name: ${terminal.name}`);
|
||||
});
|
||||
|
||||
// vscode.window.onDidChangeActiveTerminal
|
||||
vscode.window.onDidChangeActiveTerminal(e => {
|
||||
console.log(`Active terminal changed, name=${e ? e.name : 'undefined'}`);
|
||||
});
|
||||
|
||||
// vscode.window.createTerminal
|
||||
context.subscriptions.push(vscode.commands.registerCommand('terminalTest.createTerminal', () => {
|
||||
vscode.window.createTerminal(`Ext Terminal #${NEXT_TERM_ID++}`);
|
||||
vscode.window.showInformationMessage('Hello World 2!');
|
||||
}));
|
||||
context.subscriptions.push(vscode.commands.registerCommand('terminalTest.createTerminalHideFromUser', () => {
|
||||
vscode.window.createTerminal({
|
||||
name: `Ext Terminal #${NEXT_TERM_ID++}`,
|
||||
hideFromUser: true
|
||||
} as any);
|
||||
}));
|
||||
context.subscriptions.push(vscode.commands.registerCommand('terminalTest.createAndSend', () => {
|
||||
const terminal = vscode.window.createTerminal(`Ext Terminal #${NEXT_TERM_ID++}`);
|
||||
terminal.sendText("echo 'Sent text immediately after creating'");
|
||||
}));
|
||||
context.subscriptions.push(vscode.commands.registerCommand('terminalTest.createZshLoginShell', () => {
|
||||
vscode.window.createTerminal(`Ext Terminal #${NEXT_TERM_ID++}`, '/bin/zsh', ['-l']);
|
||||
}));
|
||||
|
||||
// Terminal.hide
|
||||
context.subscriptions.push(vscode.commands.registerCommand('terminalTest.hide', () => {
|
||||
if (ensureTerminalExists()) {
|
||||
selectTerminal().then(terminal => {
|
||||
if (terminal) {
|
||||
terminal.hide();
|
||||
}
|
||||
});
|
||||
}
|
||||
}));
|
||||
|
||||
// Terminal.show
|
||||
context.subscriptions.push(vscode.commands.registerCommand('terminalTest.show', () => {
|
||||
if (ensureTerminalExists()) {
|
||||
selectTerminal().then(terminal => {
|
||||
if (terminal) {
|
||||
terminal.show();
|
||||
}
|
||||
});
|
||||
}
|
||||
}));
|
||||
context.subscriptions.push(vscode.commands.registerCommand('terminalTest.showPreserveFocus', () => {
|
||||
if (ensureTerminalExists()) {
|
||||
selectTerminal().then(terminal => {
|
||||
if (terminal) {
|
||||
terminal.show(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
}));
|
||||
|
||||
// Terminal.sendText
|
||||
context.subscriptions.push(vscode.commands.registerCommand('terminalTest.sendText', () => {
|
||||
if (ensureTerminalExists()) {
|
||||
selectTerminal().then(terminal => {
|
||||
if (terminal) {
|
||||
terminal.sendText("echo 'Hello world!'");
|
||||
}
|
||||
});
|
||||
}
|
||||
}));
|
||||
context.subscriptions.push(vscode.commands.registerCommand('terminalTest.sendTextNoNewLine', () => {
|
||||
if (ensureTerminalExists()) {
|
||||
selectTerminal().then(terminal => {
|
||||
if (terminal) {
|
||||
terminal.sendText("echo 'Hello world!'", false);
|
||||
}
|
||||
});
|
||||
}
|
||||
}));
|
||||
|
||||
// Terminal.dispose
|
||||
context.subscriptions.push(vscode.commands.registerCommand('terminalTest.dispose', () => {
|
||||
if (ensureTerminalExists()) {
|
||||
selectTerminal().then(terminal => {
|
||||
if (terminal) {
|
||||
terminal.dispose();
|
||||
}
|
||||
});
|
||||
}
|
||||
}));
|
||||
|
||||
// Terminal.processId
|
||||
context.subscriptions.push(vscode.commands.registerCommand('terminalTest.processId', () => {
|
||||
selectTerminal().then(terminal => {
|
||||
if (!terminal) {
|
||||
return;
|
||||
}
|
||||
terminal.processId.then((processId) => {
|
||||
if (processId) {
|
||||
vscode.window.showInformationMessage(`Terminal.processId: ${processId}`);
|
||||
} else {
|
||||
vscode.window.showInformationMessage('Terminal does not have a process ID');
|
||||
}
|
||||
});
|
||||
});
|
||||
}));
|
||||
|
||||
// vscode.window.onDidCloseTerminal
|
||||
vscode.window.onDidCloseTerminal((terminal) => {
|
||||
vscode.window.showInformationMessage(`onDidCloseTerminal, name: ${terminal.name}`);
|
||||
});
|
||||
|
||||
// vscode.window.terminals
|
||||
context.subscriptions.push(vscode.commands.registerCommand('terminalTest.terminals', () => {
|
||||
selectTerminal();
|
||||
}));
|
||||
|
||||
// ExtensionContext.environmentVariableCollection
|
||||
context.subscriptions.push(vscode.commands.registerCommand('terminalTest.updateEnvironment', () => {
|
||||
const collection = context.environmentVariableCollection;
|
||||
collection.replace('FOO', 'BAR');
|
||||
collection.append('PATH', '/test/path');
|
||||
}));
|
||||
|
||||
context.subscriptions.push(vscode.commands.registerCommand('terminalTest.clearEnvironment', () => {
|
||||
context.environmentVariableCollection.clear();
|
||||
}));
|
||||
|
||||
// vvv Proposed APIs below vvv
|
||||
|
||||
// vscode.window.onDidWriteTerminalData
|
||||
context.subscriptions.push(vscode.commands.registerCommand('terminalTest.onDidWriteTerminalData', () => {
|
||||
(<any>vscode.window).onDidWriteTerminalData((e: any) => {
|
||||
vscode.window.showInformationMessage(`onDidWriteTerminalData listener attached, check the devtools console to see events`);
|
||||
console.log('onDidWriteData', e);
|
||||
});
|
||||
}));
|
||||
|
||||
// vscode.window.onDidChangeTerminalDimensions
|
||||
context.subscriptions.push(vscode.commands.registerCommand('terminalTest.onDidChangeTerminalDimensions', () => {
|
||||
vscode.window.showInformationMessage(`Listening to onDidChangeTerminalDimensions, check the devtools console to see events`);
|
||||
(<any>vscode.window).onDidChangeTerminalDimensions((event: any) => {
|
||||
console.log(`onDidChangeTerminalDimensions: terminal:${event.terminal.name}, columns=${event.dimensions.columns}, rows=${event.dimensions.rows}`);
|
||||
});
|
||||
}));
|
||||
|
||||
// vscode.window.registerTerminalLinkProvider
|
||||
context.subscriptions.push(vscode.commands.registerCommand('terminalTest.registerTerminalLinkProvider', () => {
|
||||
(<any>vscode.window).registerTerminalLinkProvider({
|
||||
provideTerminalLinks: (context: any, token: vscode.CancellationToken) => {
|
||||
// Detect the first instance of the word "link" if it exists and linkify it
|
||||
const startIndex = (context.line as string).indexOf('link');
|
||||
if (startIndex === -1) {
|
||||
return [];
|
||||
}
|
||||
return [
|
||||
{
|
||||
startIndex,
|
||||
length: 'link'.length,
|
||||
tooltip: 'Show a notification',
|
||||
// You can return data in this object to access inside handleTerminalLink
|
||||
data: 'Example data'
|
||||
}
|
||||
];
|
||||
},
|
||||
handleTerminalLink: (link: any) => {
|
||||
vscode.window.showInformationMessage(`Link activated (data = ${link.data})`);
|
||||
}
|
||||
});
|
||||
}));
|
||||
|
||||
context.subscriptions.push(vscode.window.registerTerminalProfileProvider('terminalTest.terminal-profile', {
|
||||
provideTerminalProfile(token: vscode.CancellationToken): vscode.ProviderResult<vscode.TerminalProfile> {
|
||||
return {
|
||||
options: {
|
||||
name: 'Terminal API',
|
||||
shellPath: process.title || 'C:/Windows/System32/cmd.exe'
|
||||
}
|
||||
};
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
function colorText(text: string): string {
|
||||
let output = '';
|
||||
let colorIndex = 1;
|
||||
for (let i = 0; i < text.length; i++) {
|
||||
const char = text.charAt(i);
|
||||
if (char === ' ' || char === '\r' || char === '\n') {
|
||||
output += char;
|
||||
} else {
|
||||
output += `\x1b[3${colorIndex++}m${text.charAt(i)}\x1b[0m`;
|
||||
if (colorIndex > 6) {
|
||||
colorIndex = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
function selectTerminal(): Thenable<vscode.Terminal | undefined> {
|
||||
interface TerminalQuickPickItem extends vscode.QuickPickItem {
|
||||
terminal: vscode.Terminal;
|
||||
}
|
||||
const terminals = <vscode.Terminal[]>(<any>vscode.window).terminals;
|
||||
const items: TerminalQuickPickItem[] = terminals.map(t => {
|
||||
return {
|
||||
label: `name: ${t.name}`,
|
||||
terminal: t
|
||||
};
|
||||
});
|
||||
return vscode.window.showQuickPick(items).then(item => {
|
||||
return item ? item.terminal : undefined;
|
||||
});
|
||||
}
|
||||
|
||||
function ensureTerminalExists(): boolean {
|
||||
if ((<any>vscode.window).terminals.length === 0) {
|
||||
vscode.window.showErrorMessage('No active terminals');
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -1,186 +1,186 @@
|
||||
import * as vscode from 'vscode';
|
||||
import { getContentFromFilesystem, MarkdownTestData, TestCase, testData, TestFile } from './testTree';
|
||||
|
||||
export async function activate(context: vscode.ExtensionContext) {
|
||||
const ctrl = vscode.tests.createTestController('mathTestController', 'Markdown Math');
|
||||
context.subscriptions.push(ctrl);
|
||||
|
||||
const runHandler = (request: vscode.TestRunRequest, cancellation: vscode.CancellationToken) => {
|
||||
const queue: { test: vscode.TestItem; data: TestCase }[] = [];
|
||||
const run = ctrl.createTestRun(request);
|
||||
// map of file uris to statements on each line:
|
||||
const coveredLines = new Map</* file uri */ string, (vscode.StatementCoverage | undefined)[]>();
|
||||
|
||||
const discoverTests = async (tests: Iterable<vscode.TestItem>) => {
|
||||
for (const test of tests) {
|
||||
if (request.exclude?.includes(test)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const data = testData.get(test);
|
||||
if (data instanceof TestCase) {
|
||||
run.enqueued(test);
|
||||
queue.push({ test, data });
|
||||
} else {
|
||||
if (data instanceof TestFile && !data.didResolve) {
|
||||
await data.updateFromDisk(ctrl, test);
|
||||
}
|
||||
|
||||
await discoverTests(gatherTestItems(test.children));
|
||||
}
|
||||
|
||||
if (test.uri && !coveredLines.has(test.uri.toString())) {
|
||||
try {
|
||||
const lines = (await getContentFromFilesystem(test.uri)).split('\n');
|
||||
coveredLines.set(
|
||||
test.uri.toString(),
|
||||
lines.map((lineText, lineNo) =>
|
||||
lineText.trim().length ? new vscode.StatementCoverage(0, new vscode.Position(lineNo, 0)) : undefined
|
||||
)
|
||||
);
|
||||
} catch {
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const runTestQueue = async () => {
|
||||
for (const { test, data } of queue) {
|
||||
run.appendOutput(`Running ${test.id}\r\n`);
|
||||
if (cancellation.isCancellationRequested) {
|
||||
run.skipped(test);
|
||||
} else {
|
||||
run.started(test);
|
||||
await data.run(test, run);
|
||||
}
|
||||
|
||||
const lineNo = test.range!.start.line;
|
||||
const fileCoverage = coveredLines.get(test.uri!.toString());
|
||||
if (fileCoverage) {
|
||||
fileCoverage[lineNo]!.executionCount++;
|
||||
}
|
||||
|
||||
run.appendOutput(`Completed ${test.id}\r\n`);
|
||||
}
|
||||
|
||||
run.end();
|
||||
};
|
||||
|
||||
run.coverageProvider = {
|
||||
provideFileCoverage() {
|
||||
const coverage: vscode.FileCoverage[] = [];
|
||||
for (const [uri, statements] of coveredLines) {
|
||||
coverage.push(
|
||||
vscode.FileCoverage.fromDetails(
|
||||
vscode.Uri.parse(uri),
|
||||
statements.filter((s): s is vscode.StatementCoverage => !!s)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return coverage;
|
||||
},
|
||||
};
|
||||
|
||||
discoverTests(request.include ?? gatherTestItems(ctrl.items)).then(runTestQueue);
|
||||
};
|
||||
|
||||
ctrl.refreshHandler = async () => {
|
||||
await Promise.all(getWorkspaceTestPatterns().map(({ pattern }) => findInitialFiles(ctrl, pattern)));
|
||||
};
|
||||
|
||||
ctrl.createRunProfile('Run Tests', vscode.TestRunProfileKind.Run, runHandler, true);
|
||||
|
||||
ctrl.resolveHandler = async item => {
|
||||
if (!item) {
|
||||
context.subscriptions.push(...startWatchingWorkspace(ctrl));
|
||||
return;
|
||||
}
|
||||
|
||||
const data = testData.get(item);
|
||||
if (data instanceof TestFile) {
|
||||
await data.updateFromDisk(ctrl, item);
|
||||
}
|
||||
};
|
||||
|
||||
function updateNodeForDocument(e: vscode.TextDocument) {
|
||||
if (e.uri.scheme !== 'file') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!e.uri.path.endsWith('.md')) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { file, data } = getOrCreateFile(ctrl, e.uri);
|
||||
data.updateFromContents(ctrl, e.getText(), file);
|
||||
}
|
||||
|
||||
for (const document of vscode.workspace.textDocuments) {
|
||||
updateNodeForDocument(document);
|
||||
}
|
||||
|
||||
context.subscriptions.push(
|
||||
vscode.workspace.onDidOpenTextDocument(updateNodeForDocument),
|
||||
vscode.workspace.onDidChangeTextDocument(e => updateNodeForDocument(e.document)),
|
||||
);
|
||||
}
|
||||
|
||||
function getOrCreateFile(controller: vscode.TestController, uri: vscode.Uri) {
|
||||
const existing = controller.items.get(uri.toString());
|
||||
if (existing) {
|
||||
return { file: existing, data: testData.get(existing) as TestFile };
|
||||
}
|
||||
|
||||
const file = controller.createTestItem(uri.toString(), uri.path.split('/').pop()!, uri);
|
||||
controller.items.add(file);
|
||||
|
||||
const data = new TestFile();
|
||||
testData.set(file, data);
|
||||
|
||||
file.canResolveChildren = true;
|
||||
return { file, data };
|
||||
}
|
||||
|
||||
function gatherTestItems(collection: vscode.TestItemCollection) {
|
||||
const items: vscode.TestItem[] = [];
|
||||
collection.forEach(item => items.push(item));
|
||||
return items;
|
||||
}
|
||||
|
||||
function getWorkspaceTestPatterns() {
|
||||
if (!vscode.workspace.workspaceFolders) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return vscode.workspace.workspaceFolders.map(workspaceFolder => ({
|
||||
workspaceFolder,
|
||||
pattern: new vscode.RelativePattern(workspaceFolder, '**/*.md'),
|
||||
}));
|
||||
}
|
||||
|
||||
async function findInitialFiles(controller: vscode.TestController, pattern: vscode.GlobPattern) {
|
||||
for (const file of await vscode.workspace.findFiles(pattern)) {
|
||||
getOrCreateFile(controller, file);
|
||||
}
|
||||
}
|
||||
|
||||
function startWatchingWorkspace(controller: vscode.TestController) {
|
||||
return getWorkspaceTestPatterns().map(({ workspaceFolder, pattern }) => {
|
||||
const watcher = vscode.workspace.createFileSystemWatcher(pattern);
|
||||
|
||||
watcher.onDidCreate(uri => getOrCreateFile(controller, uri));
|
||||
watcher.onDidChange(uri => {
|
||||
const { file, data } = getOrCreateFile(controller, uri);
|
||||
if (data.didResolve) {
|
||||
data.updateFromDisk(controller, file);
|
||||
}
|
||||
});
|
||||
watcher.onDidDelete(uri => controller.items.delete(uri.toString()));
|
||||
|
||||
findInitialFiles(controller, pattern);
|
||||
|
||||
return watcher;
|
||||
});
|
||||
}
|
||||
import * as vscode from 'vscode';
|
||||
import { getContentFromFilesystem, MarkdownTestData, TestCase, testData, TestFile } from './testTree';
|
||||
|
||||
export async function activate(context: vscode.ExtensionContext) {
|
||||
const ctrl = vscode.tests.createTestController('mathTestController', 'Markdown Math');
|
||||
context.subscriptions.push(ctrl);
|
||||
|
||||
const runHandler = (request: vscode.TestRunRequest, cancellation: vscode.CancellationToken) => {
|
||||
const queue: { test: vscode.TestItem; data: TestCase }[] = [];
|
||||
const run = ctrl.createTestRun(request);
|
||||
// map of file uris to statements on each line:
|
||||
const coveredLines = new Map</* file uri */ string, (vscode.StatementCoverage | undefined)[]>();
|
||||
|
||||
const discoverTests = async (tests: Iterable<vscode.TestItem>) => {
|
||||
for (const test of tests) {
|
||||
if (request.exclude?.includes(test)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const data = testData.get(test);
|
||||
if (data instanceof TestCase) {
|
||||
run.enqueued(test);
|
||||
queue.push({ test, data });
|
||||
} else {
|
||||
if (data instanceof TestFile && !data.didResolve) {
|
||||
await data.updateFromDisk(ctrl, test);
|
||||
}
|
||||
|
||||
await discoverTests(gatherTestItems(test.children));
|
||||
}
|
||||
|
||||
if (test.uri && !coveredLines.has(test.uri.toString())) {
|
||||
try {
|
||||
const lines = (await getContentFromFilesystem(test.uri)).split('\n');
|
||||
coveredLines.set(
|
||||
test.uri.toString(),
|
||||
lines.map((lineText, lineNo) =>
|
||||
lineText.trim().length ? new vscode.StatementCoverage(0, new vscode.Position(lineNo, 0)) : undefined
|
||||
)
|
||||
);
|
||||
} catch {
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const runTestQueue = async () => {
|
||||
for (const { test, data } of queue) {
|
||||
run.appendOutput(`Running ${test.id}\r\n`);
|
||||
if (cancellation.isCancellationRequested) {
|
||||
run.skipped(test);
|
||||
} else {
|
||||
run.started(test);
|
||||
await data.run(test, run);
|
||||
}
|
||||
|
||||
const lineNo = test.range!.start.line;
|
||||
const fileCoverage = coveredLines.get(test.uri!.toString());
|
||||
if (fileCoverage) {
|
||||
fileCoverage[lineNo]!.executionCount++;
|
||||
}
|
||||
|
||||
run.appendOutput(`Completed ${test.id}\r\n`);
|
||||
}
|
||||
|
||||
run.end();
|
||||
};
|
||||
|
||||
run.coverageProvider = {
|
||||
provideFileCoverage() {
|
||||
const coverage: vscode.FileCoverage[] = [];
|
||||
for (const [uri, statements] of coveredLines) {
|
||||
coverage.push(
|
||||
vscode.FileCoverage.fromDetails(
|
||||
vscode.Uri.parse(uri),
|
||||
statements.filter((s): s is vscode.StatementCoverage => !!s)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return coverage;
|
||||
},
|
||||
};
|
||||
|
||||
discoverTests(request.include ?? gatherTestItems(ctrl.items)).then(runTestQueue);
|
||||
};
|
||||
|
||||
ctrl.refreshHandler = async () => {
|
||||
await Promise.all(getWorkspaceTestPatterns().map(({ pattern }) => findInitialFiles(ctrl, pattern)));
|
||||
};
|
||||
|
||||
ctrl.createRunProfile('Run Tests', vscode.TestRunProfileKind.Run, runHandler, true);
|
||||
|
||||
ctrl.resolveHandler = async item => {
|
||||
if (!item) {
|
||||
context.subscriptions.push(...startWatchingWorkspace(ctrl));
|
||||
return;
|
||||
}
|
||||
|
||||
const data = testData.get(item);
|
||||
if (data instanceof TestFile) {
|
||||
await data.updateFromDisk(ctrl, item);
|
||||
}
|
||||
};
|
||||
|
||||
function updateNodeForDocument(e: vscode.TextDocument) {
|
||||
if (e.uri.scheme !== 'file') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!e.uri.path.endsWith('.md')) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { file, data } = getOrCreateFile(ctrl, e.uri);
|
||||
data.updateFromContents(ctrl, e.getText(), file);
|
||||
}
|
||||
|
||||
for (const document of vscode.workspace.textDocuments) {
|
||||
updateNodeForDocument(document);
|
||||
}
|
||||
|
||||
context.subscriptions.push(
|
||||
vscode.workspace.onDidOpenTextDocument(updateNodeForDocument),
|
||||
vscode.workspace.onDidChangeTextDocument(e => updateNodeForDocument(e.document)),
|
||||
);
|
||||
}
|
||||
|
||||
function getOrCreateFile(controller: vscode.TestController, uri: vscode.Uri) {
|
||||
const existing = controller.items.get(uri.toString());
|
||||
if (existing) {
|
||||
return { file: existing, data: testData.get(existing) as TestFile };
|
||||
}
|
||||
|
||||
const file = controller.createTestItem(uri.toString(), uri.path.split('/').pop()!, uri);
|
||||
controller.items.add(file);
|
||||
|
||||
const data = new TestFile();
|
||||
testData.set(file, data);
|
||||
|
||||
file.canResolveChildren = true;
|
||||
return { file, data };
|
||||
}
|
||||
|
||||
function gatherTestItems(collection: vscode.TestItemCollection) {
|
||||
const items: vscode.TestItem[] = [];
|
||||
collection.forEach(item => items.push(item));
|
||||
return items;
|
||||
}
|
||||
|
||||
function getWorkspaceTestPatterns() {
|
||||
if (!vscode.workspace.workspaceFolders) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return vscode.workspace.workspaceFolders.map(workspaceFolder => ({
|
||||
workspaceFolder,
|
||||
pattern: new vscode.RelativePattern(workspaceFolder, '**/*.md'),
|
||||
}));
|
||||
}
|
||||
|
||||
async function findInitialFiles(controller: vscode.TestController, pattern: vscode.GlobPattern) {
|
||||
for (const file of await vscode.workspace.findFiles(pattern)) {
|
||||
getOrCreateFile(controller, file);
|
||||
}
|
||||
}
|
||||
|
||||
function startWatchingWorkspace(controller: vscode.TestController) {
|
||||
return getWorkspaceTestPatterns().map(({ workspaceFolder, pattern }) => {
|
||||
const watcher = vscode.workspace.createFileSystemWatcher(pattern);
|
||||
|
||||
watcher.onDidCreate(uri => getOrCreateFile(controller, uri));
|
||||
watcher.onDidChange(uri => {
|
||||
const { file, data } = getOrCreateFile(controller, uri);
|
||||
if (data.didResolve) {
|
||||
data.updateFromDisk(controller, file);
|
||||
}
|
||||
});
|
||||
watcher.onDidDelete(uri => controller.items.delete(uri.toString()));
|
||||
|
||||
findInitialFiles(controller, pattern);
|
||||
|
||||
return watcher;
|
||||
});
|
||||
}
|
||||
|
||||
@ -1,29 +1,29 @@
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
const testRe = /^([0-9]+)\s*([+*/-])\s*([0-9]+)\s*=\s*([0-9]+)/;
|
||||
const headingRe = /^(#+)\s*(.+)$/;
|
||||
|
||||
export const parseMarkdown = (text: string, events: {
|
||||
onTest(range: vscode.Range, a: number, operator: string, b: number, expected: number): void;
|
||||
onHeading(range: vscode.Range, name: string, depth: number): void;
|
||||
}) => {
|
||||
const lines = text.split('\n');
|
||||
|
||||
for (let lineNo = 0; lineNo < lines.length; lineNo++) {
|
||||
const line = lines[lineNo];
|
||||
const test = testRe.exec(line);
|
||||
if (test) {
|
||||
const [, a, operator, b, expected] = test;
|
||||
const range = new vscode.Range(new vscode.Position(lineNo, 0), new vscode.Position(lineNo, test[0].length));
|
||||
events.onTest(range, Number(a), operator, Number(b), Number(expected));
|
||||
continue;
|
||||
}
|
||||
|
||||
const heading = headingRe.exec(line);
|
||||
if (heading) {
|
||||
const [, pounds, name] = heading;
|
||||
const range = new vscode.Range(new vscode.Position(lineNo, 0), new vscode.Position(lineNo, line.length));
|
||||
events.onHeading(range, name, pounds.length);
|
||||
}
|
||||
}
|
||||
};
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
const testRe = /^([0-9]+)\s*([+*/-])\s*([0-9]+)\s*=\s*([0-9]+)/;
|
||||
const headingRe = /^(#+)\s*(.+)$/;
|
||||
|
||||
export const parseMarkdown = (text: string, events: {
|
||||
onTest(range: vscode.Range, a: number, operator: string, b: number, expected: number): void;
|
||||
onHeading(range: vscode.Range, name: string, depth: number): void;
|
||||
}) => {
|
||||
const lines = text.split('\n');
|
||||
|
||||
for (let lineNo = 0; lineNo < lines.length; lineNo++) {
|
||||
const line = lines[lineNo];
|
||||
const test = testRe.exec(line);
|
||||
if (test) {
|
||||
const [, a, operator, b, expected] = test;
|
||||
const range = new vscode.Range(new vscode.Position(lineNo, 0), new vscode.Position(lineNo, test[0].length));
|
||||
events.onTest(range, Number(a), operator, Number(b), Number(expected));
|
||||
continue;
|
||||
}
|
||||
|
||||
const heading = headingRe.exec(line);
|
||||
if (heading) {
|
||||
const [, pounds, name] = heading;
|
||||
const range = new vscode.Range(new vscode.Position(lineNo, 0), new vscode.Position(lineNo, line.length));
|
||||
events.onHeading(range, name, pounds.length);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@ -1,128 +1,128 @@
|
||||
import { TextDecoder } from 'util';
|
||||
import * as vscode from 'vscode';
|
||||
import { parseMarkdown } from './parser';
|
||||
|
||||
const textDecoder = new TextDecoder('utf-8');
|
||||
|
||||
export type MarkdownTestData = TestFile | TestHeading | TestCase;
|
||||
|
||||
export const testData = new WeakMap<vscode.TestItem, MarkdownTestData>();
|
||||
|
||||
let generationCounter = 0;
|
||||
|
||||
export const getContentFromFilesystem = async (uri: vscode.Uri) => {
|
||||
try {
|
||||
const rawContent = await vscode.workspace.fs.readFile(uri);
|
||||
return textDecoder.decode(rawContent);
|
||||
} catch (e) {
|
||||
console.warn(`Error providing tests for ${uri.fsPath}`, e);
|
||||
return '';
|
||||
}
|
||||
};
|
||||
|
||||
export class TestFile {
|
||||
public didResolve = false;
|
||||
|
||||
public async updateFromDisk(controller: vscode.TestController, item: vscode.TestItem) {
|
||||
try {
|
||||
const content = await getContentFromFilesystem(item.uri!);
|
||||
item.error = undefined;
|
||||
this.updateFromContents(controller, content, item);
|
||||
} catch (e) {
|
||||
item.error = (e as Error).stack;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the tests from the input text, and updates the tests contained
|
||||
* by this file to be those from the text,
|
||||
*/
|
||||
public updateFromContents(controller: vscode.TestController, content: string, item: vscode.TestItem) {
|
||||
const ancestors = [{ item, children: [] as vscode.TestItem[] }];
|
||||
const thisGeneration = generationCounter++;
|
||||
this.didResolve = true;
|
||||
|
||||
const ascend = (depth: number) => {
|
||||
while (ancestors.length > depth) {
|
||||
const finished = ancestors.pop()!;
|
||||
finished.item.children.replace(finished.children);
|
||||
}
|
||||
};
|
||||
|
||||
parseMarkdown(content, {
|
||||
onTest: (range, a, operator, b, expected) => {
|
||||
const parent = ancestors[ancestors.length - 1];
|
||||
const data = new TestCase(a, operator as Operator, b, expected, thisGeneration);
|
||||
const id = `${item.uri}/${data.getLabel()}`;
|
||||
|
||||
|
||||
const tcase = controller.createTestItem(id, data.getLabel(), item.uri);
|
||||
testData.set(tcase, data);
|
||||
tcase.range = range;
|
||||
parent.children.push(tcase);
|
||||
},
|
||||
|
||||
onHeading: (range, name, depth) => {
|
||||
ascend(depth);
|
||||
const parent = ancestors[ancestors.length - 1];
|
||||
const id = `${item.uri}/${name}`;
|
||||
|
||||
const thead = controller.createTestItem(id, name, item.uri);
|
||||
thead.range = range;
|
||||
testData.set(thead, new TestHeading(thisGeneration));
|
||||
parent.children.push(thead);
|
||||
ancestors.push({ item: thead, children: [] });
|
||||
},
|
||||
});
|
||||
|
||||
ascend(0); // finish and assign children for all remaining items
|
||||
}
|
||||
}
|
||||
|
||||
export class TestHeading {
|
||||
constructor(public generation: number) { }
|
||||
}
|
||||
|
||||
type Operator = '+' | '-' | '*' | '/';
|
||||
|
||||
export class TestCase {
|
||||
constructor(
|
||||
private readonly a: number,
|
||||
private readonly operator: Operator,
|
||||
private readonly b: number,
|
||||
private readonly expected: number,
|
||||
public generation: number
|
||||
) { }
|
||||
|
||||
getLabel() {
|
||||
return `${this.a} ${this.operator} ${this.b} = ${this.expected}`;
|
||||
}
|
||||
|
||||
async run(item: vscode.TestItem, options: vscode.TestRun): Promise<void> {
|
||||
const start = Date.now();
|
||||
await new Promise(resolve => setTimeout(resolve, 1000 + Math.random() * 1000));
|
||||
const actual = this.evaluate();
|
||||
const duration = Date.now() - start;
|
||||
|
||||
if (actual === this.expected) {
|
||||
options.passed(item, duration);
|
||||
} else {
|
||||
const message = vscode.TestMessage.diff(`Expected ${item.label}`, String(this.expected), String(actual));
|
||||
message.location = new vscode.Location(item.uri!, item.range!);
|
||||
options.failed(item, message, duration);
|
||||
}
|
||||
}
|
||||
|
||||
private evaluate() {
|
||||
switch (this.operator) {
|
||||
case '-':
|
||||
return this.a - this.b;
|
||||
case '+':
|
||||
return this.a + this.b;
|
||||
case '/':
|
||||
return Math.floor(this.a / this.b);
|
||||
case '*':
|
||||
return this.a * this.b;
|
||||
}
|
||||
}
|
||||
}
|
||||
import { TextDecoder } from 'util';
|
||||
import * as vscode from 'vscode';
|
||||
import { parseMarkdown } from './parser';
|
||||
|
||||
const textDecoder = new TextDecoder('utf-8');
|
||||
|
||||
export type MarkdownTestData = TestFile | TestHeading | TestCase;
|
||||
|
||||
export const testData = new WeakMap<vscode.TestItem, MarkdownTestData>();
|
||||
|
||||
let generationCounter = 0;
|
||||
|
||||
export const getContentFromFilesystem = async (uri: vscode.Uri) => {
|
||||
try {
|
||||
const rawContent = await vscode.workspace.fs.readFile(uri);
|
||||
return textDecoder.decode(rawContent);
|
||||
} catch (e) {
|
||||
console.warn(`Error providing tests for ${uri.fsPath}`, e);
|
||||
return '';
|
||||
}
|
||||
};
|
||||
|
||||
export class TestFile {
|
||||
public didResolve = false;
|
||||
|
||||
public async updateFromDisk(controller: vscode.TestController, item: vscode.TestItem) {
|
||||
try {
|
||||
const content = await getContentFromFilesystem(item.uri!);
|
||||
item.error = undefined;
|
||||
this.updateFromContents(controller, content, item);
|
||||
} catch (e) {
|
||||
item.error = (e as Error).stack;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the tests from the input text, and updates the tests contained
|
||||
* by this file to be those from the text,
|
||||
*/
|
||||
public updateFromContents(controller: vscode.TestController, content: string, item: vscode.TestItem) {
|
||||
const ancestors = [{ item, children: [] as vscode.TestItem[] }];
|
||||
const thisGeneration = generationCounter++;
|
||||
this.didResolve = true;
|
||||
|
||||
const ascend = (depth: number) => {
|
||||
while (ancestors.length > depth) {
|
||||
const finished = ancestors.pop()!;
|
||||
finished.item.children.replace(finished.children);
|
||||
}
|
||||
};
|
||||
|
||||
parseMarkdown(content, {
|
||||
onTest: (range, a, operator, b, expected) => {
|
||||
const parent = ancestors[ancestors.length - 1];
|
||||
const data = new TestCase(a, operator as Operator, b, expected, thisGeneration);
|
||||
const id = `${item.uri}/${data.getLabel()}`;
|
||||
|
||||
|
||||
const tcase = controller.createTestItem(id, data.getLabel(), item.uri);
|
||||
testData.set(tcase, data);
|
||||
tcase.range = range;
|
||||
parent.children.push(tcase);
|
||||
},
|
||||
|
||||
onHeading: (range, name, depth) => {
|
||||
ascend(depth);
|
||||
const parent = ancestors[ancestors.length - 1];
|
||||
const id = `${item.uri}/${name}`;
|
||||
|
||||
const thead = controller.createTestItem(id, name, item.uri);
|
||||
thead.range = range;
|
||||
testData.set(thead, new TestHeading(thisGeneration));
|
||||
parent.children.push(thead);
|
||||
ancestors.push({ item: thead, children: [] });
|
||||
},
|
||||
});
|
||||
|
||||
ascend(0); // finish and assign children for all remaining items
|
||||
}
|
||||
}
|
||||
|
||||
export class TestHeading {
|
||||
constructor(public generation: number) { }
|
||||
}
|
||||
|
||||
type Operator = '+' | '-' | '*' | '/';
|
||||
|
||||
export class TestCase {
|
||||
constructor(
|
||||
private readonly a: number,
|
||||
private readonly operator: Operator,
|
||||
private readonly b: number,
|
||||
private readonly expected: number,
|
||||
public generation: number
|
||||
) { }
|
||||
|
||||
getLabel() {
|
||||
return `${this.a} ${this.operator} ${this.b} = ${this.expected}`;
|
||||
}
|
||||
|
||||
async run(item: vscode.TestItem, options: vscode.TestRun): Promise<void> {
|
||||
const start = Date.now();
|
||||
await new Promise(resolve => setTimeout(resolve, 1000 + Math.random() * 1000));
|
||||
const actual = this.evaluate();
|
||||
const duration = Date.now() - start;
|
||||
|
||||
if (actual === this.expected) {
|
||||
options.passed(item, duration);
|
||||
} else {
|
||||
const message = vscode.TestMessage.diff(`Expected ${item.label}`, String(this.expected), String(actual));
|
||||
message.location = new vscode.Location(item.uri!, item.range!);
|
||||
options.failed(item, message, duration);
|
||||
}
|
||||
}
|
||||
|
||||
private evaluate() {
|
||||
switch (this.operator) {
|
||||
case '-':
|
||||
return this.a - this.b;
|
||||
case '+':
|
||||
return this.a + this.b;
|
||||
case '/':
|
||||
return Math.floor(this.a / this.b);
|
||||
case '*':
|
||||
return this.a * this.b;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,54 +1,54 @@
|
||||
'use strict';
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
import { DepNodeProvider, Dependency } from './nodeDependencies';
|
||||
import { JsonOutlineProvider } from './jsonOutline';
|
||||
import { FtpExplorer } from './ftpExplorer';
|
||||
import { FileExplorer } from './fileExplorer';
|
||||
import { TestViewDragAndDrop } from './testViewDragAndDrop';
|
||||
import { TestView } from './testView';
|
||||
|
||||
export function activate(context: vscode.ExtensionContext) {
|
||||
const rootPath = (vscode.workspace.workspaceFolders && (vscode.workspace.workspaceFolders.length > 0))
|
||||
? vscode.workspace.workspaceFolders[0].uri.fsPath : undefined;
|
||||
|
||||
// Samples of `window.registerTreeDataProvider`
|
||||
const nodeDependenciesProvider = new DepNodeProvider(rootPath);
|
||||
vscode.window.registerTreeDataProvider('nodeDependencies', nodeDependenciesProvider);
|
||||
vscode.commands.registerCommand('nodeDependencies.refreshEntry', () => nodeDependenciesProvider.refresh());
|
||||
vscode.commands.registerCommand('extension.openPackageOnNpm', moduleName => vscode.commands.executeCommand('vscode.open', vscode.Uri.parse(`https://www.npmjs.com/package/${moduleName}`)));
|
||||
vscode.commands.registerCommand('nodeDependencies.addEntry', () => vscode.window.showInformationMessage(`Successfully called add entry.`));
|
||||
vscode.commands.registerCommand('nodeDependencies.editEntry', (node: Dependency) => vscode.window.showInformationMessage(`Successfully called edit entry on ${node.label}.`));
|
||||
vscode.commands.registerCommand('nodeDependencies.deleteEntry', (node: Dependency) => vscode.window.showInformationMessage(`Successfully called delete entry on ${node.label}.`));
|
||||
|
||||
const jsonOutlineProvider = new JsonOutlineProvider(context);
|
||||
vscode.window.registerTreeDataProvider('jsonOutline', jsonOutlineProvider);
|
||||
vscode.commands.registerCommand('jsonOutline.refresh', () => jsonOutlineProvider.refresh());
|
||||
vscode.commands.registerCommand('jsonOutline.refreshNode', offset => jsonOutlineProvider.refresh(offset));
|
||||
vscode.commands.registerCommand('jsonOutline.renameNode', args => {
|
||||
let offset = undefined;
|
||||
if(args.selectedTreeItems && args.selectedTreeItems.length){
|
||||
offset = args.selectedTreeItems[0];
|
||||
}else if(typeof args === 'number'){
|
||||
offset = args;
|
||||
}
|
||||
if(offset){
|
||||
jsonOutlineProvider.rename(offset);
|
||||
}
|
||||
});
|
||||
vscode.commands.registerCommand('extension.openJsonSelection', range => jsonOutlineProvider.select(range));
|
||||
|
||||
// Samples of `window.createView`
|
||||
new FtpExplorer(context);
|
||||
new FileExplorer(context);
|
||||
|
||||
// Test View
|
||||
new TestView(context);
|
||||
|
||||
// Drag and Drop proposed API sample
|
||||
// This check is for older versions of VS Code that don't have the most up-to-date tree drag and drop API proposal.
|
||||
if (typeof vscode.DataTransferItem === 'function') {
|
||||
new TestViewDragAndDrop(context);
|
||||
}
|
||||
'use strict';
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
import { DepNodeProvider, Dependency } from './nodeDependencies';
|
||||
import { JsonOutlineProvider } from './jsonOutline';
|
||||
import { FtpExplorer } from './ftpExplorer';
|
||||
import { FileExplorer } from './fileExplorer';
|
||||
import { TestViewDragAndDrop } from './testViewDragAndDrop';
|
||||
import { TestView } from './testView';
|
||||
|
||||
export function activate(context: vscode.ExtensionContext) {
|
||||
const rootPath = (vscode.workspace.workspaceFolders && (vscode.workspace.workspaceFolders.length > 0))
|
||||
? vscode.workspace.workspaceFolders[0].uri.fsPath : undefined;
|
||||
|
||||
// Samples of `window.registerTreeDataProvider`
|
||||
const nodeDependenciesProvider = new DepNodeProvider(rootPath);
|
||||
vscode.window.registerTreeDataProvider('nodeDependencies', nodeDependenciesProvider);
|
||||
vscode.commands.registerCommand('nodeDependencies.refreshEntry', () => nodeDependenciesProvider.refresh());
|
||||
vscode.commands.registerCommand('extension.openPackageOnNpm', moduleName => vscode.commands.executeCommand('vscode.open', vscode.Uri.parse(`https://www.npmjs.com/package/${moduleName}`)));
|
||||
vscode.commands.registerCommand('nodeDependencies.addEntry', () => vscode.window.showInformationMessage(`Successfully called add entry.`));
|
||||
vscode.commands.registerCommand('nodeDependencies.editEntry', (node: Dependency) => vscode.window.showInformationMessage(`Successfully called edit entry on ${node.label}.`));
|
||||
vscode.commands.registerCommand('nodeDependencies.deleteEntry', (node: Dependency) => vscode.window.showInformationMessage(`Successfully called delete entry on ${node.label}.`));
|
||||
|
||||
const jsonOutlineProvider = new JsonOutlineProvider(context);
|
||||
vscode.window.registerTreeDataProvider('jsonOutline', jsonOutlineProvider);
|
||||
vscode.commands.registerCommand('jsonOutline.refresh', () => jsonOutlineProvider.refresh());
|
||||
vscode.commands.registerCommand('jsonOutline.refreshNode', offset => jsonOutlineProvider.refresh(offset));
|
||||
vscode.commands.registerCommand('jsonOutline.renameNode', args => {
|
||||
let offset = undefined;
|
||||
if (args.selectedTreeItems && args.selectedTreeItems.length) {
|
||||
offset = args.selectedTreeItems[0];
|
||||
} else if (typeof args === 'number') {
|
||||
offset = args;
|
||||
}
|
||||
if (offset) {
|
||||
jsonOutlineProvider.rename(offset);
|
||||
}
|
||||
});
|
||||
vscode.commands.registerCommand('extension.openJsonSelection', range => jsonOutlineProvider.select(range));
|
||||
|
||||
// Samples of `window.createView`
|
||||
new FtpExplorer(context);
|
||||
new FileExplorer(context);
|
||||
|
||||
// Test View
|
||||
new TestView(context);
|
||||
|
||||
// Drag and Drop proposed API sample
|
||||
// This check is for older versions of VS Code that don't have the most up-to-date tree drag and drop API proposal.
|
||||
if (typeof vscode.DataTransferItem === 'function') {
|
||||
new TestViewDragAndDrop(context);
|
||||
}
|
||||
}
|
||||
@ -1,308 +1,308 @@
|
||||
import * as vscode from 'vscode';
|
||||
import * as path from 'path';
|
||||
import * as fs from 'fs';
|
||||
import * as mkdirp from 'mkdirp';
|
||||
import * as rimraf from 'rimraf';
|
||||
|
||||
//#region Utilities
|
||||
|
||||
namespace _ {
|
||||
|
||||
function handleResult<T>(resolve: (result: T) => void, reject: (error: Error) => void, error: Error | null | undefined, result: T): void {
|
||||
if (error) {
|
||||
reject(massageError(error));
|
||||
} else {
|
||||
resolve(result);
|
||||
}
|
||||
}
|
||||
|
||||
function massageError(error: Error & { code?: string }): Error {
|
||||
if (error.code === 'ENOENT') {
|
||||
return vscode.FileSystemError.FileNotFound();
|
||||
}
|
||||
|
||||
if (error.code === 'EISDIR') {
|
||||
return vscode.FileSystemError.FileIsADirectory();
|
||||
}
|
||||
|
||||
if (error.code === 'EEXIST') {
|
||||
return vscode.FileSystemError.FileExists();
|
||||
}
|
||||
|
||||
if (error.code === 'EPERM' || error.code === 'EACCESS') {
|
||||
return vscode.FileSystemError.NoPermissions();
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
export function checkCancellation(token: vscode.CancellationToken): void {
|
||||
if (token.isCancellationRequested) {
|
||||
throw new Error('Operation cancelled');
|
||||
}
|
||||
}
|
||||
|
||||
export function normalizeNFC(items: string): string;
|
||||
export function normalizeNFC(items: string[]): string[];
|
||||
export function normalizeNFC(items: string | string[]): string | string[] {
|
||||
if (process.platform !== 'darwin') {
|
||||
return items;
|
||||
}
|
||||
|
||||
if (Array.isArray(items)) {
|
||||
return items.map(item => item.normalize('NFC'));
|
||||
}
|
||||
|
||||
return items.normalize('NFC');
|
||||
}
|
||||
|
||||
export function readdir(path: string): Promise<string[]> {
|
||||
return new Promise<string[]>((resolve, reject) => {
|
||||
fs.readdir(path, (error, children) => handleResult(resolve, reject, error, normalizeNFC(children)));
|
||||
});
|
||||
}
|
||||
|
||||
export function stat(path: string): Promise<fs.Stats> {
|
||||
return new Promise<fs.Stats>((resolve, reject) => {
|
||||
fs.stat(path, (error, stat) => handleResult(resolve, reject, error, stat));
|
||||
});
|
||||
}
|
||||
|
||||
export function readfile(path: string): Promise<Buffer> {
|
||||
return new Promise<Buffer>((resolve, reject) => {
|
||||
fs.readFile(path, (error, buffer) => handleResult(resolve, reject, error, buffer));
|
||||
});
|
||||
}
|
||||
|
||||
export function writefile(path: string, content: Buffer): Promise<void> {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
fs.writeFile(path, content, error => handleResult(resolve, reject, error, void 0));
|
||||
});
|
||||
}
|
||||
|
||||
export function exists(path: string): Promise<boolean> {
|
||||
return new Promise<boolean>((resolve, reject) => {
|
||||
fs.exists(path, exists => handleResult(resolve, reject, null, exists));
|
||||
});
|
||||
}
|
||||
|
||||
export function rmrf(path: string): Promise<void> {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
rimraf(path, error => handleResult(resolve, reject, error, void 0));
|
||||
});
|
||||
}
|
||||
|
||||
export function mkdir(path: string): Promise<void> {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
mkdirp(path, error => handleResult(resolve, reject, error, void 0));
|
||||
});
|
||||
}
|
||||
|
||||
export function rename(oldPath: string, newPath: string): Promise<void> {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
fs.rename(oldPath, newPath, error => handleResult(resolve, reject, error, void 0));
|
||||
});
|
||||
}
|
||||
|
||||
export function unlink(path: string): Promise<void> {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
fs.unlink(path, error => handleResult(resolve, reject, error, void 0));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export class FileStat implements vscode.FileStat {
|
||||
|
||||
constructor(private fsStat: fs.Stats) { }
|
||||
|
||||
get type(): vscode.FileType {
|
||||
return this.fsStat.isFile() ? vscode.FileType.File : this.fsStat.isDirectory() ? vscode.FileType.Directory : this.fsStat.isSymbolicLink() ? vscode.FileType.SymbolicLink : vscode.FileType.Unknown;
|
||||
}
|
||||
|
||||
get isFile(): boolean | undefined {
|
||||
return this.fsStat.isFile();
|
||||
}
|
||||
|
||||
get isDirectory(): boolean | undefined {
|
||||
return this.fsStat.isDirectory();
|
||||
}
|
||||
|
||||
get isSymbolicLink(): boolean | undefined {
|
||||
return this.fsStat.isSymbolicLink();
|
||||
}
|
||||
|
||||
get size(): number {
|
||||
return this.fsStat.size;
|
||||
}
|
||||
|
||||
get ctime(): number {
|
||||
return this.fsStat.ctime.getTime();
|
||||
}
|
||||
|
||||
get mtime(): number {
|
||||
return this.fsStat.mtime.getTime();
|
||||
}
|
||||
}
|
||||
|
||||
interface Entry {
|
||||
uri: vscode.Uri;
|
||||
type: vscode.FileType;
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
export class FileSystemProvider implements vscode.TreeDataProvider<Entry>, vscode.FileSystemProvider {
|
||||
|
||||
private _onDidChangeFile: vscode.EventEmitter<vscode.FileChangeEvent[]>;
|
||||
|
||||
constructor() {
|
||||
this._onDidChangeFile = new vscode.EventEmitter<vscode.FileChangeEvent[]>();
|
||||
}
|
||||
|
||||
get onDidChangeFile(): vscode.Event<vscode.FileChangeEvent[]> {
|
||||
return this._onDidChangeFile.event;
|
||||
}
|
||||
|
||||
watch(uri: vscode.Uri, options: { recursive: boolean; excludes: string[]; }): vscode.Disposable {
|
||||
const watcher = fs.watch(uri.fsPath, { recursive: options.recursive }, async (event: string, filename: string | Buffer) => {
|
||||
const filepath = path.join(uri.fsPath, _.normalizeNFC(filename.toString()));
|
||||
|
||||
// TODO support excludes (using minimatch library?)
|
||||
|
||||
this._onDidChangeFile.fire([{
|
||||
type: event === 'change' ? vscode.FileChangeType.Changed : await _.exists(filepath) ? vscode.FileChangeType.Created : vscode.FileChangeType.Deleted,
|
||||
uri: uri.with({ path: filepath })
|
||||
} as vscode.FileChangeEvent]);
|
||||
});
|
||||
|
||||
return { dispose: () => watcher.close() };
|
||||
}
|
||||
|
||||
stat(uri: vscode.Uri): vscode.FileStat | Thenable<vscode.FileStat> {
|
||||
return this._stat(uri.fsPath);
|
||||
}
|
||||
|
||||
async _stat(path: string): Promise<vscode.FileStat> {
|
||||
return new FileStat(await _.stat(path));
|
||||
}
|
||||
|
||||
readDirectory(uri: vscode.Uri): [string, vscode.FileType][] | Thenable<[string, vscode.FileType][]> {
|
||||
return this._readDirectory(uri);
|
||||
}
|
||||
|
||||
async _readDirectory(uri: vscode.Uri): Promise<[string, vscode.FileType][]> {
|
||||
const children = await _.readdir(uri.fsPath);
|
||||
|
||||
const result: [string, vscode.FileType][] = [];
|
||||
for (let i = 0; i < children.length; i++) {
|
||||
const child = children[i];
|
||||
const stat = await this._stat(path.join(uri.fsPath, child));
|
||||
result.push([child, stat.type]);
|
||||
}
|
||||
|
||||
return Promise.resolve(result);
|
||||
}
|
||||
|
||||
createDirectory(uri: vscode.Uri): void | Thenable<void> {
|
||||
return _.mkdir(uri.fsPath);
|
||||
}
|
||||
|
||||
readFile(uri: vscode.Uri): Uint8Array | Thenable<Uint8Array> {
|
||||
return _.readfile(uri.fsPath);
|
||||
}
|
||||
|
||||
writeFile(uri: vscode.Uri, content: Uint8Array, options: { create: boolean; overwrite: boolean; }): void | Thenable<void> {
|
||||
return this._writeFile(uri, content, options);
|
||||
}
|
||||
|
||||
async _writeFile(uri: vscode.Uri, content: Uint8Array, options: { create: boolean; overwrite: boolean; }): Promise<void> {
|
||||
const exists = await _.exists(uri.fsPath);
|
||||
if (!exists) {
|
||||
if (!options.create) {
|
||||
throw vscode.FileSystemError.FileNotFound();
|
||||
}
|
||||
|
||||
await _.mkdir(path.dirname(uri.fsPath));
|
||||
} else {
|
||||
if (!options.overwrite) {
|
||||
throw vscode.FileSystemError.FileExists();
|
||||
}
|
||||
}
|
||||
|
||||
return _.writefile(uri.fsPath, content as Buffer);
|
||||
}
|
||||
|
||||
delete(uri: vscode.Uri, options: { recursive: boolean; }): void | Thenable<void> {
|
||||
if (options.recursive) {
|
||||
return _.rmrf(uri.fsPath);
|
||||
}
|
||||
|
||||
return _.unlink(uri.fsPath);
|
||||
}
|
||||
|
||||
rename(oldUri: vscode.Uri, newUri: vscode.Uri, options: { overwrite: boolean; }): void | Thenable<void> {
|
||||
return this._rename(oldUri, newUri, options);
|
||||
}
|
||||
|
||||
async _rename(oldUri: vscode.Uri, newUri: vscode.Uri, options: { overwrite: boolean; }): Promise<void> {
|
||||
const exists = await _.exists(newUri.fsPath);
|
||||
if (exists) {
|
||||
if (!options.overwrite) {
|
||||
throw vscode.FileSystemError.FileExists();
|
||||
} else {
|
||||
await _.rmrf(newUri.fsPath);
|
||||
}
|
||||
}
|
||||
|
||||
const parentExists = await _.exists(path.dirname(newUri.fsPath));
|
||||
if (!parentExists) {
|
||||
await _.mkdir(path.dirname(newUri.fsPath));
|
||||
}
|
||||
|
||||
return _.rename(oldUri.fsPath, newUri.fsPath);
|
||||
}
|
||||
|
||||
// tree data provider
|
||||
|
||||
async getChildren(element?: Entry): Promise<Entry[]> {
|
||||
if (element) {
|
||||
const children = await this.readDirectory(element.uri);
|
||||
return children.map(([name, type]) => ({ uri: vscode.Uri.file(path.join(element.uri.fsPath, name)), type }));
|
||||
}
|
||||
|
||||
const workspaceFolder = (vscode.workspace.workspaceFolders ?? []).filter(folder => folder.uri.scheme === 'file')[0];
|
||||
if (workspaceFolder) {
|
||||
const children = await this.readDirectory(workspaceFolder.uri);
|
||||
children.sort((a, b) => {
|
||||
if (a[1] === b[1]) {
|
||||
return a[0].localeCompare(b[0]);
|
||||
}
|
||||
return a[1] === vscode.FileType.Directory ? -1 : 1;
|
||||
});
|
||||
return children.map(([name, type]) => ({ uri: vscode.Uri.file(path.join(workspaceFolder.uri.fsPath, name)), type }));
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
getTreeItem(element: Entry): vscode.TreeItem {
|
||||
const treeItem = new vscode.TreeItem(element.uri, element.type === vscode.FileType.Directory ? vscode.TreeItemCollapsibleState.Collapsed : vscode.TreeItemCollapsibleState.None);
|
||||
if (element.type === vscode.FileType.File) {
|
||||
treeItem.command = { command: 'fileExplorer.openFile', title: "Open File", arguments: [element.uri], };
|
||||
treeItem.contextValue = 'file';
|
||||
}
|
||||
return treeItem;
|
||||
}
|
||||
}
|
||||
|
||||
export class FileExplorer {
|
||||
constructor(context: vscode.ExtensionContext) {
|
||||
const treeDataProvider = new FileSystemProvider();
|
||||
context.subscriptions.push(vscode.window.createTreeView('fileExplorer', { treeDataProvider }));
|
||||
vscode.commands.registerCommand('fileExplorer.openFile', (resource) => this.openResource(resource));
|
||||
}
|
||||
|
||||
private openResource(resource: vscode.Uri): void {
|
||||
vscode.window.showTextDocument(resource);
|
||||
}
|
||||
import * as vscode from 'vscode';
|
||||
import * as path from 'path';
|
||||
import * as fs from 'fs';
|
||||
import * as mkdirp from 'mkdirp';
|
||||
import * as rimraf from 'rimraf';
|
||||
|
||||
//#region Utilities
|
||||
|
||||
namespace _ {
|
||||
|
||||
function handleResult<T>(resolve: (result: T) => void, reject: (error: Error) => void, error: Error | null | undefined, result: T): void {
|
||||
if (error) {
|
||||
reject(massageError(error));
|
||||
} else {
|
||||
resolve(result);
|
||||
}
|
||||
}
|
||||
|
||||
function massageError(error: Error & { code?: string }): Error {
|
||||
if (error.code === 'ENOENT') {
|
||||
return vscode.FileSystemError.FileNotFound();
|
||||
}
|
||||
|
||||
if (error.code === 'EISDIR') {
|
||||
return vscode.FileSystemError.FileIsADirectory();
|
||||
}
|
||||
|
||||
if (error.code === 'EEXIST') {
|
||||
return vscode.FileSystemError.FileExists();
|
||||
}
|
||||
|
||||
if (error.code === 'EPERM' || error.code === 'EACCESS') {
|
||||
return vscode.FileSystemError.NoPermissions();
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
export function checkCancellation(token: vscode.CancellationToken): void {
|
||||
if (token.isCancellationRequested) {
|
||||
throw new Error('Operation cancelled');
|
||||
}
|
||||
}
|
||||
|
||||
export function normalizeNFC(items: string): string;
|
||||
export function normalizeNFC(items: string[]): string[];
|
||||
export function normalizeNFC(items: string | string[]): string | string[] {
|
||||
if (process.platform !== 'darwin') {
|
||||
return items;
|
||||
}
|
||||
|
||||
if (Array.isArray(items)) {
|
||||
return items.map(item => item.normalize('NFC'));
|
||||
}
|
||||
|
||||
return items.normalize('NFC');
|
||||
}
|
||||
|
||||
export function readdir(path: string): Promise<string[]> {
|
||||
return new Promise<string[]>((resolve, reject) => {
|
||||
fs.readdir(path, (error, children) => handleResult(resolve, reject, error, normalizeNFC(children)));
|
||||
});
|
||||
}
|
||||
|
||||
export function stat(path: string): Promise<fs.Stats> {
|
||||
return new Promise<fs.Stats>((resolve, reject) => {
|
||||
fs.stat(path, (error, stat) => handleResult(resolve, reject, error, stat));
|
||||
});
|
||||
}
|
||||
|
||||
export function readfile(path: string): Promise<Buffer> {
|
||||
return new Promise<Buffer>((resolve, reject) => {
|
||||
fs.readFile(path, (error, buffer) => handleResult(resolve, reject, error, buffer));
|
||||
});
|
||||
}
|
||||
|
||||
export function writefile(path: string, content: Buffer): Promise<void> {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
fs.writeFile(path, content, error => handleResult(resolve, reject, error, void 0));
|
||||
});
|
||||
}
|
||||
|
||||
export function exists(path: string): Promise<boolean> {
|
||||
return new Promise<boolean>((resolve, reject) => {
|
||||
fs.exists(path, exists => handleResult(resolve, reject, null, exists));
|
||||
});
|
||||
}
|
||||
|
||||
export function rmrf(path: string): Promise<void> {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
rimraf(path, error => handleResult(resolve, reject, error, void 0));
|
||||
});
|
||||
}
|
||||
|
||||
export function mkdir(path: string): Promise<void> {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
mkdirp(path, error => handleResult(resolve, reject, error, void 0));
|
||||
});
|
||||
}
|
||||
|
||||
export function rename(oldPath: string, newPath: string): Promise<void> {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
fs.rename(oldPath, newPath, error => handleResult(resolve, reject, error, void 0));
|
||||
});
|
||||
}
|
||||
|
||||
export function unlink(path: string): Promise<void> {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
fs.unlink(path, error => handleResult(resolve, reject, error, void 0));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export class FileStat implements vscode.FileStat {
|
||||
|
||||
constructor(private fsStat: fs.Stats) { }
|
||||
|
||||
get type(): vscode.FileType {
|
||||
return this.fsStat.isFile() ? vscode.FileType.File : this.fsStat.isDirectory() ? vscode.FileType.Directory : this.fsStat.isSymbolicLink() ? vscode.FileType.SymbolicLink : vscode.FileType.Unknown;
|
||||
}
|
||||
|
||||
get isFile(): boolean | undefined {
|
||||
return this.fsStat.isFile();
|
||||
}
|
||||
|
||||
get isDirectory(): boolean | undefined {
|
||||
return this.fsStat.isDirectory();
|
||||
}
|
||||
|
||||
get isSymbolicLink(): boolean | undefined {
|
||||
return this.fsStat.isSymbolicLink();
|
||||
}
|
||||
|
||||
get size(): number {
|
||||
return this.fsStat.size;
|
||||
}
|
||||
|
||||
get ctime(): number {
|
||||
return this.fsStat.ctime.getTime();
|
||||
}
|
||||
|
||||
get mtime(): number {
|
||||
return this.fsStat.mtime.getTime();
|
||||
}
|
||||
}
|
||||
|
||||
interface Entry {
|
||||
uri: vscode.Uri;
|
||||
type: vscode.FileType;
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
export class FileSystemProvider implements vscode.TreeDataProvider<Entry>, vscode.FileSystemProvider {
|
||||
|
||||
private _onDidChangeFile: vscode.EventEmitter<vscode.FileChangeEvent[]>;
|
||||
|
||||
constructor() {
|
||||
this._onDidChangeFile = new vscode.EventEmitter<vscode.FileChangeEvent[]>();
|
||||
}
|
||||
|
||||
get onDidChangeFile(): vscode.Event<vscode.FileChangeEvent[]> {
|
||||
return this._onDidChangeFile.event;
|
||||
}
|
||||
|
||||
watch(uri: vscode.Uri, options: { recursive: boolean; excludes: string[]; }): vscode.Disposable {
|
||||
const watcher = fs.watch(uri.fsPath, { recursive: options.recursive }, async (event: string, filename: string | Buffer) => {
|
||||
const filepath = path.join(uri.fsPath, _.normalizeNFC(filename.toString()));
|
||||
|
||||
// TODO support excludes (using minimatch library?)
|
||||
|
||||
this._onDidChangeFile.fire([{
|
||||
type: event === 'change' ? vscode.FileChangeType.Changed : await _.exists(filepath) ? vscode.FileChangeType.Created : vscode.FileChangeType.Deleted,
|
||||
uri: uri.with({ path: filepath })
|
||||
} as vscode.FileChangeEvent]);
|
||||
});
|
||||
|
||||
return { dispose: () => watcher.close() };
|
||||
}
|
||||
|
||||
stat(uri: vscode.Uri): vscode.FileStat | Thenable<vscode.FileStat> {
|
||||
return this._stat(uri.fsPath);
|
||||
}
|
||||
|
||||
async _stat(path: string): Promise<vscode.FileStat> {
|
||||
return new FileStat(await _.stat(path));
|
||||
}
|
||||
|
||||
readDirectory(uri: vscode.Uri): [string, vscode.FileType][] | Thenable<[string, vscode.FileType][]> {
|
||||
return this._readDirectory(uri);
|
||||
}
|
||||
|
||||
async _readDirectory(uri: vscode.Uri): Promise<[string, vscode.FileType][]> {
|
||||
const children = await _.readdir(uri.fsPath);
|
||||
|
||||
const result: [string, vscode.FileType][] = [];
|
||||
for (let i = 0; i < children.length; i++) {
|
||||
const child = children[i];
|
||||
const stat = await this._stat(path.join(uri.fsPath, child));
|
||||
result.push([child, stat.type]);
|
||||
}
|
||||
|
||||
return Promise.resolve(result);
|
||||
}
|
||||
|
||||
createDirectory(uri: vscode.Uri): void | Thenable<void> {
|
||||
return _.mkdir(uri.fsPath);
|
||||
}
|
||||
|
||||
readFile(uri: vscode.Uri): Uint8Array | Thenable<Uint8Array> {
|
||||
return _.readfile(uri.fsPath);
|
||||
}
|
||||
|
||||
writeFile(uri: vscode.Uri, content: Uint8Array, options: { create: boolean; overwrite: boolean; }): void | Thenable<void> {
|
||||
return this._writeFile(uri, content, options);
|
||||
}
|
||||
|
||||
async _writeFile(uri: vscode.Uri, content: Uint8Array, options: { create: boolean; overwrite: boolean; }): Promise<void> {
|
||||
const exists = await _.exists(uri.fsPath);
|
||||
if (!exists) {
|
||||
if (!options.create) {
|
||||
throw vscode.FileSystemError.FileNotFound();
|
||||
}
|
||||
|
||||
await _.mkdir(path.dirname(uri.fsPath));
|
||||
} else {
|
||||
if (!options.overwrite) {
|
||||
throw vscode.FileSystemError.FileExists();
|
||||
}
|
||||
}
|
||||
|
||||
return _.writefile(uri.fsPath, content as Buffer);
|
||||
}
|
||||
|
||||
delete(uri: vscode.Uri, options: { recursive: boolean; }): void | Thenable<void> {
|
||||
if (options.recursive) {
|
||||
return _.rmrf(uri.fsPath);
|
||||
}
|
||||
|
||||
return _.unlink(uri.fsPath);
|
||||
}
|
||||
|
||||
rename(oldUri: vscode.Uri, newUri: vscode.Uri, options: { overwrite: boolean; }): void | Thenable<void> {
|
||||
return this._rename(oldUri, newUri, options);
|
||||
}
|
||||
|
||||
async _rename(oldUri: vscode.Uri, newUri: vscode.Uri, options: { overwrite: boolean; }): Promise<void> {
|
||||
const exists = await _.exists(newUri.fsPath);
|
||||
if (exists) {
|
||||
if (!options.overwrite) {
|
||||
throw vscode.FileSystemError.FileExists();
|
||||
} else {
|
||||
await _.rmrf(newUri.fsPath);
|
||||
}
|
||||
}
|
||||
|
||||
const parentExists = await _.exists(path.dirname(newUri.fsPath));
|
||||
if (!parentExists) {
|
||||
await _.mkdir(path.dirname(newUri.fsPath));
|
||||
}
|
||||
|
||||
return _.rename(oldUri.fsPath, newUri.fsPath);
|
||||
}
|
||||
|
||||
// tree data provider
|
||||
|
||||
async getChildren(element?: Entry): Promise<Entry[]> {
|
||||
if (element) {
|
||||
const children = await this.readDirectory(element.uri);
|
||||
return children.map(([name, type]) => ({ uri: vscode.Uri.file(path.join(element.uri.fsPath, name)), type }));
|
||||
}
|
||||
|
||||
const workspaceFolder = (vscode.workspace.workspaceFolders ?? []).filter(folder => folder.uri.scheme === 'file')[0];
|
||||
if (workspaceFolder) {
|
||||
const children = await this.readDirectory(workspaceFolder.uri);
|
||||
children.sort((a, b) => {
|
||||
if (a[1] === b[1]) {
|
||||
return a[0].localeCompare(b[0]);
|
||||
}
|
||||
return a[1] === vscode.FileType.Directory ? -1 : 1;
|
||||
});
|
||||
return children.map(([name, type]) => ({ uri: vscode.Uri.file(path.join(workspaceFolder.uri.fsPath, name)), type }));
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
getTreeItem(element: Entry): vscode.TreeItem {
|
||||
const treeItem = new vscode.TreeItem(element.uri, element.type === vscode.FileType.Directory ? vscode.TreeItemCollapsibleState.Collapsed : vscode.TreeItemCollapsibleState.None);
|
||||
if (element.type === vscode.FileType.File) {
|
||||
treeItem.command = { command: 'fileExplorer.openFile', title: "Open File", arguments: [element.uri], };
|
||||
treeItem.contextValue = 'file';
|
||||
}
|
||||
return treeItem;
|
||||
}
|
||||
}
|
||||
|
||||
export class FileExplorer {
|
||||
constructor(context: vscode.ExtensionContext) {
|
||||
const treeDataProvider = new FileSystemProvider();
|
||||
context.subscriptions.push(vscode.window.createTreeView('fileExplorer', { treeDataProvider }));
|
||||
vscode.commands.registerCommand('fileExplorer.openFile', (resource) => this.openResource(resource));
|
||||
}
|
||||
|
||||
private openResource(resource: vscode.Uri): void {
|
||||
vscode.window.showTextDocument(resource);
|
||||
}
|
||||
}
|
||||
@ -1,186 +1,186 @@
|
||||
import * as vscode from 'vscode';
|
||||
import * as Client from 'ftp';
|
||||
import { basename, dirname, join } from 'path';
|
||||
|
||||
interface IEntry {
|
||||
name: string;
|
||||
type: string;
|
||||
}
|
||||
|
||||
export interface FtpNode {
|
||||
|
||||
resource: vscode.Uri;
|
||||
isDirectory: boolean;
|
||||
|
||||
}
|
||||
|
||||
export class FtpModel {
|
||||
constructor(readonly host: string, private user: string, private password: string) {
|
||||
}
|
||||
|
||||
public connect(): Thenable<Client> {
|
||||
return new Promise((c, e) => {
|
||||
const client = new Client();
|
||||
client.on('ready', () => {
|
||||
c(client);
|
||||
});
|
||||
|
||||
client.on('error', error => {
|
||||
e('Error while connecting: ' + error.message);
|
||||
});
|
||||
|
||||
client.connect({
|
||||
host: this.host,
|
||||
user: this.user,
|
||||
password: this.password
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public get roots(): Thenable<FtpNode[]> {
|
||||
return this.connect().then(client => {
|
||||
return new Promise((c, e) => {
|
||||
client.list((err, list) => {
|
||||
if (err) {
|
||||
return e(err);
|
||||
}
|
||||
|
||||
client.end();
|
||||
|
||||
return c(this.sort(list.map(entry => ({ resource: vscode.Uri.parse(`ftp://${this.host}///${entry.name}`), isDirectory: entry.type === 'd' }))));
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public getChildren(node: FtpNode): Thenable<FtpNode[]> {
|
||||
return this.connect().then(client => {
|
||||
return new Promise((c, e) => {
|
||||
client.list(node.resource.fsPath, (err, list) => {
|
||||
if (err) {
|
||||
return e(err);
|
||||
}
|
||||
|
||||
client.end();
|
||||
|
||||
return c(this.sort(list.map(entry => ({ resource: vscode.Uri.parse(`${node.resource.fsPath}/${entry.name}`), isDirectory: entry.type === 'd' }))));
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private sort(nodes: FtpNode[]): FtpNode[] {
|
||||
return nodes.sort((n1, n2) => {
|
||||
if (n1.isDirectory && !n2.isDirectory) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!n1.isDirectory && n2.isDirectory) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return basename(n1.resource.fsPath).localeCompare(basename(n2.resource.fsPath));
|
||||
});
|
||||
}
|
||||
|
||||
public getContent(resource: vscode.Uri): Thenable<string> {
|
||||
return this.connect().then(client => {
|
||||
return new Promise((c, e) => {
|
||||
client.get(resource.path.substr(2), (err, stream) => {
|
||||
if (err) {
|
||||
return e(err);
|
||||
}
|
||||
|
||||
let string = '';
|
||||
stream.on('data', function (buffer) {
|
||||
if (buffer) {
|
||||
const part = buffer.toString();
|
||||
string += part;
|
||||
}
|
||||
});
|
||||
|
||||
stream.on('end', function () {
|
||||
client.end();
|
||||
c(string);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export class FtpTreeDataProvider implements vscode.TreeDataProvider<FtpNode>, vscode.TextDocumentContentProvider {
|
||||
|
||||
private _onDidChangeTreeData: vscode.EventEmitter<any> = new vscode.EventEmitter<any>();
|
||||
readonly onDidChangeTreeData: vscode.Event<any> = this._onDidChangeTreeData.event;
|
||||
|
||||
constructor(private readonly model: FtpModel) { }
|
||||
|
||||
public refresh(): any {
|
||||
this._onDidChangeTreeData.fire(undefined);
|
||||
}
|
||||
|
||||
|
||||
public getTreeItem(element: FtpNode): vscode.TreeItem {
|
||||
return {
|
||||
resourceUri: element.resource,
|
||||
collapsibleState: element.isDirectory ? vscode.TreeItemCollapsibleState.Collapsed : void 0,
|
||||
command: element.isDirectory ? void 0 : {
|
||||
command: 'ftpExplorer.openFtpResource',
|
||||
arguments: [element.resource],
|
||||
title: 'Open FTP Resource'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public getChildren(element?: FtpNode): FtpNode[] | Thenable<FtpNode[]> {
|
||||
return element ? this.model.getChildren(element) : this.model.roots;
|
||||
}
|
||||
|
||||
public getParent(element: FtpNode): FtpNode | undefined{
|
||||
const parent = element.resource.with({ path: dirname(element.resource.path) });
|
||||
return parent.path !== '//' ? { resource: parent, isDirectory: true } : undefined;
|
||||
}
|
||||
|
||||
public provideTextDocumentContent(uri: vscode.Uri, token: vscode.CancellationToken): vscode.ProviderResult<string> {
|
||||
return this.model.getContent(uri).then(content => content);
|
||||
}
|
||||
}
|
||||
|
||||
export class FtpExplorer {
|
||||
|
||||
private ftpViewer: vscode.TreeView<FtpNode>;
|
||||
|
||||
constructor(context: vscode.ExtensionContext) {
|
||||
/* Please note that login information is hardcoded only for this example purpose and recommended not to do it in general. */
|
||||
const ftpModel = new FtpModel('mirror.switch.ch', 'anonymous', 'anonymous@anonymous.de');
|
||||
const treeDataProvider = new FtpTreeDataProvider(ftpModel);
|
||||
context.subscriptions.push(vscode.workspace.registerTextDocumentContentProvider('ftp', treeDataProvider));
|
||||
|
||||
this.ftpViewer = vscode.window.createTreeView('ftpExplorer', { treeDataProvider });
|
||||
|
||||
vscode.commands.registerCommand('ftpExplorer.refresh', () => treeDataProvider.refresh());
|
||||
vscode.commands.registerCommand('ftpExplorer.openFtpResource', resource => this.openResource(resource));
|
||||
vscode.commands.registerCommand('ftpExplorer.revealResource', () => this.reveal());
|
||||
}
|
||||
|
||||
private openResource(resource: vscode.Uri): void {
|
||||
vscode.window.showTextDocument(resource);
|
||||
}
|
||||
|
||||
private async reveal(): Promise<void> {
|
||||
const node = this.getNode();
|
||||
if (node) {
|
||||
return this.ftpViewer.reveal(node);
|
||||
}
|
||||
}
|
||||
|
||||
private getNode(): FtpNode | undefined {
|
||||
if (vscode.window.activeTextEditor) {
|
||||
if (vscode.window.activeTextEditor.document.uri.scheme === 'ftp') {
|
||||
return { resource: vscode.window.activeTextEditor.document.uri, isDirectory: false };
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
import * as vscode from 'vscode';
|
||||
import * as Client from 'ftp';
|
||||
import { basename, dirname, join } from 'path';
|
||||
|
||||
interface IEntry {
|
||||
name: string;
|
||||
type: string;
|
||||
}
|
||||
|
||||
export interface FtpNode {
|
||||
|
||||
resource: vscode.Uri;
|
||||
isDirectory: boolean;
|
||||
|
||||
}
|
||||
|
||||
export class FtpModel {
|
||||
constructor(readonly host: string, private user: string, private password: string) {
|
||||
}
|
||||
|
||||
public connect(): Thenable<Client> {
|
||||
return new Promise((c, e) => {
|
||||
const client = new Client();
|
||||
client.on('ready', () => {
|
||||
c(client);
|
||||
});
|
||||
|
||||
client.on('error', error => {
|
||||
e('Error while connecting: ' + error.message);
|
||||
});
|
||||
|
||||
client.connect({
|
||||
host: this.host,
|
||||
user: this.user,
|
||||
password: this.password
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public get roots(): Thenable<FtpNode[]> {
|
||||
return this.connect().then(client => {
|
||||
return new Promise((c, e) => {
|
||||
client.list((err, list) => {
|
||||
if (err) {
|
||||
return e(err);
|
||||
}
|
||||
|
||||
client.end();
|
||||
|
||||
return c(this.sort(list.map(entry => ({ resource: vscode.Uri.parse(`ftp://${this.host}///${entry.name}`), isDirectory: entry.type === 'd' }))));
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public getChildren(node: FtpNode): Thenable<FtpNode[]> {
|
||||
return this.connect().then(client => {
|
||||
return new Promise((c, e) => {
|
||||
client.list(node.resource.fsPath, (err, list) => {
|
||||
if (err) {
|
||||
return e(err);
|
||||
}
|
||||
|
||||
client.end();
|
||||
|
||||
return c(this.sort(list.map(entry => ({ resource: vscode.Uri.parse(`${node.resource.fsPath}/${entry.name}`), isDirectory: entry.type === 'd' }))));
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private sort(nodes: FtpNode[]): FtpNode[] {
|
||||
return nodes.sort((n1, n2) => {
|
||||
if (n1.isDirectory && !n2.isDirectory) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!n1.isDirectory && n2.isDirectory) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return basename(n1.resource.fsPath).localeCompare(basename(n2.resource.fsPath));
|
||||
});
|
||||
}
|
||||
|
||||
public getContent(resource: vscode.Uri): Thenable<string> {
|
||||
return this.connect().then(client => {
|
||||
return new Promise((c, e) => {
|
||||
client.get(resource.path.substr(2), (err, stream) => {
|
||||
if (err) {
|
||||
return e(err);
|
||||
}
|
||||
|
||||
let string = '';
|
||||
stream.on('data', function(buffer) {
|
||||
if (buffer) {
|
||||
const part = buffer.toString();
|
||||
string += part;
|
||||
}
|
||||
});
|
||||
|
||||
stream.on('end', function() {
|
||||
client.end();
|
||||
c(string);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export class FtpTreeDataProvider implements vscode.TreeDataProvider<FtpNode>, vscode.TextDocumentContentProvider {
|
||||
|
||||
private _onDidChangeTreeData: vscode.EventEmitter<any> = new vscode.EventEmitter<any>();
|
||||
readonly onDidChangeTreeData: vscode.Event<any> = this._onDidChangeTreeData.event;
|
||||
|
||||
constructor(private readonly model: FtpModel) { }
|
||||
|
||||
public refresh(): any {
|
||||
this._onDidChangeTreeData.fire(undefined);
|
||||
}
|
||||
|
||||
|
||||
public getTreeItem(element: FtpNode): vscode.TreeItem {
|
||||
return {
|
||||
resourceUri: element.resource,
|
||||
collapsibleState: element.isDirectory ? vscode.TreeItemCollapsibleState.Collapsed : void 0,
|
||||
command: element.isDirectory ? void 0 : {
|
||||
command: 'ftpExplorer.openFtpResource',
|
||||
arguments: [element.resource],
|
||||
title: 'Open FTP Resource'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public getChildren(element?: FtpNode): FtpNode[] | Thenable<FtpNode[]> {
|
||||
return element ? this.model.getChildren(element) : this.model.roots;
|
||||
}
|
||||
|
||||
public getParent(element: FtpNode): FtpNode | undefined {
|
||||
const parent = element.resource.with({ path: dirname(element.resource.path) });
|
||||
return parent.path !== '//' ? { resource: parent, isDirectory: true } : undefined;
|
||||
}
|
||||
|
||||
public provideTextDocumentContent(uri: vscode.Uri, token: vscode.CancellationToken): vscode.ProviderResult<string> {
|
||||
return this.model.getContent(uri).then(content => content);
|
||||
}
|
||||
}
|
||||
|
||||
export class FtpExplorer {
|
||||
|
||||
private ftpViewer: vscode.TreeView<FtpNode>;
|
||||
|
||||
constructor(context: vscode.ExtensionContext) {
|
||||
/* Please note that login information is hardcoded only for this example purpose and recommended not to do it in general. */
|
||||
const ftpModel = new FtpModel('mirror.switch.ch', 'anonymous', 'anonymous@anonymous.de');
|
||||
const treeDataProvider = new FtpTreeDataProvider(ftpModel);
|
||||
context.subscriptions.push(vscode.workspace.registerTextDocumentContentProvider('ftp', treeDataProvider));
|
||||
|
||||
this.ftpViewer = vscode.window.createTreeView('ftpExplorer', { treeDataProvider });
|
||||
|
||||
vscode.commands.registerCommand('ftpExplorer.refresh', () => treeDataProvider.refresh());
|
||||
vscode.commands.registerCommand('ftpExplorer.openFtpResource', resource => this.openResource(resource));
|
||||
vscode.commands.registerCommand('ftpExplorer.revealResource', () => this.reveal());
|
||||
}
|
||||
|
||||
private openResource(resource: vscode.Uri): void {
|
||||
vscode.window.showTextDocument(resource);
|
||||
}
|
||||
|
||||
private async reveal(): Promise<void> {
|
||||
const node = this.getNode();
|
||||
if (node) {
|
||||
return this.ftpViewer.reveal(node);
|
||||
}
|
||||
}
|
||||
|
||||
private getNode(): FtpNode | undefined {
|
||||
if (vscode.window.activeTextEditor) {
|
||||
if (vscode.window.activeTextEditor.document.uri.scheme === 'ftp') {
|
||||
return { resource: vscode.window.activeTextEditor.document.uri, isDirectory: false };
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
92
tree-view-sample/src/jsftp.d.ts
vendored
92
tree-view-sample/src/jsftp.d.ts
vendored
@ -1,46 +1,46 @@
|
||||
|
||||
|
||||
import { Readable } from 'stream';
|
||||
import { EventEmitter } from 'events';
|
||||
|
||||
declare namespace JSFtp {
|
||||
|
||||
interface JSFtpOptions {
|
||||
host: string;
|
||||
port?: number | 21;
|
||||
user?: string | 'anonymous';
|
||||
pass?: string | '@anonymous';
|
||||
useList?: boolean
|
||||
}
|
||||
|
||||
interface Callback<T> {
|
||||
(err: any, result: T): void;
|
||||
}
|
||||
|
||||
interface Entry {
|
||||
name: string;
|
||||
size: number;
|
||||
time: number;
|
||||
type: 0 | 1;
|
||||
}
|
||||
}
|
||||
|
||||
interface JSFtp extends EventEmitter {
|
||||
auth(user: string, password: string, callback: JSFtp.Callback<void>): void
|
||||
keepAlive(wait?: number): void;
|
||||
ls(path: string, callback: JSFtp.Callback<JSFtp.Entry[]>): void;
|
||||
list(path: string, callback: JSFtp.Callback<any>): void;
|
||||
put(buffer: Buffer, path: string, callback: JSFtp.Callback<void>): void;
|
||||
get(path: string, callback: JSFtp.Callback<Readable>): void;
|
||||
setType(type: 'A' | 'AN' | 'AT' | 'AC' | 'E' | 'I' | 'L', callback: JSFtp.Callback<any>): void;
|
||||
raw(command: string, args: any[], callback: JSFtp.Callback<void>): void;
|
||||
raw<T>(command: string, args: any[], callback: JSFtp.Callback<T>): void;
|
||||
}
|
||||
|
||||
interface JSFtpConstructor {
|
||||
new(options: JSFtp.JSFtpOptions): JSFtp;
|
||||
}
|
||||
|
||||
declare const JSFtp: JSFtpConstructor;
|
||||
|
||||
export = JSFtp;
|
||||
|
||||
|
||||
import { Readable } from 'stream';
|
||||
import { EventEmitter } from 'events';
|
||||
|
||||
declare namespace JSFtp {
|
||||
|
||||
interface JSFtpOptions {
|
||||
host: string;
|
||||
port?: number | 21;
|
||||
user?: string | 'anonymous';
|
||||
pass?: string | '@anonymous';
|
||||
useList?: boolean
|
||||
}
|
||||
|
||||
interface Callback<T> {
|
||||
(err: any, result: T): void;
|
||||
}
|
||||
|
||||
interface Entry {
|
||||
name: string;
|
||||
size: number;
|
||||
time: number;
|
||||
type: 0 | 1;
|
||||
}
|
||||
}
|
||||
|
||||
interface JSFtp extends EventEmitter {
|
||||
auth(user: string, password: string, callback: JSFtp.Callback<void>): void
|
||||
keepAlive(wait?: number): void;
|
||||
ls(path: string, callback: JSFtp.Callback<JSFtp.Entry[]>): void;
|
||||
list(path: string, callback: JSFtp.Callback<any>): void;
|
||||
put(buffer: Buffer, path: string, callback: JSFtp.Callback<void>): void;
|
||||
get(path: string, callback: JSFtp.Callback<Readable>): void;
|
||||
setType(type: 'A' | 'AN' | 'AT' | 'AC' | 'E' | 'I' | 'L', callback: JSFtp.Callback<any>): void;
|
||||
raw(command: string, args: any[], callback: JSFtp.Callback<void>): void;
|
||||
raw<T>(command: string, args: any[], callback: JSFtp.Callback<T>): void;
|
||||
}
|
||||
|
||||
interface JSFtpConstructor {
|
||||
new(options: JSFtp.JSFtpOptions): JSFtp;
|
||||
}
|
||||
|
||||
declare const JSFtp: JSFtpConstructor;
|
||||
|
||||
export = JSFtp;
|
||||
|
||||
@ -1,197 +1,197 @@
|
||||
import * as vscode from 'vscode';
|
||||
import * as json from 'jsonc-parser';
|
||||
import * as path from 'path';
|
||||
|
||||
export class JsonOutlineProvider implements vscode.TreeDataProvider<number> {
|
||||
|
||||
private _onDidChangeTreeData: vscode.EventEmitter<number | undefined> = new vscode.EventEmitter<number | undefined>();
|
||||
readonly onDidChangeTreeData: vscode.Event<number | undefined> = this._onDidChangeTreeData.event;
|
||||
|
||||
private tree: json.Node | undefined;
|
||||
private text = '';
|
||||
private editor: vscode.TextEditor | undefined;
|
||||
private autoRefresh = true;
|
||||
|
||||
constructor(private context: vscode.ExtensionContext) {
|
||||
vscode.window.onDidChangeActiveTextEditor(() => this.onActiveEditorChanged());
|
||||
vscode.workspace.onDidChangeTextDocument(e => this.onDocumentChanged(e));
|
||||
this.autoRefresh = vscode.workspace.getConfiguration('jsonOutline').get('autorefresh', false);
|
||||
vscode.workspace.onDidChangeConfiguration(() => {
|
||||
this.autoRefresh = vscode.workspace.getConfiguration('jsonOutline').get('autorefresh', false);
|
||||
});
|
||||
this.onActiveEditorChanged();
|
||||
}
|
||||
|
||||
refresh(offset?: number): void {
|
||||
this.parseTree();
|
||||
if (offset) {
|
||||
this._onDidChangeTreeData.fire(offset);
|
||||
} else {
|
||||
this._onDidChangeTreeData.fire(undefined);
|
||||
}
|
||||
}
|
||||
|
||||
rename(offset: number): void {
|
||||
vscode.window.showInputBox({ placeHolder: 'Enter the new label' }).then(value => {
|
||||
const editor = this.editor;
|
||||
const tree = this.tree;
|
||||
if (value !== null && value !== undefined && editor && tree) {
|
||||
editor.edit(editBuilder => {
|
||||
const path = json.getLocation(this.text, offset).path;
|
||||
let propertyNode: json.Node | undefined = json.findNodeAtLocation(tree, path);
|
||||
if (propertyNode.parent?.type !== 'array') {
|
||||
propertyNode = propertyNode.parent?.children ? propertyNode.parent.children[0] : undefined;
|
||||
}
|
||||
if (propertyNode) {
|
||||
const range = new vscode.Range(editor.document.positionAt(propertyNode.offset), editor.document.positionAt(propertyNode.offset + propertyNode.length));
|
||||
editBuilder.replace(range, `"${value}"`);
|
||||
setTimeout(() => {
|
||||
this.parseTree();
|
||||
this.refresh(offset);
|
||||
}, 100);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private onActiveEditorChanged(): void {
|
||||
if (vscode.window.activeTextEditor) {
|
||||
if (vscode.window.activeTextEditor.document.uri.scheme === 'file') {
|
||||
const enabled = vscode.window.activeTextEditor.document.languageId === 'json' || vscode.window.activeTextEditor.document.languageId === 'jsonc';
|
||||
vscode.commands.executeCommand('setContext', 'jsonOutlineEnabled', enabled);
|
||||
if (enabled) {
|
||||
this.refresh();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
vscode.commands.executeCommand('setContext', 'jsonOutlineEnabled', false);
|
||||
}
|
||||
}
|
||||
|
||||
private onDocumentChanged(changeEvent: vscode.TextDocumentChangeEvent): void {
|
||||
if (this.tree && this.autoRefresh && changeEvent.document.uri.toString() === this.editor?.document.uri.toString()) {
|
||||
for (const change of changeEvent.contentChanges) {
|
||||
const path = json.getLocation(this.text, this.editor.document.offsetAt(change.range.start)).path;
|
||||
path.pop();
|
||||
const node = path.length ? json.findNodeAtLocation(this.tree, path) : void 0;
|
||||
this.parseTree();
|
||||
this._onDidChangeTreeData.fire(node ? node.offset : void 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private parseTree(): void {
|
||||
this.text = '';
|
||||
this.tree = undefined;
|
||||
this.editor = vscode.window.activeTextEditor;
|
||||
if (this.editor && this.editor.document) {
|
||||
this.text = this.editor.document.getText();
|
||||
this.tree = json.parseTree(this.text);
|
||||
}
|
||||
}
|
||||
|
||||
getChildren(offset?: number): Thenable<number[]> {
|
||||
if (offset && this.tree) {
|
||||
const path = json.getLocation(this.text, offset).path;
|
||||
const node = json.findNodeAtLocation(this.tree, path);
|
||||
return Promise.resolve(this.getChildrenOffsets(node));
|
||||
} else {
|
||||
return Promise.resolve(this.tree ? this.getChildrenOffsets(this.tree) : []);
|
||||
}
|
||||
}
|
||||
|
||||
private getChildrenOffsets(node: json.Node): number[] {
|
||||
const offsets: number[] = [];
|
||||
if (node.children && this.tree) {
|
||||
for (const child of node.children) {
|
||||
const childPath = json.getLocation(this.text, child.offset).path;
|
||||
const childNode = json.findNodeAtLocation(this.tree, childPath);
|
||||
if (childNode) {
|
||||
offsets.push(childNode.offset);
|
||||
}
|
||||
}
|
||||
}
|
||||
return offsets;
|
||||
}
|
||||
|
||||
getTreeItem(offset: number): vscode.TreeItem {
|
||||
if (!this.tree) {
|
||||
throw new Error('Invalid tree');
|
||||
}
|
||||
if (!this.editor) {
|
||||
throw new Error('Invalid editor');
|
||||
}
|
||||
|
||||
const path = json.getLocation(this.text, offset).path;
|
||||
const valueNode = json.findNodeAtLocation(this.tree, path);
|
||||
if (valueNode) {
|
||||
const hasChildren = valueNode.type === 'object' || valueNode.type === 'array';
|
||||
const treeItem: vscode.TreeItem = new vscode.TreeItem(this.getLabel(valueNode), hasChildren ? valueNode.type === 'object' ? vscode.TreeItemCollapsibleState.Expanded : vscode.TreeItemCollapsibleState.Collapsed : vscode.TreeItemCollapsibleState.None);
|
||||
treeItem.command = {
|
||||
command: 'extension.openJsonSelection',
|
||||
title: '',
|
||||
arguments: [new vscode.Range(this.editor.document.positionAt(valueNode.offset), this.editor.document.positionAt(valueNode.offset + valueNode.length))]
|
||||
};
|
||||
treeItem.iconPath = this.getIcon(valueNode);
|
||||
treeItem.contextValue = valueNode.type;
|
||||
return treeItem;
|
||||
}
|
||||
throw (new Error(`Could not find json node at ${path}`));
|
||||
}
|
||||
|
||||
select(range: vscode.Range) {
|
||||
if (this.editor) {
|
||||
this.editor.selection = new vscode.Selection(range.start, range.end);
|
||||
}
|
||||
}
|
||||
|
||||
private getIcon(node: json.Node): any {
|
||||
const nodeType = node.type;
|
||||
if (nodeType === 'boolean') {
|
||||
return {
|
||||
light: this.context.asAbsolutePath(path.join('resources', 'light', 'boolean.svg')),
|
||||
dark: this.context.asAbsolutePath(path.join('resources', 'dark', 'boolean.svg'))
|
||||
};
|
||||
}
|
||||
if (nodeType === 'string') {
|
||||
return {
|
||||
light: this.context.asAbsolutePath(path.join('resources', 'light', 'string.svg')),
|
||||
dark: this.context.asAbsolutePath(path.join('resources', 'dark', 'string.svg'))
|
||||
};
|
||||
}
|
||||
if (nodeType === 'number') {
|
||||
return {
|
||||
light: this.context.asAbsolutePath(path.join('resources', 'light', 'number.svg')),
|
||||
dark: this.context.asAbsolutePath(path.join('resources', 'dark', 'number.svg'))
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private getLabel(node: json.Node): string {
|
||||
if (node.parent?.type === 'array') {
|
||||
const prefix = node.parent.children?.indexOf(node).toString();
|
||||
if (node.type === 'object') {
|
||||
return prefix + ':{ }';
|
||||
}
|
||||
if (node.type === 'array') {
|
||||
return prefix + ':[ ]';
|
||||
}
|
||||
return prefix + ':' + node.value.toString();
|
||||
}
|
||||
else {
|
||||
const property = node.parent?.children ? node.parent.children[0].value.toString() : '';
|
||||
if (node.type === 'array' || node.type === 'object') {
|
||||
if (node.type === 'object') {
|
||||
return '{ } ' + property;
|
||||
}
|
||||
if (node.type === 'array') {
|
||||
return '[ ] ' + property;
|
||||
}
|
||||
}
|
||||
const value = this.editor?.document.getText(new vscode.Range(this.editor.document.positionAt(node.offset), this.editor.document.positionAt(node.offset + node.length)));
|
||||
return `${property}: ${value}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
import * as vscode from 'vscode';
|
||||
import * as json from 'jsonc-parser';
|
||||
import * as path from 'path';
|
||||
|
||||
export class JsonOutlineProvider implements vscode.TreeDataProvider<number> {
|
||||
|
||||
private _onDidChangeTreeData: vscode.EventEmitter<number | undefined> = new vscode.EventEmitter<number | undefined>();
|
||||
readonly onDidChangeTreeData: vscode.Event<number | undefined> = this._onDidChangeTreeData.event;
|
||||
|
||||
private tree: json.Node | undefined;
|
||||
private text = '';
|
||||
private editor: vscode.TextEditor | undefined;
|
||||
private autoRefresh = true;
|
||||
|
||||
constructor(private context: vscode.ExtensionContext) {
|
||||
vscode.window.onDidChangeActiveTextEditor(() => this.onActiveEditorChanged());
|
||||
vscode.workspace.onDidChangeTextDocument(e => this.onDocumentChanged(e));
|
||||
this.autoRefresh = vscode.workspace.getConfiguration('jsonOutline').get('autorefresh', false);
|
||||
vscode.workspace.onDidChangeConfiguration(() => {
|
||||
this.autoRefresh = vscode.workspace.getConfiguration('jsonOutline').get('autorefresh', false);
|
||||
});
|
||||
this.onActiveEditorChanged();
|
||||
}
|
||||
|
||||
refresh(offset?: number): void {
|
||||
this.parseTree();
|
||||
if (offset) {
|
||||
this._onDidChangeTreeData.fire(offset);
|
||||
} else {
|
||||
this._onDidChangeTreeData.fire(undefined);
|
||||
}
|
||||
}
|
||||
|
||||
rename(offset: number): void {
|
||||
vscode.window.showInputBox({ placeHolder: 'Enter the new label' }).then(value => {
|
||||
const editor = this.editor;
|
||||
const tree = this.tree;
|
||||
if (value !== null && value !== undefined && editor && tree) {
|
||||
editor.edit(editBuilder => {
|
||||
const path = json.getLocation(this.text, offset).path;
|
||||
let propertyNode: json.Node | undefined = json.findNodeAtLocation(tree, path);
|
||||
if (propertyNode.parent?.type !== 'array') {
|
||||
propertyNode = propertyNode.parent?.children ? propertyNode.parent.children[0] : undefined;
|
||||
}
|
||||
if (propertyNode) {
|
||||
const range = new vscode.Range(editor.document.positionAt(propertyNode.offset), editor.document.positionAt(propertyNode.offset + propertyNode.length));
|
||||
editBuilder.replace(range, `"${value}"`);
|
||||
setTimeout(() => {
|
||||
this.parseTree();
|
||||
this.refresh(offset);
|
||||
}, 100);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private onActiveEditorChanged(): void {
|
||||
if (vscode.window.activeTextEditor) {
|
||||
if (vscode.window.activeTextEditor.document.uri.scheme === 'file') {
|
||||
const enabled = vscode.window.activeTextEditor.document.languageId === 'json' || vscode.window.activeTextEditor.document.languageId === 'jsonc';
|
||||
vscode.commands.executeCommand('setContext', 'jsonOutlineEnabled', enabled);
|
||||
if (enabled) {
|
||||
this.refresh();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
vscode.commands.executeCommand('setContext', 'jsonOutlineEnabled', false);
|
||||
}
|
||||
}
|
||||
|
||||
private onDocumentChanged(changeEvent: vscode.TextDocumentChangeEvent): void {
|
||||
if (this.tree && this.autoRefresh && changeEvent.document.uri.toString() === this.editor?.document.uri.toString()) {
|
||||
for (const change of changeEvent.contentChanges) {
|
||||
const path = json.getLocation(this.text, this.editor.document.offsetAt(change.range.start)).path;
|
||||
path.pop();
|
||||
const node = path.length ? json.findNodeAtLocation(this.tree, path) : void 0;
|
||||
this.parseTree();
|
||||
this._onDidChangeTreeData.fire(node ? node.offset : void 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private parseTree(): void {
|
||||
this.text = '';
|
||||
this.tree = undefined;
|
||||
this.editor = vscode.window.activeTextEditor;
|
||||
if (this.editor && this.editor.document) {
|
||||
this.text = this.editor.document.getText();
|
||||
this.tree = json.parseTree(this.text);
|
||||
}
|
||||
}
|
||||
|
||||
getChildren(offset?: number): Thenable<number[]> {
|
||||
if (offset && this.tree) {
|
||||
const path = json.getLocation(this.text, offset).path;
|
||||
const node = json.findNodeAtLocation(this.tree, path);
|
||||
return Promise.resolve(this.getChildrenOffsets(node));
|
||||
} else {
|
||||
return Promise.resolve(this.tree ? this.getChildrenOffsets(this.tree) : []);
|
||||
}
|
||||
}
|
||||
|
||||
private getChildrenOffsets(node: json.Node): number[] {
|
||||
const offsets: number[] = [];
|
||||
if (node.children && this.tree) {
|
||||
for (const child of node.children) {
|
||||
const childPath = json.getLocation(this.text, child.offset).path;
|
||||
const childNode = json.findNodeAtLocation(this.tree, childPath);
|
||||
if (childNode) {
|
||||
offsets.push(childNode.offset);
|
||||
}
|
||||
}
|
||||
}
|
||||
return offsets;
|
||||
}
|
||||
|
||||
getTreeItem(offset: number): vscode.TreeItem {
|
||||
if (!this.tree) {
|
||||
throw new Error('Invalid tree');
|
||||
}
|
||||
if (!this.editor) {
|
||||
throw new Error('Invalid editor');
|
||||
}
|
||||
|
||||
const path = json.getLocation(this.text, offset).path;
|
||||
const valueNode = json.findNodeAtLocation(this.tree, path);
|
||||
if (valueNode) {
|
||||
const hasChildren = valueNode.type === 'object' || valueNode.type === 'array';
|
||||
const treeItem: vscode.TreeItem = new vscode.TreeItem(this.getLabel(valueNode), hasChildren ? valueNode.type === 'object' ? vscode.TreeItemCollapsibleState.Expanded : vscode.TreeItemCollapsibleState.Collapsed : vscode.TreeItemCollapsibleState.None);
|
||||
treeItem.command = {
|
||||
command: 'extension.openJsonSelection',
|
||||
title: '',
|
||||
arguments: [new vscode.Range(this.editor.document.positionAt(valueNode.offset), this.editor.document.positionAt(valueNode.offset + valueNode.length))]
|
||||
};
|
||||
treeItem.iconPath = this.getIcon(valueNode);
|
||||
treeItem.contextValue = valueNode.type;
|
||||
return treeItem;
|
||||
}
|
||||
throw (new Error(`Could not find json node at ${path}`));
|
||||
}
|
||||
|
||||
select(range: vscode.Range) {
|
||||
if (this.editor) {
|
||||
this.editor.selection = new vscode.Selection(range.start, range.end);
|
||||
}
|
||||
}
|
||||
|
||||
private getIcon(node: json.Node): any {
|
||||
const nodeType = node.type;
|
||||
if (nodeType === 'boolean') {
|
||||
return {
|
||||
light: this.context.asAbsolutePath(path.join('resources', 'light', 'boolean.svg')),
|
||||
dark: this.context.asAbsolutePath(path.join('resources', 'dark', 'boolean.svg'))
|
||||
};
|
||||
}
|
||||
if (nodeType === 'string') {
|
||||
return {
|
||||
light: this.context.asAbsolutePath(path.join('resources', 'light', 'string.svg')),
|
||||
dark: this.context.asAbsolutePath(path.join('resources', 'dark', 'string.svg'))
|
||||
};
|
||||
}
|
||||
if (nodeType === 'number') {
|
||||
return {
|
||||
light: this.context.asAbsolutePath(path.join('resources', 'light', 'number.svg')),
|
||||
dark: this.context.asAbsolutePath(path.join('resources', 'dark', 'number.svg'))
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private getLabel(node: json.Node): string {
|
||||
if (node.parent?.type === 'array') {
|
||||
const prefix = node.parent.children?.indexOf(node).toString();
|
||||
if (node.type === 'object') {
|
||||
return prefix + ':{ }';
|
||||
}
|
||||
if (node.type === 'array') {
|
||||
return prefix + ':[ ]';
|
||||
}
|
||||
return prefix + ':' + node.value.toString();
|
||||
}
|
||||
else {
|
||||
const property = node.parent?.children ? node.parent.children[0].value.toString() : '';
|
||||
if (node.type === 'array' || node.type === 'object') {
|
||||
if (node.type === 'object') {
|
||||
return '{ } ' + property;
|
||||
}
|
||||
if (node.type === 'array') {
|
||||
return '[ ] ' + property;
|
||||
}
|
||||
}
|
||||
const value = this.editor?.document.getText(new vscode.Range(this.editor.document.positionAt(node.offset), this.editor.document.positionAt(node.offset + node.length)));
|
||||
return `${property}: ${value}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,104 +1,104 @@
|
||||
import * as vscode from 'vscode';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
|
||||
export class DepNodeProvider implements vscode.TreeDataProvider<Dependency> {
|
||||
|
||||
private _onDidChangeTreeData: vscode.EventEmitter<Dependency | undefined | void> = new vscode.EventEmitter<Dependency | undefined | void>();
|
||||
readonly onDidChangeTreeData: vscode.Event<Dependency | undefined | void> = this._onDidChangeTreeData.event;
|
||||
|
||||
constructor(private workspaceRoot: string | undefined) {
|
||||
}
|
||||
|
||||
refresh(): void {
|
||||
this._onDidChangeTreeData.fire();
|
||||
}
|
||||
|
||||
getTreeItem(element: Dependency): vscode.TreeItem {
|
||||
return element;
|
||||
}
|
||||
|
||||
getChildren(element?: Dependency): Thenable<Dependency[]> {
|
||||
if (!this.workspaceRoot) {
|
||||
vscode.window.showInformationMessage('No dependency in empty workspace');
|
||||
return Promise.resolve([]);
|
||||
}
|
||||
|
||||
if (element) {
|
||||
return Promise.resolve(this.getDepsInPackageJson(path.join(this.workspaceRoot, 'node_modules', element.label, 'package.json')));
|
||||
} else {
|
||||
const packageJsonPath = path.join(this.workspaceRoot, 'package.json');
|
||||
if (this.pathExists(packageJsonPath)) {
|
||||
return Promise.resolve(this.getDepsInPackageJson(packageJsonPath));
|
||||
} else {
|
||||
vscode.window.showInformationMessage('Workspace has no package.json');
|
||||
return Promise.resolve([]);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Given the path to package.json, read all its dependencies and devDependencies.
|
||||
*/
|
||||
private getDepsInPackageJson(packageJsonPath: string): Dependency[] {
|
||||
const workspaceRoot = this.workspaceRoot;
|
||||
if (this.pathExists(packageJsonPath) && workspaceRoot) {
|
||||
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
|
||||
|
||||
const toDep = (moduleName: string, version: string): Dependency => {
|
||||
if (this.pathExists(path.join(workspaceRoot, 'node_modules', moduleName))) {
|
||||
return new Dependency(moduleName, version, vscode.TreeItemCollapsibleState.Collapsed);
|
||||
} else {
|
||||
return new Dependency(moduleName, version, vscode.TreeItemCollapsibleState.None, {
|
||||
command: 'extension.openPackageOnNpm',
|
||||
title: '',
|
||||
arguments: [moduleName]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const deps = packageJson.dependencies
|
||||
? Object.keys(packageJson.dependencies).map(dep => toDep(dep, packageJson.dependencies[dep]))
|
||||
: [];
|
||||
const devDeps = packageJson.devDependencies
|
||||
? Object.keys(packageJson.devDependencies).map(dep => toDep(dep, packageJson.devDependencies[dep]))
|
||||
: [];
|
||||
return deps.concat(devDeps);
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
private pathExists(p: string): boolean {
|
||||
try {
|
||||
fs.accessSync(p);
|
||||
} catch (err) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
export class Dependency extends vscode.TreeItem {
|
||||
|
||||
constructor(
|
||||
public readonly label: string,
|
||||
private readonly version: string,
|
||||
public readonly collapsibleState: vscode.TreeItemCollapsibleState,
|
||||
public readonly command?: vscode.Command
|
||||
) {
|
||||
super(label, collapsibleState);
|
||||
|
||||
this.tooltip = `${this.label}-${this.version}`;
|
||||
this.description = this.version;
|
||||
}
|
||||
|
||||
iconPath = {
|
||||
light: path.join(__filename, '..', '..', 'resources', 'light', 'dependency.svg'),
|
||||
dark: path.join(__filename, '..', '..', 'resources', 'dark', 'dependency.svg')
|
||||
};
|
||||
|
||||
contextValue = 'dependency';
|
||||
}
|
||||
import * as vscode from 'vscode';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
|
||||
export class DepNodeProvider implements vscode.TreeDataProvider<Dependency> {
|
||||
|
||||
private _onDidChangeTreeData: vscode.EventEmitter<Dependency | undefined | void> = new vscode.EventEmitter<Dependency | undefined | void>();
|
||||
readonly onDidChangeTreeData: vscode.Event<Dependency | undefined | void> = this._onDidChangeTreeData.event;
|
||||
|
||||
constructor(private workspaceRoot: string | undefined) {
|
||||
}
|
||||
|
||||
refresh(): void {
|
||||
this._onDidChangeTreeData.fire();
|
||||
}
|
||||
|
||||
getTreeItem(element: Dependency): vscode.TreeItem {
|
||||
return element;
|
||||
}
|
||||
|
||||
getChildren(element?: Dependency): Thenable<Dependency[]> {
|
||||
if (!this.workspaceRoot) {
|
||||
vscode.window.showInformationMessage('No dependency in empty workspace');
|
||||
return Promise.resolve([]);
|
||||
}
|
||||
|
||||
if (element) {
|
||||
return Promise.resolve(this.getDepsInPackageJson(path.join(this.workspaceRoot, 'node_modules', element.label, 'package.json')));
|
||||
} else {
|
||||
const packageJsonPath = path.join(this.workspaceRoot, 'package.json');
|
||||
if (this.pathExists(packageJsonPath)) {
|
||||
return Promise.resolve(this.getDepsInPackageJson(packageJsonPath));
|
||||
} else {
|
||||
vscode.window.showInformationMessage('Workspace has no package.json');
|
||||
return Promise.resolve([]);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Given the path to package.json, read all its dependencies and devDependencies.
|
||||
*/
|
||||
private getDepsInPackageJson(packageJsonPath: string): Dependency[] {
|
||||
const workspaceRoot = this.workspaceRoot;
|
||||
if (this.pathExists(packageJsonPath) && workspaceRoot) {
|
||||
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
|
||||
|
||||
const toDep = (moduleName: string, version: string): Dependency => {
|
||||
if (this.pathExists(path.join(workspaceRoot, 'node_modules', moduleName))) {
|
||||
return new Dependency(moduleName, version, vscode.TreeItemCollapsibleState.Collapsed);
|
||||
} else {
|
||||
return new Dependency(moduleName, version, vscode.TreeItemCollapsibleState.None, {
|
||||
command: 'extension.openPackageOnNpm',
|
||||
title: '',
|
||||
arguments: [moduleName]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const deps = packageJson.dependencies
|
||||
? Object.keys(packageJson.dependencies).map(dep => toDep(dep, packageJson.dependencies[dep]))
|
||||
: [];
|
||||
const devDeps = packageJson.devDependencies
|
||||
? Object.keys(packageJson.devDependencies).map(dep => toDep(dep, packageJson.devDependencies[dep]))
|
||||
: [];
|
||||
return deps.concat(devDeps);
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
private pathExists(p: string): boolean {
|
||||
try {
|
||||
fs.accessSync(p);
|
||||
} catch (err) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
export class Dependency extends vscode.TreeItem {
|
||||
|
||||
constructor(
|
||||
public readonly label: string,
|
||||
private readonly version: string,
|
||||
public readonly collapsibleState: vscode.TreeItemCollapsibleState,
|
||||
public readonly command?: vscode.Command
|
||||
) {
|
||||
super(label, collapsibleState);
|
||||
|
||||
this.tooltip = `${this.label}-${this.version}`;
|
||||
this.description = this.version;
|
||||
}
|
||||
|
||||
iconPath = {
|
||||
light: path.join(__filename, '..', '..', 'resources', 'light', 'dependency.svg'),
|
||||
dark: path.join(__filename, '..', '..', 'resources', 'dark', 'dependency.svg')
|
||||
};
|
||||
|
||||
contextValue = 'dependency';
|
||||
}
|
||||
|
||||
@ -1,213 +1,213 @@
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
export class TestViewDragAndDrop implements vscode.TreeDataProvider<Node>, vscode.TreeDragAndDropController<Node> {
|
||||
dropMimeTypes = ['application/vnd.code.tree.testViewDragAndDrop'];
|
||||
dragMimeTypes = ['text/uri-list'];
|
||||
private _onDidChangeTreeData: vscode.EventEmitter<(Node | undefined)[] | undefined> = new vscode.EventEmitter<Node[] | undefined>();
|
||||
// We want to use an array as the event type, but the API for this is currently being finalized. Until it's finalized, use any.
|
||||
public onDidChangeTreeData: vscode.Event<any> = this._onDidChangeTreeData.event;
|
||||
public tree: any = {
|
||||
'a': {
|
||||
'aa': {
|
||||
'aaa': {
|
||||
'aaaa': {
|
||||
'aaaaa': {
|
||||
'aaaaaa': {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
'ab': {}
|
||||
},
|
||||
'b': {
|
||||
'ba': {},
|
||||
'bb': {}
|
||||
}
|
||||
};
|
||||
// Keep track of any nodes we create so that we can re-use the same objects.
|
||||
private nodes: any = {};
|
||||
|
||||
constructor(context: vscode.ExtensionContext) {
|
||||
const view = vscode.window.createTreeView('testViewDragAndDrop', { treeDataProvider: this, showCollapseAll: true, canSelectMany: true, dragAndDropController: this });
|
||||
context.subscriptions.push(view);
|
||||
}
|
||||
|
||||
// Tree data provider
|
||||
|
||||
public getChildren(element: Node): Node[] {
|
||||
return this._getChildren(element ? element.key : undefined).map(key => this._getNode(key));
|
||||
}
|
||||
|
||||
public getTreeItem(element: Node): vscode.TreeItem {
|
||||
const treeItem = this._getTreeItem(element.key);
|
||||
treeItem.id = element.key;
|
||||
return treeItem;
|
||||
}
|
||||
public getParent(element: Node): Node {
|
||||
return this._getParent(element.key);
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
// nothing to dispose
|
||||
}
|
||||
|
||||
// Drag and drop controller
|
||||
|
||||
public async handleDrop(target: Node | undefined, sources: vscode.DataTransfer, token: vscode.CancellationToken): Promise<void> {
|
||||
const transferItem = sources.get('application/vnd.code.tree.testViewDragAndDrop');
|
||||
if (!transferItem) {
|
||||
return;
|
||||
}
|
||||
const treeItems: Node[] = transferItem.value;
|
||||
let roots = this._getLocalRoots(treeItems);
|
||||
// Remove nodes that are already target's parent nodes
|
||||
roots = roots.filter(r => !this._isChild(this._getTreeElement(r.key), target));
|
||||
if (roots.length > 0) {
|
||||
// Reload parents of the moving elements
|
||||
const parents = roots.map(r => this.getParent(r));
|
||||
roots.forEach(r => this._reparentNode(r, target));
|
||||
this._onDidChangeTreeData.fire([...parents, target]);
|
||||
}
|
||||
}
|
||||
|
||||
public async handleDrag(source: Node[], treeDataTransfer: vscode.DataTransfer, token: vscode.CancellationToken): Promise<void> {
|
||||
treeDataTransfer.set('application/vnd.code.tree.testViewDragAndDrop', new vscode.DataTransferItem(source));
|
||||
}
|
||||
|
||||
// Helper methods
|
||||
|
||||
_isChild(node: Node, child: Node | undefined): boolean {
|
||||
if (!child) {
|
||||
return false;
|
||||
}
|
||||
for (const prop in node) {
|
||||
if (prop === child.key) {
|
||||
return true;
|
||||
} else {
|
||||
const isChild = this._isChild((node as any)[prop], child);
|
||||
if (isChild) {
|
||||
return isChild;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// From the given nodes, filter out all nodes who's parent is already in the the array of Nodes.
|
||||
_getLocalRoots(nodes: Node[]): Node[] {
|
||||
const localRoots = [];
|
||||
for (let i = 0; i < nodes.length; i++) {
|
||||
const parent = this.getParent(nodes[i]);
|
||||
if (parent) {
|
||||
const isInList = nodes.find(n => n.key === parent.key);
|
||||
if (isInList === undefined) {
|
||||
localRoots.push(nodes[i]);
|
||||
}
|
||||
} else {
|
||||
localRoots.push(nodes[i]);
|
||||
}
|
||||
}
|
||||
return localRoots;
|
||||
}
|
||||
|
||||
// Remove node from current position and add node to new target element
|
||||
_reparentNode(node: Node, target: Node | undefined): void {
|
||||
const element: any = {};
|
||||
element[node.key] = this._getTreeElement(node.key);
|
||||
const elementCopy = { ...element };
|
||||
this._removeNode(node);
|
||||
const targetElement = this._getTreeElement(target?.key);
|
||||
if (Object.keys(element).length === 0) {
|
||||
targetElement[node.key] = {};
|
||||
} else {
|
||||
Object.assign(targetElement, elementCopy);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove node from tree
|
||||
_removeNode(element: Node, tree?: any): void {
|
||||
const subTree = tree ? tree : this.tree;
|
||||
for (const prop in subTree) {
|
||||
if (prop === element.key) {
|
||||
const parent = this.getParent(element);
|
||||
if (parent) {
|
||||
const parentObject = this._getTreeElement(parent.key);
|
||||
delete parentObject[prop];
|
||||
} else {
|
||||
delete this.tree[prop];
|
||||
}
|
||||
} else {
|
||||
this._removeNode(element, subTree[prop]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_getChildren(key: string | undefined): string[] {
|
||||
if (!key) {
|
||||
return Object.keys(this.tree);
|
||||
}
|
||||
const treeElement = this._getTreeElement(key);
|
||||
if (treeElement) {
|
||||
return Object.keys(treeElement);
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
_getTreeItem(key: string): vscode.TreeItem {
|
||||
const treeElement = this._getTreeElement(key);
|
||||
// An example of how to use codicons in a MarkdownString in a tree item tooltip.
|
||||
const tooltip = new vscode.MarkdownString(`$(zap) Tooltip for ${key}`, true);
|
||||
return {
|
||||
label: /**vscode.TreeItemLabel**/<any>{ label: key, highlights: key.length > 1 ? [[key.length - 2, key.length - 1]] : void 0 },
|
||||
tooltip,
|
||||
collapsibleState: treeElement && Object.keys(treeElement).length ? vscode.TreeItemCollapsibleState.Collapsed : vscode.TreeItemCollapsibleState.None,
|
||||
resourceUri: vscode.Uri.parse(`/tmp/${key}`),
|
||||
};
|
||||
}
|
||||
|
||||
_getTreeElement(element: string | undefined, tree?: any): any {
|
||||
if (!element) {
|
||||
return this.tree;
|
||||
}
|
||||
const currentNode = tree ?? this.tree;
|
||||
for (const prop in currentNode) {
|
||||
if (prop === element) {
|
||||
return currentNode[prop];
|
||||
} else {
|
||||
const treeElement = this._getTreeElement(element, currentNode[prop]);
|
||||
if (treeElement) {
|
||||
return treeElement;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_getParent(element: string, parent?: string, tree?: any): any {
|
||||
const currentNode = tree ?? this.tree;
|
||||
for (const prop in currentNode) {
|
||||
if (prop === element && parent) {
|
||||
return this._getNode(parent);
|
||||
} else {
|
||||
const parent = this._getParent(element, prop, currentNode[prop]);
|
||||
if (parent) {
|
||||
return parent;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_getNode(key: string): Node {
|
||||
if (!this.nodes[key]) {
|
||||
this.nodes[key] = new Key(key);
|
||||
}
|
||||
return this.nodes[key];
|
||||
}
|
||||
}
|
||||
|
||||
type Node = { key: string };
|
||||
|
||||
class Key {
|
||||
constructor(readonly key: string) { }
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
export class TestViewDragAndDrop implements vscode.TreeDataProvider<Node>, vscode.TreeDragAndDropController<Node> {
|
||||
dropMimeTypes = ['application/vnd.code.tree.testViewDragAndDrop'];
|
||||
dragMimeTypes = ['text/uri-list'];
|
||||
private _onDidChangeTreeData: vscode.EventEmitter<(Node | undefined)[] | undefined> = new vscode.EventEmitter<Node[] | undefined>();
|
||||
// We want to use an array as the event type, but the API for this is currently being finalized. Until it's finalized, use any.
|
||||
public onDidChangeTreeData: vscode.Event<any> = this._onDidChangeTreeData.event;
|
||||
public tree: any = {
|
||||
'a': {
|
||||
'aa': {
|
||||
'aaa': {
|
||||
'aaaa': {
|
||||
'aaaaa': {
|
||||
'aaaaaa': {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
'ab': {}
|
||||
},
|
||||
'b': {
|
||||
'ba': {},
|
||||
'bb': {}
|
||||
}
|
||||
};
|
||||
// Keep track of any nodes we create so that we can re-use the same objects.
|
||||
private nodes: any = {};
|
||||
|
||||
constructor(context: vscode.ExtensionContext) {
|
||||
const view = vscode.window.createTreeView('testViewDragAndDrop', { treeDataProvider: this, showCollapseAll: true, canSelectMany: true, dragAndDropController: this });
|
||||
context.subscriptions.push(view);
|
||||
}
|
||||
|
||||
// Tree data provider
|
||||
|
||||
public getChildren(element: Node): Node[] {
|
||||
return this._getChildren(element ? element.key : undefined).map(key => this._getNode(key));
|
||||
}
|
||||
|
||||
public getTreeItem(element: Node): vscode.TreeItem {
|
||||
const treeItem = this._getTreeItem(element.key);
|
||||
treeItem.id = element.key;
|
||||
return treeItem;
|
||||
}
|
||||
public getParent(element: Node): Node {
|
||||
return this._getParent(element.key);
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
// nothing to dispose
|
||||
}
|
||||
|
||||
// Drag and drop controller
|
||||
|
||||
public async handleDrop(target: Node | undefined, sources: vscode.DataTransfer, token: vscode.CancellationToken): Promise<void> {
|
||||
const transferItem = sources.get('application/vnd.code.tree.testViewDragAndDrop');
|
||||
if (!transferItem) {
|
||||
return;
|
||||
}
|
||||
const treeItems: Node[] = transferItem.value;
|
||||
let roots = this._getLocalRoots(treeItems);
|
||||
// Remove nodes that are already target's parent nodes
|
||||
roots = roots.filter(r => !this._isChild(this._getTreeElement(r.key), target));
|
||||
if (roots.length > 0) {
|
||||
// Reload parents of the moving elements
|
||||
const parents = roots.map(r => this.getParent(r));
|
||||
roots.forEach(r => this._reparentNode(r, target));
|
||||
this._onDidChangeTreeData.fire([...parents, target]);
|
||||
}
|
||||
}
|
||||
|
||||
public async handleDrag(source: Node[], treeDataTransfer: vscode.DataTransfer, token: vscode.CancellationToken): Promise<void> {
|
||||
treeDataTransfer.set('application/vnd.code.tree.testViewDragAndDrop', new vscode.DataTransferItem(source));
|
||||
}
|
||||
|
||||
// Helper methods
|
||||
|
||||
_isChild(node: Node, child: Node | undefined): boolean {
|
||||
if (!child) {
|
||||
return false;
|
||||
}
|
||||
for (const prop in node) {
|
||||
if (prop === child.key) {
|
||||
return true;
|
||||
} else {
|
||||
const isChild = this._isChild((node as any)[prop], child);
|
||||
if (isChild) {
|
||||
return isChild;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// From the given nodes, filter out all nodes who's parent is already in the the array of Nodes.
|
||||
_getLocalRoots(nodes: Node[]): Node[] {
|
||||
const localRoots = [];
|
||||
for (let i = 0; i < nodes.length; i++) {
|
||||
const parent = this.getParent(nodes[i]);
|
||||
if (parent) {
|
||||
const isInList = nodes.find(n => n.key === parent.key);
|
||||
if (isInList === undefined) {
|
||||
localRoots.push(nodes[i]);
|
||||
}
|
||||
} else {
|
||||
localRoots.push(nodes[i]);
|
||||
}
|
||||
}
|
||||
return localRoots;
|
||||
}
|
||||
|
||||
// Remove node from current position and add node to new target element
|
||||
_reparentNode(node: Node, target: Node | undefined): void {
|
||||
const element: any = {};
|
||||
element[node.key] = this._getTreeElement(node.key);
|
||||
const elementCopy = { ...element };
|
||||
this._removeNode(node);
|
||||
const targetElement = this._getTreeElement(target?.key);
|
||||
if (Object.keys(element).length === 0) {
|
||||
targetElement[node.key] = {};
|
||||
} else {
|
||||
Object.assign(targetElement, elementCopy);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove node from tree
|
||||
_removeNode(element: Node, tree?: any): void {
|
||||
const subTree = tree ? tree : this.tree;
|
||||
for (const prop in subTree) {
|
||||
if (prop === element.key) {
|
||||
const parent = this.getParent(element);
|
||||
if (parent) {
|
||||
const parentObject = this._getTreeElement(parent.key);
|
||||
delete parentObject[prop];
|
||||
} else {
|
||||
delete this.tree[prop];
|
||||
}
|
||||
} else {
|
||||
this._removeNode(element, subTree[prop]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_getChildren(key: string | undefined): string[] {
|
||||
if (!key) {
|
||||
return Object.keys(this.tree);
|
||||
}
|
||||
const treeElement = this._getTreeElement(key);
|
||||
if (treeElement) {
|
||||
return Object.keys(treeElement);
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
_getTreeItem(key: string): vscode.TreeItem {
|
||||
const treeElement = this._getTreeElement(key);
|
||||
// An example of how to use codicons in a MarkdownString in a tree item tooltip.
|
||||
const tooltip = new vscode.MarkdownString(`$(zap) Tooltip for ${key}`, true);
|
||||
return {
|
||||
label: /**vscode.TreeItemLabel**/<any>{ label: key, highlights: key.length > 1 ? [[key.length - 2, key.length - 1]] : void 0 },
|
||||
tooltip,
|
||||
collapsibleState: treeElement && Object.keys(treeElement).length ? vscode.TreeItemCollapsibleState.Collapsed : vscode.TreeItemCollapsibleState.None,
|
||||
resourceUri: vscode.Uri.parse(`/tmp/${key}`),
|
||||
};
|
||||
}
|
||||
|
||||
_getTreeElement(element: string | undefined, tree?: any): any {
|
||||
if (!element) {
|
||||
return this.tree;
|
||||
}
|
||||
const currentNode = tree ?? this.tree;
|
||||
for (const prop in currentNode) {
|
||||
if (prop === element) {
|
||||
return currentNode[prop];
|
||||
} else {
|
||||
const treeElement = this._getTreeElement(element, currentNode[prop]);
|
||||
if (treeElement) {
|
||||
return treeElement;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_getParent(element: string, parent?: string, tree?: any): any {
|
||||
const currentNode = tree ?? this.tree;
|
||||
for (const prop in currentNode) {
|
||||
if (prop === element && parent) {
|
||||
return this._getNode(parent);
|
||||
} else {
|
||||
const parent = this._getParent(element, prop, currentNode[prop]);
|
||||
if (parent) {
|
||||
return parent;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_getNode(key: string): Node {
|
||||
if (!this.nodes[key]) {
|
||||
this.nodes[key] = new Key(key);
|
||||
}
|
||||
return this.nodes[key];
|
||||
}
|
||||
}
|
||||
|
||||
type Node = { key: string };
|
||||
|
||||
class Key {
|
||||
constructor(readonly key: string) { }
|
||||
}
|
||||
@ -1,35 +1,35 @@
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
// Our implementation of a UriHandler.
|
||||
class MyUriHandler implements vscode.UriHandler {
|
||||
// This function will get run when something redirects to VS Code
|
||||
// with your extension id as the authority.
|
||||
handleUri(uri: vscode.Uri): vscode.ProviderResult<void> {
|
||||
let message = "Handled a Uri!";
|
||||
if (uri.query) {
|
||||
message += ` It came with this query: ${uri.query}`;
|
||||
}
|
||||
vscode.window.showInformationMessage(message);
|
||||
}
|
||||
}
|
||||
|
||||
export function activate(context: vscode.ExtensionContext) {
|
||||
|
||||
let disposable = vscode.commands.registerCommand('uri-handler-sample.start', async () => {
|
||||
// Create our new UriHandler
|
||||
const uriHandler = new MyUriHandler();
|
||||
|
||||
// And register it with VS Code. You can only register a single UriHandler for your extension.
|
||||
context.subscriptions.push(vscode.window.registerUriHandler(uriHandler));
|
||||
|
||||
// You don't have to get the Uri from the `vscode.env.asExternalUri` API but it will add a query
|
||||
// parameter (ex: "windowId%3D14") that will help VS Code decide which window to redirect to.
|
||||
// If this query parameter isn't specified, VS Code will pick the last windows that was focused.
|
||||
const uri = await vscode.env.asExternalUri(vscode.Uri.parse(`${vscode.env.uriScheme}://vscode-samples.uri-handler-sample`));
|
||||
vscode.window.showInformationMessage(`Starting to handle Uris. Open ${uri} in your browser to test.`);
|
||||
});
|
||||
|
||||
context.subscriptions.push(disposable);
|
||||
}
|
||||
|
||||
export function deactivate() {}
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
// Our implementation of a UriHandler.
|
||||
class MyUriHandler implements vscode.UriHandler {
|
||||
// This function will get run when something redirects to VS Code
|
||||
// with your extension id as the authority.
|
||||
handleUri(uri: vscode.Uri): vscode.ProviderResult<void> {
|
||||
let message = "Handled a Uri!";
|
||||
if (uri.query) {
|
||||
message += ` It came with this query: ${uri.query}`;
|
||||
}
|
||||
vscode.window.showInformationMessage(message);
|
||||
}
|
||||
}
|
||||
|
||||
export function activate(context: vscode.ExtensionContext) {
|
||||
|
||||
let disposable = vscode.commands.registerCommand('uri-handler-sample.start', async () => {
|
||||
// Create our new UriHandler
|
||||
const uriHandler = new MyUriHandler();
|
||||
|
||||
// And register it with VS Code. You can only register a single UriHandler for your extension.
|
||||
context.subscriptions.push(vscode.window.registerUriHandler(uriHandler));
|
||||
|
||||
// You don't have to get the Uri from the `vscode.env.asExternalUri` API but it will add a query
|
||||
// parameter (ex: "windowId%3D14") that will help VS Code decide which window to redirect to.
|
||||
// If this query parameter isn't specified, VS Code will pick the last windows that was focused.
|
||||
const uri = await vscode.env.asExternalUri(vscode.Uri.parse(`${vscode.env.uriScheme}://vscode-samples.uri-handler-sample`));
|
||||
vscode.window.showInformationMessage(`Starting to handle Uris. Open ${uri} in your browser to test.`);
|
||||
});
|
||||
|
||||
context.subscriptions.push(disposable);
|
||||
}
|
||||
|
||||
export function deactivate() { }
|
||||
|
||||
@ -1,51 +1,51 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { MotionState, Motion } from './motions';
|
||||
|
||||
export enum Mode {
|
||||
INSERT,
|
||||
NORMAL,
|
||||
REPLACE
|
||||
}
|
||||
|
||||
export interface ModifierKeys {
|
||||
ctrl?: boolean;
|
||||
alt?: boolean;
|
||||
shifit?: boolean;
|
||||
}
|
||||
|
||||
export class DeleteRegister {
|
||||
public isWholeLine: boolean;
|
||||
public content: string;
|
||||
|
||||
constructor(isWholeLine: boolean, content: string) {
|
||||
this.isWholeLine = isWholeLine;
|
||||
this.content = content;
|
||||
}
|
||||
}
|
||||
|
||||
export interface IController {
|
||||
motionState: MotionState;
|
||||
|
||||
setMode(mode: Mode): void;
|
||||
setVisual(newVisual: boolean): void;
|
||||
findMotion(input: string): Motion;
|
||||
isMotionPrefix(input: string): boolean;
|
||||
|
||||
setDeleteRegister(register: DeleteRegister): void;
|
||||
getDeleteRegister(): DeleteRegister;
|
||||
}
|
||||
|
||||
export abstract class AbstractCommandDescriptor {
|
||||
|
||||
public abstract createCommand(args?: any): Command;
|
||||
|
||||
}
|
||||
|
||||
export interface Command {
|
||||
commandId: string;
|
||||
args?: any[];
|
||||
}
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { MotionState, Motion } from './motions';
|
||||
|
||||
export enum Mode {
|
||||
INSERT,
|
||||
NORMAL,
|
||||
REPLACE
|
||||
}
|
||||
|
||||
export interface ModifierKeys {
|
||||
ctrl?: boolean;
|
||||
alt?: boolean;
|
||||
shifit?: boolean;
|
||||
}
|
||||
|
||||
export class DeleteRegister {
|
||||
public isWholeLine: boolean;
|
||||
public content: string;
|
||||
|
||||
constructor(isWholeLine: boolean, content: string) {
|
||||
this.isWholeLine = isWholeLine;
|
||||
this.content = content;
|
||||
}
|
||||
}
|
||||
|
||||
export interface IController {
|
||||
motionState: MotionState;
|
||||
|
||||
setMode(mode: Mode): void;
|
||||
setVisual(newVisual: boolean): void;
|
||||
findMotion(input: string): Motion;
|
||||
isMotionPrefix(input: string): boolean;
|
||||
|
||||
setDeleteRegister(register: DeleteRegister): void;
|
||||
getDeleteRegister(): DeleteRegister;
|
||||
}
|
||||
|
||||
export abstract class AbstractCommandDescriptor {
|
||||
|
||||
public abstract createCommand(args?: any): Command;
|
||||
|
||||
}
|
||||
|
||||
export interface Command {
|
||||
commandId: string;
|
||||
args?: any[];
|
||||
}
|
||||
|
||||
@ -1,303 +1,303 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { TextEditorCursorStyle, Position, Range, Selection, TextEditor, TextEditorRevealType, window } from 'vscode';
|
||||
|
||||
import { Words } from './words';
|
||||
import { MotionState, Motion } from './motions';
|
||||
import { Mode, IController, DeleteRegister, Command, ModifierKeys } from './common';
|
||||
import { Mappings } from './mappings';
|
||||
|
||||
export interface ITypeResult {
|
||||
hasConsumedInput: boolean;
|
||||
executeEditorCommand: Command;
|
||||
}
|
||||
|
||||
export class Controller implements IController {
|
||||
|
||||
private _currentMode: Mode;
|
||||
private _currentInput: string;
|
||||
private _motionState: MotionState;
|
||||
private _isVisual: boolean;
|
||||
|
||||
public get motionState(): MotionState { return this._motionState; }
|
||||
public findMotion(input: string): Motion { return Mappings.findMotion(input); }
|
||||
public isMotionPrefix(input: string): boolean { return Mappings.isMotionPrefix(input); }
|
||||
|
||||
private _deleteRegister: DeleteRegister;
|
||||
public setDeleteRegister(register: DeleteRegister): void { this._deleteRegister = register; }
|
||||
public getDeleteRegister(): DeleteRegister { return this._deleteRegister; }
|
||||
|
||||
constructor() {
|
||||
this._motionState = new MotionState();
|
||||
this._deleteRegister = null;
|
||||
this.setVisual(false);
|
||||
this.setMode(Mode.NORMAL);
|
||||
}
|
||||
|
||||
public setWordSeparators(wordSeparators: string): void {
|
||||
this._motionState.wordCharacterClass = Words.createWordCharacters(wordSeparators);
|
||||
}
|
||||
|
||||
public ensureNormalModePosition(editor: TextEditor): void {
|
||||
if (this._currentMode !== Mode.NORMAL) {
|
||||
return;
|
||||
}
|
||||
if (this._isVisual) {
|
||||
return;
|
||||
}
|
||||
const sel = editor.selection;
|
||||
const pos = sel.active;
|
||||
const doc = editor.document;
|
||||
const lineContent = doc.lineAt(pos.line).text;
|
||||
if (lineContent.length === 0) {
|
||||
return;
|
||||
}
|
||||
const maxCharacter = lineContent.length - 1;
|
||||
if (pos.character > maxCharacter) {
|
||||
setPositionAndReveal(editor, pos.line, maxCharacter);
|
||||
}
|
||||
}
|
||||
|
||||
public hasInput(): boolean {
|
||||
return this._currentInput.length > 0;
|
||||
}
|
||||
|
||||
public clearInput(): void {
|
||||
this._currentInput = '';
|
||||
}
|
||||
|
||||
public getMode(): Mode {
|
||||
return this._currentMode;
|
||||
}
|
||||
|
||||
public setMode(newMode: Mode): void {
|
||||
if (newMode !== this._currentMode) {
|
||||
this._currentMode = newMode;
|
||||
this._motionState.cursorDesiredCharacter = -1; // uninitialized
|
||||
this._currentInput = '';
|
||||
}
|
||||
}
|
||||
|
||||
public setVisual(newVisual: boolean): void {
|
||||
if (this._isVisual !== newVisual) {
|
||||
this._isVisual = newVisual;
|
||||
}
|
||||
}
|
||||
|
||||
public getVisual(): boolean {
|
||||
return this._isVisual;
|
||||
}
|
||||
|
||||
public getCursorStyle(): TextEditorCursorStyle {
|
||||
if (this._currentMode === Mode.NORMAL) {
|
||||
if (/^([1-9]\d*)?(r|c)/.test(this._currentInput)) {
|
||||
return TextEditorCursorStyle.Underline;
|
||||
}
|
||||
return TextEditorCursorStyle.Block;
|
||||
}
|
||||
if (this._currentMode === Mode.REPLACE) {
|
||||
return TextEditorCursorStyle.Underline;
|
||||
}
|
||||
return TextEditorCursorStyle.Line;
|
||||
}
|
||||
|
||||
private _getModeLabel(): string {
|
||||
if (this._currentMode === Mode.NORMAL) {
|
||||
if (this._isVisual) {
|
||||
return '-- VISUAL --';
|
||||
}
|
||||
return '-- NORMAL --';
|
||||
}
|
||||
|
||||
if (this._currentMode === Mode.REPLACE) {
|
||||
if (this._isVisual) {
|
||||
return '-- (replace) VISUAL --';
|
||||
}
|
||||
return '-- REPLACE --';
|
||||
}
|
||||
|
||||
if (this._isVisual) {
|
||||
return '-- (insert) VISUAL --';
|
||||
}
|
||||
return '-- INSERT --';
|
||||
}
|
||||
|
||||
public getStatusText(): string {
|
||||
const label = this._getModeLabel();
|
||||
return `VIM:> ${label}` + (this._currentInput ? ` >${this._currentInput}` : ``);
|
||||
}
|
||||
|
||||
private _isInComposition = false;
|
||||
private _composingText = '';
|
||||
|
||||
public compositionStart(editor: TextEditor): void {
|
||||
this._isInComposition = true;
|
||||
this._composingText = '';
|
||||
}
|
||||
|
||||
public compositionEnd(editor: TextEditor): Thenable<ITypeResult> {
|
||||
this._isInComposition = false;
|
||||
const text = this._composingText;
|
||||
this._composingText = '';
|
||||
|
||||
if (text.length === 0) {
|
||||
return Promise.resolve({
|
||||
hasConsumedInput: true,
|
||||
executeEditorCommand: null
|
||||
});
|
||||
}
|
||||
|
||||
return this.type(editor, text, {});
|
||||
}
|
||||
|
||||
public type(editor: TextEditor, text: string, modifierKeys: ModifierKeys): Thenable<ITypeResult> {
|
||||
if (this._currentMode !== Mode.NORMAL && this._currentMode !== Mode.REPLACE) {
|
||||
return Promise.resolve({
|
||||
hasConsumedInput: false,
|
||||
executeEditorCommand: null
|
||||
});
|
||||
}
|
||||
|
||||
if (this._isInComposition) {
|
||||
this._composingText += text;
|
||||
return Promise.resolve({
|
||||
hasConsumedInput: true,
|
||||
executeEditorCommand: null
|
||||
});
|
||||
}
|
||||
|
||||
if (this._currentMode === Mode.REPLACE) {
|
||||
const pos = editor.selection.active;
|
||||
editor.edit((builder) => {
|
||||
builder.replace(new Range(pos.line, pos.character, pos.line, pos.character + 1), text);
|
||||
}).then(() => {
|
||||
setPositionAndReveal(editor, pos.line, pos.character + 1);
|
||||
});
|
||||
|
||||
return Promise.resolve({
|
||||
hasConsumedInput: true,
|
||||
executeEditorCommand: null
|
||||
});
|
||||
}
|
||||
this._currentInput += text;
|
||||
return this._interpretNormalModeInput(editor, modifierKeys);
|
||||
}
|
||||
|
||||
public replacePrevChar(editor: TextEditor, text: string, replaceCharCnt: number): boolean {
|
||||
if (this._currentMode !== Mode.NORMAL && this._currentMode !== Mode.REPLACE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this._isInComposition) {
|
||||
this._composingText = this._composingText.substr(0, this._composingText.length - replaceCharCnt) + text;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this._currentMode === Mode.REPLACE) {
|
||||
const pos = editor.selection.active;
|
||||
editor.edit((builder) => {
|
||||
builder.replace(new Range(pos.line, pos.character - replaceCharCnt, pos.line, pos.character), text);
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private _interpretNormalModeInput(editor: TextEditor, modifierKeys: ModifierKeys): Thenable<ITypeResult> {
|
||||
if (this._currentInput.startsWith(':')) {
|
||||
return window.showInputBox({ value: 'tabm' }).then((value) => {
|
||||
return this._findMapping(value || '', editor, modifierKeys);
|
||||
});
|
||||
}
|
||||
const result = this._findMapping(this._currentInput, editor, modifierKeys);
|
||||
return Promise.resolve(result);
|
||||
}
|
||||
|
||||
private _findMapping(input: string, editor: TextEditor, modifierKeys: ModifierKeys): ITypeResult {
|
||||
const command = Mappings.findCommand(input, modifierKeys);
|
||||
if (command) {
|
||||
this._currentInput = '';
|
||||
return {
|
||||
hasConsumedInput: true,
|
||||
executeEditorCommand: command
|
||||
};
|
||||
}
|
||||
|
||||
const operator = Mappings.findOperator(input, modifierKeys);
|
||||
if (operator) {
|
||||
if (this._isVisual) {
|
||||
if (operator.runVisual(this, editor)) {
|
||||
this._currentInput = '';
|
||||
}
|
||||
} else {
|
||||
// Mode.NORMAL
|
||||
if (operator.runNormal(this, editor)) {
|
||||
this._currentInput = '';
|
||||
}
|
||||
}
|
||||
return {
|
||||
hasConsumedInput: true,
|
||||
executeEditorCommand: null
|
||||
};
|
||||
}
|
||||
|
||||
const motionCommand = Mappings.findMotionCommand(input, this._isVisual, modifierKeys);
|
||||
if (motionCommand) {
|
||||
this._currentInput = '';
|
||||
return {
|
||||
hasConsumedInput: true,
|
||||
executeEditorCommand: motionCommand
|
||||
};
|
||||
}
|
||||
|
||||
const motion = Mappings.findMotion(input);
|
||||
if (motion) {
|
||||
const newPos = motion.run(editor.document, editor.selection.active, this._motionState);
|
||||
if (this._isVisual) {
|
||||
setSelectionAndReveal(editor, this._motionState.anchor, newPos.line, newPos.character);
|
||||
} else {
|
||||
// Mode.NORMAL
|
||||
setPositionAndReveal(editor, newPos.line, newPos.character);
|
||||
}
|
||||
this._currentInput = '';
|
||||
return {
|
||||
hasConsumedInput: true,
|
||||
executeEditorCommand: null
|
||||
};
|
||||
}
|
||||
|
||||
// is it motion building
|
||||
if (this.isMotionPrefix(input)) {
|
||||
return {
|
||||
hasConsumedInput: true,
|
||||
executeEditorCommand: null
|
||||
};
|
||||
}
|
||||
|
||||
// INVALID INPUT - beep!!
|
||||
this._currentInput = '';
|
||||
return {
|
||||
hasConsumedInput: true,
|
||||
executeEditorCommand: null
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function setSelectionAndReveal(editor: TextEditor, anchor: Position, line: number, char: number): void {
|
||||
editor.selection = new Selection(anchor, new Position(line, char));
|
||||
revealPosition(editor, line, char);
|
||||
}
|
||||
|
||||
function setPositionAndReveal(editor: TextEditor, line: number, char: number): void {
|
||||
editor.selection = new Selection(new Position(line, char), new Position(line, char));
|
||||
revealPosition(editor, line, char);
|
||||
}
|
||||
|
||||
function revealPosition(editor: TextEditor, line: number, char: number): void {
|
||||
editor.revealRange(new Range(line, char, line, char), TextEditorRevealType.Default);
|
||||
}
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { TextEditorCursorStyle, Position, Range, Selection, TextEditor, TextEditorRevealType, window } from 'vscode';
|
||||
|
||||
import { Words } from './words';
|
||||
import { MotionState, Motion } from './motions';
|
||||
import { Mode, IController, DeleteRegister, Command, ModifierKeys } from './common';
|
||||
import { Mappings } from './mappings';
|
||||
|
||||
export interface ITypeResult {
|
||||
hasConsumedInput: boolean;
|
||||
executeEditorCommand: Command;
|
||||
}
|
||||
|
||||
export class Controller implements IController {
|
||||
|
||||
private _currentMode: Mode;
|
||||
private _currentInput: string;
|
||||
private _motionState: MotionState;
|
||||
private _isVisual: boolean;
|
||||
|
||||
public get motionState(): MotionState { return this._motionState; }
|
||||
public findMotion(input: string): Motion { return Mappings.findMotion(input); }
|
||||
public isMotionPrefix(input: string): boolean { return Mappings.isMotionPrefix(input); }
|
||||
|
||||
private _deleteRegister: DeleteRegister;
|
||||
public setDeleteRegister(register: DeleteRegister): void { this._deleteRegister = register; }
|
||||
public getDeleteRegister(): DeleteRegister { return this._deleteRegister; }
|
||||
|
||||
constructor() {
|
||||
this._motionState = new MotionState();
|
||||
this._deleteRegister = null;
|
||||
this.setVisual(false);
|
||||
this.setMode(Mode.NORMAL);
|
||||
}
|
||||
|
||||
public setWordSeparators(wordSeparators: string): void {
|
||||
this._motionState.wordCharacterClass = Words.createWordCharacters(wordSeparators);
|
||||
}
|
||||
|
||||
public ensureNormalModePosition(editor: TextEditor): void {
|
||||
if (this._currentMode !== Mode.NORMAL) {
|
||||
return;
|
||||
}
|
||||
if (this._isVisual) {
|
||||
return;
|
||||
}
|
||||
const sel = editor.selection;
|
||||
const pos = sel.active;
|
||||
const doc = editor.document;
|
||||
const lineContent = doc.lineAt(pos.line).text;
|
||||
if (lineContent.length === 0) {
|
||||
return;
|
||||
}
|
||||
const maxCharacter = lineContent.length - 1;
|
||||
if (pos.character > maxCharacter) {
|
||||
setPositionAndReveal(editor, pos.line, maxCharacter);
|
||||
}
|
||||
}
|
||||
|
||||
public hasInput(): boolean {
|
||||
return this._currentInput.length > 0;
|
||||
}
|
||||
|
||||
public clearInput(): void {
|
||||
this._currentInput = '';
|
||||
}
|
||||
|
||||
public getMode(): Mode {
|
||||
return this._currentMode;
|
||||
}
|
||||
|
||||
public setMode(newMode: Mode): void {
|
||||
if (newMode !== this._currentMode) {
|
||||
this._currentMode = newMode;
|
||||
this._motionState.cursorDesiredCharacter = -1; // uninitialized
|
||||
this._currentInput = '';
|
||||
}
|
||||
}
|
||||
|
||||
public setVisual(newVisual: boolean): void {
|
||||
if (this._isVisual !== newVisual) {
|
||||
this._isVisual = newVisual;
|
||||
}
|
||||
}
|
||||
|
||||
public getVisual(): boolean {
|
||||
return this._isVisual;
|
||||
}
|
||||
|
||||
public getCursorStyle(): TextEditorCursorStyle {
|
||||
if (this._currentMode === Mode.NORMAL) {
|
||||
if (/^([1-9]\d*)?(r|c)/.test(this._currentInput)) {
|
||||
return TextEditorCursorStyle.Underline;
|
||||
}
|
||||
return TextEditorCursorStyle.Block;
|
||||
}
|
||||
if (this._currentMode === Mode.REPLACE) {
|
||||
return TextEditorCursorStyle.Underline;
|
||||
}
|
||||
return TextEditorCursorStyle.Line;
|
||||
}
|
||||
|
||||
private _getModeLabel(): string {
|
||||
if (this._currentMode === Mode.NORMAL) {
|
||||
if (this._isVisual) {
|
||||
return '-- VISUAL --';
|
||||
}
|
||||
return '-- NORMAL --';
|
||||
}
|
||||
|
||||
if (this._currentMode === Mode.REPLACE) {
|
||||
if (this._isVisual) {
|
||||
return '-- (replace) VISUAL --';
|
||||
}
|
||||
return '-- REPLACE --';
|
||||
}
|
||||
|
||||
if (this._isVisual) {
|
||||
return '-- (insert) VISUAL --';
|
||||
}
|
||||
return '-- INSERT --';
|
||||
}
|
||||
|
||||
public getStatusText(): string {
|
||||
const label = this._getModeLabel();
|
||||
return `VIM:> ${label}` + (this._currentInput ? ` >${this._currentInput}` : ``);
|
||||
}
|
||||
|
||||
private _isInComposition = false;
|
||||
private _composingText = '';
|
||||
|
||||
public compositionStart(editor: TextEditor): void {
|
||||
this._isInComposition = true;
|
||||
this._composingText = '';
|
||||
}
|
||||
|
||||
public compositionEnd(editor: TextEditor): Thenable<ITypeResult> {
|
||||
this._isInComposition = false;
|
||||
const text = this._composingText;
|
||||
this._composingText = '';
|
||||
|
||||
if (text.length === 0) {
|
||||
return Promise.resolve({
|
||||
hasConsumedInput: true,
|
||||
executeEditorCommand: null
|
||||
});
|
||||
}
|
||||
|
||||
return this.type(editor, text, {});
|
||||
}
|
||||
|
||||
public type(editor: TextEditor, text: string, modifierKeys: ModifierKeys): Thenable<ITypeResult> {
|
||||
if (this._currentMode !== Mode.NORMAL && this._currentMode !== Mode.REPLACE) {
|
||||
return Promise.resolve({
|
||||
hasConsumedInput: false,
|
||||
executeEditorCommand: null
|
||||
});
|
||||
}
|
||||
|
||||
if (this._isInComposition) {
|
||||
this._composingText += text;
|
||||
return Promise.resolve({
|
||||
hasConsumedInput: true,
|
||||
executeEditorCommand: null
|
||||
});
|
||||
}
|
||||
|
||||
if (this._currentMode === Mode.REPLACE) {
|
||||
const pos = editor.selection.active;
|
||||
editor.edit((builder) => {
|
||||
builder.replace(new Range(pos.line, pos.character, pos.line, pos.character + 1), text);
|
||||
}).then(() => {
|
||||
setPositionAndReveal(editor, pos.line, pos.character + 1);
|
||||
});
|
||||
|
||||
return Promise.resolve({
|
||||
hasConsumedInput: true,
|
||||
executeEditorCommand: null
|
||||
});
|
||||
}
|
||||
this._currentInput += text;
|
||||
return this._interpretNormalModeInput(editor, modifierKeys);
|
||||
}
|
||||
|
||||
public replacePrevChar(editor: TextEditor, text: string, replaceCharCnt: number): boolean {
|
||||
if (this._currentMode !== Mode.NORMAL && this._currentMode !== Mode.REPLACE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this._isInComposition) {
|
||||
this._composingText = this._composingText.substr(0, this._composingText.length - replaceCharCnt) + text;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this._currentMode === Mode.REPLACE) {
|
||||
const pos = editor.selection.active;
|
||||
editor.edit((builder) => {
|
||||
builder.replace(new Range(pos.line, pos.character - replaceCharCnt, pos.line, pos.character), text);
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private _interpretNormalModeInput(editor: TextEditor, modifierKeys: ModifierKeys): Thenable<ITypeResult> {
|
||||
if (this._currentInput.startsWith(':')) {
|
||||
return window.showInputBox({ value: 'tabm' }).then((value) => {
|
||||
return this._findMapping(value || '', editor, modifierKeys);
|
||||
});
|
||||
}
|
||||
const result = this._findMapping(this._currentInput, editor, modifierKeys);
|
||||
return Promise.resolve(result);
|
||||
}
|
||||
|
||||
private _findMapping(input: string, editor: TextEditor, modifierKeys: ModifierKeys): ITypeResult {
|
||||
const command = Mappings.findCommand(input, modifierKeys);
|
||||
if (command) {
|
||||
this._currentInput = '';
|
||||
return {
|
||||
hasConsumedInput: true,
|
||||
executeEditorCommand: command
|
||||
};
|
||||
}
|
||||
|
||||
const operator = Mappings.findOperator(input, modifierKeys);
|
||||
if (operator) {
|
||||
if (this._isVisual) {
|
||||
if (operator.runVisual(this, editor)) {
|
||||
this._currentInput = '';
|
||||
}
|
||||
} else {
|
||||
// Mode.NORMAL
|
||||
if (operator.runNormal(this, editor)) {
|
||||
this._currentInput = '';
|
||||
}
|
||||
}
|
||||
return {
|
||||
hasConsumedInput: true,
|
||||
executeEditorCommand: null
|
||||
};
|
||||
}
|
||||
|
||||
const motionCommand = Mappings.findMotionCommand(input, this._isVisual, modifierKeys);
|
||||
if (motionCommand) {
|
||||
this._currentInput = '';
|
||||
return {
|
||||
hasConsumedInput: true,
|
||||
executeEditorCommand: motionCommand
|
||||
};
|
||||
}
|
||||
|
||||
const motion = Mappings.findMotion(input);
|
||||
if (motion) {
|
||||
const newPos = motion.run(editor.document, editor.selection.active, this._motionState);
|
||||
if (this._isVisual) {
|
||||
setSelectionAndReveal(editor, this._motionState.anchor, newPos.line, newPos.character);
|
||||
} else {
|
||||
// Mode.NORMAL
|
||||
setPositionAndReveal(editor, newPos.line, newPos.character);
|
||||
}
|
||||
this._currentInput = '';
|
||||
return {
|
||||
hasConsumedInput: true,
|
||||
executeEditorCommand: null
|
||||
};
|
||||
}
|
||||
|
||||
// is it motion building
|
||||
if (this.isMotionPrefix(input)) {
|
||||
return {
|
||||
hasConsumedInput: true,
|
||||
executeEditorCommand: null
|
||||
};
|
||||
}
|
||||
|
||||
// INVALID INPUT - beep!!
|
||||
this._currentInput = '';
|
||||
return {
|
||||
hasConsumedInput: true,
|
||||
executeEditorCommand: null
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function setSelectionAndReveal(editor: TextEditor, anchor: Position, line: number, char: number): void {
|
||||
editor.selection = new Selection(anchor, new Position(line, char));
|
||||
revealPosition(editor, line, char);
|
||||
}
|
||||
|
||||
function setPositionAndReveal(editor: TextEditor, line: number, char: number): void {
|
||||
editor.selection = new Selection(new Position(line, char), new Position(line, char));
|
||||
revealPosition(editor, line, char);
|
||||
}
|
||||
|
||||
function revealPosition(editor: TextEditor, line: number, char: number): void {
|
||||
editor.revealRange(new Range(line, char, line, char), TextEditorRevealType.Default);
|
||||
}
|
||||
|
||||
@ -13,7 +13,7 @@ export function activate(context: vscode.ExtensionContext) {
|
||||
context.subscriptions.push(vscode.commands.registerCommand(commandId, run));
|
||||
}
|
||||
function registerCtrlKeyBinding(key: string): void {
|
||||
registerCommandNice(key, function (args) {
|
||||
registerCommandNice(key, function(args) {
|
||||
if (!vscode.window.activeTextEditor) {
|
||||
return;
|
||||
}
|
||||
@ -23,34 +23,34 @@ export function activate(context: vscode.ExtensionContext) {
|
||||
|
||||
const vimExt = new VimExt();
|
||||
|
||||
registerCommandNice('type', function (args) {
|
||||
registerCommandNice('type', function(args) {
|
||||
if (!vscode.window.activeTextEditor) {
|
||||
return;
|
||||
}
|
||||
vimExt.type(args.text);
|
||||
});
|
||||
registerCommandNice('replacePreviousChar', function (args) {
|
||||
registerCommandNice('replacePreviousChar', function(args) {
|
||||
if (!vscode.window.activeTextEditor) {
|
||||
return;
|
||||
}
|
||||
vimExt.replacePrevChar(args.text, args.replaceCharCnt);
|
||||
});
|
||||
registerCommandNice('compositionStart', function (args) {
|
||||
registerCommandNice('compositionStart', function(args) {
|
||||
if (!vscode.window.activeTextEditor) {
|
||||
return;
|
||||
}
|
||||
vimExt.compositionStart();
|
||||
});
|
||||
registerCommandNice('compositionEnd', function (args) {
|
||||
registerCommandNice('compositionEnd', function(args) {
|
||||
if (!vscode.window.activeTextEditor) {
|
||||
return;
|
||||
}
|
||||
vimExt.compositionEnd();
|
||||
});
|
||||
registerCommandNice('vim.goToNormalMode', function (args) {
|
||||
registerCommandNice('vim.goToNormalMode', function(args) {
|
||||
vimExt.goToNormalMode();
|
||||
});
|
||||
registerCommandNice('vim.clearInput', function (args) {
|
||||
registerCommandNice('vim.clearInput', function(args) {
|
||||
vimExt.clearInput();
|
||||
});
|
||||
// registerCommandNice('paste', function(args) {
|
||||
|
||||
@ -1,232 +1,232 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { TextEditor } from 'vscode';
|
||||
import { Motion, Motions } from './motions';
|
||||
import { Operator, Operators } from './operators';
|
||||
import { IController, Command, AbstractCommandDescriptor, ModifierKeys } from './common';
|
||||
|
||||
const CHAR_TO_BINDING: { [char: string]: any; } = {};
|
||||
function defineBinding(char: string, value: any, modifierKeys: ModifierKeys): void {
|
||||
const key = modifierKeys.ctrl ? 'CTRL + ' + char : char;
|
||||
CHAR_TO_BINDING[key] = value;
|
||||
}
|
||||
function getBinding(char: string, modifierKeys: ModifierKeys): any {
|
||||
const key = modifierKeys.ctrl ? 'CTRL + ' + char : char;
|
||||
return CHAR_TO_BINDING[key];
|
||||
}
|
||||
|
||||
function defineOperator(char: string, operator: Operator, modifierKeys: ModifierKeys = {}): void {
|
||||
defineBinding(char + '__operator__', operator, modifierKeys);
|
||||
}
|
||||
function getOperator(char: string, modifierKeys: ModifierKeys = {}): Operator {
|
||||
return getBinding(char + '__operator__', modifierKeys);
|
||||
}
|
||||
|
||||
function defineCommand(char: string, commandId: string, modifierKeys: ModifierKeys = {}): void {
|
||||
defineBinding(char + '__command__', { commandId: commandId }, modifierKeys);
|
||||
}
|
||||
function getCommand(char: string, modifierKeys: ModifierKeys = {}): Command {
|
||||
return getBinding(char + '__command__', modifierKeys);
|
||||
}
|
||||
|
||||
function defineMotion(char: string, motion: Motion, modifierKeys: ModifierKeys = {}): void {
|
||||
defineBinding(char + '__motion__', motion, modifierKeys);
|
||||
}
|
||||
function getMotion(char: string, modifierKeys: ModifierKeys = {}): Motion {
|
||||
return getBinding(char + '__motion__', modifierKeys);
|
||||
}
|
||||
|
||||
function defineMotionCommand(char: string, motionCommand: AbstractCommandDescriptor, modifierKeys: ModifierKeys = {}): void {
|
||||
defineBinding(char + '__motioncommand__', motionCommand, modifierKeys);
|
||||
}
|
||||
function getMotionCommand(char: string, modifierKeys: ModifierKeys = {}): AbstractCommandDescriptor {
|
||||
return getBinding(char + '__motioncommand__', modifierKeys);
|
||||
}
|
||||
|
||||
// Operators
|
||||
defineOperator('x', Operators.DeleteCharUnderCursor);
|
||||
defineOperator('i', Operators.Insert);
|
||||
defineOperator('a', Operators.Append);
|
||||
defineOperator('A', Operators.AppendEndOfLine);
|
||||
defineOperator('d', Operators.DeleteTo);
|
||||
defineOperator('p', Operators.Put);
|
||||
defineOperator('r', Operators.Replace);
|
||||
defineOperator('R', Operators.ReplaceMode);
|
||||
defineOperator('c', Operators.Change);
|
||||
defineOperator('v', Operators.Visual);
|
||||
|
||||
// Commands
|
||||
defineCommand('u', 'undo');
|
||||
defineCommand('U', 'undo');
|
||||
|
||||
// Left-right motions
|
||||
defineMotionCommand('h', Motions.Left);
|
||||
defineMotionCommand('l', Motions.Right);
|
||||
defineMotion('0', Motions.StartOfLine);
|
||||
defineMotion('$', Motions.EndOfLine);
|
||||
defineMotionCommand('g0', Motions.WrappedLineStart);
|
||||
defineMotionCommand('g^', Motions.WrappedLineFirstNonWhiteSpaceCharacter);
|
||||
defineMotionCommand('gm', Motions.WrappedLineColumnCenter);
|
||||
defineMotionCommand('g$', Motions.WrappedLineEnd);
|
||||
defineMotionCommand('g_', Motions.WrappedLineLastNonWhiteSpaceCharacter);
|
||||
|
||||
// Cursor scroll motions
|
||||
defineMotionCommand('zh', Motions.CursorScrollLeft);
|
||||
defineMotionCommand('zl', Motions.CursorScrollRight);
|
||||
defineMotionCommand('zH', Motions.CursorScrollLeftByHalfLine);
|
||||
defineMotionCommand('zL', Motions.CursorScrollRightByHalfLine);
|
||||
|
||||
// Up-down motions
|
||||
defineMotionCommand('j', Motions.Down);
|
||||
defineMotionCommand('k', Motions.Up);
|
||||
defineMotionCommand('gj', Motions.WrappedLineDown);
|
||||
defineMotionCommand('gk', Motions.WrappedLineUp);
|
||||
defineMotion('G', Motions.GoToLine);
|
||||
defineMotion('gg', Motions.GoToFirstLine);
|
||||
|
||||
defineMotionCommand('H', Motions.ViewPortTop);
|
||||
defineMotionCommand('M', Motions.ViewPortCenter);
|
||||
defineMotionCommand('L', Motions.ViewPortBottom);
|
||||
|
||||
// Word motions
|
||||
defineMotion('w', Motions.NextWordStart);
|
||||
defineMotion('e', Motions.NextWordEnd);
|
||||
|
||||
// Tab motions
|
||||
defineMotionCommand('tabm', Motions.MoveActiveEditor);
|
||||
defineMotionCommand('tabm<', Motions.MoveActiveEditorLeft);
|
||||
defineMotionCommand('tabm>', Motions.MoveActiveEditorRight);
|
||||
defineMotionCommand('tabm<<', Motions.MoveActiveEditorFirst);
|
||||
defineMotionCommand('tabm>>', Motions.MoveActiveEditorLast);
|
||||
defineMotionCommand('tabm.', Motions.MoveActiveEditorCenter);
|
||||
|
||||
// Scroll motions
|
||||
defineMotionCommand('e', Motions.ScrollDownByLine, { ctrl: true });
|
||||
defineMotionCommand('d', Motions.ScrollDownByHalfPage, { ctrl: true });
|
||||
defineMotionCommand('f', Motions.ScrollDownByPage, { ctrl: true });
|
||||
defineMotionCommand('y', Motions.ScrollUpByLine, { ctrl: true });
|
||||
defineMotionCommand('u', Motions.ScrollUpByHalfPage, { ctrl: true });
|
||||
defineMotionCommand('b', Motions.ScrollUpByPage, { ctrl: true });
|
||||
|
||||
defineMotionCommand('zt', Motions.RevealCurrentLineAtTop);
|
||||
defineMotionCommand('zz', Motions.RevealCurrentLineAtCenter);
|
||||
defineMotionCommand('zb', Motions.RevealCurrentLineAtBottom);
|
||||
|
||||
// Folding
|
||||
defineMotionCommand('zc', Motions.FoldUnder);
|
||||
defineMotionCommand('zo', Motions.UnfoldUnder);
|
||||
|
||||
|
||||
export interface IFoundOperator {
|
||||
runNormal(controller: IController, editor: TextEditor): boolean;
|
||||
runVisual(controller: IController, editor: TextEditor): boolean;
|
||||
}
|
||||
|
||||
export class Mappings {
|
||||
|
||||
public static findMotion(input: string): Motion {
|
||||
const parsed = _parseNumberAndString(input);
|
||||
let motion = getMotion(parsed.input.substr(0, 1));
|
||||
if (!motion) {
|
||||
motion = getMotion(parsed.input.substr(0, 2));
|
||||
if (!motion) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return motion.repeat(parsed.hasRepeatCount, parsed.repeatCount);
|
||||
}
|
||||
|
||||
public static findMotionCommand(input: string, isVisual: boolean, modifierKeys: ModifierKeys): Command {
|
||||
let parsed = _parseNumberAndString(input);
|
||||
let command = Mappings.findMotionCommandFromNumberAndString(parsed, isVisual, modifierKeys);
|
||||
if (!command) {
|
||||
parsed = _parseNumberAndString(input, false);
|
||||
command = Mappings.findMotionCommandFromNumberAndString(parsed, isVisual, modifierKeys);
|
||||
}
|
||||
return command;
|
||||
}
|
||||
|
||||
private static findMotionCommandFromNumberAndString(numberAndString: INumberAndString, isVisual: boolean, modifierKeys: ModifierKeys): Command {
|
||||
let motionCommand = getMotionCommand(numberAndString.input.substr(0, 1), modifierKeys);
|
||||
if (!motionCommand) {
|
||||
motionCommand = getMotionCommand(numberAndString.input.substr(0, 2), modifierKeys);
|
||||
}
|
||||
if (!motionCommand) {
|
||||
motionCommand = getMotionCommand(numberAndString.input.substr(1, 2), modifierKeys);
|
||||
}
|
||||
if (!motionCommand) {
|
||||
motionCommand = getMotionCommand(numberAndString.input.substr(1, 3), modifierKeys);
|
||||
}
|
||||
if (!motionCommand) {
|
||||
motionCommand = getMotionCommand(numberAndString.input, modifierKeys);
|
||||
}
|
||||
return motionCommand ? motionCommand.createCommand({ isVisual: isVisual, repeat: numberAndString.hasRepeatCount ? numberAndString.repeatCount : undefined }) : null;
|
||||
}
|
||||
|
||||
public static findOperator(input: string, modifierKeys: ModifierKeys): IFoundOperator {
|
||||
const parsed = _parseNumberAndString(input);
|
||||
const operator = getOperator(parsed.input.substr(0, 1), modifierKeys);
|
||||
if (!operator) {
|
||||
return null;
|
||||
}
|
||||
const operatorArgs = parsed.input.substr(1);
|
||||
return {
|
||||
runNormal: (controller: IController, editor: TextEditor) => {
|
||||
return operator.runNormalMode(controller, editor, parsed.repeatCount, operatorArgs);
|
||||
},
|
||||
runVisual: (controller: IController, editor: TextEditor) => {
|
||||
return operator.runVisualMode(controller, editor, operatorArgs);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static findCommand(input: string, modifierKeys: ModifierKeys): Command {
|
||||
return getCommand(input, modifierKeys) || null;
|
||||
}
|
||||
|
||||
public static isMotionPrefix(input: string): boolean {
|
||||
if (input.length === 0) {
|
||||
return true;
|
||||
}
|
||||
if (input === 'g' || input === 'v' || input === 'z') {
|
||||
return true;
|
||||
}
|
||||
return /^[1-9]\d*v?g?z?$/.test(input);
|
||||
}
|
||||
}
|
||||
|
||||
function _parseNumberAndString(input: string, numberAtBeginning = true): INumberAndString {
|
||||
if (numberAtBeginning) {
|
||||
const repeatCountMatch = input.match(/^([1-9]\d*)/);
|
||||
if (repeatCountMatch) {
|
||||
return {
|
||||
hasRepeatCount: true,
|
||||
repeatCount: parseInt(repeatCountMatch[0], 10),
|
||||
input: input.substr(repeatCountMatch[0].length)
|
||||
};
|
||||
}
|
||||
} else {
|
||||
const repeatCountMatch = input.match(/(\d+)$/);
|
||||
if (repeatCountMatch) {
|
||||
return {
|
||||
hasRepeatCount: true,
|
||||
repeatCount: parseInt(repeatCountMatch[1], 10),
|
||||
input: input.substr(0, input.length - repeatCountMatch[1].length)
|
||||
};
|
||||
}
|
||||
}
|
||||
return {
|
||||
hasRepeatCount: false,
|
||||
repeatCount: 1,
|
||||
input: input
|
||||
};
|
||||
}
|
||||
|
||||
interface INumberAndString {
|
||||
hasRepeatCount: boolean;
|
||||
repeatCount: number;
|
||||
input: string;
|
||||
}
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { TextEditor } from 'vscode';
|
||||
import { Motion, Motions } from './motions';
|
||||
import { Operator, Operators } from './operators';
|
||||
import { IController, Command, AbstractCommandDescriptor, ModifierKeys } from './common';
|
||||
|
||||
const CHAR_TO_BINDING: { [char: string]: any; } = {};
|
||||
function defineBinding(char: string, value: any, modifierKeys: ModifierKeys): void {
|
||||
const key = modifierKeys.ctrl ? 'CTRL + ' + char : char;
|
||||
CHAR_TO_BINDING[key] = value;
|
||||
}
|
||||
function getBinding(char: string, modifierKeys: ModifierKeys): any {
|
||||
const key = modifierKeys.ctrl ? 'CTRL + ' + char : char;
|
||||
return CHAR_TO_BINDING[key];
|
||||
}
|
||||
|
||||
function defineOperator(char: string, operator: Operator, modifierKeys: ModifierKeys = {}): void {
|
||||
defineBinding(char + '__operator__', operator, modifierKeys);
|
||||
}
|
||||
function getOperator(char: string, modifierKeys: ModifierKeys = {}): Operator {
|
||||
return getBinding(char + '__operator__', modifierKeys);
|
||||
}
|
||||
|
||||
function defineCommand(char: string, commandId: string, modifierKeys: ModifierKeys = {}): void {
|
||||
defineBinding(char + '__command__', { commandId: commandId }, modifierKeys);
|
||||
}
|
||||
function getCommand(char: string, modifierKeys: ModifierKeys = {}): Command {
|
||||
return getBinding(char + '__command__', modifierKeys);
|
||||
}
|
||||
|
||||
function defineMotion(char: string, motion: Motion, modifierKeys: ModifierKeys = {}): void {
|
||||
defineBinding(char + '__motion__', motion, modifierKeys);
|
||||
}
|
||||
function getMotion(char: string, modifierKeys: ModifierKeys = {}): Motion {
|
||||
return getBinding(char + '__motion__', modifierKeys);
|
||||
}
|
||||
|
||||
function defineMotionCommand(char: string, motionCommand: AbstractCommandDescriptor, modifierKeys: ModifierKeys = {}): void {
|
||||
defineBinding(char + '__motioncommand__', motionCommand, modifierKeys);
|
||||
}
|
||||
function getMotionCommand(char: string, modifierKeys: ModifierKeys = {}): AbstractCommandDescriptor {
|
||||
return getBinding(char + '__motioncommand__', modifierKeys);
|
||||
}
|
||||
|
||||
// Operators
|
||||
defineOperator('x', Operators.DeleteCharUnderCursor);
|
||||
defineOperator('i', Operators.Insert);
|
||||
defineOperator('a', Operators.Append);
|
||||
defineOperator('A', Operators.AppendEndOfLine);
|
||||
defineOperator('d', Operators.DeleteTo);
|
||||
defineOperator('p', Operators.Put);
|
||||
defineOperator('r', Operators.Replace);
|
||||
defineOperator('R', Operators.ReplaceMode);
|
||||
defineOperator('c', Operators.Change);
|
||||
defineOperator('v', Operators.Visual);
|
||||
|
||||
// Commands
|
||||
defineCommand('u', 'undo');
|
||||
defineCommand('U', 'undo');
|
||||
|
||||
// Left-right motions
|
||||
defineMotionCommand('h', Motions.Left);
|
||||
defineMotionCommand('l', Motions.Right);
|
||||
defineMotion('0', Motions.StartOfLine);
|
||||
defineMotion('$', Motions.EndOfLine);
|
||||
defineMotionCommand('g0', Motions.WrappedLineStart);
|
||||
defineMotionCommand('g^', Motions.WrappedLineFirstNonWhiteSpaceCharacter);
|
||||
defineMotionCommand('gm', Motions.WrappedLineColumnCenter);
|
||||
defineMotionCommand('g$', Motions.WrappedLineEnd);
|
||||
defineMotionCommand('g_', Motions.WrappedLineLastNonWhiteSpaceCharacter);
|
||||
|
||||
// Cursor scroll motions
|
||||
defineMotionCommand('zh', Motions.CursorScrollLeft);
|
||||
defineMotionCommand('zl', Motions.CursorScrollRight);
|
||||
defineMotionCommand('zH', Motions.CursorScrollLeftByHalfLine);
|
||||
defineMotionCommand('zL', Motions.CursorScrollRightByHalfLine);
|
||||
|
||||
// Up-down motions
|
||||
defineMotionCommand('j', Motions.Down);
|
||||
defineMotionCommand('k', Motions.Up);
|
||||
defineMotionCommand('gj', Motions.WrappedLineDown);
|
||||
defineMotionCommand('gk', Motions.WrappedLineUp);
|
||||
defineMotion('G', Motions.GoToLine);
|
||||
defineMotion('gg', Motions.GoToFirstLine);
|
||||
|
||||
defineMotionCommand('H', Motions.ViewPortTop);
|
||||
defineMotionCommand('M', Motions.ViewPortCenter);
|
||||
defineMotionCommand('L', Motions.ViewPortBottom);
|
||||
|
||||
// Word motions
|
||||
defineMotion('w', Motions.NextWordStart);
|
||||
defineMotion('e', Motions.NextWordEnd);
|
||||
|
||||
// Tab motions
|
||||
defineMotionCommand('tabm', Motions.MoveActiveEditor);
|
||||
defineMotionCommand('tabm<', Motions.MoveActiveEditorLeft);
|
||||
defineMotionCommand('tabm>', Motions.MoveActiveEditorRight);
|
||||
defineMotionCommand('tabm<<', Motions.MoveActiveEditorFirst);
|
||||
defineMotionCommand('tabm>>', Motions.MoveActiveEditorLast);
|
||||
defineMotionCommand('tabm.', Motions.MoveActiveEditorCenter);
|
||||
|
||||
// Scroll motions
|
||||
defineMotionCommand('e', Motions.ScrollDownByLine, { ctrl: true });
|
||||
defineMotionCommand('d', Motions.ScrollDownByHalfPage, { ctrl: true });
|
||||
defineMotionCommand('f', Motions.ScrollDownByPage, { ctrl: true });
|
||||
defineMotionCommand('y', Motions.ScrollUpByLine, { ctrl: true });
|
||||
defineMotionCommand('u', Motions.ScrollUpByHalfPage, { ctrl: true });
|
||||
defineMotionCommand('b', Motions.ScrollUpByPage, { ctrl: true });
|
||||
|
||||
defineMotionCommand('zt', Motions.RevealCurrentLineAtTop);
|
||||
defineMotionCommand('zz', Motions.RevealCurrentLineAtCenter);
|
||||
defineMotionCommand('zb', Motions.RevealCurrentLineAtBottom);
|
||||
|
||||
// Folding
|
||||
defineMotionCommand('zc', Motions.FoldUnder);
|
||||
defineMotionCommand('zo', Motions.UnfoldUnder);
|
||||
|
||||
|
||||
export interface IFoundOperator {
|
||||
runNormal(controller: IController, editor: TextEditor): boolean;
|
||||
runVisual(controller: IController, editor: TextEditor): boolean;
|
||||
}
|
||||
|
||||
export class Mappings {
|
||||
|
||||
public static findMotion(input: string): Motion {
|
||||
const parsed = _parseNumberAndString(input);
|
||||
let motion = getMotion(parsed.input.substr(0, 1));
|
||||
if (!motion) {
|
||||
motion = getMotion(parsed.input.substr(0, 2));
|
||||
if (!motion) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return motion.repeat(parsed.hasRepeatCount, parsed.repeatCount);
|
||||
}
|
||||
|
||||
public static findMotionCommand(input: string, isVisual: boolean, modifierKeys: ModifierKeys): Command {
|
||||
let parsed = _parseNumberAndString(input);
|
||||
let command = Mappings.findMotionCommandFromNumberAndString(parsed, isVisual, modifierKeys);
|
||||
if (!command) {
|
||||
parsed = _parseNumberAndString(input, false);
|
||||
command = Mappings.findMotionCommandFromNumberAndString(parsed, isVisual, modifierKeys);
|
||||
}
|
||||
return command;
|
||||
}
|
||||
|
||||
private static findMotionCommandFromNumberAndString(numberAndString: INumberAndString, isVisual: boolean, modifierKeys: ModifierKeys): Command {
|
||||
let motionCommand = getMotionCommand(numberAndString.input.substr(0, 1), modifierKeys);
|
||||
if (!motionCommand) {
|
||||
motionCommand = getMotionCommand(numberAndString.input.substr(0, 2), modifierKeys);
|
||||
}
|
||||
if (!motionCommand) {
|
||||
motionCommand = getMotionCommand(numberAndString.input.substr(1, 2), modifierKeys);
|
||||
}
|
||||
if (!motionCommand) {
|
||||
motionCommand = getMotionCommand(numberAndString.input.substr(1, 3), modifierKeys);
|
||||
}
|
||||
if (!motionCommand) {
|
||||
motionCommand = getMotionCommand(numberAndString.input, modifierKeys);
|
||||
}
|
||||
return motionCommand ? motionCommand.createCommand({ isVisual: isVisual, repeat: numberAndString.hasRepeatCount ? numberAndString.repeatCount : undefined }) : null;
|
||||
}
|
||||
|
||||
public static findOperator(input: string, modifierKeys: ModifierKeys): IFoundOperator {
|
||||
const parsed = _parseNumberAndString(input);
|
||||
const operator = getOperator(parsed.input.substr(0, 1), modifierKeys);
|
||||
if (!operator) {
|
||||
return null;
|
||||
}
|
||||
const operatorArgs = parsed.input.substr(1);
|
||||
return {
|
||||
runNormal: (controller: IController, editor: TextEditor) => {
|
||||
return operator.runNormalMode(controller, editor, parsed.repeatCount, operatorArgs);
|
||||
},
|
||||
runVisual: (controller: IController, editor: TextEditor) => {
|
||||
return operator.runVisualMode(controller, editor, operatorArgs);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static findCommand(input: string, modifierKeys: ModifierKeys): Command {
|
||||
return getCommand(input, modifierKeys) || null;
|
||||
}
|
||||
|
||||
public static isMotionPrefix(input: string): boolean {
|
||||
if (input.length === 0) {
|
||||
return true;
|
||||
}
|
||||
if (input === 'g' || input === 'v' || input === 'z') {
|
||||
return true;
|
||||
}
|
||||
return /^[1-9]\d*v?g?z?$/.test(input);
|
||||
}
|
||||
}
|
||||
|
||||
function _parseNumberAndString(input: string, numberAtBeginning = true): INumberAndString {
|
||||
if (numberAtBeginning) {
|
||||
const repeatCountMatch = input.match(/^([1-9]\d*)/);
|
||||
if (repeatCountMatch) {
|
||||
return {
|
||||
hasRepeatCount: true,
|
||||
repeatCount: parseInt(repeatCountMatch[0], 10),
|
||||
input: input.substr(repeatCountMatch[0].length)
|
||||
};
|
||||
}
|
||||
} else {
|
||||
const repeatCountMatch = input.match(/(\d+)$/);
|
||||
if (repeatCountMatch) {
|
||||
return {
|
||||
hasRepeatCount: true,
|
||||
repeatCount: parseInt(repeatCountMatch[1], 10),
|
||||
input: input.substr(0, input.length - repeatCountMatch[1].length)
|
||||
};
|
||||
}
|
||||
}
|
||||
return {
|
||||
hasRepeatCount: false,
|
||||
repeatCount: 1,
|
||||
input: input
|
||||
};
|
||||
}
|
||||
|
||||
interface INumberAndString {
|
||||
hasRepeatCount: boolean;
|
||||
repeatCount: number;
|
||||
input: string;
|
||||
}
|
||||
|
||||
@ -1,432 +1,432 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Position, TextDocument, window } from 'vscode';
|
||||
import { Words, WordCharacters } from './words';
|
||||
import { Command, AbstractCommandDescriptor } from './common';
|
||||
|
||||
export class MotionState {
|
||||
|
||||
public anchor: Position | null;
|
||||
public cursorDesiredCharacter: number;
|
||||
public wordCharacterClass: WordCharacters | null;
|
||||
|
||||
constructor() {
|
||||
this.cursorDesiredCharacter = -1;
|
||||
this.wordCharacterClass = null;
|
||||
this.anchor = null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export abstract class Motion {
|
||||
public abstract run(doc: TextDocument, pos: Position, state: MotionState): Position;
|
||||
|
||||
public repeat(hasRepeatCount: boolean, count: number): Motion {
|
||||
if (!hasRepeatCount) {
|
||||
return this;
|
||||
}
|
||||
return new RepeatingMotion(this, count);
|
||||
}
|
||||
}
|
||||
|
||||
class RepeatingMotion extends Motion {
|
||||
|
||||
private _actual: Motion;
|
||||
private _repeatCount: number;
|
||||
|
||||
constructor(actual: Motion, repeatCount: number) {
|
||||
super();
|
||||
this._actual = actual;
|
||||
this._repeatCount = repeatCount;
|
||||
}
|
||||
|
||||
public run(doc: TextDocument, pos: Position, state: MotionState): Position {
|
||||
for (let cnt = 0; cnt < this._repeatCount; cnt++) {
|
||||
pos = this._actual.run(doc, pos, state);
|
||||
}
|
||||
return pos;
|
||||
}
|
||||
}
|
||||
|
||||
class NextCharacterMotion extends Motion {
|
||||
public run(doc: TextDocument, pos: Position, state: MotionState): Position {
|
||||
if (pos.character === doc.lineAt(pos.line).text.length) {
|
||||
// on last character
|
||||
return ((pos.line + 1 < doc.lineCount) ? new Position(pos.line + 1, 0) : pos);
|
||||
}
|
||||
|
||||
return new Position(pos.line, pos.character + 1);
|
||||
}
|
||||
}
|
||||
|
||||
class LeftMotion extends Motion {
|
||||
public run(doc: TextDocument, pos: Position, state: MotionState): Position {
|
||||
const line = pos.line;
|
||||
|
||||
if (pos.character > 0) {
|
||||
state.cursorDesiredCharacter = pos.character - 1;
|
||||
return new Position(line, state.cursorDesiredCharacter);
|
||||
}
|
||||
|
||||
return pos;
|
||||
}
|
||||
}
|
||||
|
||||
class DownMotion extends Motion {
|
||||
public run(doc: TextDocument, pos: Position, state: MotionState): Position {
|
||||
let line = pos.line;
|
||||
|
||||
state.cursorDesiredCharacter = (state.cursorDesiredCharacter === -1 ? pos.character : state.cursorDesiredCharacter);
|
||||
|
||||
if (line < doc.lineCount - 1) {
|
||||
line++;
|
||||
return new Position(line, Math.min(state.cursorDesiredCharacter, doc.lineAt(line).text.length));
|
||||
}
|
||||
|
||||
return pos;
|
||||
}
|
||||
}
|
||||
|
||||
class UpMotion extends Motion {
|
||||
public run(doc: TextDocument, pos: Position, state: MotionState): Position {
|
||||
let line = pos.line;
|
||||
|
||||
state.cursorDesiredCharacter = (state.cursorDesiredCharacter === -1 ? pos.character : state.cursorDesiredCharacter);
|
||||
|
||||
if (line > 0) {
|
||||
line--;
|
||||
return new Position(line, Math.min(state.cursorDesiredCharacter, doc.lineAt(line).text.length));
|
||||
}
|
||||
|
||||
return pos;
|
||||
}
|
||||
}
|
||||
|
||||
class RightMotion extends Motion {
|
||||
public run(doc: TextDocument, pos: Position, state: MotionState): Position {
|
||||
const line = pos.line;
|
||||
const maxCharacter = doc.lineAt(line).text.length;
|
||||
|
||||
if (pos.character < maxCharacter) {
|
||||
state.cursorDesiredCharacter = pos.character + 1;
|
||||
return new Position(line, state.cursorDesiredCharacter);
|
||||
}
|
||||
|
||||
return pos;
|
||||
}
|
||||
}
|
||||
|
||||
class EndOfLineMotion extends Motion {
|
||||
public run(doc: TextDocument, pos: Position, state: MotionState): Position {
|
||||
return new Position(pos.line, doc.lineAt(pos.line).text.length);
|
||||
}
|
||||
}
|
||||
|
||||
class StartOfLineMotion extends Motion {
|
||||
public run(doc: TextDocument, pos: Position, state: MotionState): Position {
|
||||
return new Position(pos.line, 0);
|
||||
}
|
||||
}
|
||||
|
||||
class NextWordStartMotion extends Motion {
|
||||
public run(doc: TextDocument, pos: Position, state: MotionState): Position {
|
||||
const lineContent = doc.lineAt(pos.line).text;
|
||||
|
||||
if (pos.character >= lineContent.length - 1) {
|
||||
// cursor at end of line
|
||||
return ((pos.line + 1 < doc.lineCount) ? new Position(pos.line + 1, 0) : pos);
|
||||
}
|
||||
|
||||
const nextWord = Words.findNextWord(doc, pos, state.wordCharacterClass);
|
||||
|
||||
if (!nextWord) {
|
||||
// return end of the line
|
||||
return Motions.EndOfLine.run(doc, pos, state);
|
||||
}
|
||||
|
||||
if (nextWord.start <= pos.character && pos.character < nextWord.end) {
|
||||
// Sitting on a word
|
||||
const nextNextWord = Words.findNextWord(doc, new Position(pos.line, nextWord.end), state.wordCharacterClass);
|
||||
if (nextNextWord) {
|
||||
// return start of the next next word
|
||||
return new Position(pos.line, nextNextWord.start);
|
||||
} else {
|
||||
// return end of line
|
||||
return Motions.EndOfLine.run(doc, pos, state);
|
||||
}
|
||||
} else {
|
||||
// return start of the next word
|
||||
return new Position(pos.line, nextWord.start);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class NextWordEndMotion extends Motion {
|
||||
public run(doc: TextDocument, pos: Position, state: MotionState): Position {
|
||||
const lineContent = doc.lineAt(pos.line).text;
|
||||
|
||||
if (pos.character >= lineContent.length - 1) {
|
||||
// no content on this line or cursor at end of line
|
||||
return ((pos.line + 1 < doc.lineCount) ? new Position(pos.line + 1, 0) : pos);
|
||||
}
|
||||
|
||||
const nextWord = Words.findNextWord(doc, pos, state.wordCharacterClass);
|
||||
|
||||
if (!nextWord) {
|
||||
// return end of the line
|
||||
return Motions.EndOfLine.run(doc, pos, state);
|
||||
}
|
||||
|
||||
// return start of the next word
|
||||
return new Position(pos.line, nextWord.end);
|
||||
}
|
||||
}
|
||||
|
||||
class GoToLineUndefinedMotion extends Motion {
|
||||
public run(doc: TextDocument, pos: Position, state: MotionState): Position {
|
||||
// does not do anything
|
||||
return pos;
|
||||
}
|
||||
|
||||
public repeat(hasRepeatCount: boolean, count: number): Motion {
|
||||
if (!hasRepeatCount) {
|
||||
return Motions.GoToLastLine;
|
||||
}
|
||||
return new GoToLineDefinedMotion(count);
|
||||
}
|
||||
}
|
||||
|
||||
abstract class GoToLineMotion extends Motion {
|
||||
|
||||
protected firstNonWhitespaceChar(doc: TextDocument, line: number): number {
|
||||
const lineContent = doc.lineAt(line).text;
|
||||
let character = 0;
|
||||
while (character < lineContent.length) {
|
||||
const ch = lineContent.charAt(character);
|
||||
if (ch !== ' ' && ch !== '\t') {
|
||||
break;
|
||||
}
|
||||
character++;
|
||||
}
|
||||
return character;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class GoToFirstLineMotion extends GoToLineMotion {
|
||||
public run(doc: TextDocument, pos: Position, state: MotionState): Position {
|
||||
return new Position(0, this.firstNonWhitespaceChar(doc, 0));
|
||||
}
|
||||
}
|
||||
|
||||
class GoToLastLineMotion extends GoToLineMotion {
|
||||
public run(doc: TextDocument, pos: Position, state: MotionState): Position {
|
||||
const lastLine = doc.lineCount - 1;
|
||||
return new Position(lastLine, this.firstNonWhitespaceChar(doc, lastLine));
|
||||
}
|
||||
}
|
||||
|
||||
class GoToLineDefinedMotion extends GoToLineMotion {
|
||||
private _lineNumber: number;
|
||||
|
||||
constructor(lineNumber: number) {
|
||||
super();
|
||||
this._lineNumber = lineNumber;
|
||||
}
|
||||
|
||||
public run(doc: TextDocument, pos: Position, state: MotionState): Position {
|
||||
const line = Math.min(doc.lineCount - 1, Math.max(0, this._lineNumber - 1));
|
||||
return new Position(line, this.firstNonWhitespaceChar(doc, line));
|
||||
}
|
||||
}
|
||||
|
||||
class CursorMoveCommand extends AbstractCommandDescriptor {
|
||||
|
||||
constructor(private to: string, private by?: string) {
|
||||
super();
|
||||
}
|
||||
|
||||
public createCommand(args?: any): Command {
|
||||
const cursorMoveArgs: any = {
|
||||
to: this.to,
|
||||
by: this.by,
|
||||
value: args.repeat || 1,
|
||||
select: !!args.isVisual
|
||||
};
|
||||
return {
|
||||
commandId: 'cursorMove',
|
||||
args: cursorMoveArgs
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class EditorScrollCommand extends AbstractCommandDescriptor {
|
||||
|
||||
constructor(private to: string, private by?: string) {
|
||||
super();
|
||||
}
|
||||
|
||||
public createCommand(args?: any): Command {
|
||||
const editorScrollArgs: any = {
|
||||
to: this.to,
|
||||
by: this.by,
|
||||
value: args.repeat || 1,
|
||||
revealCursor: true
|
||||
};
|
||||
return {
|
||||
commandId: 'editorScroll',
|
||||
args: editorScrollArgs
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class RevealCurrentLineCommand extends AbstractCommandDescriptor {
|
||||
|
||||
constructor(private at: string) {
|
||||
super();
|
||||
}
|
||||
|
||||
public createCommand(args?: any): Command {
|
||||
const lineNumber = window.activeTextEditor.selection.start.line;
|
||||
const revealLineArgs: any = {
|
||||
lineNumber,
|
||||
at: this.at
|
||||
};
|
||||
return {
|
||||
commandId: 'revealLine',
|
||||
args: revealLineArgs
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class MoveActiveEditorCommandByPosition extends AbstractCommandDescriptor {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
public createCommand(args?: any): Command {
|
||||
const moveActiveEditorArgs: any = {
|
||||
to: args.repeat === void 0 ? 'last' : 'position',
|
||||
value: args.repeat !== void 0 ? args.repeat + 1 : undefined
|
||||
};
|
||||
return {
|
||||
commandId: 'moveActiveEditor',
|
||||
args: moveActiveEditorArgs
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class MoveActiveEditorCommand extends AbstractCommandDescriptor {
|
||||
|
||||
constructor(private to: string) {
|
||||
super();
|
||||
}
|
||||
|
||||
public createCommand(args?: any): Command {
|
||||
const moveActiveEditorArgs: any = {
|
||||
to: this.to,
|
||||
value: args.repeat ? args.repeat : 1
|
||||
};
|
||||
return {
|
||||
commandId: 'moveActiveEditor',
|
||||
args: moveActiveEditorArgs
|
||||
};
|
||||
}
|
||||
}
|
||||
class FoldCommand extends AbstractCommandDescriptor {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
public createCommand(args?: any): Command {
|
||||
const foldEditorArgs: any = {
|
||||
levels: args.repeat ? args.repeat : 1,
|
||||
direction: 'up'
|
||||
};
|
||||
return {
|
||||
commandId: 'editor.fold',
|
||||
args: foldEditorArgs
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class UnfoldCommand extends AbstractCommandDescriptor {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
public createCommand(args?: any): Command {
|
||||
const foldEditorArgs: any = {
|
||||
levels: args.repeat ? args.repeat : 1,
|
||||
direction: 'up'
|
||||
};
|
||||
return {
|
||||
commandId: 'editor.unfold',
|
||||
args: foldEditorArgs
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export const Motions = {
|
||||
RightMotion: new RightMotion(),
|
||||
|
||||
NextCharacter: new NextCharacterMotion(),
|
||||
|
||||
Left: new CursorMoveCommand('left'),
|
||||
Right: new CursorMoveCommand('right'),
|
||||
Down: new CursorMoveCommand('down'),
|
||||
Up: new CursorMoveCommand('up'),
|
||||
|
||||
EndOfLine: new EndOfLineMotion(),
|
||||
StartOfLine: new StartOfLineMotion(),
|
||||
NextWordStart: new NextWordStartMotion(),
|
||||
NextWordEnd: new NextWordEndMotion(),
|
||||
GoToLine: new GoToLineUndefinedMotion(),
|
||||
GoToFirstLine: new GoToFirstLineMotion(),
|
||||
GoToLastLine: new GoToLastLineMotion(),
|
||||
|
||||
CursorScrollLeft: new CursorMoveCommand('left'),
|
||||
CursorScrollRight: new CursorMoveCommand('right'),
|
||||
CursorScrollLeftByHalfLine: new CursorMoveCommand('left', 'halfLine'),
|
||||
CursorScrollRightByHalfLine: new CursorMoveCommand('right', 'halfLine'),
|
||||
|
||||
WrappedLineUp: new CursorMoveCommand('up', 'wrappedLine'),
|
||||
WrappedLineDown: new CursorMoveCommand('down', 'wrappedLine'),
|
||||
|
||||
WrappedLineStart: new CursorMoveCommand('wrappedLineStart'),
|
||||
WrappedLineFirstNonWhiteSpaceCharacter: new CursorMoveCommand('wrappedLineFirstNonWhitespaceCharacter'),
|
||||
WrappedLineColumnCenter: new CursorMoveCommand('wrappedLineColumnCenter'),
|
||||
WrappedLineEnd: new CursorMoveCommand('wrappedLineEnd'),
|
||||
WrappedLineLastNonWhiteSpaceCharacter: new CursorMoveCommand('wrappedLineLastNonWhitespaceCharacter'),
|
||||
|
||||
ViewPortTop: new CursorMoveCommand('viewPortTop'),
|
||||
ViewPortBottom: new CursorMoveCommand('viewPortBottom'),
|
||||
ViewPortCenter: new CursorMoveCommand('viewPortCenter'),
|
||||
|
||||
MoveActiveEditor: new MoveActiveEditorCommandByPosition(),
|
||||
MoveActiveEditorLeft: new MoveActiveEditorCommand('left'),
|
||||
MoveActiveEditorRight: new MoveActiveEditorCommand('right'),
|
||||
MoveActiveEditorFirst: new MoveActiveEditorCommand('first'),
|
||||
MoveActiveEditorLast: new MoveActiveEditorCommand('last'),
|
||||
MoveActiveEditorCenter: new MoveActiveEditorCommand('center'),
|
||||
|
||||
ScrollDownByLine: new EditorScrollCommand('down', 'line'),
|
||||
ScrollDownByHalfPage: new EditorScrollCommand('down', 'halfPage'),
|
||||
ScrollDownByPage: new EditorScrollCommand('down', 'page'),
|
||||
ScrollUpByLine: new EditorScrollCommand('up', 'line'),
|
||||
ScrollUpByHalfPage: new EditorScrollCommand('up', 'halfPage'),
|
||||
ScrollUpByPage: new EditorScrollCommand('up', 'page'),
|
||||
|
||||
RevealCurrentLineAtTop: new RevealCurrentLineCommand('top'),
|
||||
RevealCurrentLineAtCenter: new RevealCurrentLineCommand('center'),
|
||||
RevealCurrentLineAtBottom: new RevealCurrentLineCommand('bottom'),
|
||||
|
||||
FoldUnder: new FoldCommand(),
|
||||
UnfoldUnder: new UnfoldCommand()
|
||||
};
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Position, TextDocument, window } from 'vscode';
|
||||
import { Words, WordCharacters } from './words';
|
||||
import { Command, AbstractCommandDescriptor } from './common';
|
||||
|
||||
export class MotionState {
|
||||
|
||||
public anchor: Position | null;
|
||||
public cursorDesiredCharacter: number;
|
||||
public wordCharacterClass: WordCharacters | null;
|
||||
|
||||
constructor() {
|
||||
this.cursorDesiredCharacter = -1;
|
||||
this.wordCharacterClass = null;
|
||||
this.anchor = null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export abstract class Motion {
|
||||
public abstract run(doc: TextDocument, pos: Position, state: MotionState): Position;
|
||||
|
||||
public repeat(hasRepeatCount: boolean, count: number): Motion {
|
||||
if (!hasRepeatCount) {
|
||||
return this;
|
||||
}
|
||||
return new RepeatingMotion(this, count);
|
||||
}
|
||||
}
|
||||
|
||||
class RepeatingMotion extends Motion {
|
||||
|
||||
private _actual: Motion;
|
||||
private _repeatCount: number;
|
||||
|
||||
constructor(actual: Motion, repeatCount: number) {
|
||||
super();
|
||||
this._actual = actual;
|
||||
this._repeatCount = repeatCount;
|
||||
}
|
||||
|
||||
public run(doc: TextDocument, pos: Position, state: MotionState): Position {
|
||||
for (let cnt = 0; cnt < this._repeatCount; cnt++) {
|
||||
pos = this._actual.run(doc, pos, state);
|
||||
}
|
||||
return pos;
|
||||
}
|
||||
}
|
||||
|
||||
class NextCharacterMotion extends Motion {
|
||||
public run(doc: TextDocument, pos: Position, state: MotionState): Position {
|
||||
if (pos.character === doc.lineAt(pos.line).text.length) {
|
||||
// on last character
|
||||
return ((pos.line + 1 < doc.lineCount) ? new Position(pos.line + 1, 0) : pos);
|
||||
}
|
||||
|
||||
return new Position(pos.line, pos.character + 1);
|
||||
}
|
||||
}
|
||||
|
||||
class LeftMotion extends Motion {
|
||||
public run(doc: TextDocument, pos: Position, state: MotionState): Position {
|
||||
const line = pos.line;
|
||||
|
||||
if (pos.character > 0) {
|
||||
state.cursorDesiredCharacter = pos.character - 1;
|
||||
return new Position(line, state.cursorDesiredCharacter);
|
||||
}
|
||||
|
||||
return pos;
|
||||
}
|
||||
}
|
||||
|
||||
class DownMotion extends Motion {
|
||||
public run(doc: TextDocument, pos: Position, state: MotionState): Position {
|
||||
let line = pos.line;
|
||||
|
||||
state.cursorDesiredCharacter = (state.cursorDesiredCharacter === -1 ? pos.character : state.cursorDesiredCharacter);
|
||||
|
||||
if (line < doc.lineCount - 1) {
|
||||
line++;
|
||||
return new Position(line, Math.min(state.cursorDesiredCharacter, doc.lineAt(line).text.length));
|
||||
}
|
||||
|
||||
return pos;
|
||||
}
|
||||
}
|
||||
|
||||
class UpMotion extends Motion {
|
||||
public run(doc: TextDocument, pos: Position, state: MotionState): Position {
|
||||
let line = pos.line;
|
||||
|
||||
state.cursorDesiredCharacter = (state.cursorDesiredCharacter === -1 ? pos.character : state.cursorDesiredCharacter);
|
||||
|
||||
if (line > 0) {
|
||||
line--;
|
||||
return new Position(line, Math.min(state.cursorDesiredCharacter, doc.lineAt(line).text.length));
|
||||
}
|
||||
|
||||
return pos;
|
||||
}
|
||||
}
|
||||
|
||||
class RightMotion extends Motion {
|
||||
public run(doc: TextDocument, pos: Position, state: MotionState): Position {
|
||||
const line = pos.line;
|
||||
const maxCharacter = doc.lineAt(line).text.length;
|
||||
|
||||
if (pos.character < maxCharacter) {
|
||||
state.cursorDesiredCharacter = pos.character + 1;
|
||||
return new Position(line, state.cursorDesiredCharacter);
|
||||
}
|
||||
|
||||
return pos;
|
||||
}
|
||||
}
|
||||
|
||||
class EndOfLineMotion extends Motion {
|
||||
public run(doc: TextDocument, pos: Position, state: MotionState): Position {
|
||||
return new Position(pos.line, doc.lineAt(pos.line).text.length);
|
||||
}
|
||||
}
|
||||
|
||||
class StartOfLineMotion extends Motion {
|
||||
public run(doc: TextDocument, pos: Position, state: MotionState): Position {
|
||||
return new Position(pos.line, 0);
|
||||
}
|
||||
}
|
||||
|
||||
class NextWordStartMotion extends Motion {
|
||||
public run(doc: TextDocument, pos: Position, state: MotionState): Position {
|
||||
const lineContent = doc.lineAt(pos.line).text;
|
||||
|
||||
if (pos.character >= lineContent.length - 1) {
|
||||
// cursor at end of line
|
||||
return ((pos.line + 1 < doc.lineCount) ? new Position(pos.line + 1, 0) : pos);
|
||||
}
|
||||
|
||||
const nextWord = Words.findNextWord(doc, pos, state.wordCharacterClass);
|
||||
|
||||
if (!nextWord) {
|
||||
// return end of the line
|
||||
return Motions.EndOfLine.run(doc, pos, state);
|
||||
}
|
||||
|
||||
if (nextWord.start <= pos.character && pos.character < nextWord.end) {
|
||||
// Sitting on a word
|
||||
const nextNextWord = Words.findNextWord(doc, new Position(pos.line, nextWord.end), state.wordCharacterClass);
|
||||
if (nextNextWord) {
|
||||
// return start of the next next word
|
||||
return new Position(pos.line, nextNextWord.start);
|
||||
} else {
|
||||
// return end of line
|
||||
return Motions.EndOfLine.run(doc, pos, state);
|
||||
}
|
||||
} else {
|
||||
// return start of the next word
|
||||
return new Position(pos.line, nextWord.start);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class NextWordEndMotion extends Motion {
|
||||
public run(doc: TextDocument, pos: Position, state: MotionState): Position {
|
||||
const lineContent = doc.lineAt(pos.line).text;
|
||||
|
||||
if (pos.character >= lineContent.length - 1) {
|
||||
// no content on this line or cursor at end of line
|
||||
return ((pos.line + 1 < doc.lineCount) ? new Position(pos.line + 1, 0) : pos);
|
||||
}
|
||||
|
||||
const nextWord = Words.findNextWord(doc, pos, state.wordCharacterClass);
|
||||
|
||||
if (!nextWord) {
|
||||
// return end of the line
|
||||
return Motions.EndOfLine.run(doc, pos, state);
|
||||
}
|
||||
|
||||
// return start of the next word
|
||||
return new Position(pos.line, nextWord.end);
|
||||
}
|
||||
}
|
||||
|
||||
class GoToLineUndefinedMotion extends Motion {
|
||||
public run(doc: TextDocument, pos: Position, state: MotionState): Position {
|
||||
// does not do anything
|
||||
return pos;
|
||||
}
|
||||
|
||||
public repeat(hasRepeatCount: boolean, count: number): Motion {
|
||||
if (!hasRepeatCount) {
|
||||
return Motions.GoToLastLine;
|
||||
}
|
||||
return new GoToLineDefinedMotion(count);
|
||||
}
|
||||
}
|
||||
|
||||
abstract class GoToLineMotion extends Motion {
|
||||
|
||||
protected firstNonWhitespaceChar(doc: TextDocument, line: number): number {
|
||||
const lineContent = doc.lineAt(line).text;
|
||||
let character = 0;
|
||||
while (character < lineContent.length) {
|
||||
const ch = lineContent.charAt(character);
|
||||
if (ch !== ' ' && ch !== '\t') {
|
||||
break;
|
||||
}
|
||||
character++;
|
||||
}
|
||||
return character;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class GoToFirstLineMotion extends GoToLineMotion {
|
||||
public run(doc: TextDocument, pos: Position, state: MotionState): Position {
|
||||
return new Position(0, this.firstNonWhitespaceChar(doc, 0));
|
||||
}
|
||||
}
|
||||
|
||||
class GoToLastLineMotion extends GoToLineMotion {
|
||||
public run(doc: TextDocument, pos: Position, state: MotionState): Position {
|
||||
const lastLine = doc.lineCount - 1;
|
||||
return new Position(lastLine, this.firstNonWhitespaceChar(doc, lastLine));
|
||||
}
|
||||
}
|
||||
|
||||
class GoToLineDefinedMotion extends GoToLineMotion {
|
||||
private _lineNumber: number;
|
||||
|
||||
constructor(lineNumber: number) {
|
||||
super();
|
||||
this._lineNumber = lineNumber;
|
||||
}
|
||||
|
||||
public run(doc: TextDocument, pos: Position, state: MotionState): Position {
|
||||
const line = Math.min(doc.lineCount - 1, Math.max(0, this._lineNumber - 1));
|
||||
return new Position(line, this.firstNonWhitespaceChar(doc, line));
|
||||
}
|
||||
}
|
||||
|
||||
class CursorMoveCommand extends AbstractCommandDescriptor {
|
||||
|
||||
constructor(private to: string, private by?: string) {
|
||||
super();
|
||||
}
|
||||
|
||||
public createCommand(args?: any): Command {
|
||||
const cursorMoveArgs: any = {
|
||||
to: this.to,
|
||||
by: this.by,
|
||||
value: args.repeat || 1,
|
||||
select: !!args.isVisual
|
||||
};
|
||||
return {
|
||||
commandId: 'cursorMove',
|
||||
args: cursorMoveArgs
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class EditorScrollCommand extends AbstractCommandDescriptor {
|
||||
|
||||
constructor(private to: string, private by?: string) {
|
||||
super();
|
||||
}
|
||||
|
||||
public createCommand(args?: any): Command {
|
||||
const editorScrollArgs: any = {
|
||||
to: this.to,
|
||||
by: this.by,
|
||||
value: args.repeat || 1,
|
||||
revealCursor: true
|
||||
};
|
||||
return {
|
||||
commandId: 'editorScroll',
|
||||
args: editorScrollArgs
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class RevealCurrentLineCommand extends AbstractCommandDescriptor {
|
||||
|
||||
constructor(private at: string) {
|
||||
super();
|
||||
}
|
||||
|
||||
public createCommand(args?: any): Command {
|
||||
const lineNumber = window.activeTextEditor.selection.start.line;
|
||||
const revealLineArgs: any = {
|
||||
lineNumber,
|
||||
at: this.at
|
||||
};
|
||||
return {
|
||||
commandId: 'revealLine',
|
||||
args: revealLineArgs
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class MoveActiveEditorCommandByPosition extends AbstractCommandDescriptor {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
public createCommand(args?: any): Command {
|
||||
const moveActiveEditorArgs: any = {
|
||||
to: args.repeat === void 0 ? 'last' : 'position',
|
||||
value: args.repeat !== void 0 ? args.repeat + 1 : undefined
|
||||
};
|
||||
return {
|
||||
commandId: 'moveActiveEditor',
|
||||
args: moveActiveEditorArgs
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class MoveActiveEditorCommand extends AbstractCommandDescriptor {
|
||||
|
||||
constructor(private to: string) {
|
||||
super();
|
||||
}
|
||||
|
||||
public createCommand(args?: any): Command {
|
||||
const moveActiveEditorArgs: any = {
|
||||
to: this.to,
|
||||
value: args.repeat ? args.repeat : 1
|
||||
};
|
||||
return {
|
||||
commandId: 'moveActiveEditor',
|
||||
args: moveActiveEditorArgs
|
||||
};
|
||||
}
|
||||
}
|
||||
class FoldCommand extends AbstractCommandDescriptor {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
public createCommand(args?: any): Command {
|
||||
const foldEditorArgs: any = {
|
||||
levels: args.repeat ? args.repeat : 1,
|
||||
direction: 'up'
|
||||
};
|
||||
return {
|
||||
commandId: 'editor.fold',
|
||||
args: foldEditorArgs
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class UnfoldCommand extends AbstractCommandDescriptor {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
public createCommand(args?: any): Command {
|
||||
const foldEditorArgs: any = {
|
||||
levels: args.repeat ? args.repeat : 1,
|
||||
direction: 'up'
|
||||
};
|
||||
return {
|
||||
commandId: 'editor.unfold',
|
||||
args: foldEditorArgs
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export const Motions = {
|
||||
RightMotion: new RightMotion(),
|
||||
|
||||
NextCharacter: new NextCharacterMotion(),
|
||||
|
||||
Left: new CursorMoveCommand('left'),
|
||||
Right: new CursorMoveCommand('right'),
|
||||
Down: new CursorMoveCommand('down'),
|
||||
Up: new CursorMoveCommand('up'),
|
||||
|
||||
EndOfLine: new EndOfLineMotion(),
|
||||
StartOfLine: new StartOfLineMotion(),
|
||||
NextWordStart: new NextWordStartMotion(),
|
||||
NextWordEnd: new NextWordEndMotion(),
|
||||
GoToLine: new GoToLineUndefinedMotion(),
|
||||
GoToFirstLine: new GoToFirstLineMotion(),
|
||||
GoToLastLine: new GoToLastLineMotion(),
|
||||
|
||||
CursorScrollLeft: new CursorMoveCommand('left'),
|
||||
CursorScrollRight: new CursorMoveCommand('right'),
|
||||
CursorScrollLeftByHalfLine: new CursorMoveCommand('left', 'halfLine'),
|
||||
CursorScrollRightByHalfLine: new CursorMoveCommand('right', 'halfLine'),
|
||||
|
||||
WrappedLineUp: new CursorMoveCommand('up', 'wrappedLine'),
|
||||
WrappedLineDown: new CursorMoveCommand('down', 'wrappedLine'),
|
||||
|
||||
WrappedLineStart: new CursorMoveCommand('wrappedLineStart'),
|
||||
WrappedLineFirstNonWhiteSpaceCharacter: new CursorMoveCommand('wrappedLineFirstNonWhitespaceCharacter'),
|
||||
WrappedLineColumnCenter: new CursorMoveCommand('wrappedLineColumnCenter'),
|
||||
WrappedLineEnd: new CursorMoveCommand('wrappedLineEnd'),
|
||||
WrappedLineLastNonWhiteSpaceCharacter: new CursorMoveCommand('wrappedLineLastNonWhitespaceCharacter'),
|
||||
|
||||
ViewPortTop: new CursorMoveCommand('viewPortTop'),
|
||||
ViewPortBottom: new CursorMoveCommand('viewPortBottom'),
|
||||
ViewPortCenter: new CursorMoveCommand('viewPortCenter'),
|
||||
|
||||
MoveActiveEditor: new MoveActiveEditorCommandByPosition(),
|
||||
MoveActiveEditorLeft: new MoveActiveEditorCommand('left'),
|
||||
MoveActiveEditorRight: new MoveActiveEditorCommand('right'),
|
||||
MoveActiveEditorFirst: new MoveActiveEditorCommand('first'),
|
||||
MoveActiveEditorLast: new MoveActiveEditorCommand('last'),
|
||||
MoveActiveEditorCenter: new MoveActiveEditorCommand('center'),
|
||||
|
||||
ScrollDownByLine: new EditorScrollCommand('down', 'line'),
|
||||
ScrollDownByHalfPage: new EditorScrollCommand('down', 'halfPage'),
|
||||
ScrollDownByPage: new EditorScrollCommand('down', 'page'),
|
||||
ScrollUpByLine: new EditorScrollCommand('up', 'line'),
|
||||
ScrollUpByHalfPage: new EditorScrollCommand('up', 'halfPage'),
|
||||
ScrollUpByPage: new EditorScrollCommand('up', 'page'),
|
||||
|
||||
RevealCurrentLineAtTop: new RevealCurrentLineCommand('top'),
|
||||
RevealCurrentLineAtCenter: new RevealCurrentLineCommand('center'),
|
||||
RevealCurrentLineAtBottom: new RevealCurrentLineCommand('bottom'),
|
||||
|
||||
FoldUnder: new FoldCommand(),
|
||||
UnfoldUnder: new UnfoldCommand()
|
||||
};
|
||||
|
||||
@ -1,343 +1,343 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Position, Selection, Range, TextDocument, TextEditor, TextEditorRevealType } from 'vscode';
|
||||
import { Motion, Motions } from './motions';
|
||||
import { Mode, IController, DeleteRegister } from './common';
|
||||
|
||||
export abstract class Operator {
|
||||
|
||||
public abstract runNormalMode(ctrl: IController, ed: TextEditor, repeatCount: number, args: string): boolean;
|
||||
public abstract runVisualMode(ctrl: IController, ed: TextEditor, args: string): boolean;
|
||||
|
||||
protected doc(ed: TextEditor): TextDocument {
|
||||
return ed.document;
|
||||
}
|
||||
|
||||
protected pos(ed: TextEditor): Position {
|
||||
return ed.selection.active;
|
||||
}
|
||||
|
||||
protected sel(ed: TextEditor): Selection {
|
||||
return ed.selection;
|
||||
}
|
||||
|
||||
protected setPosReveal(ed: TextEditor, line: number, char: number): void {
|
||||
ed.selection = new Selection(new Position(line, char), new Position(line, char));
|
||||
ed.revealRange(ed.selection, TextEditorRevealType.Default);
|
||||
}
|
||||
|
||||
protected delete(ctrl: IController, ed: TextEditor, isWholeLine: boolean, range: Range): void {
|
||||
ctrl.setDeleteRegister(new DeleteRegister(isWholeLine, ed.document.getText(range)));
|
||||
ed.edit((builder) => {
|
||||
builder.delete(range);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
abstract class OperatorWithNoArgs extends Operator {
|
||||
public runNormalMode(ctrl: IController, ed: TextEditor, repeatCount: number, args: string): boolean {
|
||||
this._run(ctrl, ed);
|
||||
return true;
|
||||
}
|
||||
public runVisualMode(ctrl: IController, ed: TextEditor, args: string): boolean {
|
||||
this._run(ctrl, ed);
|
||||
return true;
|
||||
}
|
||||
protected abstract _run(ctrl: IController, ed: TextEditor): void;
|
||||
}
|
||||
|
||||
class InsertOperator extends OperatorWithNoArgs {
|
||||
protected _run(ctrl: IController, ed: TextEditor): void {
|
||||
ctrl.setMode(Mode.INSERT);
|
||||
}
|
||||
}
|
||||
|
||||
class AppendOperator extends OperatorWithNoArgs {
|
||||
protected _run(ctrl: IController, ed: TextEditor): void {
|
||||
const newPos = Motions.RightMotion.run(this.doc(ed), this.pos(ed), ctrl.motionState);
|
||||
this.setPosReveal(ed, newPos.line, newPos.character);
|
||||
ctrl.setMode(Mode.INSERT);
|
||||
}
|
||||
}
|
||||
|
||||
class AppendEndOfLineOperator extends OperatorWithNoArgs {
|
||||
protected _run(ctrl: IController, ed: TextEditor): void {
|
||||
const newPos = Motions.EndOfLine.run(this.doc(ed), this.pos(ed), ctrl.motionState);
|
||||
this.setPosReveal(ed, newPos.line, newPos.character);
|
||||
ctrl.setMode(Mode.INSERT);
|
||||
}
|
||||
}
|
||||
|
||||
class VisualOperator extends OperatorWithNoArgs {
|
||||
protected _run(ctrl: IController, ed: TextEditor): void {
|
||||
ctrl.motionState.anchor = this.pos(ed);
|
||||
ctrl.setVisual(true);
|
||||
}
|
||||
}
|
||||
|
||||
class DeleteCharUnderCursorOperator extends Operator {
|
||||
public runNormalMode(ctrl: IController, ed: TextEditor, repeatCount: number, args: string): boolean {
|
||||
const to = Motions.NextCharacter.repeat(repeatCount > 1, repeatCount).run(this.doc(ed), this.pos(ed), ctrl.motionState);
|
||||
const from = this.pos(ed);
|
||||
|
||||
this.delete(ctrl, ed, false, new Range(from.line, from.character, to.line, to.character));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public runVisualMode(ctrl: IController, ed: TextEditor, args: string): boolean {
|
||||
const sel = this.sel(ed);
|
||||
this.delete(ctrl, ed, false, sel);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
class DeleteLineOperator extends Operator {
|
||||
public runNormalMode(ctrl: IController, ed: TextEditor, repeatCount: number, args: string): boolean {
|
||||
const pos = this.pos(ed);
|
||||
const doc = this.doc(ed);
|
||||
|
||||
let fromLine = pos.line;
|
||||
let fromCharacter = 0;
|
||||
|
||||
let toLine = fromLine + repeatCount;
|
||||
let toCharacter = 0;
|
||||
|
||||
if (toLine >= doc.lineCount - 1) {
|
||||
// Deleting last line
|
||||
toLine = doc.lineCount - 1;
|
||||
toCharacter = doc.lineAt(toLine).text.length;
|
||||
|
||||
if (fromLine > 0) {
|
||||
fromLine = fromLine - 1;
|
||||
fromCharacter = doc.lineAt(fromLine).text.length;
|
||||
}
|
||||
}
|
||||
|
||||
this.delete(ctrl, ed, true, new Range(fromLine, fromCharacter, toLine, toCharacter));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public runVisualMode(ctrl: IController, ed: TextEditor, args: string): boolean {
|
||||
const sel = this.sel(ed);
|
||||
this.delete(ctrl, ed, false, sel);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
abstract class OperatorWithMotion extends Operator {
|
||||
public runNormalMode(ctrl: IController, ed: TextEditor, repeatCount: number, args: string): boolean {
|
||||
const motion = ctrl.findMotion(args);
|
||||
if (!motion) {
|
||||
|
||||
// is it motion building
|
||||
if (ctrl.isMotionPrefix(args)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// INVALID INPUT - beep!!
|
||||
return true;
|
||||
}
|
||||
|
||||
return this._runNormalMode(ctrl, ed, motion.repeat(repeatCount > 1, repeatCount));
|
||||
}
|
||||
|
||||
protected abstract _runNormalMode(ctrl: IController, ed: TextEditor, motion: Motion): boolean;
|
||||
}
|
||||
|
||||
class DeleteToOperator extends OperatorWithMotion {
|
||||
|
||||
public runNormalMode(ctrl: IController, ed: TextEditor, repeatCount: number, args: string): boolean {
|
||||
if (args === 'd') {
|
||||
// dd
|
||||
return Operators.DeleteLine.runNormalMode(ctrl, ed, repeatCount, args);
|
||||
}
|
||||
return super.runNormalMode(ctrl, ed, repeatCount, args);
|
||||
}
|
||||
|
||||
protected _runNormalMode(ctrl: IController, ed: TextEditor, motion: Motion): boolean {
|
||||
const to = motion.run(this.doc(ed), this.pos(ed), ctrl.motionState);
|
||||
const from = this.pos(ed);
|
||||
|
||||
this.delete(ctrl, ed, false, new Range(from.line, from.character, to.line, to.character));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public runVisualMode(ctrl: IController, ed: TextEditor, args: string): boolean {
|
||||
const sel = this.sel(ed);
|
||||
this.delete(ctrl, ed, false, sel);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
class PutOperator extends Operator {
|
||||
|
||||
public runNormalMode(ctrl: IController, ed: TextEditor, repeatCount: number, args: string): boolean {
|
||||
const register = ctrl.getDeleteRegister();
|
||||
if (!register) {
|
||||
// No delete register - beep!!
|
||||
return true;
|
||||
}
|
||||
|
||||
let str = repeatString(register.content, repeatCount);
|
||||
|
||||
const pos = this.pos(ed);
|
||||
if (!register.isWholeLine) {
|
||||
ed.edit((builder) => {
|
||||
builder.insert(new Position(pos.line, pos.character + 1), str);
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
const doc = this.doc(ed);
|
||||
let insertLine = pos.line + 1;
|
||||
let insertCharacter = 0;
|
||||
|
||||
if (insertLine >= doc.lineCount) {
|
||||
// on last line
|
||||
insertLine = doc.lineCount - 1;
|
||||
insertCharacter = doc.lineAt(insertLine).text.length;
|
||||
str = '\n' + str;
|
||||
}
|
||||
|
||||
ed.edit((builder) => {
|
||||
builder.insert(new Position(insertLine, insertCharacter), str);
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public runVisualMode(ctrl: IController, ed: TextEditor, args: string): boolean {
|
||||
const register = ctrl.getDeleteRegister();
|
||||
if (!register) {
|
||||
// No delete register - beep!!
|
||||
return false;
|
||||
}
|
||||
|
||||
const str = register.content;
|
||||
|
||||
const sel = this.sel(ed);
|
||||
ed.edit((builder) => {
|
||||
builder.replace(sel, str);
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
class ReplaceOperator extends Operator {
|
||||
|
||||
public runNormalMode(ctrl: IController, ed: TextEditor, repeatCount: number, args: string): boolean {
|
||||
if (args.length === 0) {
|
||||
// input not ready
|
||||
return false;
|
||||
}
|
||||
|
||||
const doc = this.doc(ed);
|
||||
const pos = this.pos(ed);
|
||||
const toCharacter = pos.character + repeatCount;
|
||||
if (toCharacter > doc.lineAt(pos).text.length) {
|
||||
// invalid replace (beep!)
|
||||
return true;
|
||||
}
|
||||
|
||||
ed.edit((builder) => {
|
||||
builder.replace(new Range(pos.line, pos.character, pos.line, toCharacter), repeatString(args, repeatCount));
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public runVisualMode(ctrl: IController, ed: TextEditor, args: string): boolean {
|
||||
if (args.length === 0) {
|
||||
// input not ready
|
||||
return false;
|
||||
}
|
||||
|
||||
const doc = this.doc(ed);
|
||||
const sel = this.sel(ed);
|
||||
|
||||
const srcString = doc.getText(sel);
|
||||
let dstString = '';
|
||||
for (let i = 0; i < srcString.length; i++) {
|
||||
const ch = srcString.charAt(i);
|
||||
if (ch === '\r' || ch === '\n') {
|
||||
dstString += ch;
|
||||
} else {
|
||||
dstString += args;
|
||||
}
|
||||
}
|
||||
|
||||
ed.edit((builder) => {
|
||||
builder.replace(sel, dstString);
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
class ReplaceModeOperator extends Operator {
|
||||
|
||||
public runNormalMode(ctrl: IController, ed: TextEditor, repeatCount: number, args: string): boolean {
|
||||
ctrl.setMode(Mode.REPLACE);
|
||||
return true;
|
||||
}
|
||||
|
||||
public runVisualMode(ctrl: IController, ed: TextEditor, args: string): boolean {
|
||||
this.delete(ctrl, ed, false, this.sel(ed));
|
||||
ctrl.setMode(Mode.INSERT);
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class ChangeOperator extends OperatorWithMotion {
|
||||
|
||||
protected _runNormalMode(ctrl: IController, ed: TextEditor, motion: Motion): boolean {
|
||||
const to = motion.run(this.doc(ed), this.pos(ed), ctrl.motionState);
|
||||
const from = this.pos(ed);
|
||||
|
||||
this.delete(ctrl, ed, false, new Range(from.line, from.character, to.line, to.character));
|
||||
|
||||
ctrl.setMode(Mode.INSERT);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public runVisualMode(ctrl: IController, ed: TextEditor, args: string): boolean {
|
||||
const sel = this.sel(ed);
|
||||
|
||||
this.delete(ctrl, ed, false, sel);
|
||||
|
||||
ctrl.setMode(Mode.INSERT);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
function repeatString(str: string, repeatCount: number): string {
|
||||
let result = '';
|
||||
for (let i = 0; i < repeatCount; i++) {
|
||||
result += str;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
export const Operators = {
|
||||
Insert: new InsertOperator(),
|
||||
Visual: new VisualOperator(),
|
||||
Append: new AppendOperator(),
|
||||
AppendEndOfLine: new AppendEndOfLineOperator(),
|
||||
DeleteCharUnderCursor: new DeleteCharUnderCursorOperator(),
|
||||
DeleteTo: new DeleteToOperator(),
|
||||
DeleteLine: new DeleteLineOperator(),
|
||||
Put: new PutOperator(),
|
||||
Replace: new ReplaceOperator(),
|
||||
Change: new ChangeOperator(),
|
||||
ReplaceMode: new ReplaceModeOperator(),
|
||||
};
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Position, Selection, Range, TextDocument, TextEditor, TextEditorRevealType } from 'vscode';
|
||||
import { Motion, Motions } from './motions';
|
||||
import { Mode, IController, DeleteRegister } from './common';
|
||||
|
||||
export abstract class Operator {
|
||||
|
||||
public abstract runNormalMode(ctrl: IController, ed: TextEditor, repeatCount: number, args: string): boolean;
|
||||
public abstract runVisualMode(ctrl: IController, ed: TextEditor, args: string): boolean;
|
||||
|
||||
protected doc(ed: TextEditor): TextDocument {
|
||||
return ed.document;
|
||||
}
|
||||
|
||||
protected pos(ed: TextEditor): Position {
|
||||
return ed.selection.active;
|
||||
}
|
||||
|
||||
protected sel(ed: TextEditor): Selection {
|
||||
return ed.selection;
|
||||
}
|
||||
|
||||
protected setPosReveal(ed: TextEditor, line: number, char: number): void {
|
||||
ed.selection = new Selection(new Position(line, char), new Position(line, char));
|
||||
ed.revealRange(ed.selection, TextEditorRevealType.Default);
|
||||
}
|
||||
|
||||
protected delete(ctrl: IController, ed: TextEditor, isWholeLine: boolean, range: Range): void {
|
||||
ctrl.setDeleteRegister(new DeleteRegister(isWholeLine, ed.document.getText(range)));
|
||||
ed.edit((builder) => {
|
||||
builder.delete(range);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
abstract class OperatorWithNoArgs extends Operator {
|
||||
public runNormalMode(ctrl: IController, ed: TextEditor, repeatCount: number, args: string): boolean {
|
||||
this._run(ctrl, ed);
|
||||
return true;
|
||||
}
|
||||
public runVisualMode(ctrl: IController, ed: TextEditor, args: string): boolean {
|
||||
this._run(ctrl, ed);
|
||||
return true;
|
||||
}
|
||||
protected abstract _run(ctrl: IController, ed: TextEditor): void;
|
||||
}
|
||||
|
||||
class InsertOperator extends OperatorWithNoArgs {
|
||||
protected _run(ctrl: IController, ed: TextEditor): void {
|
||||
ctrl.setMode(Mode.INSERT);
|
||||
}
|
||||
}
|
||||
|
||||
class AppendOperator extends OperatorWithNoArgs {
|
||||
protected _run(ctrl: IController, ed: TextEditor): void {
|
||||
const newPos = Motions.RightMotion.run(this.doc(ed), this.pos(ed), ctrl.motionState);
|
||||
this.setPosReveal(ed, newPos.line, newPos.character);
|
||||
ctrl.setMode(Mode.INSERT);
|
||||
}
|
||||
}
|
||||
|
||||
class AppendEndOfLineOperator extends OperatorWithNoArgs {
|
||||
protected _run(ctrl: IController, ed: TextEditor): void {
|
||||
const newPos = Motions.EndOfLine.run(this.doc(ed), this.pos(ed), ctrl.motionState);
|
||||
this.setPosReveal(ed, newPos.line, newPos.character);
|
||||
ctrl.setMode(Mode.INSERT);
|
||||
}
|
||||
}
|
||||
|
||||
class VisualOperator extends OperatorWithNoArgs {
|
||||
protected _run(ctrl: IController, ed: TextEditor): void {
|
||||
ctrl.motionState.anchor = this.pos(ed);
|
||||
ctrl.setVisual(true);
|
||||
}
|
||||
}
|
||||
|
||||
class DeleteCharUnderCursorOperator extends Operator {
|
||||
public runNormalMode(ctrl: IController, ed: TextEditor, repeatCount: number, args: string): boolean {
|
||||
const to = Motions.NextCharacter.repeat(repeatCount > 1, repeatCount).run(this.doc(ed), this.pos(ed), ctrl.motionState);
|
||||
const from = this.pos(ed);
|
||||
|
||||
this.delete(ctrl, ed, false, new Range(from.line, from.character, to.line, to.character));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public runVisualMode(ctrl: IController, ed: TextEditor, args: string): boolean {
|
||||
const sel = this.sel(ed);
|
||||
this.delete(ctrl, ed, false, sel);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
class DeleteLineOperator extends Operator {
|
||||
public runNormalMode(ctrl: IController, ed: TextEditor, repeatCount: number, args: string): boolean {
|
||||
const pos = this.pos(ed);
|
||||
const doc = this.doc(ed);
|
||||
|
||||
let fromLine = pos.line;
|
||||
let fromCharacter = 0;
|
||||
|
||||
let toLine = fromLine + repeatCount;
|
||||
let toCharacter = 0;
|
||||
|
||||
if (toLine >= doc.lineCount - 1) {
|
||||
// Deleting last line
|
||||
toLine = doc.lineCount - 1;
|
||||
toCharacter = doc.lineAt(toLine).text.length;
|
||||
|
||||
if (fromLine > 0) {
|
||||
fromLine = fromLine - 1;
|
||||
fromCharacter = doc.lineAt(fromLine).text.length;
|
||||
}
|
||||
}
|
||||
|
||||
this.delete(ctrl, ed, true, new Range(fromLine, fromCharacter, toLine, toCharacter));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public runVisualMode(ctrl: IController, ed: TextEditor, args: string): boolean {
|
||||
const sel = this.sel(ed);
|
||||
this.delete(ctrl, ed, false, sel);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
abstract class OperatorWithMotion extends Operator {
|
||||
public runNormalMode(ctrl: IController, ed: TextEditor, repeatCount: number, args: string): boolean {
|
||||
const motion = ctrl.findMotion(args);
|
||||
if (!motion) {
|
||||
|
||||
// is it motion building
|
||||
if (ctrl.isMotionPrefix(args)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// INVALID INPUT - beep!!
|
||||
return true;
|
||||
}
|
||||
|
||||
return this._runNormalMode(ctrl, ed, motion.repeat(repeatCount > 1, repeatCount));
|
||||
}
|
||||
|
||||
protected abstract _runNormalMode(ctrl: IController, ed: TextEditor, motion: Motion): boolean;
|
||||
}
|
||||
|
||||
class DeleteToOperator extends OperatorWithMotion {
|
||||
|
||||
public runNormalMode(ctrl: IController, ed: TextEditor, repeatCount: number, args: string): boolean {
|
||||
if (args === 'd') {
|
||||
// dd
|
||||
return Operators.DeleteLine.runNormalMode(ctrl, ed, repeatCount, args);
|
||||
}
|
||||
return super.runNormalMode(ctrl, ed, repeatCount, args);
|
||||
}
|
||||
|
||||
protected _runNormalMode(ctrl: IController, ed: TextEditor, motion: Motion): boolean {
|
||||
const to = motion.run(this.doc(ed), this.pos(ed), ctrl.motionState);
|
||||
const from = this.pos(ed);
|
||||
|
||||
this.delete(ctrl, ed, false, new Range(from.line, from.character, to.line, to.character));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public runVisualMode(ctrl: IController, ed: TextEditor, args: string): boolean {
|
||||
const sel = this.sel(ed);
|
||||
this.delete(ctrl, ed, false, sel);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
class PutOperator extends Operator {
|
||||
|
||||
public runNormalMode(ctrl: IController, ed: TextEditor, repeatCount: number, args: string): boolean {
|
||||
const register = ctrl.getDeleteRegister();
|
||||
if (!register) {
|
||||
// No delete register - beep!!
|
||||
return true;
|
||||
}
|
||||
|
||||
let str = repeatString(register.content, repeatCount);
|
||||
|
||||
const pos = this.pos(ed);
|
||||
if (!register.isWholeLine) {
|
||||
ed.edit((builder) => {
|
||||
builder.insert(new Position(pos.line, pos.character + 1), str);
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
const doc = this.doc(ed);
|
||||
let insertLine = pos.line + 1;
|
||||
let insertCharacter = 0;
|
||||
|
||||
if (insertLine >= doc.lineCount) {
|
||||
// on last line
|
||||
insertLine = doc.lineCount - 1;
|
||||
insertCharacter = doc.lineAt(insertLine).text.length;
|
||||
str = '\n' + str;
|
||||
}
|
||||
|
||||
ed.edit((builder) => {
|
||||
builder.insert(new Position(insertLine, insertCharacter), str);
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public runVisualMode(ctrl: IController, ed: TextEditor, args: string): boolean {
|
||||
const register = ctrl.getDeleteRegister();
|
||||
if (!register) {
|
||||
// No delete register - beep!!
|
||||
return false;
|
||||
}
|
||||
|
||||
const str = register.content;
|
||||
|
||||
const sel = this.sel(ed);
|
||||
ed.edit((builder) => {
|
||||
builder.replace(sel, str);
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
class ReplaceOperator extends Operator {
|
||||
|
||||
public runNormalMode(ctrl: IController, ed: TextEditor, repeatCount: number, args: string): boolean {
|
||||
if (args.length === 0) {
|
||||
// input not ready
|
||||
return false;
|
||||
}
|
||||
|
||||
const doc = this.doc(ed);
|
||||
const pos = this.pos(ed);
|
||||
const toCharacter = pos.character + repeatCount;
|
||||
if (toCharacter > doc.lineAt(pos).text.length) {
|
||||
// invalid replace (beep!)
|
||||
return true;
|
||||
}
|
||||
|
||||
ed.edit((builder) => {
|
||||
builder.replace(new Range(pos.line, pos.character, pos.line, toCharacter), repeatString(args, repeatCount));
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public runVisualMode(ctrl: IController, ed: TextEditor, args: string): boolean {
|
||||
if (args.length === 0) {
|
||||
// input not ready
|
||||
return false;
|
||||
}
|
||||
|
||||
const doc = this.doc(ed);
|
||||
const sel = this.sel(ed);
|
||||
|
||||
const srcString = doc.getText(sel);
|
||||
let dstString = '';
|
||||
for (let i = 0; i < srcString.length; i++) {
|
||||
const ch = srcString.charAt(i);
|
||||
if (ch === '\r' || ch === '\n') {
|
||||
dstString += ch;
|
||||
} else {
|
||||
dstString += args;
|
||||
}
|
||||
}
|
||||
|
||||
ed.edit((builder) => {
|
||||
builder.replace(sel, dstString);
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
class ReplaceModeOperator extends Operator {
|
||||
|
||||
public runNormalMode(ctrl: IController, ed: TextEditor, repeatCount: number, args: string): boolean {
|
||||
ctrl.setMode(Mode.REPLACE);
|
||||
return true;
|
||||
}
|
||||
|
||||
public runVisualMode(ctrl: IController, ed: TextEditor, args: string): boolean {
|
||||
this.delete(ctrl, ed, false, this.sel(ed));
|
||||
ctrl.setMode(Mode.INSERT);
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class ChangeOperator extends OperatorWithMotion {
|
||||
|
||||
protected _runNormalMode(ctrl: IController, ed: TextEditor, motion: Motion): boolean {
|
||||
const to = motion.run(this.doc(ed), this.pos(ed), ctrl.motionState);
|
||||
const from = this.pos(ed);
|
||||
|
||||
this.delete(ctrl, ed, false, new Range(from.line, from.character, to.line, to.character));
|
||||
|
||||
ctrl.setMode(Mode.INSERT);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public runVisualMode(ctrl: IController, ed: TextEditor, args: string): boolean {
|
||||
const sel = this.sel(ed);
|
||||
|
||||
this.delete(ctrl, ed, false, sel);
|
||||
|
||||
ctrl.setMode(Mode.INSERT);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
function repeatString(str: string, repeatCount: number): string {
|
||||
let result = '';
|
||||
for (let i = 0; i < repeatCount; i++) {
|
||||
result += str;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
export const Operators = {
|
||||
Insert: new InsertOperator(),
|
||||
Visual: new VisualOperator(),
|
||||
Append: new AppendOperator(),
|
||||
AppendEndOfLine: new AppendEndOfLineOperator(),
|
||||
DeleteCharUnderCursor: new DeleteCharUnderCursorOperator(),
|
||||
DeleteTo: new DeleteToOperator(),
|
||||
DeleteLine: new DeleteLineOperator(),
|
||||
Put: new PutOperator(),
|
||||
Replace: new ReplaceOperator(),
|
||||
Change: new ChangeOperator(),
|
||||
ReplaceMode: new ReplaceModeOperator(),
|
||||
};
|
||||
|
||||
@ -1,104 +1,104 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Position, TextDocument } from 'vscode';
|
||||
|
||||
export enum CharacterClass {
|
||||
REGULAR = 0,
|
||||
WORD_SEPARATOR = 1,
|
||||
WHITESPACE = 2
|
||||
}
|
||||
|
||||
export enum WordType {
|
||||
NONE = 0,
|
||||
SEPARATOR = 1,
|
||||
REGULAR = 2
|
||||
}
|
||||
|
||||
export type WordCharacters = CharacterClass[];
|
||||
|
||||
export interface IWord {
|
||||
start: number;
|
||||
end: number;
|
||||
wordType: WordType;
|
||||
}
|
||||
|
||||
export class Words {
|
||||
|
||||
public static createWordCharacters(wordSeparators: string): WordCharacters {
|
||||
const result: CharacterClass[] = [];
|
||||
|
||||
// Make array fast for ASCII text
|
||||
for (let chCode = 0; chCode < 256; chCode++) {
|
||||
result[chCode] = CharacterClass.REGULAR;
|
||||
}
|
||||
|
||||
for (let i = 0, len = wordSeparators.length; i < len; i++) {
|
||||
result[wordSeparators.charCodeAt(i)] = CharacterClass.WORD_SEPARATOR;
|
||||
}
|
||||
|
||||
result[' '.charCodeAt(0)] = CharacterClass.WHITESPACE;
|
||||
result['\t'.charCodeAt(0)] = CharacterClass.WHITESPACE;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static findNextWord(doc: TextDocument, pos: Position, wordCharacterClass: WordCharacters): IWord | null {
|
||||
|
||||
const lineContent = doc.lineAt(pos.line).text;
|
||||
let wordType = WordType.NONE;
|
||||
const len = lineContent.length;
|
||||
|
||||
for (let chIndex = pos.character; chIndex < len; chIndex++) {
|
||||
const chCode = lineContent.charCodeAt(chIndex);
|
||||
const chClass = (wordCharacterClass[chCode] || CharacterClass.REGULAR);
|
||||
|
||||
if (chClass === CharacterClass.REGULAR) {
|
||||
if (wordType === WordType.SEPARATOR) {
|
||||
return this._createWord(lineContent, wordType, this._findStartOfWord(lineContent, wordCharacterClass, wordType, chIndex - 1), chIndex);
|
||||
}
|
||||
wordType = WordType.REGULAR;
|
||||
} else if (chClass === CharacterClass.WORD_SEPARATOR) {
|
||||
if (wordType === WordType.REGULAR) {
|
||||
return this._createWord(lineContent, wordType, this._findStartOfWord(lineContent, wordCharacterClass, wordType, chIndex - 1), chIndex);
|
||||
}
|
||||
wordType = WordType.SEPARATOR;
|
||||
} else if (chClass === CharacterClass.WHITESPACE) {
|
||||
if (wordType !== WordType.NONE) {
|
||||
return this._createWord(lineContent, wordType, this._findStartOfWord(lineContent, wordCharacterClass, wordType, chIndex - 1), chIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (wordType !== WordType.NONE) {
|
||||
return this._createWord(lineContent, wordType, this._findStartOfWord(lineContent, wordCharacterClass, wordType, len - 1), len);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static _findStartOfWord(lineContent: string, wordCharacterClass: WordCharacters, wordType: WordType, startIndex: number): number {
|
||||
for (let chIndex = startIndex; chIndex >= 0; chIndex--) {
|
||||
const chCode = lineContent.charCodeAt(chIndex);
|
||||
const chClass = (wordCharacterClass[chCode] || CharacterClass.REGULAR);
|
||||
|
||||
if (chClass === CharacterClass.WHITESPACE) {
|
||||
return chIndex + 1;
|
||||
}
|
||||
if (wordType === WordType.REGULAR && chClass === CharacterClass.WORD_SEPARATOR) {
|
||||
return chIndex + 1;
|
||||
}
|
||||
if (wordType === WordType.SEPARATOR && chClass === CharacterClass.REGULAR) {
|
||||
return chIndex + 1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static _createWord(lineContent: string, wordType: WordType, start: number, end: number): IWord {
|
||||
// console.log('WORD ==> ' + start + ' => ' + end + ':::: <<<' + lineContent.substring(start, end) + '>>>');
|
||||
return { start: start, end: end, wordType: wordType };
|
||||
}
|
||||
}
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Position, TextDocument } from 'vscode';
|
||||
|
||||
export enum CharacterClass {
|
||||
REGULAR = 0,
|
||||
WORD_SEPARATOR = 1,
|
||||
WHITESPACE = 2
|
||||
}
|
||||
|
||||
export enum WordType {
|
||||
NONE = 0,
|
||||
SEPARATOR = 1,
|
||||
REGULAR = 2
|
||||
}
|
||||
|
||||
export type WordCharacters = CharacterClass[];
|
||||
|
||||
export interface IWord {
|
||||
start: number;
|
||||
end: number;
|
||||
wordType: WordType;
|
||||
}
|
||||
|
||||
export class Words {
|
||||
|
||||
public static createWordCharacters(wordSeparators: string): WordCharacters {
|
||||
const result: CharacterClass[] = [];
|
||||
|
||||
// Make array fast for ASCII text
|
||||
for (let chCode = 0; chCode < 256; chCode++) {
|
||||
result[chCode] = CharacterClass.REGULAR;
|
||||
}
|
||||
|
||||
for (let i = 0, len = wordSeparators.length; i < len; i++) {
|
||||
result[wordSeparators.charCodeAt(i)] = CharacterClass.WORD_SEPARATOR;
|
||||
}
|
||||
|
||||
result[' '.charCodeAt(0)] = CharacterClass.WHITESPACE;
|
||||
result['\t'.charCodeAt(0)] = CharacterClass.WHITESPACE;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static findNextWord(doc: TextDocument, pos: Position, wordCharacterClass: WordCharacters): IWord | null {
|
||||
|
||||
const lineContent = doc.lineAt(pos.line).text;
|
||||
let wordType = WordType.NONE;
|
||||
const len = lineContent.length;
|
||||
|
||||
for (let chIndex = pos.character; chIndex < len; chIndex++) {
|
||||
const chCode = lineContent.charCodeAt(chIndex);
|
||||
const chClass = (wordCharacterClass[chCode] || CharacterClass.REGULAR);
|
||||
|
||||
if (chClass === CharacterClass.REGULAR) {
|
||||
if (wordType === WordType.SEPARATOR) {
|
||||
return this._createWord(lineContent, wordType, this._findStartOfWord(lineContent, wordCharacterClass, wordType, chIndex - 1), chIndex);
|
||||
}
|
||||
wordType = WordType.REGULAR;
|
||||
} else if (chClass === CharacterClass.WORD_SEPARATOR) {
|
||||
if (wordType === WordType.REGULAR) {
|
||||
return this._createWord(lineContent, wordType, this._findStartOfWord(lineContent, wordCharacterClass, wordType, chIndex - 1), chIndex);
|
||||
}
|
||||
wordType = WordType.SEPARATOR;
|
||||
} else if (chClass === CharacterClass.WHITESPACE) {
|
||||
if (wordType !== WordType.NONE) {
|
||||
return this._createWord(lineContent, wordType, this._findStartOfWord(lineContent, wordCharacterClass, wordType, chIndex - 1), chIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (wordType !== WordType.NONE) {
|
||||
return this._createWord(lineContent, wordType, this._findStartOfWord(lineContent, wordCharacterClass, wordType, len - 1), len);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static _findStartOfWord(lineContent: string, wordCharacterClass: WordCharacters, wordType: WordType, startIndex: number): number {
|
||||
for (let chIndex = startIndex; chIndex >= 0; chIndex--) {
|
||||
const chCode = lineContent.charCodeAt(chIndex);
|
||||
const chClass = (wordCharacterClass[chCode] || CharacterClass.REGULAR);
|
||||
|
||||
if (chClass === CharacterClass.WHITESPACE) {
|
||||
return chIndex + 1;
|
||||
}
|
||||
if (wordType === WordType.REGULAR && chClass === CharacterClass.WORD_SEPARATOR) {
|
||||
return chIndex + 1;
|
||||
}
|
||||
if (wordType === WordType.SEPARATOR && chClass === CharacterClass.REGULAR) {
|
||||
return chIndex + 1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static _createWord(lineContent: string, wordType: WordType, start: number, end: number): IWord {
|
||||
// console.log('WORD ==> ' + start + ' => ' + end + ':::: <<<' + lineContent.substring(start, end) + '>>>');
|
||||
return { start: start, end: end, wordType: wordType };
|
||||
}
|
||||
}
|
||||
|
||||
28
virtual-document-sample/src/cowsay.d.ts
vendored
28
virtual-document-sample/src/cowsay.d.ts
vendored
@ -1,14 +1,14 @@
|
||||
declare module 'cowsay' {
|
||||
|
||||
export interface CowsayOptions {
|
||||
text: string;
|
||||
cow?: string;
|
||||
eyes?: string;
|
||||
tongue?: string;
|
||||
wrap?: boolean;
|
||||
wrapLength?: number;
|
||||
mode?: 'b' | 'd' | 'g' | 'p' | 's' | 't' | 'w' | 'y'
|
||||
}
|
||||
|
||||
export function say(options: CowsayOptions): string;
|
||||
}
|
||||
declare module 'cowsay' {
|
||||
|
||||
export interface CowsayOptions {
|
||||
text: string;
|
||||
cow?: string;
|
||||
eyes?: string;
|
||||
tongue?: string;
|
||||
wrap?: boolean;
|
||||
wrapLength?: number;
|
||||
mode?: 'b' | 'd' | 'g' | 'p' | 's' | 't' | 'w' | 'y'
|
||||
}
|
||||
|
||||
export function say(options: CowsayOptions): string;
|
||||
}
|
||||
|
||||
@ -1,50 +1,50 @@
|
||||
/*---------------------------------------------------------
|
||||
* Copyright (C) Microsoft Corporation. All rights reserved.
|
||||
*--------------------------------------------------------*/
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import * as cowsay from 'cowsay';
|
||||
|
||||
export function activate({ subscriptions }: vscode.ExtensionContext) {
|
||||
|
||||
// register a content provider for the cowsay-scheme
|
||||
const myScheme = 'cowsay';
|
||||
const myProvider = new class implements vscode.TextDocumentContentProvider {
|
||||
|
||||
// emitter and its event
|
||||
onDidChangeEmitter = new vscode.EventEmitter<vscode.Uri>();
|
||||
onDidChange = this.onDidChangeEmitter.event;
|
||||
|
||||
provideTextDocumentContent(uri: vscode.Uri): string {
|
||||
// simply invoke cowsay, use uri-path as text
|
||||
return cowsay.say({ text: uri.path });
|
||||
}
|
||||
};
|
||||
subscriptions.push(vscode.workspace.registerTextDocumentContentProvider(myScheme, myProvider));
|
||||
|
||||
// register a command that opens a cowsay-document
|
||||
subscriptions.push(vscode.commands.registerCommand('cowsay.say', async () => {
|
||||
const what = await vscode.window.showInputBox({ placeHolder: 'cowsay...' });
|
||||
if (what) {
|
||||
const uri = vscode.Uri.parse('cowsay:' + what);
|
||||
const doc = await vscode.workspace.openTextDocument(uri); // calls back into the provider
|
||||
await vscode.window.showTextDocument(doc, { preview: false });
|
||||
}
|
||||
}));
|
||||
|
||||
// register a command that updates the current cowsay
|
||||
subscriptions.push(vscode.commands.registerCommand('cowsay.backwards', async () => {
|
||||
if (!vscode.window.activeTextEditor) {
|
||||
return; // no editor
|
||||
}
|
||||
const { document } = vscode.window.activeTextEditor;
|
||||
if (document.uri.scheme !== myScheme) {
|
||||
return; // not my scheme
|
||||
}
|
||||
// get path-components, reverse it, and create a new uri
|
||||
const say = document.uri.path;
|
||||
const newSay = say.split('').reverse().join('');
|
||||
const newUri = document.uri.with({ path: newSay });
|
||||
await vscode.window.showTextDocument(newUri, { preview: false });
|
||||
}));
|
||||
}
|
||||
/*---------------------------------------------------------
|
||||
* Copyright (C) Microsoft Corporation. All rights reserved.
|
||||
*--------------------------------------------------------*/
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import * as cowsay from 'cowsay';
|
||||
|
||||
export function activate({ subscriptions }: vscode.ExtensionContext) {
|
||||
|
||||
// register a content provider for the cowsay-scheme
|
||||
const myScheme = 'cowsay';
|
||||
const myProvider = new class implements vscode.TextDocumentContentProvider {
|
||||
|
||||
// emitter and its event
|
||||
onDidChangeEmitter = new vscode.EventEmitter<vscode.Uri>();
|
||||
onDidChange = this.onDidChangeEmitter.event;
|
||||
|
||||
provideTextDocumentContent(uri: vscode.Uri): string {
|
||||
// simply invoke cowsay, use uri-path as text
|
||||
return cowsay.say({ text: uri.path });
|
||||
}
|
||||
};
|
||||
subscriptions.push(vscode.workspace.registerTextDocumentContentProvider(myScheme, myProvider));
|
||||
|
||||
// register a command that opens a cowsay-document
|
||||
subscriptions.push(vscode.commands.registerCommand('cowsay.say', async () => {
|
||||
const what = await vscode.window.showInputBox({ placeHolder: 'cowsay...' });
|
||||
if (what) {
|
||||
const uri = vscode.Uri.parse('cowsay:' + what);
|
||||
const doc = await vscode.workspace.openTextDocument(uri); // calls back into the provider
|
||||
await vscode.window.showTextDocument(doc, { preview: false });
|
||||
}
|
||||
}));
|
||||
|
||||
// register a command that updates the current cowsay
|
||||
subscriptions.push(vscode.commands.registerCommand('cowsay.backwards', async () => {
|
||||
if (!vscode.window.activeTextEditor) {
|
||||
return; // no editor
|
||||
}
|
||||
const { document } = vscode.window.activeTextEditor;
|
||||
if (document.uri.scheme !== myScheme) {
|
||||
return; // not my scheme
|
||||
}
|
||||
// get path-components, reverse it, and create a new uri
|
||||
const say = document.uri.path;
|
||||
const newSay = say.split('').reverse().join('');
|
||||
const newUri = document.uri.with({ path: newSay });
|
||||
await vscode.window.showTextDocument(newUri, { preview: false });
|
||||
}));
|
||||
}
|
||||
|
||||
@ -1,16 +1,16 @@
|
||||
/*---------------------------------------------------------
|
||||
* Copyright (C) Microsoft Corporation. All rights reserved.
|
||||
*--------------------------------------------------------*/
|
||||
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import { add } from './math';
|
||||
|
||||
export function activate(context: vscode.ExtensionContext) {
|
||||
|
||||
const disposable = vscode.commands.registerCommand('extension.helloWebpack', () => {
|
||||
vscode.window.showInformationMessage(`41 + 1 = ${add(41, 1)}`);
|
||||
});
|
||||
|
||||
context.subscriptions.push(disposable);
|
||||
}
|
||||
/*---------------------------------------------------------
|
||||
* Copyright (C) Microsoft Corporation. All rights reserved.
|
||||
*--------------------------------------------------------*/
|
||||
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import { add } from './math';
|
||||
|
||||
export function activate(context: vscode.ExtensionContext) {
|
||||
|
||||
const disposable = vscode.commands.registerCommand('extension.helloWebpack', () => {
|
||||
vscode.window.showInformationMessage(`41 + 1 = ${add(41, 1)}`);
|
||||
});
|
||||
|
||||
context.subscriptions.push(disposable);
|
||||
}
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
/*---------------------------------------------------------
|
||||
* Copyright (C) Microsoft Corporation. All rights reserved.
|
||||
*--------------------------------------------------------*/
|
||||
|
||||
|
||||
export function add(a: number, b: number): number {
|
||||
return a + b;
|
||||
}
|
||||
|
||||
export function sub(a: number, b: number): number {
|
||||
return a - b;
|
||||
}
|
||||
/*---------------------------------------------------------
|
||||
* Copyright (C) Microsoft Corporation. All rights reserved.
|
||||
*--------------------------------------------------------*/
|
||||
|
||||
|
||||
export function add(a: number, b: number): number {
|
||||
return a + b;
|
||||
}
|
||||
|
||||
export function sub(a: number, b: number): number {
|
||||
return a - b;
|
||||
}
|
||||
|
||||
@ -1,385 +1,385 @@
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
export function activate(context: vscode.ExtensionContext) {
|
||||
context.subscriptions.push(
|
||||
vscode.commands.registerCommand('catCodicons.show', () => {
|
||||
CatCodiconsPanel.show(context.extensionUri);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
class CatCodiconsPanel {
|
||||
|
||||
public static readonly viewType = 'catCodicons';
|
||||
|
||||
public static show(extensionUri: vscode.Uri) {
|
||||
const column = vscode.window.activeTextEditor
|
||||
? vscode.window.activeTextEditor.viewColumn
|
||||
: undefined;
|
||||
|
||||
const panel = vscode.window.createWebviewPanel(
|
||||
CatCodiconsPanel.viewType,
|
||||
"Cat Codicons",
|
||||
column || vscode.ViewColumn.One
|
||||
);
|
||||
|
||||
panel.webview.html = this._getHtmlForWebview(panel.webview, extensionUri);
|
||||
}
|
||||
|
||||
private static _getHtmlForWebview(webview: vscode.Webview, extensionUri: vscode.Uri) {
|
||||
|
||||
// Get resource paths
|
||||
const styleUri = webview.asWebviewUri(vscode.Uri.joinPath(extensionUri, 'media', 'styles.css'));
|
||||
const codiconsUri = webview.asWebviewUri(vscode.Uri.joinPath(extensionUri, 'node_modules', '@vscode/codicons', 'dist', 'codicon.css'));
|
||||
|
||||
return `<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
|
||||
<!--
|
||||
Use a content security policy to only allow loading specific resources in the webview
|
||||
-->
|
||||
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; font-src ${webview.cspSource}; style-src ${webview.cspSource};">
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Cat Coding</title>
|
||||
|
||||
<link href="${styleUri}" rel="stylesheet" />
|
||||
<link href="${codiconsUri}" rel="stylesheet" />
|
||||
</head>
|
||||
<body>
|
||||
<h1>codicons</h1>
|
||||
<div id="icons">
|
||||
<div class="icon"><i class="codicon codicon-account"></i> account</div>
|
||||
<div class="icon"><i class="codicon codicon-activate-breakpoints"></i> activate-breakpoints</div>
|
||||
<div class="icon"><i class="codicon codicon-add"></i> add</div>
|
||||
<div class="icon"><i class="codicon codicon-archive"></i> archive</div>
|
||||
<div class="icon"><i class="codicon codicon-arrow-both"></i> arrow-both</div>
|
||||
<div class="icon"><i class="codicon codicon-arrow-down"></i> arrow-down</div>
|
||||
<div class="icon"><i class="codicon codicon-arrow-left"></i> arrow-left</div>
|
||||
<div class="icon"><i class="codicon codicon-arrow-right"></i> arrow-right</div>
|
||||
<div class="icon"><i class="codicon codicon-arrow-small-down"></i> arrow-small-down</div>
|
||||
<div class="icon"><i class="codicon codicon-arrow-small-left"></i> arrow-small-left</div>
|
||||
<div class="icon"><i class="codicon codicon-arrow-small-right"></i> arrow-small-right</div>
|
||||
<div class="icon"><i class="codicon codicon-arrow-small-up"></i> arrow-small-up</div>
|
||||
<div class="icon"><i class="codicon codicon-arrow-up"></i> arrow-up</div>
|
||||
<div class="icon"><i class="codicon codicon-beaker"></i> beaker</div>
|
||||
<div class="icon"><i class="codicon codicon-bell-dot"></i> bell-dot</div>
|
||||
<div class="icon"><i class="codicon codicon-bell"></i> bell</div>
|
||||
<div class="icon"><i class="codicon codicon-bold"></i> bold</div>
|
||||
<div class="icon"><i class="codicon codicon-book"></i> book</div>
|
||||
<div class="icon"><i class="codicon codicon-bookmark"></i> bookmark</div>
|
||||
<div class="icon"><i class="codicon codicon-briefcase"></i> briefcase</div>
|
||||
<div class="icon"><i class="codicon codicon-broadcast"></i> broadcast</div>
|
||||
<div class="icon"><i class="codicon codicon-browser"></i> browser</div>
|
||||
<div class="icon"><i class="codicon codicon-bug"></i> bug</div>
|
||||
<div class="icon"><i class="codicon codicon-calendar"></i> calendar</div>
|
||||
<div class="icon"><i class="codicon codicon-call-incoming"></i> call-incoming</div>
|
||||
<div class="icon"><i class="codicon codicon-call-outgoing"></i> call-outgoing</div>
|
||||
<div class="icon"><i class="codicon codicon-case-sensitive"></i> case-sensitive</div>
|
||||
<div class="icon"><i class="codicon codicon-check"></i> check</div>
|
||||
<div class="icon"><i class="codicon codicon-checklist"></i> checklist</div>
|
||||
<div class="icon"><i class="codicon codicon-chevron-down"></i> chevron-down</div>
|
||||
<div class="icon"><i class="codicon codicon-chevron-left"></i> chevron-left</div>
|
||||
<div class="icon"><i class="codicon codicon-chevron-right"></i> chevron-right</div>
|
||||
<div class="icon"><i class="codicon codicon-chevron-up"></i> chevron-up</div>
|
||||
<div class="icon"><i class="codicon codicon-chrome-close"></i> chrome-close</div>
|
||||
<div class="icon"><i class="codicon codicon-chrome-maximize"></i> chrome-maximize</div>
|
||||
<div class="icon"><i class="codicon codicon-chrome-minimize"></i> chrome-minimize</div>
|
||||
<div class="icon"><i class="codicon codicon-chrome-restore"></i> chrome-restore</div>
|
||||
<div class="icon"><i class="codicon codicon-circle-filled"></i> circle-filled</div>
|
||||
<div class="icon"><i class="codicon codicon-circle-outline"></i> circle-outline</div>
|
||||
<div class="icon"><i class="codicon codicon-circle-slash"></i> circle-slash</div>
|
||||
<div class="icon"><i class="codicon codicon-circuit-board"></i> circuit-board</div>
|
||||
<div class="icon"><i class="codicon codicon-clear-all"></i> clear-all</div>
|
||||
<div class="icon"><i class="codicon codicon-clippy"></i> clippy</div>
|
||||
<div class="icon"><i class="codicon codicon-close-all"></i> close-all</div>
|
||||
<div class="icon"><i class="codicon codicon-close"></i> close</div>
|
||||
<div class="icon"><i class="codicon codicon-cloud-download"></i> cloud-download</div>
|
||||
<div class="icon"><i class="codicon codicon-cloud-upload"></i> cloud-upload</div>
|
||||
<div class="icon"><i class="codicon codicon-cloud"></i> cloud</div>
|
||||
<div class="icon"><i class="codicon codicon-code"></i> code</div>
|
||||
<div class="icon"><i class="codicon codicon-collapse-all"></i> collapse-all</div>
|
||||
<div class="icon"><i class="codicon codicon-color-mode"></i> color-mode</div>
|
||||
<div class="icon"><i class="codicon codicon-comment-discussion"></i> comment-discussion</div>
|
||||
<div class="icon"><i class="codicon codicon-comment"></i> comment</div>
|
||||
<div class="icon"><i class="codicon codicon-credit-card"></i> credit-card</div>
|
||||
<div class="icon"><i class="codicon codicon-dash"></i> dash</div>
|
||||
<div class="icon"><i class="codicon codicon-dashboard"></i> dashboard</div>
|
||||
<div class="icon"><i class="codicon codicon-database"></i> database</div>
|
||||
<div class="icon"><i class="codicon codicon-debug-alt-small"></i> debug-alt-small</div>
|
||||
<div class="icon"><i class="codicon codicon-debug-alt"></i> debug-alt</div>
|
||||
<div class="icon"><i class="codicon codicon-debug-breakpoint-conditional-unverified"></i> debug-breakpoint-conditional-unverified</div>
|
||||
<div class="icon"><i class="codicon codicon-debug-breakpoint-conditional"></i> debug-breakpoint-conditional</div>
|
||||
<div class="icon"><i class="codicon codicon-debug-breakpoint-data-unverified"></i> debug-breakpoint-data-unverified</div>
|
||||
<div class="icon"><i class="codicon codicon-debug-breakpoint-data"></i> debug-breakpoint-data</div>
|
||||
<div class="icon"><i class="codicon codicon-debug-breakpoint-function-unverified"></i> debug-breakpoint-function-unverified</div>
|
||||
<div class="icon"><i class="codicon codicon-debug-breakpoint-function"></i> debug-breakpoint-function</div>
|
||||
<div class="icon"><i class="codicon codicon-debug-breakpoint-log-unverified"></i> debug-breakpoint-log-unverified</div>
|
||||
<div class="icon"><i class="codicon codicon-debug-breakpoint-log"></i> debug-breakpoint-log</div>
|
||||
<div class="icon"><i class="codicon codicon-debug-breakpoint-unsupported"></i> debug-breakpoint-unsupported</div>
|
||||
<div class="icon"><i class="codicon codicon-debug-console"></i> debug-console</div>
|
||||
<div class="icon"><i class="codicon codicon-debug-continue"></i> debug-continue</div>
|
||||
<div class="icon"><i class="codicon codicon-debug-disconnect"></i> debug-disconnect</div>
|
||||
<div class="icon"><i class="codicon codicon-debug-pause"></i> debug-pause</div>
|
||||
<div class="icon"><i class="codicon codicon-debug-restart-frame"></i> debug-restart-frame</div>
|
||||
<div class="icon"><i class="codicon codicon-debug-restart"></i> debug-restart</div>
|
||||
<div class="icon"><i class="codicon codicon-debug-reverse-continue"></i> debug-reverse-continue</div>
|
||||
<div class="icon"><i class="codicon codicon-debug-stackframe-active"></i> debug-stackframe-active</div>
|
||||
<div class="icon"><i class="codicon codicon-debug-stackframe-dot"></i> debug-stackframe-dot</div>
|
||||
<div class="icon"><i class="codicon codicon-debug-stackframe"></i> debug-stackframe</div>
|
||||
<div class="icon"><i class="codicon codicon-debug-start"></i> debug-start</div>
|
||||
<div class="icon"><i class="codicon codicon-debug-step-back"></i> debug-step-back</div>
|
||||
<div class="icon"><i class="codicon codicon-debug-step-into"></i> debug-step-into</div>
|
||||
<div class="icon"><i class="codicon codicon-debug-step-out"></i> debug-step-out</div>
|
||||
<div class="icon"><i class="codicon codicon-debug-step-over"></i> debug-step-over</div>
|
||||
<div class="icon"><i class="codicon codicon-debug-stop"></i> debug-stop</div>
|
||||
<div class="icon"><i class="codicon codicon-debug"></i> debug</div>
|
||||
<div class="icon"><i class="codicon codicon-desktop-download"></i> desktop-download</div>
|
||||
<div class="icon"><i class="codicon codicon-device-camera-video"></i> device-camera-video</div>
|
||||
<div class="icon"><i class="codicon codicon-device-camera"></i> device-camera</div>
|
||||
<div class="icon"><i class="codicon codicon-device-mobile"></i> device-mobile</div>
|
||||
<div class="icon"><i class="codicon codicon-diff-added"></i> diff-added</div>
|
||||
<div class="icon"><i class="codicon codicon-diff-ignored"></i> diff-ignored</div>
|
||||
<div class="icon"><i class="codicon codicon-diff-modified"></i> diff-modified</div>
|
||||
<div class="icon"><i class="codicon codicon-diff-removed"></i> diff-removed</div>
|
||||
<div class="icon"><i class="codicon codicon-diff-renamed"></i> diff-renamed</div>
|
||||
<div class="icon"><i class="codicon codicon-diff"></i> diff</div>
|
||||
<div class="icon"><i class="codicon codicon-discard"></i> discard</div>
|
||||
<div class="icon"><i class="codicon codicon-edit"></i> edit</div>
|
||||
<div class="icon"><i class="codicon codicon-editor-layout"></i> editor-layout</div>
|
||||
<div class="icon"><i class="codicon codicon-ellipsis"></i> ellipsis</div>
|
||||
<div class="icon"><i class="codicon codicon-empty-window"></i> empty-window</div>
|
||||
<div class="icon"><i class="codicon codicon-error"></i> error</div>
|
||||
<div class="icon"><i class="codicon codicon-exclude"></i> exclude</div>
|
||||
<div class="icon"><i class="codicon codicon-expand-all"></i> expand-all</div>
|
||||
<div class="icon"><i class="codicon codicon-extensions"></i> extensions</div>
|
||||
<div class="icon"><i class="codicon codicon-eye-closed"></i> eye-closed</div>
|
||||
<div class="icon"><i class="codicon codicon-eye"></i> eye</div>
|
||||
<div class="icon"><i class="codicon codicon-feedback"></i> feedback</div>
|
||||
<div class="icon"><i class="codicon codicon-file-binary"></i> file-binary</div>
|
||||
<div class="icon"><i class="codicon codicon-file-code"></i> file-code</div>
|
||||
<div class="icon"><i class="codicon codicon-file-media"></i> file-media</div>
|
||||
<div class="icon"><i class="codicon codicon-file-pdf"></i> file-pdf</div>
|
||||
<div class="icon"><i class="codicon codicon-file-submodule"></i> file-submodule</div>
|
||||
<div class="icon"><i class="codicon codicon-file-symlink-directory"></i> file-symlink-directory</div>
|
||||
<div class="icon"><i class="codicon codicon-file-symlink-file"></i> file-symlink-file</div>
|
||||
<div class="icon"><i class="codicon codicon-file-zip"></i> file-zip</div>
|
||||
<div class="icon"><i class="codicon codicon-file"></i> file</div>
|
||||
<div class="icon"><i class="codicon codicon-files"></i> files</div>
|
||||
<div class="icon"><i class="codicon codicon-filter"></i> filter</div>
|
||||
<div class="icon"><i class="codicon codicon-flame"></i> flame</div>
|
||||
<div class="icon"><i class="codicon codicon-fold-down"></i> fold-down</div>
|
||||
<div class="icon"><i class="codicon codicon-fold-up"></i> fold-up</div>
|
||||
<div class="icon"><i class="codicon codicon-fold"></i> fold</div>
|
||||
<div class="icon"><i class="codicon codicon-folder-active"></i> folder-active</div>
|
||||
<div class="icon"><i class="codicon codicon-folder-opened"></i> folder-opened</div>
|
||||
<div class="icon"><i class="codicon codicon-folder"></i> folder</div>
|
||||
<div class="icon"><i class="codicon codicon-gear"></i> gear</div>
|
||||
<div class="icon"><i class="codicon codicon-gift"></i> gift</div>
|
||||
<div class="icon"><i class="codicon codicon-gist-secret"></i> gist-secret</div>
|
||||
<div class="icon"><i class="codicon codicon-gist"></i> gist</div>
|
||||
<div class="icon"><i class="codicon codicon-git-commit"></i> git-commit</div>
|
||||
<div class="icon"><i class="codicon codicon-git-compare"></i> git-compare</div>
|
||||
<div class="icon"><i class="codicon codicon-git-merge"></i> git-merge</div>
|
||||
<div class="icon"><i class="codicon codicon-git-pull-request"></i> git-pull-request</div>
|
||||
<div class="icon"><i class="codicon codicon-github-action"></i> github-action</div>
|
||||
<div class="icon"><i class="codicon codicon-github-alt"></i> github-alt</div>
|
||||
<div class="icon"><i class="codicon codicon-github-inverted"></i> github-inverted</div>
|
||||
<div class="icon"><i class="codicon codicon-github"></i> github</div>
|
||||
<div class="icon"><i class="codicon codicon-globe"></i> globe</div>
|
||||
<div class="icon"><i class="codicon codicon-go-to-file"></i> go-to-file</div>
|
||||
<div class="icon"><i class="codicon codicon-grabber"></i> grabber</div>
|
||||
<div class="icon"><i class="codicon codicon-graph"></i> graph</div>
|
||||
<div class="icon"><i class="codicon codicon-gripper"></i> gripper</div>
|
||||
<div class="icon"><i class="codicon codicon-group-by-ref-type"></i> group-by-ref-type</div>
|
||||
<div class="icon"><i class="codicon codicon-heart"></i> heart</div>
|
||||
<div class="icon"><i class="codicon codicon-history"></i> history</div>
|
||||
<div class="icon"><i class="codicon codicon-home"></i> home</div>
|
||||
<div class="icon"><i class="codicon codicon-horizontal-rule"></i> horizontal-rule</div>
|
||||
<div class="icon"><i class="codicon codicon-hubot"></i> hubot</div>
|
||||
<div class="icon"><i class="codicon codicon-inbox"></i> inbox</div>
|
||||
<div class="icon"><i class="codicon codicon-info"></i> info</div>
|
||||
<div class="icon"><i class="codicon codicon-issue-closed"></i> issue-closed</div>
|
||||
<div class="icon"><i class="codicon codicon-issue-reopened"></i> issue-reopened</div>
|
||||
<div class="icon"><i class="codicon codicon-issues"></i> issues</div>
|
||||
<div class="icon"><i class="codicon codicon-italic"></i> italic</div>
|
||||
<div class="icon"><i class="codicon codicon-jersey"></i> jersey</div>
|
||||
<div class="icon"><i class="codicon codicon-json"></i> json</div>
|
||||
<div class="icon"><i class="codicon codicon-kebab-vertical"></i> kebab-vertical</div>
|
||||
<div class="icon"><i class="codicon codicon-key"></i> key</div>
|
||||
<div class="icon"><i class="codicon codicon-law"></i> law</div>
|
||||
<div class="icon"><i class="codicon codicon-library"></i> library</div>
|
||||
<div class="icon"><i class="codicon codicon-lightbulb-autofix"></i> lightbulb-autofix</div>
|
||||
<div class="icon"><i class="codicon codicon-lightbulb"></i> lightbulb</div>
|
||||
<div class="icon"><i class="codicon codicon-link-external"></i> link-external</div>
|
||||
<div class="icon"><i class="codicon codicon-link"></i> link</div>
|
||||
<div class="icon"><i class="codicon codicon-list-filter"></i> list-filter</div>
|
||||
<div class="icon"><i class="codicon codicon-list-flat"></i> list-flat</div>
|
||||
<div class="icon"><i class="codicon codicon-list-ordered"></i> list-ordered</div>
|
||||
<div class="icon"><i class="codicon codicon-list-selection"></i> list-selection</div>
|
||||
<div class="icon"><i class="codicon codicon-list-tree"></i> list-tree</div>
|
||||
<div class="icon"><i class="codicon codicon-list-unordered"></i> list-unordered</div>
|
||||
<div class="icon"><i class="codicon codicon-live-share"></i> live-share</div>
|
||||
<div class="icon"><i class="codicon codicon-loading"></i> loading</div>
|
||||
<div class="icon"><i class="codicon codicon-location"></i> location</div>
|
||||
<div class="icon"><i class="codicon codicon-lock"></i> lock</div>
|
||||
<div class="icon"><i class="codicon codicon-mail-read"></i> mail-read</div>
|
||||
<div class="icon"><i class="codicon codicon-mail"></i> mail</div>
|
||||
<div class="icon"><i class="codicon codicon-markdown"></i> markdown</div>
|
||||
<div class="icon"><i class="codicon codicon-megaphone"></i> megaphone</div>
|
||||
<div class="icon"><i class="codicon codicon-mention"></i> mention</div>
|
||||
<div class="icon"><i class="codicon codicon-menu"></i> menu</div>
|
||||
<div class="icon"><i class="codicon codicon-merge"></i> merge</div>
|
||||
<div class="icon"><i class="codicon codicon-milestone"></i> milestone</div>
|
||||
<div class="icon"><i class="codicon codicon-mirror"></i> mirror</div>
|
||||
<div class="icon"><i class="codicon codicon-mortar-board"></i> mortar-board</div>
|
||||
<div class="icon"><i class="codicon codicon-move"></i> move</div>
|
||||
<div class="icon"><i class="codicon codicon-multiple-windows"></i> multiple-windows</div>
|
||||
<div class="icon"><i class="codicon codicon-mute"></i> mute</div>
|
||||
<div class="icon"><i class="codicon codicon-new-file"></i> new-file</div>
|
||||
<div class="icon"><i class="codicon codicon-new-folder"></i> new-folder</div>
|
||||
<div class="icon"><i class="codicon codicon-no-newline"></i> no-newline</div>
|
||||
<div class="icon"><i class="codicon codicon-note"></i> note</div>
|
||||
<div class="icon"><i class="codicon codicon-octoface"></i> octoface</div>
|
||||
<div class="icon"><i class="codicon codicon-open-preview"></i> open-preview</div>
|
||||
<div class="icon"><i class="codicon codicon-organization"></i> organization</div>
|
||||
<div class="icon"><i class="codicon codicon-output"></i> output</div>
|
||||
<div class="icon"><i class="codicon codicon-package"></i> package</div>
|
||||
<div class="icon"><i class="codicon codicon-paintcan"></i> paintcan</div>
|
||||
<div class="icon"><i class="codicon codicon-pass"></i> pass</div>
|
||||
<div class="icon"><i class="codicon codicon-person"></i> person</div>
|
||||
<div class="icon"><i class="codicon codicon-pin"></i> pin</div>
|
||||
<div class="icon"><i class="codicon codicon-pinned"></i> pinned</div>
|
||||
<div class="icon"><i class="codicon codicon-play-circle"></i> play-circle</div>
|
||||
<div class="icon"><i class="codicon codicon-play"></i> play</div>
|
||||
<div class="icon"><i class="codicon codicon-plug"></i> plug</div>
|
||||
<div class="icon"><i class="codicon codicon-preserve-case"></i> preserve-case</div>
|
||||
<div class="icon"><i class="codicon codicon-preview"></i> preview</div>
|
||||
<div class="icon"><i class="codicon codicon-primitive-square"></i> primitive-square</div>
|
||||
<div class="icon"><i class="codicon codicon-project"></i> project</div>
|
||||
<div class="icon"><i class="codicon codicon-pulse"></i> pulse</div>
|
||||
<div class="icon"><i class="codicon codicon-question"></i> question</div>
|
||||
<div class="icon"><i class="codicon codicon-quote"></i> quote</div>
|
||||
<div class="icon"><i class="codicon codicon-radio-tower"></i> radio-tower</div>
|
||||
<div class="icon"><i class="codicon codicon-reactions"></i> reactions</div>
|
||||
<div class="icon"><i class="codicon codicon-record-keys"></i> record-keys</div>
|
||||
<div class="icon"><i class="codicon codicon-record"></i> record</div>
|
||||
<div class="icon"><i class="codicon codicon-references"></i> references</div>
|
||||
<div class="icon"><i class="codicon codicon-refresh"></i> refresh</div>
|
||||
<div class="icon"><i class="codicon codicon-regex"></i> regex</div>
|
||||
<div class="icon"><i class="codicon codicon-remote-explorer"></i> remote-explorer</div>
|
||||
<div class="icon"><i class="codicon codicon-remote"></i> remote</div>
|
||||
<div class="icon"><i class="codicon codicon-remove"></i> remove</div>
|
||||
<div class="icon"><i class="codicon codicon-replace-all"></i> replace-all</div>
|
||||
<div class="icon"><i class="codicon codicon-replace"></i> replace</div>
|
||||
<div class="icon"><i class="codicon codicon-reply"></i> reply</div>
|
||||
<div class="icon"><i class="codicon codicon-repo-clone"></i> repo-clone</div>
|
||||
<div class="icon"><i class="codicon codicon-repo-force-push"></i> repo-force-push</div>
|
||||
<div class="icon"><i class="codicon codicon-repo-forked"></i> repo-forked</div>
|
||||
<div class="icon"><i class="codicon codicon-repo-pull"></i> repo-pull</div>
|
||||
<div class="icon"><i class="codicon codicon-repo-push"></i> repo-push</div>
|
||||
<div class="icon"><i class="codicon codicon-repo"></i> repo</div>
|
||||
<div class="icon"><i class="codicon codicon-report"></i> report</div>
|
||||
<div class="icon"><i class="codicon codicon-request-changes"></i> request-changes</div>
|
||||
<div class="icon"><i class="codicon codicon-rocket"></i> rocket</div>
|
||||
<div class="icon"><i class="codicon codicon-root-folder-opened"></i> root-folder-opened</div>
|
||||
<div class="icon"><i class="codicon codicon-root-folder"></i> root-folder</div>
|
||||
<div class="icon"><i class="codicon codicon-rss"></i> rss</div>
|
||||
<div class="icon"><i class="codicon codicon-ruby"></i> ruby</div>
|
||||
<div class="icon"><i class="codicon codicon-run-all"></i> run-all</div>
|
||||
<div class="icon"><i class="codicon codicon-save-all"></i> save-all</div>
|
||||
<div class="icon"><i class="codicon codicon-save-as"></i> save-as</div>
|
||||
<div class="icon"><i class="codicon codicon-save"></i> save</div>
|
||||
<div class="icon"><i class="codicon codicon-screen-full"></i> screen-full</div>
|
||||
<div class="icon"><i class="codicon codicon-screen-normal"></i> screen-normal</div>
|
||||
<div class="icon"><i class="codicon codicon-search-stop"></i> search-stop</div>
|
||||
<div class="icon"><i class="codicon codicon-search"></i> search</div>
|
||||
<div class="icon"><i class="codicon codicon-server-environment"></i> server-environment</div>
|
||||
<div class="icon"><i class="codicon codicon-server-process"></i> server-process</div>
|
||||
<div class="icon"><i class="codicon codicon-server"></i> server</div>
|
||||
<div class="icon"><i class="codicon codicon-settings-gear"></i> settings-gear</div>
|
||||
<div class="icon"><i class="codicon codicon-settings"></i> settings</div>
|
||||
<div class="icon"><i class="codicon codicon-shield"></i> shield</div>
|
||||
<div class="icon"><i class="codicon codicon-sign-in"></i> sign-in</div>
|
||||
<div class="icon"><i class="codicon codicon-sign-out"></i> sign-out</div>
|
||||
<div class="icon"><i class="codicon codicon-smiley"></i> smiley</div>
|
||||
<div class="icon"><i class="codicon codicon-sort-precedence"></i> sort-precedence</div>
|
||||
<div class="icon"><i class="codicon codicon-source-control"></i> source-control</div>
|
||||
<div class="icon"><i class="codicon codicon-split-horizontal"></i> split-horizontal</div>
|
||||
<div class="icon"><i class="codicon codicon-split-vertical"></i> split-vertical</div>
|
||||
<div class="icon"><i class="codicon codicon-squirrel"></i> squirrel</div>
|
||||
<div class="icon"><i class="codicon codicon-star-empty"></i> star-empty</div>
|
||||
<div class="icon"><i class="codicon codicon-star-full"></i> star-full</div>
|
||||
<div class="icon"><i class="codicon codicon-star-half"></i> star-half</div>
|
||||
<div class="icon"><i class="codicon codicon-stop-circle"></i> stop-circle</div>
|
||||
<div class="icon"><i class="codicon codicon-symbol-array"></i> symbol-array</div>
|
||||
<div class="icon"><i class="codicon codicon-symbol-boolean"></i> symbol-boolean</div>
|
||||
<div class="icon"><i class="codicon codicon-symbol-class"></i> symbol-class</div>
|
||||
<div class="icon"><i class="codicon codicon-symbol-color"></i> symbol-color</div>
|
||||
<div class="icon"><i class="codicon codicon-symbol-constant"></i> symbol-constant</div>
|
||||
<div class="icon"><i class="codicon codicon-symbol-enum-member"></i> symbol-enum-member</div>
|
||||
<div class="icon"><i class="codicon codicon-symbol-enum"></i> symbol-enum</div>
|
||||
<div class="icon"><i class="codicon codicon-symbol-event"></i> symbol-event</div>
|
||||
<div class="icon"><i class="codicon codicon-symbol-field"></i> symbol-field</div>
|
||||
<div class="icon"><i class="codicon codicon-symbol-file"></i> symbol-file</div>
|
||||
<div class="icon"><i class="codicon codicon-symbol-interface"></i> symbol-interface</div>
|
||||
<div class="icon"><i class="codicon codicon-symbol-key"></i> symbol-key</div>
|
||||
<div class="icon"><i class="codicon codicon-symbol-keyword"></i> symbol-keyword</div>
|
||||
<div class="icon"><i class="codicon codicon-symbol-method"></i> symbol-method</div>
|
||||
<div class="icon"><i class="codicon codicon-symbol-misc"></i> symbol-misc</div>
|
||||
<div class="icon"><i class="codicon codicon-symbol-namespace"></i> symbol-namespace</div>
|
||||
<div class="icon"><i class="codicon codicon-symbol-numeric"></i> symbol-numeric</div>
|
||||
<div class="icon"><i class="codicon codicon-symbol-operator"></i> symbol-operator</div>
|
||||
<div class="icon"><i class="codicon codicon-symbol-parameter"></i> symbol-parameter</div>
|
||||
<div class="icon"><i class="codicon codicon-symbol-property"></i> symbol-property</div>
|
||||
<div class="icon"><i class="codicon codicon-symbol-ruler"></i> symbol-ruler</div>
|
||||
<div class="icon"><i class="codicon codicon-symbol-snippet"></i> symbol-snippet</div>
|
||||
<div class="icon"><i class="codicon codicon-symbol-string"></i> symbol-string</div>
|
||||
<div class="icon"><i class="codicon codicon-symbol-structure"></i> symbol-structure</div>
|
||||
<div class="icon"><i class="codicon codicon-symbol-variable"></i> symbol-variable</div>
|
||||
<div class="icon"><i class="codicon codicon-sync-ignored"></i> sync-ignored</div>
|
||||
<div class="icon"><i class="codicon codicon-sync"></i> sync</div>
|
||||
<div class="icon"><i class="codicon codicon-tag"></i> tag</div>
|
||||
<div class="icon"><i class="codicon codicon-tasklist"></i> tasklist</div>
|
||||
<div class="icon"><i class="codicon codicon-telescope"></i> telescope</div>
|
||||
<div class="icon"><i class="codicon codicon-terminal"></i> terminal</div>
|
||||
<div class="icon"><i class="codicon codicon-text-size"></i> text-size</div>
|
||||
<div class="icon"><i class="codicon codicon-three-bars"></i> three-bars</div>
|
||||
<div class="icon"><i class="codicon codicon-thumbsdown"></i> thumbsdown</div>
|
||||
<div class="icon"><i class="codicon codicon-thumbsup"></i> thumbsup</div>
|
||||
<div class="icon"><i class="codicon codicon-tools"></i> tools</div>
|
||||
<div class="icon"><i class="codicon codicon-trash"></i> trash</div>
|
||||
<div class="icon"><i class="codicon codicon-triangle-down"></i> triangle-down</div>
|
||||
<div class="icon"><i class="codicon codicon-triangle-left"></i> triangle-left</div>
|
||||
<div class="icon"><i class="codicon codicon-triangle-right"></i> triangle-right</div>
|
||||
<div class="icon"><i class="codicon codicon-triangle-up"></i> triangle-up</div>
|
||||
<div class="icon"><i class="codicon codicon-twitter"></i> twitter</div>
|
||||
<div class="icon"><i class="codicon codicon-unfold"></i> unfold</div>
|
||||
<div class="icon"><i class="codicon codicon-ungroup-by-ref-type"></i> ungroup-by-ref-type</div>
|
||||
<div class="icon"><i class="codicon codicon-unlock"></i> unlock</div>
|
||||
<div class="icon"><i class="codicon codicon-unmute"></i> unmute</div>
|
||||
<div class="icon"><i class="codicon codicon-unverified"></i> unverified</div>
|
||||
<div class="icon"><i class="codicon codicon-verified"></i> verified</div>
|
||||
<div class="icon"><i class="codicon codicon-versions"></i> versions</div>
|
||||
<div class="icon"><i class="codicon codicon-vm-active"></i> vm-active</div>
|
||||
<div class="icon"><i class="codicon codicon-vm-connect"></i> vm-connect</div>
|
||||
<div class="icon"><i class="codicon codicon-vm-outline"></i> vm-outline</div>
|
||||
<div class="icon"><i class="codicon codicon-vm-running"></i> vm-running</div>
|
||||
<div class="icon"><i class="codicon codicon-vm"></i> vm</div>
|
||||
<div class="icon"><i class="codicon codicon-warning"></i> warning</div>
|
||||
<div class="icon"><i class="codicon codicon-watch"></i> watch</div>
|
||||
<div class="icon"><i class="codicon codicon-whitespace"></i> whitespace</div>
|
||||
<div class="icon"><i class="codicon codicon-whole-word"></i> whole-word</div>
|
||||
<div class="icon"><i class="codicon codicon-window"></i> window</div>
|
||||
<div class="icon"><i class="codicon codicon-word-wrap"></i> word-wrap</div>
|
||||
<div class="icon"><i class="codicon codicon-zoom-in"></i> zoom-in</div>
|
||||
<div class="icon"><i class="codicon codicon-zoom-out"></i> zoom-out</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>`;
|
||||
}
|
||||
}
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
export function activate(context: vscode.ExtensionContext) {
|
||||
context.subscriptions.push(
|
||||
vscode.commands.registerCommand('catCodicons.show', () => {
|
||||
CatCodiconsPanel.show(context.extensionUri);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
class CatCodiconsPanel {
|
||||
|
||||
public static readonly viewType = 'catCodicons';
|
||||
|
||||
public static show(extensionUri: vscode.Uri) {
|
||||
const column = vscode.window.activeTextEditor
|
||||
? vscode.window.activeTextEditor.viewColumn
|
||||
: undefined;
|
||||
|
||||
const panel = vscode.window.createWebviewPanel(
|
||||
CatCodiconsPanel.viewType,
|
||||
"Cat Codicons",
|
||||
column || vscode.ViewColumn.One
|
||||
);
|
||||
|
||||
panel.webview.html = this._getHtmlForWebview(panel.webview, extensionUri);
|
||||
}
|
||||
|
||||
private static _getHtmlForWebview(webview: vscode.Webview, extensionUri: vscode.Uri) {
|
||||
|
||||
// Get resource paths
|
||||
const styleUri = webview.asWebviewUri(vscode.Uri.joinPath(extensionUri, 'media', 'styles.css'));
|
||||
const codiconsUri = webview.asWebviewUri(vscode.Uri.joinPath(extensionUri, 'node_modules', '@vscode/codicons', 'dist', 'codicon.css'));
|
||||
|
||||
return `<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
|
||||
<!--
|
||||
Use a content security policy to only allow loading specific resources in the webview
|
||||
-->
|
||||
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; font-src ${webview.cspSource}; style-src ${webview.cspSource};">
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Cat Coding</title>
|
||||
|
||||
<link href="${styleUri}" rel="stylesheet" />
|
||||
<link href="${codiconsUri}" rel="stylesheet" />
|
||||
</head>
|
||||
<body>
|
||||
<h1>codicons</h1>
|
||||
<div id="icons">
|
||||
<div class="icon"><i class="codicon codicon-account"></i> account</div>
|
||||
<div class="icon"><i class="codicon codicon-activate-breakpoints"></i> activate-breakpoints</div>
|
||||
<div class="icon"><i class="codicon codicon-add"></i> add</div>
|
||||
<div class="icon"><i class="codicon codicon-archive"></i> archive</div>
|
||||
<div class="icon"><i class="codicon codicon-arrow-both"></i> arrow-both</div>
|
||||
<div class="icon"><i class="codicon codicon-arrow-down"></i> arrow-down</div>
|
||||
<div class="icon"><i class="codicon codicon-arrow-left"></i> arrow-left</div>
|
||||
<div class="icon"><i class="codicon codicon-arrow-right"></i> arrow-right</div>
|
||||
<div class="icon"><i class="codicon codicon-arrow-small-down"></i> arrow-small-down</div>
|
||||
<div class="icon"><i class="codicon codicon-arrow-small-left"></i> arrow-small-left</div>
|
||||
<div class="icon"><i class="codicon codicon-arrow-small-right"></i> arrow-small-right</div>
|
||||
<div class="icon"><i class="codicon codicon-arrow-small-up"></i> arrow-small-up</div>
|
||||
<div class="icon"><i class="codicon codicon-arrow-up"></i> arrow-up</div>
|
||||
<div class="icon"><i class="codicon codicon-beaker"></i> beaker</div>
|
||||
<div class="icon"><i class="codicon codicon-bell-dot"></i> bell-dot</div>
|
||||
<div class="icon"><i class="codicon codicon-bell"></i> bell</div>
|
||||
<div class="icon"><i class="codicon codicon-bold"></i> bold</div>
|
||||
<div class="icon"><i class="codicon codicon-book"></i> book</div>
|
||||
<div class="icon"><i class="codicon codicon-bookmark"></i> bookmark</div>
|
||||
<div class="icon"><i class="codicon codicon-briefcase"></i> briefcase</div>
|
||||
<div class="icon"><i class="codicon codicon-broadcast"></i> broadcast</div>
|
||||
<div class="icon"><i class="codicon codicon-browser"></i> browser</div>
|
||||
<div class="icon"><i class="codicon codicon-bug"></i> bug</div>
|
||||
<div class="icon"><i class="codicon codicon-calendar"></i> calendar</div>
|
||||
<div class="icon"><i class="codicon codicon-call-incoming"></i> call-incoming</div>
|
||||
<div class="icon"><i class="codicon codicon-call-outgoing"></i> call-outgoing</div>
|
||||
<div class="icon"><i class="codicon codicon-case-sensitive"></i> case-sensitive</div>
|
||||
<div class="icon"><i class="codicon codicon-check"></i> check</div>
|
||||
<div class="icon"><i class="codicon codicon-checklist"></i> checklist</div>
|
||||
<div class="icon"><i class="codicon codicon-chevron-down"></i> chevron-down</div>
|
||||
<div class="icon"><i class="codicon codicon-chevron-left"></i> chevron-left</div>
|
||||
<div class="icon"><i class="codicon codicon-chevron-right"></i> chevron-right</div>
|
||||
<div class="icon"><i class="codicon codicon-chevron-up"></i> chevron-up</div>
|
||||
<div class="icon"><i class="codicon codicon-chrome-close"></i> chrome-close</div>
|
||||
<div class="icon"><i class="codicon codicon-chrome-maximize"></i> chrome-maximize</div>
|
||||
<div class="icon"><i class="codicon codicon-chrome-minimize"></i> chrome-minimize</div>
|
||||
<div class="icon"><i class="codicon codicon-chrome-restore"></i> chrome-restore</div>
|
||||
<div class="icon"><i class="codicon codicon-circle-filled"></i> circle-filled</div>
|
||||
<div class="icon"><i class="codicon codicon-circle-outline"></i> circle-outline</div>
|
||||
<div class="icon"><i class="codicon codicon-circle-slash"></i> circle-slash</div>
|
||||
<div class="icon"><i class="codicon codicon-circuit-board"></i> circuit-board</div>
|
||||
<div class="icon"><i class="codicon codicon-clear-all"></i> clear-all</div>
|
||||
<div class="icon"><i class="codicon codicon-clippy"></i> clippy</div>
|
||||
<div class="icon"><i class="codicon codicon-close-all"></i> close-all</div>
|
||||
<div class="icon"><i class="codicon codicon-close"></i> close</div>
|
||||
<div class="icon"><i class="codicon codicon-cloud-download"></i> cloud-download</div>
|
||||
<div class="icon"><i class="codicon codicon-cloud-upload"></i> cloud-upload</div>
|
||||
<div class="icon"><i class="codicon codicon-cloud"></i> cloud</div>
|
||||
<div class="icon"><i class="codicon codicon-code"></i> code</div>
|
||||
<div class="icon"><i class="codicon codicon-collapse-all"></i> collapse-all</div>
|
||||
<div class="icon"><i class="codicon codicon-color-mode"></i> color-mode</div>
|
||||
<div class="icon"><i class="codicon codicon-comment-discussion"></i> comment-discussion</div>
|
||||
<div class="icon"><i class="codicon codicon-comment"></i> comment</div>
|
||||
<div class="icon"><i class="codicon codicon-credit-card"></i> credit-card</div>
|
||||
<div class="icon"><i class="codicon codicon-dash"></i> dash</div>
|
||||
<div class="icon"><i class="codicon codicon-dashboard"></i> dashboard</div>
|
||||
<div class="icon"><i class="codicon codicon-database"></i> database</div>
|
||||
<div class="icon"><i class="codicon codicon-debug-alt-small"></i> debug-alt-small</div>
|
||||
<div class="icon"><i class="codicon codicon-debug-alt"></i> debug-alt</div>
|
||||
<div class="icon"><i class="codicon codicon-debug-breakpoint-conditional-unverified"></i> debug-breakpoint-conditional-unverified</div>
|
||||
<div class="icon"><i class="codicon codicon-debug-breakpoint-conditional"></i> debug-breakpoint-conditional</div>
|
||||
<div class="icon"><i class="codicon codicon-debug-breakpoint-data-unverified"></i> debug-breakpoint-data-unverified</div>
|
||||
<div class="icon"><i class="codicon codicon-debug-breakpoint-data"></i> debug-breakpoint-data</div>
|
||||
<div class="icon"><i class="codicon codicon-debug-breakpoint-function-unverified"></i> debug-breakpoint-function-unverified</div>
|
||||
<div class="icon"><i class="codicon codicon-debug-breakpoint-function"></i> debug-breakpoint-function</div>
|
||||
<div class="icon"><i class="codicon codicon-debug-breakpoint-log-unverified"></i> debug-breakpoint-log-unverified</div>
|
||||
<div class="icon"><i class="codicon codicon-debug-breakpoint-log"></i> debug-breakpoint-log</div>
|
||||
<div class="icon"><i class="codicon codicon-debug-breakpoint-unsupported"></i> debug-breakpoint-unsupported</div>
|
||||
<div class="icon"><i class="codicon codicon-debug-console"></i> debug-console</div>
|
||||
<div class="icon"><i class="codicon codicon-debug-continue"></i> debug-continue</div>
|
||||
<div class="icon"><i class="codicon codicon-debug-disconnect"></i> debug-disconnect</div>
|
||||
<div class="icon"><i class="codicon codicon-debug-pause"></i> debug-pause</div>
|
||||
<div class="icon"><i class="codicon codicon-debug-restart-frame"></i> debug-restart-frame</div>
|
||||
<div class="icon"><i class="codicon codicon-debug-restart"></i> debug-restart</div>
|
||||
<div class="icon"><i class="codicon codicon-debug-reverse-continue"></i> debug-reverse-continue</div>
|
||||
<div class="icon"><i class="codicon codicon-debug-stackframe-active"></i> debug-stackframe-active</div>
|
||||
<div class="icon"><i class="codicon codicon-debug-stackframe-dot"></i> debug-stackframe-dot</div>
|
||||
<div class="icon"><i class="codicon codicon-debug-stackframe"></i> debug-stackframe</div>
|
||||
<div class="icon"><i class="codicon codicon-debug-start"></i> debug-start</div>
|
||||
<div class="icon"><i class="codicon codicon-debug-step-back"></i> debug-step-back</div>
|
||||
<div class="icon"><i class="codicon codicon-debug-step-into"></i> debug-step-into</div>
|
||||
<div class="icon"><i class="codicon codicon-debug-step-out"></i> debug-step-out</div>
|
||||
<div class="icon"><i class="codicon codicon-debug-step-over"></i> debug-step-over</div>
|
||||
<div class="icon"><i class="codicon codicon-debug-stop"></i> debug-stop</div>
|
||||
<div class="icon"><i class="codicon codicon-debug"></i> debug</div>
|
||||
<div class="icon"><i class="codicon codicon-desktop-download"></i> desktop-download</div>
|
||||
<div class="icon"><i class="codicon codicon-device-camera-video"></i> device-camera-video</div>
|
||||
<div class="icon"><i class="codicon codicon-device-camera"></i> device-camera</div>
|
||||
<div class="icon"><i class="codicon codicon-device-mobile"></i> device-mobile</div>
|
||||
<div class="icon"><i class="codicon codicon-diff-added"></i> diff-added</div>
|
||||
<div class="icon"><i class="codicon codicon-diff-ignored"></i> diff-ignored</div>
|
||||
<div class="icon"><i class="codicon codicon-diff-modified"></i> diff-modified</div>
|
||||
<div class="icon"><i class="codicon codicon-diff-removed"></i> diff-removed</div>
|
||||
<div class="icon"><i class="codicon codicon-diff-renamed"></i> diff-renamed</div>
|
||||
<div class="icon"><i class="codicon codicon-diff"></i> diff</div>
|
||||
<div class="icon"><i class="codicon codicon-discard"></i> discard</div>
|
||||
<div class="icon"><i class="codicon codicon-edit"></i> edit</div>
|
||||
<div class="icon"><i class="codicon codicon-editor-layout"></i> editor-layout</div>
|
||||
<div class="icon"><i class="codicon codicon-ellipsis"></i> ellipsis</div>
|
||||
<div class="icon"><i class="codicon codicon-empty-window"></i> empty-window</div>
|
||||
<div class="icon"><i class="codicon codicon-error"></i> error</div>
|
||||
<div class="icon"><i class="codicon codicon-exclude"></i> exclude</div>
|
||||
<div class="icon"><i class="codicon codicon-expand-all"></i> expand-all</div>
|
||||
<div class="icon"><i class="codicon codicon-extensions"></i> extensions</div>
|
||||
<div class="icon"><i class="codicon codicon-eye-closed"></i> eye-closed</div>
|
||||
<div class="icon"><i class="codicon codicon-eye"></i> eye</div>
|
||||
<div class="icon"><i class="codicon codicon-feedback"></i> feedback</div>
|
||||
<div class="icon"><i class="codicon codicon-file-binary"></i> file-binary</div>
|
||||
<div class="icon"><i class="codicon codicon-file-code"></i> file-code</div>
|
||||
<div class="icon"><i class="codicon codicon-file-media"></i> file-media</div>
|
||||
<div class="icon"><i class="codicon codicon-file-pdf"></i> file-pdf</div>
|
||||
<div class="icon"><i class="codicon codicon-file-submodule"></i> file-submodule</div>
|
||||
<div class="icon"><i class="codicon codicon-file-symlink-directory"></i> file-symlink-directory</div>
|
||||
<div class="icon"><i class="codicon codicon-file-symlink-file"></i> file-symlink-file</div>
|
||||
<div class="icon"><i class="codicon codicon-file-zip"></i> file-zip</div>
|
||||
<div class="icon"><i class="codicon codicon-file"></i> file</div>
|
||||
<div class="icon"><i class="codicon codicon-files"></i> files</div>
|
||||
<div class="icon"><i class="codicon codicon-filter"></i> filter</div>
|
||||
<div class="icon"><i class="codicon codicon-flame"></i> flame</div>
|
||||
<div class="icon"><i class="codicon codicon-fold-down"></i> fold-down</div>
|
||||
<div class="icon"><i class="codicon codicon-fold-up"></i> fold-up</div>
|
||||
<div class="icon"><i class="codicon codicon-fold"></i> fold</div>
|
||||
<div class="icon"><i class="codicon codicon-folder-active"></i> folder-active</div>
|
||||
<div class="icon"><i class="codicon codicon-folder-opened"></i> folder-opened</div>
|
||||
<div class="icon"><i class="codicon codicon-folder"></i> folder</div>
|
||||
<div class="icon"><i class="codicon codicon-gear"></i> gear</div>
|
||||
<div class="icon"><i class="codicon codicon-gift"></i> gift</div>
|
||||
<div class="icon"><i class="codicon codicon-gist-secret"></i> gist-secret</div>
|
||||
<div class="icon"><i class="codicon codicon-gist"></i> gist</div>
|
||||
<div class="icon"><i class="codicon codicon-git-commit"></i> git-commit</div>
|
||||
<div class="icon"><i class="codicon codicon-git-compare"></i> git-compare</div>
|
||||
<div class="icon"><i class="codicon codicon-git-merge"></i> git-merge</div>
|
||||
<div class="icon"><i class="codicon codicon-git-pull-request"></i> git-pull-request</div>
|
||||
<div class="icon"><i class="codicon codicon-github-action"></i> github-action</div>
|
||||
<div class="icon"><i class="codicon codicon-github-alt"></i> github-alt</div>
|
||||
<div class="icon"><i class="codicon codicon-github-inverted"></i> github-inverted</div>
|
||||
<div class="icon"><i class="codicon codicon-github"></i> github</div>
|
||||
<div class="icon"><i class="codicon codicon-globe"></i> globe</div>
|
||||
<div class="icon"><i class="codicon codicon-go-to-file"></i> go-to-file</div>
|
||||
<div class="icon"><i class="codicon codicon-grabber"></i> grabber</div>
|
||||
<div class="icon"><i class="codicon codicon-graph"></i> graph</div>
|
||||
<div class="icon"><i class="codicon codicon-gripper"></i> gripper</div>
|
||||
<div class="icon"><i class="codicon codicon-group-by-ref-type"></i> group-by-ref-type</div>
|
||||
<div class="icon"><i class="codicon codicon-heart"></i> heart</div>
|
||||
<div class="icon"><i class="codicon codicon-history"></i> history</div>
|
||||
<div class="icon"><i class="codicon codicon-home"></i> home</div>
|
||||
<div class="icon"><i class="codicon codicon-horizontal-rule"></i> horizontal-rule</div>
|
||||
<div class="icon"><i class="codicon codicon-hubot"></i> hubot</div>
|
||||
<div class="icon"><i class="codicon codicon-inbox"></i> inbox</div>
|
||||
<div class="icon"><i class="codicon codicon-info"></i> info</div>
|
||||
<div class="icon"><i class="codicon codicon-issue-closed"></i> issue-closed</div>
|
||||
<div class="icon"><i class="codicon codicon-issue-reopened"></i> issue-reopened</div>
|
||||
<div class="icon"><i class="codicon codicon-issues"></i> issues</div>
|
||||
<div class="icon"><i class="codicon codicon-italic"></i> italic</div>
|
||||
<div class="icon"><i class="codicon codicon-jersey"></i> jersey</div>
|
||||
<div class="icon"><i class="codicon codicon-json"></i> json</div>
|
||||
<div class="icon"><i class="codicon codicon-kebab-vertical"></i> kebab-vertical</div>
|
||||
<div class="icon"><i class="codicon codicon-key"></i> key</div>
|
||||
<div class="icon"><i class="codicon codicon-law"></i> law</div>
|
||||
<div class="icon"><i class="codicon codicon-library"></i> library</div>
|
||||
<div class="icon"><i class="codicon codicon-lightbulb-autofix"></i> lightbulb-autofix</div>
|
||||
<div class="icon"><i class="codicon codicon-lightbulb"></i> lightbulb</div>
|
||||
<div class="icon"><i class="codicon codicon-link-external"></i> link-external</div>
|
||||
<div class="icon"><i class="codicon codicon-link"></i> link</div>
|
||||
<div class="icon"><i class="codicon codicon-list-filter"></i> list-filter</div>
|
||||
<div class="icon"><i class="codicon codicon-list-flat"></i> list-flat</div>
|
||||
<div class="icon"><i class="codicon codicon-list-ordered"></i> list-ordered</div>
|
||||
<div class="icon"><i class="codicon codicon-list-selection"></i> list-selection</div>
|
||||
<div class="icon"><i class="codicon codicon-list-tree"></i> list-tree</div>
|
||||
<div class="icon"><i class="codicon codicon-list-unordered"></i> list-unordered</div>
|
||||
<div class="icon"><i class="codicon codicon-live-share"></i> live-share</div>
|
||||
<div class="icon"><i class="codicon codicon-loading"></i> loading</div>
|
||||
<div class="icon"><i class="codicon codicon-location"></i> location</div>
|
||||
<div class="icon"><i class="codicon codicon-lock"></i> lock</div>
|
||||
<div class="icon"><i class="codicon codicon-mail-read"></i> mail-read</div>
|
||||
<div class="icon"><i class="codicon codicon-mail"></i> mail</div>
|
||||
<div class="icon"><i class="codicon codicon-markdown"></i> markdown</div>
|
||||
<div class="icon"><i class="codicon codicon-megaphone"></i> megaphone</div>
|
||||
<div class="icon"><i class="codicon codicon-mention"></i> mention</div>
|
||||
<div class="icon"><i class="codicon codicon-menu"></i> menu</div>
|
||||
<div class="icon"><i class="codicon codicon-merge"></i> merge</div>
|
||||
<div class="icon"><i class="codicon codicon-milestone"></i> milestone</div>
|
||||
<div class="icon"><i class="codicon codicon-mirror"></i> mirror</div>
|
||||
<div class="icon"><i class="codicon codicon-mortar-board"></i> mortar-board</div>
|
||||
<div class="icon"><i class="codicon codicon-move"></i> move</div>
|
||||
<div class="icon"><i class="codicon codicon-multiple-windows"></i> multiple-windows</div>
|
||||
<div class="icon"><i class="codicon codicon-mute"></i> mute</div>
|
||||
<div class="icon"><i class="codicon codicon-new-file"></i> new-file</div>
|
||||
<div class="icon"><i class="codicon codicon-new-folder"></i> new-folder</div>
|
||||
<div class="icon"><i class="codicon codicon-no-newline"></i> no-newline</div>
|
||||
<div class="icon"><i class="codicon codicon-note"></i> note</div>
|
||||
<div class="icon"><i class="codicon codicon-octoface"></i> octoface</div>
|
||||
<div class="icon"><i class="codicon codicon-open-preview"></i> open-preview</div>
|
||||
<div class="icon"><i class="codicon codicon-organization"></i> organization</div>
|
||||
<div class="icon"><i class="codicon codicon-output"></i> output</div>
|
||||
<div class="icon"><i class="codicon codicon-package"></i> package</div>
|
||||
<div class="icon"><i class="codicon codicon-paintcan"></i> paintcan</div>
|
||||
<div class="icon"><i class="codicon codicon-pass"></i> pass</div>
|
||||
<div class="icon"><i class="codicon codicon-person"></i> person</div>
|
||||
<div class="icon"><i class="codicon codicon-pin"></i> pin</div>
|
||||
<div class="icon"><i class="codicon codicon-pinned"></i> pinned</div>
|
||||
<div class="icon"><i class="codicon codicon-play-circle"></i> play-circle</div>
|
||||
<div class="icon"><i class="codicon codicon-play"></i> play</div>
|
||||
<div class="icon"><i class="codicon codicon-plug"></i> plug</div>
|
||||
<div class="icon"><i class="codicon codicon-preserve-case"></i> preserve-case</div>
|
||||
<div class="icon"><i class="codicon codicon-preview"></i> preview</div>
|
||||
<div class="icon"><i class="codicon codicon-primitive-square"></i> primitive-square</div>
|
||||
<div class="icon"><i class="codicon codicon-project"></i> project</div>
|
||||
<div class="icon"><i class="codicon codicon-pulse"></i> pulse</div>
|
||||
<div class="icon"><i class="codicon codicon-question"></i> question</div>
|
||||
<div class="icon"><i class="codicon codicon-quote"></i> quote</div>
|
||||
<div class="icon"><i class="codicon codicon-radio-tower"></i> radio-tower</div>
|
||||
<div class="icon"><i class="codicon codicon-reactions"></i> reactions</div>
|
||||
<div class="icon"><i class="codicon codicon-record-keys"></i> record-keys</div>
|
||||
<div class="icon"><i class="codicon codicon-record"></i> record</div>
|
||||
<div class="icon"><i class="codicon codicon-references"></i> references</div>
|
||||
<div class="icon"><i class="codicon codicon-refresh"></i> refresh</div>
|
||||
<div class="icon"><i class="codicon codicon-regex"></i> regex</div>
|
||||
<div class="icon"><i class="codicon codicon-remote-explorer"></i> remote-explorer</div>
|
||||
<div class="icon"><i class="codicon codicon-remote"></i> remote</div>
|
||||
<div class="icon"><i class="codicon codicon-remove"></i> remove</div>
|
||||
<div class="icon"><i class="codicon codicon-replace-all"></i> replace-all</div>
|
||||
<div class="icon"><i class="codicon codicon-replace"></i> replace</div>
|
||||
<div class="icon"><i class="codicon codicon-reply"></i> reply</div>
|
||||
<div class="icon"><i class="codicon codicon-repo-clone"></i> repo-clone</div>
|
||||
<div class="icon"><i class="codicon codicon-repo-force-push"></i> repo-force-push</div>
|
||||
<div class="icon"><i class="codicon codicon-repo-forked"></i> repo-forked</div>
|
||||
<div class="icon"><i class="codicon codicon-repo-pull"></i> repo-pull</div>
|
||||
<div class="icon"><i class="codicon codicon-repo-push"></i> repo-push</div>
|
||||
<div class="icon"><i class="codicon codicon-repo"></i> repo</div>
|
||||
<div class="icon"><i class="codicon codicon-report"></i> report</div>
|
||||
<div class="icon"><i class="codicon codicon-request-changes"></i> request-changes</div>
|
||||
<div class="icon"><i class="codicon codicon-rocket"></i> rocket</div>
|
||||
<div class="icon"><i class="codicon codicon-root-folder-opened"></i> root-folder-opened</div>
|
||||
<div class="icon"><i class="codicon codicon-root-folder"></i> root-folder</div>
|
||||
<div class="icon"><i class="codicon codicon-rss"></i> rss</div>
|
||||
<div class="icon"><i class="codicon codicon-ruby"></i> ruby</div>
|
||||
<div class="icon"><i class="codicon codicon-run-all"></i> run-all</div>
|
||||
<div class="icon"><i class="codicon codicon-save-all"></i> save-all</div>
|
||||
<div class="icon"><i class="codicon codicon-save-as"></i> save-as</div>
|
||||
<div class="icon"><i class="codicon codicon-save"></i> save</div>
|
||||
<div class="icon"><i class="codicon codicon-screen-full"></i> screen-full</div>
|
||||
<div class="icon"><i class="codicon codicon-screen-normal"></i> screen-normal</div>
|
||||
<div class="icon"><i class="codicon codicon-search-stop"></i> search-stop</div>
|
||||
<div class="icon"><i class="codicon codicon-search"></i> search</div>
|
||||
<div class="icon"><i class="codicon codicon-server-environment"></i> server-environment</div>
|
||||
<div class="icon"><i class="codicon codicon-server-process"></i> server-process</div>
|
||||
<div class="icon"><i class="codicon codicon-server"></i> server</div>
|
||||
<div class="icon"><i class="codicon codicon-settings-gear"></i> settings-gear</div>
|
||||
<div class="icon"><i class="codicon codicon-settings"></i> settings</div>
|
||||
<div class="icon"><i class="codicon codicon-shield"></i> shield</div>
|
||||
<div class="icon"><i class="codicon codicon-sign-in"></i> sign-in</div>
|
||||
<div class="icon"><i class="codicon codicon-sign-out"></i> sign-out</div>
|
||||
<div class="icon"><i class="codicon codicon-smiley"></i> smiley</div>
|
||||
<div class="icon"><i class="codicon codicon-sort-precedence"></i> sort-precedence</div>
|
||||
<div class="icon"><i class="codicon codicon-source-control"></i> source-control</div>
|
||||
<div class="icon"><i class="codicon codicon-split-horizontal"></i> split-horizontal</div>
|
||||
<div class="icon"><i class="codicon codicon-split-vertical"></i> split-vertical</div>
|
||||
<div class="icon"><i class="codicon codicon-squirrel"></i> squirrel</div>
|
||||
<div class="icon"><i class="codicon codicon-star-empty"></i> star-empty</div>
|
||||
<div class="icon"><i class="codicon codicon-star-full"></i> star-full</div>
|
||||
<div class="icon"><i class="codicon codicon-star-half"></i> star-half</div>
|
||||
<div class="icon"><i class="codicon codicon-stop-circle"></i> stop-circle</div>
|
||||
<div class="icon"><i class="codicon codicon-symbol-array"></i> symbol-array</div>
|
||||
<div class="icon"><i class="codicon codicon-symbol-boolean"></i> symbol-boolean</div>
|
||||
<div class="icon"><i class="codicon codicon-symbol-class"></i> symbol-class</div>
|
||||
<div class="icon"><i class="codicon codicon-symbol-color"></i> symbol-color</div>
|
||||
<div class="icon"><i class="codicon codicon-symbol-constant"></i> symbol-constant</div>
|
||||
<div class="icon"><i class="codicon codicon-symbol-enum-member"></i> symbol-enum-member</div>
|
||||
<div class="icon"><i class="codicon codicon-symbol-enum"></i> symbol-enum</div>
|
||||
<div class="icon"><i class="codicon codicon-symbol-event"></i> symbol-event</div>
|
||||
<div class="icon"><i class="codicon codicon-symbol-field"></i> symbol-field</div>
|
||||
<div class="icon"><i class="codicon codicon-symbol-file"></i> symbol-file</div>
|
||||
<div class="icon"><i class="codicon codicon-symbol-interface"></i> symbol-interface</div>
|
||||
<div class="icon"><i class="codicon codicon-symbol-key"></i> symbol-key</div>
|
||||
<div class="icon"><i class="codicon codicon-symbol-keyword"></i> symbol-keyword</div>
|
||||
<div class="icon"><i class="codicon codicon-symbol-method"></i> symbol-method</div>
|
||||
<div class="icon"><i class="codicon codicon-symbol-misc"></i> symbol-misc</div>
|
||||
<div class="icon"><i class="codicon codicon-symbol-namespace"></i> symbol-namespace</div>
|
||||
<div class="icon"><i class="codicon codicon-symbol-numeric"></i> symbol-numeric</div>
|
||||
<div class="icon"><i class="codicon codicon-symbol-operator"></i> symbol-operator</div>
|
||||
<div class="icon"><i class="codicon codicon-symbol-parameter"></i> symbol-parameter</div>
|
||||
<div class="icon"><i class="codicon codicon-symbol-property"></i> symbol-property</div>
|
||||
<div class="icon"><i class="codicon codicon-symbol-ruler"></i> symbol-ruler</div>
|
||||
<div class="icon"><i class="codicon codicon-symbol-snippet"></i> symbol-snippet</div>
|
||||
<div class="icon"><i class="codicon codicon-symbol-string"></i> symbol-string</div>
|
||||
<div class="icon"><i class="codicon codicon-symbol-structure"></i> symbol-structure</div>
|
||||
<div class="icon"><i class="codicon codicon-symbol-variable"></i> symbol-variable</div>
|
||||
<div class="icon"><i class="codicon codicon-sync-ignored"></i> sync-ignored</div>
|
||||
<div class="icon"><i class="codicon codicon-sync"></i> sync</div>
|
||||
<div class="icon"><i class="codicon codicon-tag"></i> tag</div>
|
||||
<div class="icon"><i class="codicon codicon-tasklist"></i> tasklist</div>
|
||||
<div class="icon"><i class="codicon codicon-telescope"></i> telescope</div>
|
||||
<div class="icon"><i class="codicon codicon-terminal"></i> terminal</div>
|
||||
<div class="icon"><i class="codicon codicon-text-size"></i> text-size</div>
|
||||
<div class="icon"><i class="codicon codicon-three-bars"></i> three-bars</div>
|
||||
<div class="icon"><i class="codicon codicon-thumbsdown"></i> thumbsdown</div>
|
||||
<div class="icon"><i class="codicon codicon-thumbsup"></i> thumbsup</div>
|
||||
<div class="icon"><i class="codicon codicon-tools"></i> tools</div>
|
||||
<div class="icon"><i class="codicon codicon-trash"></i> trash</div>
|
||||
<div class="icon"><i class="codicon codicon-triangle-down"></i> triangle-down</div>
|
||||
<div class="icon"><i class="codicon codicon-triangle-left"></i> triangle-left</div>
|
||||
<div class="icon"><i class="codicon codicon-triangle-right"></i> triangle-right</div>
|
||||
<div class="icon"><i class="codicon codicon-triangle-up"></i> triangle-up</div>
|
||||
<div class="icon"><i class="codicon codicon-twitter"></i> twitter</div>
|
||||
<div class="icon"><i class="codicon codicon-unfold"></i> unfold</div>
|
||||
<div class="icon"><i class="codicon codicon-ungroup-by-ref-type"></i> ungroup-by-ref-type</div>
|
||||
<div class="icon"><i class="codicon codicon-unlock"></i> unlock</div>
|
||||
<div class="icon"><i class="codicon codicon-unmute"></i> unmute</div>
|
||||
<div class="icon"><i class="codicon codicon-unverified"></i> unverified</div>
|
||||
<div class="icon"><i class="codicon codicon-verified"></i> verified</div>
|
||||
<div class="icon"><i class="codicon codicon-versions"></i> versions</div>
|
||||
<div class="icon"><i class="codicon codicon-vm-active"></i> vm-active</div>
|
||||
<div class="icon"><i class="codicon codicon-vm-connect"></i> vm-connect</div>
|
||||
<div class="icon"><i class="codicon codicon-vm-outline"></i> vm-outline</div>
|
||||
<div class="icon"><i class="codicon codicon-vm-running"></i> vm-running</div>
|
||||
<div class="icon"><i class="codicon codicon-vm"></i> vm</div>
|
||||
<div class="icon"><i class="codicon codicon-warning"></i> warning</div>
|
||||
<div class="icon"><i class="codicon codicon-watch"></i> watch</div>
|
||||
<div class="icon"><i class="codicon codicon-whitespace"></i> whitespace</div>
|
||||
<div class="icon"><i class="codicon codicon-whole-word"></i> whole-word</div>
|
||||
<div class="icon"><i class="codicon codicon-window"></i> window</div>
|
||||
<div class="icon"><i class="codicon codicon-word-wrap"></i> word-wrap</div>
|
||||
<div class="icon"><i class="codicon codicon-zoom-in"></i> zoom-in</div>
|
||||
<div class="icon"><i class="codicon codicon-zoom-out"></i> zoom-out</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>`;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,222 +1,222 @@
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
const cats = {
|
||||
'Coding Cat': 'https://media.giphy.com/media/JIX9t2j0ZTN9S/giphy.gif',
|
||||
'Compiling Cat': 'https://media.giphy.com/media/mlvseq9yvZhba/giphy.gif',
|
||||
'Testing Cat': 'https://media.giphy.com/media/3oriO0OEd9QIDdllqo/giphy.gif'
|
||||
};
|
||||
|
||||
export function activate(context: vscode.ExtensionContext) {
|
||||
context.subscriptions.push(
|
||||
vscode.commands.registerCommand('catCoding.start', () => {
|
||||
CatCodingPanel.createOrShow(context.extensionUri);
|
||||
})
|
||||
);
|
||||
|
||||
context.subscriptions.push(
|
||||
vscode.commands.registerCommand('catCoding.doRefactor', () => {
|
||||
if (CatCodingPanel.currentPanel) {
|
||||
CatCodingPanel.currentPanel.doRefactor();
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
if (vscode.window.registerWebviewPanelSerializer) {
|
||||
// Make sure we register a serializer in activation event
|
||||
vscode.window.registerWebviewPanelSerializer(CatCodingPanel.viewType, {
|
||||
async deserializeWebviewPanel(webviewPanel: vscode.WebviewPanel, state: any) {
|
||||
console.log(`Got state: ${state}`);
|
||||
// Reset the webview options so we use latest uri for `localResourceRoots`.
|
||||
webviewPanel.webview.options = getWebviewOptions(context.extensionUri);
|
||||
CatCodingPanel.revive(webviewPanel, context.extensionUri);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function getWebviewOptions(extensionUri: vscode.Uri): vscode.WebviewOptions {
|
||||
return {
|
||||
// Enable javascript in the webview
|
||||
enableScripts: true,
|
||||
|
||||
// And restrict the webview to only loading content from our extension's `media` directory.
|
||||
localResourceRoots: [vscode.Uri.joinPath(extensionUri, 'media')]
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Manages cat coding webview panels
|
||||
*/
|
||||
class CatCodingPanel {
|
||||
/**
|
||||
* Track the currently panel. Only allow a single panel to exist at a time.
|
||||
*/
|
||||
public static currentPanel: CatCodingPanel | undefined;
|
||||
|
||||
public static readonly viewType = 'catCoding';
|
||||
|
||||
private readonly _panel: vscode.WebviewPanel;
|
||||
private readonly _extensionUri: vscode.Uri;
|
||||
private _disposables: vscode.Disposable[] = [];
|
||||
|
||||
public static createOrShow(extensionUri: vscode.Uri) {
|
||||
const column = vscode.window.activeTextEditor
|
||||
? vscode.window.activeTextEditor.viewColumn
|
||||
: undefined;
|
||||
|
||||
// If we already have a panel, show it.
|
||||
if (CatCodingPanel.currentPanel) {
|
||||
CatCodingPanel.currentPanel._panel.reveal(column);
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise, create a new panel.
|
||||
const panel = vscode.window.createWebviewPanel(
|
||||
CatCodingPanel.viewType,
|
||||
'Cat Coding',
|
||||
column || vscode.ViewColumn.One,
|
||||
getWebviewOptions(extensionUri),
|
||||
);
|
||||
|
||||
CatCodingPanel.currentPanel = new CatCodingPanel(panel, extensionUri);
|
||||
}
|
||||
|
||||
public static revive(panel: vscode.WebviewPanel, extensionUri: vscode.Uri) {
|
||||
CatCodingPanel.currentPanel = new CatCodingPanel(panel, extensionUri);
|
||||
}
|
||||
|
||||
private constructor(panel: vscode.WebviewPanel, extensionUri: vscode.Uri) {
|
||||
this._panel = panel;
|
||||
this._extensionUri = extensionUri;
|
||||
|
||||
// Set the webview's initial html content
|
||||
this._update();
|
||||
|
||||
// Listen for when the panel is disposed
|
||||
// This happens when the user closes the panel or when the panel is closed programmatically
|
||||
this._panel.onDidDispose(() => this.dispose(), null, this._disposables);
|
||||
|
||||
// Update the content based on view changes
|
||||
this._panel.onDidChangeViewState(
|
||||
e => {
|
||||
if (this._panel.visible) {
|
||||
this._update();
|
||||
}
|
||||
},
|
||||
null,
|
||||
this._disposables
|
||||
);
|
||||
|
||||
// Handle messages from the webview
|
||||
this._panel.webview.onDidReceiveMessage(
|
||||
message => {
|
||||
switch (message.command) {
|
||||
case 'alert':
|
||||
vscode.window.showErrorMessage(message.text);
|
||||
return;
|
||||
}
|
||||
},
|
||||
null,
|
||||
this._disposables
|
||||
);
|
||||
}
|
||||
|
||||
public doRefactor() {
|
||||
// Send a message to the webview webview.
|
||||
// You can send any JSON serializable data.
|
||||
this._panel.webview.postMessage({ command: 'refactor' });
|
||||
}
|
||||
|
||||
public dispose() {
|
||||
CatCodingPanel.currentPanel = undefined;
|
||||
|
||||
// Clean up our resources
|
||||
this._panel.dispose();
|
||||
|
||||
while (this._disposables.length) {
|
||||
const x = this._disposables.pop();
|
||||
if (x) {
|
||||
x.dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _update() {
|
||||
const webview = this._panel.webview;
|
||||
|
||||
// Vary the webview's content based on where it is located in the editor.
|
||||
switch (this._panel.viewColumn) {
|
||||
case vscode.ViewColumn.Two:
|
||||
this._updateForCat(webview, 'Compiling Cat');
|
||||
return;
|
||||
|
||||
case vscode.ViewColumn.Three:
|
||||
this._updateForCat(webview, 'Testing Cat');
|
||||
return;
|
||||
|
||||
case vscode.ViewColumn.One:
|
||||
default:
|
||||
this._updateForCat(webview, 'Coding Cat');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private _updateForCat(webview: vscode.Webview, catName: keyof typeof cats) {
|
||||
this._panel.title = catName;
|
||||
this._panel.webview.html = this._getHtmlForWebview(webview, cats[catName]);
|
||||
}
|
||||
|
||||
private _getHtmlForWebview(webview: vscode.Webview, catGifPath: string) {
|
||||
// Local path to main script run in the webview
|
||||
const scriptPathOnDisk = vscode.Uri.joinPath(this._extensionUri, 'media', 'main.js');
|
||||
|
||||
// And the uri we use to load this script in the webview
|
||||
const scriptUri = webview.asWebviewUri(scriptPathOnDisk);
|
||||
|
||||
// Local path to css styles
|
||||
const styleResetPath = vscode.Uri.joinPath(this._extensionUri, 'media', 'reset.css');
|
||||
const stylesPathMainPath = vscode.Uri.joinPath(this._extensionUri, 'media', 'vscode.css');
|
||||
|
||||
// Uri to load styles into webview
|
||||
const stylesResetUri = webview.asWebviewUri(styleResetPath);
|
||||
const stylesMainUri = webview.asWebviewUri(stylesPathMainPath);
|
||||
|
||||
// Use a nonce to only allow specific scripts to be run
|
||||
const nonce = getNonce();
|
||||
|
||||
return `<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
|
||||
<!--
|
||||
Use a content security policy to only allow loading images from https or from our extension directory,
|
||||
and only allow scripts that have a specific nonce.
|
||||
-->
|
||||
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src ${webview.cspSource}; img-src ${webview.cspSource} https:; script-src 'nonce-${nonce}';">
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
|
||||
<link href="${stylesResetUri}" rel="stylesheet">
|
||||
<link href="${stylesMainUri}" rel="stylesheet">
|
||||
|
||||
<title>Cat Coding</title>
|
||||
</head>
|
||||
<body>
|
||||
<img src="${catGifPath}" width="300" />
|
||||
<h1 id="lines-of-code-counter">0</h1>
|
||||
|
||||
<script nonce="${nonce}" src="${scriptUri}"></script>
|
||||
</body>
|
||||
</html>`;
|
||||
}
|
||||
}
|
||||
|
||||
function getNonce() {
|
||||
let text = '';
|
||||
const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
||||
for (let i = 0; i < 32; i++) {
|
||||
text += possible.charAt(Math.floor(Math.random() * possible.length));
|
||||
}
|
||||
return text;
|
||||
}
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
const cats = {
|
||||
'Coding Cat': 'https://media.giphy.com/media/JIX9t2j0ZTN9S/giphy.gif',
|
||||
'Compiling Cat': 'https://media.giphy.com/media/mlvseq9yvZhba/giphy.gif',
|
||||
'Testing Cat': 'https://media.giphy.com/media/3oriO0OEd9QIDdllqo/giphy.gif'
|
||||
};
|
||||
|
||||
export function activate(context: vscode.ExtensionContext) {
|
||||
context.subscriptions.push(
|
||||
vscode.commands.registerCommand('catCoding.start', () => {
|
||||
CatCodingPanel.createOrShow(context.extensionUri);
|
||||
})
|
||||
);
|
||||
|
||||
context.subscriptions.push(
|
||||
vscode.commands.registerCommand('catCoding.doRefactor', () => {
|
||||
if (CatCodingPanel.currentPanel) {
|
||||
CatCodingPanel.currentPanel.doRefactor();
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
if (vscode.window.registerWebviewPanelSerializer) {
|
||||
// Make sure we register a serializer in activation event
|
||||
vscode.window.registerWebviewPanelSerializer(CatCodingPanel.viewType, {
|
||||
async deserializeWebviewPanel(webviewPanel: vscode.WebviewPanel, state: any) {
|
||||
console.log(`Got state: ${state}`);
|
||||
// Reset the webview options so we use latest uri for `localResourceRoots`.
|
||||
webviewPanel.webview.options = getWebviewOptions(context.extensionUri);
|
||||
CatCodingPanel.revive(webviewPanel, context.extensionUri);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function getWebviewOptions(extensionUri: vscode.Uri): vscode.WebviewOptions {
|
||||
return {
|
||||
// Enable javascript in the webview
|
||||
enableScripts: true,
|
||||
|
||||
// And restrict the webview to only loading content from our extension's `media` directory.
|
||||
localResourceRoots: [vscode.Uri.joinPath(extensionUri, 'media')]
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Manages cat coding webview panels
|
||||
*/
|
||||
class CatCodingPanel {
|
||||
/**
|
||||
* Track the currently panel. Only allow a single panel to exist at a time.
|
||||
*/
|
||||
public static currentPanel: CatCodingPanel | undefined;
|
||||
|
||||
public static readonly viewType = 'catCoding';
|
||||
|
||||
private readonly _panel: vscode.WebviewPanel;
|
||||
private readonly _extensionUri: vscode.Uri;
|
||||
private _disposables: vscode.Disposable[] = [];
|
||||
|
||||
public static createOrShow(extensionUri: vscode.Uri) {
|
||||
const column = vscode.window.activeTextEditor
|
||||
? vscode.window.activeTextEditor.viewColumn
|
||||
: undefined;
|
||||
|
||||
// If we already have a panel, show it.
|
||||
if (CatCodingPanel.currentPanel) {
|
||||
CatCodingPanel.currentPanel._panel.reveal(column);
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise, create a new panel.
|
||||
const panel = vscode.window.createWebviewPanel(
|
||||
CatCodingPanel.viewType,
|
||||
'Cat Coding',
|
||||
column || vscode.ViewColumn.One,
|
||||
getWebviewOptions(extensionUri),
|
||||
);
|
||||
|
||||
CatCodingPanel.currentPanel = new CatCodingPanel(panel, extensionUri);
|
||||
}
|
||||
|
||||
public static revive(panel: vscode.WebviewPanel, extensionUri: vscode.Uri) {
|
||||
CatCodingPanel.currentPanel = new CatCodingPanel(panel, extensionUri);
|
||||
}
|
||||
|
||||
private constructor(panel: vscode.WebviewPanel, extensionUri: vscode.Uri) {
|
||||
this._panel = panel;
|
||||
this._extensionUri = extensionUri;
|
||||
|
||||
// Set the webview's initial html content
|
||||
this._update();
|
||||
|
||||
// Listen for when the panel is disposed
|
||||
// This happens when the user closes the panel or when the panel is closed programmatically
|
||||
this._panel.onDidDispose(() => this.dispose(), null, this._disposables);
|
||||
|
||||
// Update the content based on view changes
|
||||
this._panel.onDidChangeViewState(
|
||||
e => {
|
||||
if (this._panel.visible) {
|
||||
this._update();
|
||||
}
|
||||
},
|
||||
null,
|
||||
this._disposables
|
||||
);
|
||||
|
||||
// Handle messages from the webview
|
||||
this._panel.webview.onDidReceiveMessage(
|
||||
message => {
|
||||
switch (message.command) {
|
||||
case 'alert':
|
||||
vscode.window.showErrorMessage(message.text);
|
||||
return;
|
||||
}
|
||||
},
|
||||
null,
|
||||
this._disposables
|
||||
);
|
||||
}
|
||||
|
||||
public doRefactor() {
|
||||
// Send a message to the webview webview.
|
||||
// You can send any JSON serializable data.
|
||||
this._panel.webview.postMessage({ command: 'refactor' });
|
||||
}
|
||||
|
||||
public dispose() {
|
||||
CatCodingPanel.currentPanel = undefined;
|
||||
|
||||
// Clean up our resources
|
||||
this._panel.dispose();
|
||||
|
||||
while (this._disposables.length) {
|
||||
const x = this._disposables.pop();
|
||||
if (x) {
|
||||
x.dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _update() {
|
||||
const webview = this._panel.webview;
|
||||
|
||||
// Vary the webview's content based on where it is located in the editor.
|
||||
switch (this._panel.viewColumn) {
|
||||
case vscode.ViewColumn.Two:
|
||||
this._updateForCat(webview, 'Compiling Cat');
|
||||
return;
|
||||
|
||||
case vscode.ViewColumn.Three:
|
||||
this._updateForCat(webview, 'Testing Cat');
|
||||
return;
|
||||
|
||||
case vscode.ViewColumn.One:
|
||||
default:
|
||||
this._updateForCat(webview, 'Coding Cat');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private _updateForCat(webview: vscode.Webview, catName: keyof typeof cats) {
|
||||
this._panel.title = catName;
|
||||
this._panel.webview.html = this._getHtmlForWebview(webview, cats[catName]);
|
||||
}
|
||||
|
||||
private _getHtmlForWebview(webview: vscode.Webview, catGifPath: string) {
|
||||
// Local path to main script run in the webview
|
||||
const scriptPathOnDisk = vscode.Uri.joinPath(this._extensionUri, 'media', 'main.js');
|
||||
|
||||
// And the uri we use to load this script in the webview
|
||||
const scriptUri = webview.asWebviewUri(scriptPathOnDisk);
|
||||
|
||||
// Local path to css styles
|
||||
const styleResetPath = vscode.Uri.joinPath(this._extensionUri, 'media', 'reset.css');
|
||||
const stylesPathMainPath = vscode.Uri.joinPath(this._extensionUri, 'media', 'vscode.css');
|
||||
|
||||
// Uri to load styles into webview
|
||||
const stylesResetUri = webview.asWebviewUri(styleResetPath);
|
||||
const stylesMainUri = webview.asWebviewUri(stylesPathMainPath);
|
||||
|
||||
// Use a nonce to only allow specific scripts to be run
|
||||
const nonce = getNonce();
|
||||
|
||||
return `<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
|
||||
<!--
|
||||
Use a content security policy to only allow loading images from https or from our extension directory,
|
||||
and only allow scripts that have a specific nonce.
|
||||
-->
|
||||
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src ${webview.cspSource}; img-src ${webview.cspSource} https:; script-src 'nonce-${nonce}';">
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
|
||||
<link href="${stylesResetUri}" rel="stylesheet">
|
||||
<link href="${stylesMainUri}" rel="stylesheet">
|
||||
|
||||
<title>Cat Coding</title>
|
||||
</head>
|
||||
<body>
|
||||
<img src="${catGifPath}" width="300" />
|
||||
<h1 id="lines-of-code-counter">0</h1>
|
||||
|
||||
<script nonce="${nonce}" src="${scriptUri}"></script>
|
||||
</body>
|
||||
</html>`;
|
||||
}
|
||||
}
|
||||
|
||||
function getNonce() {
|
||||
let text = '';
|
||||
const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
||||
for (let i = 0; i < 32; i++) {
|
||||
text += possible.charAt(Math.floor(Math.random() * possible.length));
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
@ -1,124 +1,124 @@
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
export function activate(context: vscode.ExtensionContext) {
|
||||
|
||||
const provider = new ColorsViewProvider(context.extensionUri);
|
||||
|
||||
context.subscriptions.push(
|
||||
vscode.window.registerWebviewViewProvider(ColorsViewProvider.viewType, provider));
|
||||
|
||||
context.subscriptions.push(
|
||||
vscode.commands.registerCommand('calicoColors.addColor', () => {
|
||||
provider.addColor();
|
||||
}));
|
||||
|
||||
context.subscriptions.push(
|
||||
vscode.commands.registerCommand('calicoColors.clearColors', () => {
|
||||
provider.clearColors();
|
||||
}));
|
||||
}
|
||||
|
||||
class ColorsViewProvider implements vscode.WebviewViewProvider {
|
||||
|
||||
public static readonly viewType = 'calicoColors.colorsView';
|
||||
|
||||
private _view?: vscode.WebviewView;
|
||||
|
||||
constructor(
|
||||
private readonly _extensionUri: vscode.Uri,
|
||||
) { }
|
||||
|
||||
public resolveWebviewView(
|
||||
webviewView: vscode.WebviewView,
|
||||
context: vscode.WebviewViewResolveContext,
|
||||
_token: vscode.CancellationToken,
|
||||
) {
|
||||
this._view = webviewView;
|
||||
|
||||
webviewView.webview.options = {
|
||||
// Allow scripts in the webview
|
||||
enableScripts: true,
|
||||
|
||||
localResourceRoots: [
|
||||
this._extensionUri
|
||||
]
|
||||
};
|
||||
|
||||
webviewView.webview.html = this._getHtmlForWebview(webviewView.webview);
|
||||
|
||||
webviewView.webview.onDidReceiveMessage(data => {
|
||||
switch (data.type) {
|
||||
case 'colorSelected':
|
||||
{
|
||||
vscode.window.activeTextEditor?.insertSnippet(new vscode.SnippetString(`#${data.value}`));
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public addColor() {
|
||||
if (this._view) {
|
||||
this._view.show?.(true); // `show` is not implemented in 1.49 but is for 1.50 insiders
|
||||
this._view.webview.postMessage({ type: 'addColor' });
|
||||
}
|
||||
}
|
||||
|
||||
public clearColors() {
|
||||
if (this._view) {
|
||||
this._view.webview.postMessage({ type: 'clearColors' });
|
||||
}
|
||||
}
|
||||
|
||||
private _getHtmlForWebview(webview: vscode.Webview) {
|
||||
// Get the local path to main script run in the webview, then convert it to a uri we can use in the webview.
|
||||
const scriptUri = webview.asWebviewUri(vscode.Uri.joinPath(this._extensionUri, 'media', 'main.js'));
|
||||
|
||||
// Do the same for the stylesheet.
|
||||
const styleResetUri = webview.asWebviewUri(vscode.Uri.joinPath(this._extensionUri, 'media', 'reset.css'));
|
||||
const styleVSCodeUri = webview.asWebviewUri(vscode.Uri.joinPath(this._extensionUri, 'media', 'vscode.css'));
|
||||
const styleMainUri = webview.asWebviewUri(vscode.Uri.joinPath(this._extensionUri, 'media', 'main.css'));
|
||||
|
||||
// Use a nonce to only allow a specific script to be run.
|
||||
const nonce = getNonce();
|
||||
|
||||
return `<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
|
||||
<!--
|
||||
Use a content security policy to only allow loading styles from our extension directory,
|
||||
and only allow scripts that have a specific nonce.
|
||||
(See the 'webview-sample' extension sample for img-src content security policy examples)
|
||||
-->
|
||||
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src ${webview.cspSource}; script-src 'nonce-${nonce}';">
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
|
||||
<link href="${styleResetUri}" rel="stylesheet">
|
||||
<link href="${styleVSCodeUri}" rel="stylesheet">
|
||||
<link href="${styleMainUri}" rel="stylesheet">
|
||||
|
||||
<title>Cat Colors</title>
|
||||
</head>
|
||||
<body>
|
||||
<ul class="color-list">
|
||||
</ul>
|
||||
|
||||
<button class="add-color-button">Add Color</button>
|
||||
|
||||
<script nonce="${nonce}" src="${scriptUri}"></script>
|
||||
</body>
|
||||
</html>`;
|
||||
}
|
||||
}
|
||||
|
||||
function getNonce() {
|
||||
let text = '';
|
||||
const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
||||
for (let i = 0; i < 32; i++) {
|
||||
text += possible.charAt(Math.floor(Math.random() * possible.length));
|
||||
}
|
||||
return text;
|
||||
}
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
export function activate(context: vscode.ExtensionContext) {
|
||||
|
||||
const provider = new ColorsViewProvider(context.extensionUri);
|
||||
|
||||
context.subscriptions.push(
|
||||
vscode.window.registerWebviewViewProvider(ColorsViewProvider.viewType, provider));
|
||||
|
||||
context.subscriptions.push(
|
||||
vscode.commands.registerCommand('calicoColors.addColor', () => {
|
||||
provider.addColor();
|
||||
}));
|
||||
|
||||
context.subscriptions.push(
|
||||
vscode.commands.registerCommand('calicoColors.clearColors', () => {
|
||||
provider.clearColors();
|
||||
}));
|
||||
}
|
||||
|
||||
class ColorsViewProvider implements vscode.WebviewViewProvider {
|
||||
|
||||
public static readonly viewType = 'calicoColors.colorsView';
|
||||
|
||||
private _view?: vscode.WebviewView;
|
||||
|
||||
constructor(
|
||||
private readonly _extensionUri: vscode.Uri,
|
||||
) { }
|
||||
|
||||
public resolveWebviewView(
|
||||
webviewView: vscode.WebviewView,
|
||||
context: vscode.WebviewViewResolveContext,
|
||||
_token: vscode.CancellationToken,
|
||||
) {
|
||||
this._view = webviewView;
|
||||
|
||||
webviewView.webview.options = {
|
||||
// Allow scripts in the webview
|
||||
enableScripts: true,
|
||||
|
||||
localResourceRoots: [
|
||||
this._extensionUri
|
||||
]
|
||||
};
|
||||
|
||||
webviewView.webview.html = this._getHtmlForWebview(webviewView.webview);
|
||||
|
||||
webviewView.webview.onDidReceiveMessage(data => {
|
||||
switch (data.type) {
|
||||
case 'colorSelected':
|
||||
{
|
||||
vscode.window.activeTextEditor?.insertSnippet(new vscode.SnippetString(`#${data.value}`));
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public addColor() {
|
||||
if (this._view) {
|
||||
this._view.show?.(true); // `show` is not implemented in 1.49 but is for 1.50 insiders
|
||||
this._view.webview.postMessage({ type: 'addColor' });
|
||||
}
|
||||
}
|
||||
|
||||
public clearColors() {
|
||||
if (this._view) {
|
||||
this._view.webview.postMessage({ type: 'clearColors' });
|
||||
}
|
||||
}
|
||||
|
||||
private _getHtmlForWebview(webview: vscode.Webview) {
|
||||
// Get the local path to main script run in the webview, then convert it to a uri we can use in the webview.
|
||||
const scriptUri = webview.asWebviewUri(vscode.Uri.joinPath(this._extensionUri, 'media', 'main.js'));
|
||||
|
||||
// Do the same for the stylesheet.
|
||||
const styleResetUri = webview.asWebviewUri(vscode.Uri.joinPath(this._extensionUri, 'media', 'reset.css'));
|
||||
const styleVSCodeUri = webview.asWebviewUri(vscode.Uri.joinPath(this._extensionUri, 'media', 'vscode.css'));
|
||||
const styleMainUri = webview.asWebviewUri(vscode.Uri.joinPath(this._extensionUri, 'media', 'main.css'));
|
||||
|
||||
// Use a nonce to only allow a specific script to be run.
|
||||
const nonce = getNonce();
|
||||
|
||||
return `<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
|
||||
<!--
|
||||
Use a content security policy to only allow loading styles from our extension directory,
|
||||
and only allow scripts that have a specific nonce.
|
||||
(See the 'webview-sample' extension sample for img-src content security policy examples)
|
||||
-->
|
||||
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src ${webview.cspSource}; script-src 'nonce-${nonce}';">
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
|
||||
<link href="${styleResetUri}" rel="stylesheet">
|
||||
<link href="${styleVSCodeUri}" rel="stylesheet">
|
||||
<link href="${styleMainUri}" rel="stylesheet">
|
||||
|
||||
<title>Cat Colors</title>
|
||||
</head>
|
||||
<body>
|
||||
<ul class="color-list">
|
||||
</ul>
|
||||
|
||||
<button class="add-color-button">Add Color</button>
|
||||
|
||||
<script nonce="${nonce}" src="${scriptUri}"></script>
|
||||
</body>
|
||||
</html>`;
|
||||
}
|
||||
}
|
||||
|
||||
function getNonce() {
|
||||
let text = '';
|
||||
const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
||||
for (let i = 0; i < 32; i++) {
|
||||
text += possible.charAt(Math.floor(Math.random() * possible.length));
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
@ -1,14 +1,14 @@
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
export function activate(context: vscode.ExtensionContext) {
|
||||
let disposable = vscode.commands.registerCommand(
|
||||
'welcome-view-content-sample.hello',
|
||||
async () => {
|
||||
vscode.window.showInformationMessage('Hello world!');
|
||||
}
|
||||
);
|
||||
|
||||
context.subscriptions.push(disposable);
|
||||
}
|
||||
|
||||
export function deactivate() {}
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
export function activate(context: vscode.ExtensionContext) {
|
||||
let disposable = vscode.commands.registerCommand(
|
||||
'welcome-view-content-sample.hello',
|
||||
async () => {
|
||||
vscode.window.showInformationMessage('Hello world!');
|
||||
}
|
||||
);
|
||||
|
||||
context.subscriptions.push(disposable);
|
||||
}
|
||||
|
||||
export function deactivate() { }
|
||||
|
||||
Reference in New Issue
Block a user