Merge pull request #736 from mjbvz/dev/mjbvz/tsfmt

Run tsfmt on all samples in repo
This commit is contained in:
Matt Bierner
2022-11-04 14:35:47 -07:00
committed by GitHub
97 changed files with 26331 additions and 26329 deletions

3
.editorconfig Normal file
View File

@ -0,0 +1,3 @@
[*.ts]
indent_style = tab
tab_width = 4

View File

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

View File

@ -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() { }

View File

@ -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 };
}

View File

@ -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;
}

View File

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

View File

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

View File

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

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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 = [];
}

View File

@ -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];
}
}

View File

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

View File

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

View File

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

View File

@ -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)];
}

View File

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

View File

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

View File

@ -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;
}
}

View File

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

View File

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

View File

@ -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;
}

View File

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

View File

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

View File

@ -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'],
}));
}

View File

@ -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;
}
}

View File

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

View File

@ -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;
}

View File

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

View File

@ -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');
}

View File

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

View File

@ -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') };
}));
}

View File

@ -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;
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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() { }

View File

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

View File

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

View File

@ -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;
}

File diff suppressed because it is too large Load Diff

View File

@ -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');

View File

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

View File

@ -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() { }

View File

@ -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

View File

@ -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, {});
})
}

View File

@ -1,4 +1,4 @@
declare module '*.css' {
const classes: { [className: string]: string };
export = classes;
}
declare module '*.css' {
const classes: { [className: string]: string };
export = classes;
}

View File

@ -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.
}
};
};

View File

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

View File

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

View File

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

View File

@ -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}`);
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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);

View File

@ -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 };
}

View File

@ -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];
}
}

View File

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

View File

@ -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;
}
}

View File

@ -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';

View File

@ -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;
}

View File

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

View File

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

View File

@ -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;
}

View File

@ -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;
}

View File

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

View File

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

View File

@ -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;
}
}
}

View File

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

View File

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

View File

@ -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;
}
}

View File

@ -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;

View File

@ -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}`;
}
}
}

View File

@ -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';
}

View File

@ -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) { }
}

View File

@ -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() { }

View File

@ -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[];
}

View File

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

View File

@ -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) {

View File

@ -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;
}

View File

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

View File

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

View File

@ -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 };
}
}

View File

@ -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;
}

View File

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

View File

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

View File

@ -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;
}

View File

@ -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>`;
}
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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() { }