Skip to content

Add extension API support #170

New issue

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

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

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
May 11, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 53 additions & 0 deletions examples/ExtensionExamples.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# Instructions: select the entire file and hit F8 to
# load the extensions. To see the list of registered
# extensions and run them, hit Ctrl+Shift+P, type 'addi'
# and run the "Show additional commands from PowerShell modules"
# command. A quick pick list will appear with all 3
# extensions registered. Selecting one of them will launch it.

function Invoke-MyCommand {
Write-Output "My command's function was executed!"
}

# Registering a command for an existing function

Register-EditorCommand -Verbose `
-Name "MyModule.MyCommandWithFunction" `
-DisplayName "My command with function" `
-Function Invoke-MyCommand

# Registering a command to run a ScriptBlock

Register-EditorCommand -Verbose `
-Name "MyModule.MyCommandWithScriptBlock" `
-DisplayName "My command with script block" `
-ScriptBlock { Write-Output "My command's script block was executed!" }

# A real command example:

function Invoke-MyEdit([Microsoft.PowerShell.EditorServices.Extensions.EditorContext]$context) {

# Insert text at pre-defined position

$context.CurrentFile.InsertText(
"`r`n# I was inserted by PowerShell code!`r`nGet-Process -Name chrome`r`n",
35, 1);

# TRY THIS ALSO, comment out the above 4 lines and uncomment the below

# # Insert text at cursor position

# $context.CurrentFile.InsertText(
# "Get-Process -Name chrome",
# $context.CursorPosition);
}

# After registering this command, you only need to re-evaluate the
# Invoke-MyEdit command when you've made changes to its code. The
# registration of the command persists.

