diff --git a/examples/ExtensionExamples.ps1 b/examples/ExtensionExamples.ps1 new file mode 100644 index 0000000000..a6db28b20a --- /dev/null +++ b/examples/ExtensionExamples.ps1 @@ -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 diff --git a/package.json b/package.json index 15ce40c8b7..aa889126dd 100644 --- a/package.json +++ b/package.json @@ -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", @@ -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" @@ -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": [ diff --git a/src/features/ExtensionCommands.ts b/src/features/ExtensionCommands.ts new file mode 100644 index 0000000000..9e646feb12 --- /dev/null +++ b/src/features/ExtensionCommands.ts @@ -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 = + { 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 = + { 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 { + + // 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(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 = + { get method() { return 'editor/getEditorContext'; } }; +} + +export interface GetEditorContextRequestArguments { +} + +enum EditorOperationResponse { + Unsupported = 0, + Completed +} + +export namespace InsertTextRequest { + export const type: RequestType = + { 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 = + { 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 = + { get method() { return 'editor/openFile'; } }; +} + +function openFile(filePath: string): Thenable { + + // 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)); +} diff --git a/src/main.ts b/src/main.ts index 90cf80c0e1..f321945e77 100644 --- a/src/main.ts +++ b/src/main.ts @@ -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; @@ -133,6 +134,7 @@ function registerFeatures() { registerConsoleCommands(languageServerClient); registerOpenInISECommand(); registerPowerShellFindModuleCommand(languageServerClient); + registerExtensionCommands(languageServerClient); } export function deactivate(): void {