Skip to content
4 changes: 2 additions & 2 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": "^1.10.0"
"vscode": "^1.12.0"
},
"license": "SEE LICENSE IN LICENSE.txt",
"homepage": "https://github.com/PowerShell/vscode-powershell/blob/master/README.md",
Expand Down Expand Up @@ -40,7 +40,7 @@
"@types/node": "^6.0.40",
"typescript": "^2.0.3",
"vsce": "^1.18.0",
"vscode": "^1.0.0"
"vscode": "^1.1.0"
},
"extensionDependencies": [
"vscode.powershell"
Expand Down
185 changes: 185 additions & 0 deletions src/features/HelpCompletion.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
/*---------------------------------------------------------
* Copyright (C) Microsoft Corporation. All rights reserved.
*--------------------------------------------------------*/

import { IFeature } from "../feature";
import { TextDocumentChangeEvent, workspace, Disposable, Position, window, Range, EndOfLine, SnippetString, TextDocument } from "vscode";
import { LanguageClient, RequestType } from "vscode-languageclient";

export namespace CommentHelpRequest {
export const type = new RequestType<any, any, void, void>("powerShell/getCommentHelp");
}

interface CommentHelpRequestParams {
documentUri: string;
triggerPosition: Position;
blockComment: boolean;
}

interface CommentHelpRequestResult {
content: string[];
}

enum SearchState { Searching, Locked, Found };

export class HelpCompletionFeature implements IFeature {
private helpCompletionProvider: HelpCompletionProvider;
private languageClient: LanguageClient;
private disposable: Disposable;

constructor() {
this.helpCompletionProvider = new HelpCompletionProvider();
let subscriptions = [];
workspace.onDidChangeTextDocument(this.onEvent, this, subscriptions);
this.disposable = Disposable.from(...subscriptions);
}

setLanguageClient(languageClient: LanguageClient) {
this.languageClient = languageClient;
this.helpCompletionProvider.languageClient = languageClient;
}

dispose() {
this.disposable.dispose();
}

onEvent(changeEvent: TextDocumentChangeEvent): void {
this.helpCompletionProvider.updateState(
changeEvent.document,
changeEvent.contentChanges[0].text,
changeEvent.contentChanges[0].range);

// todo raise an event when trigger is found, and attach complete() to the event.
if (this.helpCompletionProvider.triggerFound) {
this.helpCompletionProvider.complete().then(() => this.helpCompletionProvider.reset());
}

}
}

class TriggerFinder {
private state: SearchState;
private document: TextDocument;
private count: number;
constructor(private triggerCharacters: string) {
this.state = SearchState.Searching;
this.count = 0;
}

public get found(): boolean {
return this.state === SearchState.Found;
}

public updateState(document: TextDocument, changeText: string): void {
switch (this.state) {
case SearchState.Searching:
if (changeText.length === 1 && changeText[0] === this.triggerCharacters[this.count]) {
this.state = SearchState.Locked;
this.document = document;
this.count++;
}
break;

case SearchState.Locked:
if (changeText.length === 1 && changeText[0] === this.triggerCharacters[this.count] && document === this.document) {
this.count++;
if (this.count === this.triggerCharacters.length) {
this.state = SearchState.Found;
}
}
else {
this.reset();
}
break;

default:
this.reset();
break;
}
}

public reset(): void {
this.state = SearchState.Searching;
this.count = 0;
}
}

class HelpCompletionProvider {
private triggerFinderBlockComment: TriggerFinder;
private triggerFinderLineComment: TriggerFinder;
private lastChangeText: string;
private lastChangeRange: Range;
private lastDocument: TextDocument;
private langClient: LanguageClient;

constructor() {
this.triggerFinderBlockComment = new TriggerFinder("<#");
this.triggerFinderLineComment = new TriggerFinder("##");
}

public get triggerFound(): boolean {
return this.triggerFinderBlockComment.found || this.triggerFinderLineComment.found;
}

public set languageClient(value: LanguageClient) {
this.langClient = value;
}

public updateState(document: TextDocument, changeText: string, changeRange: Range): void {
this.lastDocument = document;
this.lastChangeText = changeText;
this.lastChangeRange = changeRange;
this.triggerFinderBlockComment.updateState(document, changeText);
this.triggerFinderLineComment.updateState(document, changeText);
}

public reset(): void {
this.triggerFinderBlockComment.reset();
this.triggerFinderLineComment.reset();
}

public complete(): Thenable<void> {
if (this.langClient === undefined) {
return;
}

let change = this.lastChangeText;
let triggerStartPos = this.lastChangeRange.start;
let triggerEndPos = this.lastChangeRange.end;
let doc = this.lastDocument;
return this.langClient.sendRequest(
CommentHelpRequest.type,
{
documentUri: doc.uri.toString(),
triggerPosition: triggerStartPos,
blockComment: this.triggerFinderBlockComment.found
}).then(result => {
if (result === undefined) {
return;
}

let content = result.content;
if (content === undefined) {
return;
}

// todo add indentation level to the help content
let editor = window.activeTextEditor;
let replaceRange = new Range(triggerStartPos.translate(0, -1), triggerStartPos.translate(0, 1));

// Trim the leading whitespace (used by the rule for indentation) as VSCode takes care of the indentation.
// Trim the last empty line and join the strings.
let text = content.map(x => x.trimLeft()).slice(0, -1).join(this.getEOL(doc.eol));
editor.insertSnippet(new SnippetString(text), replaceRange);
});
}

private getEOL(eol: EndOfLine): string {
// there are only two type of EndOfLine types.
if (eol === EndOfLine.CRLF) {
return "\r\n";
}

return "\n";
}
}
4 changes: 3 additions & 1 deletion src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import { FindModuleFeature } from './features/PowerShellFindModule';
import { NewFileOrProjectFeature } from './features/NewFileOrProject';
import { ExtensionCommandsFeature } from './features/ExtensionCommands';
import { DocumentFormatterFeature } from './features/DocumentFormatter';
import { HelpCompletionFeature } from "./features/HelpCompletion";

// NOTE: We will need to find a better way to deal with the required
// PS Editor Services version...
Expand Down Expand Up @@ -117,7 +118,8 @@ export function activate(context: vscode.ExtensionContext): void {
new RemoteFilesFeature(),
new DebugSessionFeature(sessionManager),
new PickPSHostProcessFeature(),
new SpecifyScriptArgsFeature(context)
new SpecifyScriptArgsFeature(context),
new HelpCompletionFeature()
];

sessionManager.setExtensionFeatures(extensionFeatures);
Expand Down