Register-EditorCommand -Verbose `
-Name "MyModule.MyEditCommand" `
-DisplayName "Apply my edit!" `
-Function Invoke-MyEdit `
-SuppressOutput
11 changes: 8 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"publisher": "ms-vscode",
"description": "Develop PowerShell scripts in Visual Studio Code!",
"engines": {
"vscode": "^0.10.10"
"vscode": "1.x.x"
},
"license": "SEE LICENSE IN LICENSE.txt",
"homepage": "https://github.com/PowerShell/vscode-powershell/blob/master/README.md",
Expand All @@ -32,8 +32,8 @@
"vscode-languageclient": "1.3.1"
},
"devDependencies": {
"vscode": "^0.11.x",
"typescript": "1.7.3"
"vscode": "^0.11.12",
"typescript": "^1.8.0"
},
"extensionDependencies": [
"vscode.powershell"
Expand Down Expand Up @@ -97,6 +97,11 @@
"command": "PowerShell.PowerShellFindModule",
"title": "Find/Install PowerShell modules from the gallery",
"category": "PowerShell"
},
{
"command": "PowerShell.ShowAdditionalCommands",
"title": "Show additional commands from PowerShell modules",
"category": "PowerShell"
}
],
"snippets": [
Expand Down
262 changes: 262 additions & 0 deletions src/features/ExtensionCommands.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,262 @@
import vscode = require('vscode');
import path = require('path');
import { LanguageClient, RequestType, NotificationType, Range, Position } from 'vscode-languageclient';

export interface ExtensionCommand {
name: string;
displayName: string;
}

export interface ExtensionCommandQuickPickItem extends vscode.QuickPickItem {
command: ExtensionCommand;
}

var extensionCommands: ExtensionCommand[] = [];

export namespace InvokeExtensionCommandRequest {
export const type: RequestType<InvokeExtensionCommandRequestArguments, void, void> =
{ get method() { return 'powerShell/invokeExtensionCommand'; } };
}

export interface EditorContext {
currentFilePath: string;
cursorPosition: Position;
selectionRange: Range;
}

export interface InvokeExtensionCommandRequestArguments {
name: string;
context: EditorContext;
}

export namespace ExtensionCommandAddedNotification {
export const type: NotificationType<ExtensionCommandAddedNotificationBody> =
{ get method() { return 'powerShell/extensionCommandAdded'; } };
}

export interface ExtensionCommandAddedNotificationBody {
name: string;
displayName: string;
}

function addExtensionCommand(command: ExtensionCommandAddedNotificationBody) {

extensionCommands.push({
name: command.name,
displayName: command.displayName
});
}

function showExtensionCommands(client: LanguageClient) : Thenable<InvokeExtensionCommandRequestArguments> {

// If no extension commands are available, show a message
if (extensionCommands.length == 0) {
vscode.window.showInformationMessage(
"No extension commands have been loaded into the current session.");

return;
}

var quickPickItems =
extensionCommands.map<ExtensionCommandQuickPickItem>(command => {
return {
label: command.displayName,
description: "",
command: command
}
});

vscode.window
.showQuickPick(
quickPickItems,
{ placeHolder: "Select a command" })
.then(command => onCommandSelected(command, client));
}

function onCommandSelected(
chosenItem: ExtensionCommandQuickPickItem,
client: LanguageClient) {

if (chosenItem !== undefined) {
client.sendRequest(
InvokeExtensionCommandRequest.type,
{ name: chosenItem.command.name,
context: getEditorContext() });
}
}

// ---------- Editor Operations ----------

function asRange(value: vscode.Range): Range {

if (value === undefined) {
return undefined;
} else if (value === null) {
return null;
}
return { start: asPosition(value.start), end: asPosition(value.end) };
}

function asPosition(value: vscode.Position): Position {

if (value === undefined) {
return undefined;
} else if (value === null) {
return null;
}
return { line: value.line, character: value.character };
}


export function asCodeRange(value: Range): vscode.Range {

if (value === undefined) {
return undefined;
} else if (value === null) {
return null;
}
return new vscode.Range(asCodePosition(value.start), asCodePosition(value.end));
}

export function asCodePosition(value: Position): vscode.Position {

if (value === undefined) {
return undefined;
} else if (value === null) {
return null;
}
return new vscode.Position(value.line, value.character);
}

function getEditorContext(): EditorContext {
return {
currentFilePath: vscode.window.activeTextEditor.document.fileName,
cursorPosition: asPosition(vscode.window.activeTextEditor.selection.active),
selectionRange:
asRange(
new vscode.Range(
vscode.window.activeTextEditor.selection.start,
vscode.window.activeTextEditor.selection.end))
}
}

export namespace GetEditorContextRequest {
export const type: RequestType<GetEditorContextRequestArguments, EditorContext, void> =
{ get method() { return 'editor/getEditorContext'; } };
}

export interface GetEditorContextRequestArguments {
}

enum EditorOperationResponse {
Unsupported = 0,
Completed
}

export namespace InsertTextRequest {
export const type: RequestType<InsertTextRequestArguments, EditorOperationResponse, void> =
{ get method() { return 'editor/insertText'; } };
}

export interface InsertTextRequestArguments {
filePath: string;
insertText: string;
insertRange: Range
}

function insertText(details: InsertTextRequestArguments): EditorOperationResponse {
var edit = new vscode.WorkspaceEdit();

edit.set(
vscode.Uri.parse(details.filePath),
[
new vscode.TextEdit(
new vscode.Range(
details.insertRange.start.line,
details.insertRange.start.character,
details.insertRange.end.line,
details.insertRange.end.character),
details.insertText)
]
);

vscode.workspace.applyEdit(edit);

return EditorOperationResponse.Completed;
}

export namespace SetSelectionRequest {
export const type: RequestType<SetSelectionRequestArguments, EditorOperationResponse, void> =
{ get method() { return 'editor/setSelection'; } };
}

export interface SetSelectionRequestArguments {
selectionRange: Range
}

function setSelection(details: SetSelectionRequestArguments): EditorOperationResponse {
vscode.window.activeTextEditor.selections = [
new vscode.Selection(
asCodePosition(details.selectionRange.start),
asCodePosition(details.selectionRange.end))
]

return EditorOperationResponse.Completed;
}

export namespace OpenFileRequest {
export const type: RequestType<string, EditorOperationResponse, void> =
{ get method() { return 'editor/openFile'; } };
}

function openFile(filePath: string): Thenable<EditorOperationResponse> {

// Make sure the file path is absolute
if (!path.win32.isAbsolute(filePath))
{
filePath = path.win32.resolve(
vscode.workspace.rootPath,
filePath);
}

var promise =
vscode.workspace.openTextDocument(filePath)
.then(doc => vscode.window.showTextDocument(doc))
.then(_ => EditorOperationResponse.Completed);

return promise;
}

export function registerExtensionCommands(client: LanguageClient): void {

vscode.commands.registerCommand('PowerShell.ShowAdditionalCommands', () => {
var editor = vscode.window.activeTextEditor;
var start = editor.selection.start;
var end = editor.selection.end;
if (editor.selection.isEmpty) {
start = new vscode.Position(start.line, 0)
}

showExtensionCommands(client);
});

client.onNotification(
ExtensionCommandAddedNotification.type,
command => addExtensionCommand(command));

client.onRequest(
GetEditorContextRequest.type,
details => getEditorContext());

client.onRequest(
InsertTextRequest.type,
details => insertText(details));

client.onRequest(
SetSelectionRequest.type,
details => setSelection(details));

client.onRequest(
OpenFileRequest.type,
filePath => openFile(filePath));
}
2 changes: 2 additions & 0 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { registerShowHelpCommand } from './features/ShowOnlineHelp';
import { registerOpenInISECommand } from './features/OpenInISE';
import { registerPowerShellFindModuleCommand } from './features/PowerShellFindModule';
import { registerConsoleCommands } from './features/Console';
import { registerExtensionCommands } from './features/ExtensionCommands';

var languageServerClient: LanguageClient = undefined;

Expand Down Expand Up @@ -133,6 +134,7 @@ function registerFeatures() {
registerConsoleCommands(languageServerClient);
registerOpenInISECommand();
registerPowerShellFindModuleCommand(languageServerClient);
registerExtensionCommands(languageServerClient);
}

export function deactivate(): void {
Expand Down