Add extension folder and connect extension with language server (#32)

* add extension folder and connect with language server

* delete the old .vscode folder and move to new .vscode folder
This commit is contained in:
ChenleiYang 2020-08-24 15:00:02 +08:00 коммит произвёл GitHub
Родитель 7a9c69b279
Коммит f02b6c3165
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
30 изменённых файлов: 5877 добавлений и 21 удалений

2
.gitignore поставляемый
Просмотреть файл

@ -42,7 +42,7 @@ Generated\ Files/
# Visual Studio Code setting directory
.vscode/
!powershell-module/.vscode/
!vscode-extension/.vscode/
!vscode-extension/extension/.vscode/
# MSTest test Results
[Tt]est[Rr]esult*/

20
vscode-extension/.vscode/tasks.json поставляемый
Просмотреть файл

@ -1,20 +0,0 @@
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
{
"version": "2.0.0",
"tasks": [
{
"type": "npm",
"script": "watch",
"problemMatcher": "$tsc-watch",
"isBackground": true,
"presentation": {
"reveal": "never"
},
"group": {
"kind": "build",
"isDefault": true
}
}
]
}

Просмотреть файл

@ -0,0 +1,19 @@
{
"root": true,
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaVersion": 6,
"sourceType": "module"
},
"plugins": [
"@typescript-eslint"
],
"rules": {
"@typescript-eslint/class-name-casing": "warn",
"@typescript-eslint/semi": "warn",
"curly": "warn",
"eqeqeq": "warn",
"no-throw-literal": "warn",
"semi": "off"
}
}

Просмотреть файл

Просмотреть файл

Просмотреть файл

33
vscode-extension/extension/.vscode/tasks.json поставляемый Normal file
Просмотреть файл

@ -0,0 +1,33 @@
{
"version": "2.0.0",
"tasks": [
{
"type": "npm",
"script": "compile",
"group": "build",
"presentation": {
"panel": "dedicated",
"reveal": "never"
},
"problemMatcher": [
"$tsc"
]
},
{
"type": "npm",
"script": "watch",
"isBackground": true,
"group": {
"kind": "build",
"isDefault": true
},
"presentation": {
"panel": "dedicated",
"reveal": "never"
},
"problemMatcher": [
"$tsc-watch"
]
}
]
}

Просмотреть файл

@ -0,0 +1,10 @@
.vscode/**
.vscode-test/**
out/test/**
src/**
.gitignore
vsc-extension-quickstart.md
**/tsconfig.json
**/.eslintrc.json
**/*.map
**/*.ts

Просмотреть файл

@ -0,0 +1,9 @@
# Change Log
All notable changes to the "vscode-extension-azurerm-migration-test" extension will be documented in this file.
Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how to structure this file.
## [Unreleased]
- Initial release

Просмотреть файл

@ -0,0 +1,65 @@
# vscode-extension-azurerm-migration-test README
This is the README for your extension "vscode-extension-azurerm-migration-test". After writing up a brief description, we recommend including the following sections.
## Features
Describe specific features of your extension including screenshots of your extension in action. Image paths are relative to this README file.
For example if there is an image subfolder under your extension project workspace:
\!\[feature X\]\(images/feature-x.png\)
> Tip: Many popular extensions utilize animations. This is an excellent way to show off your extension! We recommend short, focused animations that are easy to follow.
## Requirements
If you have any requirements or dependencies, add a section describing those and how to install and configure them.
## Extension Settings
Include if your extension adds any VS Code settings through the `contributes.configuration` extension point.
For example:
This extension contributes the following settings:
* `myExtension.enable`: enable/disable this extension
* `myExtension.thing`: set to `blah` to do something
## Known Issues
Calling out known issues can help limit users opening duplicate issues against your extension.
## Release Notes
Users appreciate release notes as you update your extension.
### 1.0.0
Initial release of ...
### 1.0.1
Fixed issue #.
### 1.1.0
Added features X, Y, and Z.
-----------------------------------------------------------------------------------------------------------
## Working with Markdown
**Note:** You can author your README using Visual Studio Code. Here are some useful editor keyboard shortcuts:
* Split the editor (`Cmd+\` on macOS or `Ctrl+\` on Windows and Linux)
* Toggle preview (`Shift+CMD+V` on macOS or `Shift+Ctrl+V` on Windows and Linux)
* Press `Ctrl+Space` (Windows, Linux) or `Cmd+Space` (macOS) to see a list of Markdown snippets
### For more information
* [Visual Studio Code's Markdown Support](http://code.visualstudio.com/docs/languages/markdown)
* [Markdown Syntax Reference](https://help.github.com/articles/markdown-basics/)
**Enjoy!**

2224
vscode-extension/extension/package-lock.json сгенерированный Normal file

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -0,0 +1,58 @@
{
"name": "azure-powershell-migration",
"displayName": "Azure PowerShell Migration",
"description": "TBD",
"version": "0.0.1",
"publisher": "azure-powershell",
"engines": {
"vscode": "^1.47.0"
},
"categories": [
"Other"
],
"activationEvents": [
"onCommand:extension.helloWorld",
"onCommand:azure-powershell-migration.selectVersion"
],
"main": "./out/extension.js",
"contributes": {
"commands": [
{
"command": "extension.helloWorld",
"title": "Hello World"
},
{
"title": "Migrate Azure PowerShell Script",
"command": "azure-powershell-migration.selectVersion"
}
]
},
"scripts": {
"vscode:prepublish": "npm run compile",
"compile": "tsc -p ./",
"lint": "eslint src --ext ts",
"watch": "tsc -watch -p ./",
"pretest": "npm run compile && npm run lint",
"test": "node ./out/test/runTest.js"
},
"dependencies": {
"node-fetch": "^2.6.0",
"semver": "^7.3.2",
"uuid": "^8.3.0",
"vscode-extension-telemetry": "~0.1.6",
"vscode-languageclient": "~6.1.3"
},
"devDependencies": {
"@types/vscode": "^1.47.0",
"@types/glob": "^7.1.1",
"@types/mocha": "^7.0.2",
"@types/node": "^13.11.0",
"eslint": "^6.8.0",
"@typescript-eslint/parser": "^2.30.0",
"@typescript-eslint/eslint-plugin": "^2.30.0",
"glob": "^7.1.6",
"mocha": "^7.1.2",
"typescript": "^3.8.3",
"vscode-test": "^1.3.0"
}
}

Просмотреть файл

@ -0,0 +1,125 @@
/*---------------------------------------------------------
* Copyright (C) Microsoft Corporation. All rights reserved.
*--------------------------------------------------------*/
import {
Disposable,
StatusBarAlignment,
StatusBarItem,
ThemeColor,
window,
Command} from "vscode";
export function showAnimatedStatusBarMessage(text: string, hideWhenDone: Thenable<any>): Disposable {
const animatedStatusBarItem: AnimatedStatusBarItem = new AnimatedStatusBarItem(text);
animatedStatusBarItem.show(hideWhenDone);
return animatedStatusBarItem;
}
class AnimatedStatusBarItem implements StatusBarItem {
private readonly animationRate: number;
private statusBarItem: StatusBarItem;
private maxCount: number;
private counter: number;
private baseText: string;
private timerInterval: number;
private elapsedTime: number;
private intervalId: NodeJS.Timer;
private suffixStates: string[];
public get alignment(): StatusBarAlignment {
return this.statusBarItem.alignment;
}
public get priority(): number {
return this.statusBarItem.priority;
}
public get text(): string {
return this.statusBarItem.text;
}
public set text(value: string) {
this.statusBarItem.text = value;
}
public get tooltip(): string {
return this.statusBarItem.tooltip;
}
public set tooltip(value: string) {
this.statusBarItem.tooltip = value;
}
public get color(): string | ThemeColor {
return this.statusBarItem.color;
}
public set color(value: string | ThemeColor) {
this.statusBarItem.color = value;
}
public get command(): string | Command {
return this.statusBarItem.command;
}
public set command(value: string | Command) {
this.statusBarItem.command = value;
}
constructor(baseText: string, alignment?: StatusBarAlignment, priority?: number) {
this.animationRate = 1;
this.statusBarItem = window.createStatusBarItem(alignment, priority);
this.baseText = baseText;
this.counter = 0;
this.suffixStates = [" ", ". ", ".. ", "..."];
this.maxCount = this.suffixStates.length;
this.timerInterval = ((1 / this.maxCount) * 1000) / this.animationRate;
this.elapsedTime = 0;
}
public show(hideWhenDone?: Thenable<any>): void {
this.statusBarItem.show();
this.start();
if (hideWhenDone !== undefined) {
hideWhenDone.then(() => this.hide());
}
}
public hide(): void {
this.stop();
this.statusBarItem.hide();
}
public dispose(): void {
this.statusBarItem.dispose();
}
private updateCounter(): void {
this.counter = (this.counter + 1) % this.maxCount;
this.elapsedTime = this.elapsedTime + this.timerInterval;
}
private updateText(): void {
this.text = this.baseText + this.suffixStates[this.counter];
}
private update(): void {
this.updateCounter();
this.updateText();
}
private reset(): void {
this.counter = 0;
this.updateText();
}
private start(): void {
this.reset();
this.intervalId = setInterval(() => this.update(), this.timerInterval);
}
private stop(): void {
clearInterval(this.intervalId);
}
}

Просмотреть файл

@ -0,0 +1,98 @@
/*---------------------------------------------------------
* Copyright (C) Microsoft Corporation. All rights reserved.
*--------------------------------------------------------*/
import vscode = require("vscode");
const confirmItemLabel: string = "$(checklist) Confirm";
const checkedPrefix: string = "[ $(check) ]";
const uncheckedPrefix: string = "[ ]";
const defaultPlaceHolder: string = "Select 'Confirm' to confirm or press 'Esc' key to cancel";
export interface ICheckboxQuickPickItem {
label: string;
description?: string;
isSelected: boolean;
}
export interface ICheckboxQuickPickOptions {
confirmPlaceHolder: string;
}
const defaultOptions: ICheckboxQuickPickOptions = { confirmPlaceHolder: defaultPlaceHolder};
export function showCheckboxQuickPick(
items: ICheckboxQuickPickItem[],
options: ICheckboxQuickPickOptions = defaultOptions): Thenable<ICheckboxQuickPickItem[]> {
return showInner(items, options).then(
(selectedItem) => {
// We're mutating the original item list so just return it for now.
// If 'selectedItem' is undefined it means the user cancelled the
// inner showQuickPick UI so pass the undefined along.
return selectedItem !== undefined ? items : undefined;
});
}
function getQuickPickItems(items: ICheckboxQuickPickItem[]): vscode.QuickPickItem[] {
const quickPickItems: vscode.QuickPickItem[] = [];
quickPickItems.push({ label: confirmItemLabel, description: "" });
items.forEach((item) =>
quickPickItems.push({
label: convertToCheckBox(item),
description: item.description,
}));
return quickPickItems;
}
function showInner(
items: ICheckboxQuickPickItem[],
options: ICheckboxQuickPickOptions): Thenable<vscode.QuickPickItem> {
const quickPickThenable: Thenable<vscode.QuickPickItem> =
vscode.window.showQuickPick(
getQuickPickItems(items),
{
ignoreFocusOut: true,
matchOnDescription: true,
placeHolder: options.confirmPlaceHolder,
});
return quickPickThenable.then(
(selection) => {
if (!selection) {
return Promise.resolve<vscode.QuickPickItem>(undefined);
}
if (selection.label === confirmItemLabel) {
return selection;
}
const index: number = getItemIndex(items, selection.label);
if (index >= 0) {
toggleSelection(items[index]);
} else {
// tslint:disable-next-line:no-console
console.log(`Couldn't find CheckboxQuickPickItem for label '${selection.label}'`);
}
return showInner(items, options);
});
}
function getItemIndex(items: ICheckboxQuickPickItem[], itemLabel: string): number {
const trimmedLabel = itemLabel.substr(itemLabel.indexOf("]") + 2);
return items.findIndex((item) => item.label === trimmedLabel);
}
function toggleSelection(item: ICheckboxQuickPickItem): void {
item.isSelected = !item.isSelected;
}
function convertToCheckBox(item: ICheckboxQuickPickItem): string {
return `${item.isSelected ? checkedPrefix : uncheckedPrefix} ${item.label}`;
}

Просмотреть файл

@ -0,0 +1,121 @@
/*---------------------------------------------------------
* Copyright (C) Microsoft Corporation. All rights reserved.
*--------------------------------------------------------*/
// import * as vscode from 'vscode';
"use strict";
import path = require("path");
import vscode = require("vscode");
import TelemetryReporter from "vscode-extension-telemetry";
import { DocumentSelector } from "vscode-languageclient";
import { CodeActionsFeature } from "./features/CodeActions";
import { Logger, LogLevel } from "./logging";
import { SessionManager } from "./session";
import Settings = require("./settings");
import { PowerShellLanguageId } from "./utils";
import { LanguageClientConsumer } from "./languageClientConsumer";
import { getSrcVersion } from "./selectVersion";
// The most reliable way to get the name and version of the current extension.
// tslint:disable-next-line: no-var-requires
const PackageJSON: any = require("../package.json");
// the application insights key (also known as instrumentation key) used for telemetry.
let logger: Logger;
let sessionManager: SessionManager;
let languageClientConsumers: LanguageClientConsumer[] = [];
let commandRegistrations: vscode.Disposable[] = [];
let telemetryReporter: TelemetryReporter;
const documentSelector: DocumentSelector = [
{ language: "powershell", scheme: "file" },
{ language: "Powershell", scheme: "file" },
{ language: "powershell", scheme: "untitled" },
];
export function activate(context: vscode.ExtensionContext): void {
console.debug('===========================================');
console.log('Congratulations, your extension "azure-powershell-migration" 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
// Create the logger
logger = new Logger();
// Set the log level
const extensionSettings = Settings.load();
logger.MinimumLogLevel = LogLevel[extensionSettings.developer.editorServicesLogLevel];
sessionManager =
new SessionManager(
logger,
documentSelector,
PackageJSON.displayName,
PackageJSON.version,
telemetryReporter);
// Register commands that do not require Language client
commandRegistrations = [
// new ExamplesFeature(),
// new GenerateBugReportFeature(sessionManager),
// new ISECompatibilityFeature(),
// new OpenInISEFeature(),
// new PesterTestsFeature(sessionManager),
// new RunCodeFeature(sessionManager),
new CodeActionsFeature(logger),
// new SpecifyScriptArgsFeature(context),
]
// Features and command registrations that require language client
languageClientConsumers = [
// new GetDiagnosticFeature(logger, context)
// new GetAstFeature(logger, context),
// new ConsoleFeature(logger),
// new ExpandAliasFeature(logger),
// new GetCommandsFeature(logger),
// new ShowHelpFeature(logger),
// new FindModuleFeature(),
// new ExtensionCommandsFeature(logger),
// new NewFileOrProjectFeature(),
// new RemoteFilesFeature(),
// new DebugSessionFeature(context, sessionManager, logger),
// new PickPSHostProcessFeature(),
// new HelpCompletionFeature(logger),
// new CustomViewsFeature(),
// new PickRunspaceFeature(),
// new ExternalApiFeature(sessionManager, logger)
];
sessionManager.setLanguageClientConsumers(languageClientConsumers);
if (extensionSettings.startAutomatically) {
sessionManager.start();
}
let disposable = vscode.commands.registerCommand('azure-powershell-migration.selectVersion', async () => {
var sourceVersion = await getSrcVersion();
vscode.window.showInformationMessage(`Updating powershell scripts from '${sourceVersion}' to latest`);
});
context.subscriptions.push(disposable);
}
export function deactivate(): void {
// Clean up all extension features
languageClientConsumers.forEach((languageClientConsumer) => {
languageClientConsumer.dispose();
});
commandRegistrations.forEach((commandRegistration) => {
commandRegistration.dispose();
});
// Dispose of the current session
sessionManager.dispose();
// Dispose of the logger
logger.dispose();
}

Просмотреть файл

@ -0,0 +1,46 @@
/*---------------------------------------------------------
* Copyright (C) Microsoft Corporation. All rights reserved.
*--------------------------------------------------------*/
import vscode = require("vscode");
import Window = vscode.window;
import { ILogger } from "../logging";
export class CodeActionsFeature implements vscode.Disposable {
private applyEditsCommand: vscode.Disposable;
private showDocumentationCommand: vscode.Disposable;
constructor(private log: ILogger) {
this.applyEditsCommand = vscode.commands.registerCommand("PowerShell.ApplyCodeActionEdits", (edit: any) => {
Window.activeTextEditor.edit((editBuilder) => {
editBuilder.replace(
new vscode.Range(
edit.StartLineNumber - 1,
edit.StartColumnNumber - 1,
edit.EndLineNumber - 1,
edit.EndColumnNumber - 1),
edit.Text);
});
});
}
public dispose() {
this.applyEditsCommand.dispose();
this.showDocumentationCommand.dispose();
}
public showRuleDocumentation(ruleId: string) {
const pssaDocBaseURL = "https://github.com/PowerShell/PSScriptAnalyzer/blob/master/RuleDocumentation";
if (!ruleId) {
this.log.writeWarning("Cannot show documentation for code action, no ruleName was supplied.");
return;
}
if (ruleId.startsWith("PS")) {
ruleId = ruleId.substr(2);
}
vscode.commands.executeCommand("vscode.open", vscode.Uri.parse(pssaDocBaseURL + `/${ruleId}.md`));
}
}

Просмотреть файл

@ -0,0 +1,302 @@
/*---------------------------------------------------------
* Copyright (C) Microsoft Corporation. All rights reserved.
*--------------------------------------------------------*/
import vscode = require("vscode");
import { LanguageClient, NotificationType, RequestType } from "vscode-languageclient";
import { ICheckboxQuickPickItem, showCheckboxQuickPick } from "../controls/checkboxQuickPick";
import { Logger } from "../logging";
import Settings = require("../settings");
import { LanguageClientConsumer } from "../languageClientConsumer";
export const EvaluateRequestType = new RequestType<IEvaluateRequestArguments, void, void, void>("evaluate");
export const OutputNotificationType = new NotificationType<IOutputNotificationBody, void>("output");
export const ExecutionStatusChangedNotificationType =
new NotificationType<IExecutionStatusDetails, void>("powerShell/executionStatusChanged");
export const ShowChoicePromptRequestType =
new RequestType<IShowChoicePromptRequestArgs,
IShowChoicePromptResponseBody, string, void>("powerShell/showChoicePrompt");
export const ShowInputPromptRequestType =
new RequestType<IShowInputPromptRequestArgs,
IShowInputPromptResponseBody, string, void>("powerShell/showInputPrompt");
export interface IEvaluateRequestArguments {
expression: string;
}
export interface IOutputNotificationBody {
category: string;
output: string;
}
interface IExecutionStatusDetails {
executionOptions: IExecutionOptions;
executionStatus: ExecutionStatus;
hadErrors: boolean;
}
interface IChoiceDetails {
label: string;
helpMessage: string;
}
interface IShowInputPromptRequestArgs {
name: string;
label: string;
}
interface IShowChoicePromptRequestArgs {
isMultiChoice: boolean;
caption: string;
message: string;
choices: IChoiceDetails[];
defaultChoices: number[];
}
interface IShowChoicePromptResponseBody {
responseText: string;
promptCancelled: boolean;
}
interface IShowInputPromptResponseBody {
responseText: string;
promptCancelled: boolean;
}
enum ExecutionStatus {
Pending,
Running,
Failed,
Aborted,
Completed,
}
interface IExecutionOptions {
writeOutputToHost: boolean;
writeErrorsToHost: boolean;
addToHistory: boolean;
interruptCommandPrompt: boolean;
}
function showChoicePrompt(
promptDetails: IShowChoicePromptRequestArgs,
client: LanguageClient): Thenable<IShowChoicePromptResponseBody> {
let resultThenable: Thenable<IShowChoicePromptResponseBody>;
if (!promptDetails.isMultiChoice) {
let quickPickItems =
promptDetails.choices.map<vscode.QuickPickItem>((choice) => {
return {
label: choice.label,
description: choice.helpMessage,
};
});
if (promptDetails.defaultChoices && promptDetails.defaultChoices.length > 0) {
// Shift the default items to the front of the
// array so that the user can select it easily
const defaultChoice = promptDetails.defaultChoices[0];
if (defaultChoice > -1 &&
defaultChoice < promptDetails.choices.length) {
const defaultChoiceItem = quickPickItems[defaultChoice];
quickPickItems.splice(defaultChoice, 1);
// Add the default choice to the head of the array
quickPickItems = [defaultChoiceItem].concat(quickPickItems);
}
}
resultThenable =
vscode.window
.showQuickPick(
quickPickItems,
{ placeHolder: promptDetails.message })
.then(onItemSelected);
} else {
const checkboxQuickPickItems =
promptDetails.choices.map<ICheckboxQuickPickItem>((choice) => {
return {
label: choice.label,
description: choice.helpMessage,
isSelected: false,
};
});
// Select the defaults
promptDetails.defaultChoices.forEach((choiceIndex) => {
checkboxQuickPickItems[choiceIndex].isSelected = true;
});
resultThenable =
showCheckboxQuickPick(
checkboxQuickPickItems,
{ confirmPlaceHolder: promptDetails.message })
.then(onItemsSelected);
}
return resultThenable;
}
function showInputPrompt(
promptDetails: IShowInputPromptRequestArgs,
client: LanguageClient): Thenable<IShowInputPromptResponseBody> {
const resultThenable =
vscode.window.showInputBox({
placeHolder: promptDetails.name + ": ",
}).then(onInputEntered);
return resultThenable;
}
function onItemsSelected(chosenItems: ICheckboxQuickPickItem[]): IShowChoicePromptResponseBody {
if (chosenItems !== undefined) {
return {
promptCancelled: false,
responseText: chosenItems.filter((item) => item.isSelected).map((item) => item.label).join(", "),
};
} else {
// User cancelled the prompt, send the cancellation
return {
promptCancelled: true,
responseText: undefined,
};
}
}
function onItemSelected(chosenItem: vscode.QuickPickItem): IShowChoicePromptResponseBody {
if (chosenItem !== undefined) {
return {
promptCancelled: false,
responseText: chosenItem.label,
};
} else {
// User cancelled the prompt, send the cancellation
return {
promptCancelled: true,
responseText: undefined,
};
}
}
function onInputEntered(responseText: string): IShowInputPromptResponseBody {
if (responseText !== undefined) {
return {
promptCancelled: false,
responseText,
};
} else {
return {
promptCancelled: true,
responseText: undefined,
};
}
}
export class ConsoleFeature extends LanguageClientConsumer {
private commands: vscode.Disposable[];
private resolveStatusBarPromise: (value?: {} | PromiseLike<{}>) => void;
constructor(private log: Logger) {
super();
this.commands = [
vscode.commands.registerCommand("PowerShell.RunSelection", async () => {
if (vscode.window.activeTerminal &&
vscode.window.activeTerminal.name !== "PowerShell Integrated Console") {
this.log.write("PSIC is not active terminal. Running in active terminal using 'runSelectedText'");
await vscode.commands.executeCommand("workbench.action.terminal.runSelectedText");
// We need to honor the focusConsoleOnExecute setting here too. However, the boolean that `show`
// takes is called `preserveFocus` which when `true` the terminal will not take focus.
// This is the inverse of focusConsoleOnExecute so we have to inverse the boolean.
vscode.window.activeTerminal.show(!Settings.load().integratedConsole.focusConsoleOnExecute);
await vscode.commands.executeCommand("workbench.action.terminal.scrollToBottom");
return;
}
const editor = vscode.window.activeTextEditor;
let selectionRange: vscode.Range;
if (!editor.selection.isEmpty) {
selectionRange =
new vscode.Range(
editor.selection.start,
editor.selection.end);
} else {
selectionRange = editor.document.lineAt(editor.selection.start.line).range;
}
this.languageClient.sendRequest(EvaluateRequestType, {
expression: editor.document.getText(selectionRange),
});
// Show the integrated console if it isn't already visible and
// scroll terminal to bottom so new output is visible
await vscode.commands.executeCommand("PowerShell.ShowSessionConsole", true);
await vscode.commands.executeCommand("workbench.action.terminal.scrollToBottom");
}),
];
}
public dispose() {
// Make sure we cancel any status bar
this.clearStatusBar();
this.commands.forEach((command) => command.dispose());
}
public setLanguageClient(languageClient: LanguageClient) {
this.languageClient = languageClient;
this.languageClient.onRequest(
ShowChoicePromptRequestType,
(promptDetails) => showChoicePrompt(promptDetails, this.languageClient));
this.languageClient.onRequest(
ShowInputPromptRequestType,
(promptDetails) => showInputPrompt(promptDetails, this.languageClient));
// Set up status bar alerts for when PowerShell is executing a script
this.languageClient.onNotification(
ExecutionStatusChangedNotificationType,
(executionStatusDetails) => {
switch (executionStatusDetails.executionStatus) {
// If execution has changed to running, make a notification
case ExecutionStatus.Running:
this.showExecutionStatus("PowerShell");
break;
// If the execution has stopped, destroy the previous notification
case ExecutionStatus.Completed:
case ExecutionStatus.Aborted:
case ExecutionStatus.Failed:
this.clearStatusBar();
break;
}
});
}
private showExecutionStatus(message: string) {
vscode.window.withProgress({
location: vscode.ProgressLocation.Window,
}, (progress) => {
return new Promise((resolve, reject) => {
this.clearStatusBar();
this.resolveStatusBarPromise = resolve;
progress.report({ message });
});
});
}
private clearStatusBar() {
if (this.resolveStatusBarPromise) {
this.resolveStatusBarPromise();
}
}
}

Просмотреть файл

@ -0,0 +1,192 @@
/*---------------------------------------------------------
* Copyright (C) Microsoft Corporation. All rights reserved.
*--------------------------------------------------------*/
import { spawn } from "child_process";
import * as fs from "fs";
import fetch, { RequestInit } from "node-fetch";
import * as os from "os";
import * as path from "path";
import * as semver from "semver";
import * as stream from "stream";
import * as util from "util";
import { MessageItem, ProgressLocation, window } from "vscode";
import { LanguageClient } from "vscode-languageclient";
import { SessionManager } from "../session";
import * as Settings from "../settings";
import { isMacOS, isWindows } from "../utils";
import { EvaluateRequestType } from "./Console";
const streamPipeline = util.promisify(stream.pipeline);
const PowerShellGitHubReleasesUrl =
"https://api.github.com/repos/PowerShell/PowerShell/releases/latest";
const PowerShellGitHubPrereleasesUrl =
"https://api.github.com/repos/PowerShell/PowerShell/releases";
export class GitHubReleaseInformation {
public static async FetchLatestRelease(preview: boolean): Promise<GitHubReleaseInformation> {
const requestConfig: RequestInit = {};
// For CI. This prevents GitHub from rate limiting us.
if (process.env.PS_TEST_GITHUB_API_USERNAME && process.env.PS_TEST_GITHUB_API_PAT) {
const authHeaderValue = Buffer
.from(`${process.env.PS_TEST_GITHUB_API_USERNAME}:${process.env.PS_TEST_GITHUB_API_PAT}`)
.toString("base64");
requestConfig.headers = {
Authorization: `Basic ${authHeaderValue}`,
};
}
// Fetch the latest PowerShell releases from GitHub.
const response = await fetch(
preview ? PowerShellGitHubPrereleasesUrl : PowerShellGitHubReleasesUrl,
requestConfig);
if (!response.ok) {
const json = await response.json();
throw new Error(json.message || json || "response was not ok.");
}
// For preview, we grab all the releases and then grab the first prerelease.
const releaseJson = preview
? (await response.json()).find((release: any) => release.prerelease)
: await response.json();
return new GitHubReleaseInformation(
releaseJson.tag_name, releaseJson.assets);
}
public version: semver.SemVer;
public isPreview: boolean = false;
public assets: any[];
public constructor(version: string | semver.SemVer, assets: any[] = []) {
this.version = semver.parse(version);
if (semver.prerelease(this.version)) {
this.isPreview = true;
}
this.assets = assets;
}
}
interface IUpdateMessageItem extends MessageItem {
id: number;
}
export async function InvokePowerShellUpdateCheck(
sessionManager: SessionManager,
languageServerClient: LanguageClient,
localVersion: semver.SemVer,
arch: string,
release: GitHubReleaseInformation) {
const options: IUpdateMessageItem[] = [
{
id: 0,
title: "Yes",
},
{
id: 1,
title: "Not now",
},
{
id: 2,
title: "Do not show this notification again",
},
];
// If our local version is up-to-date, we can return early.
if (semver.compare(localVersion, release.version) >= 0) {
return;
}
const commonText: string = `You have an old version of PowerShell (${
localVersion.raw
}). The current latest release is ${
release.version.raw
}.`;
if (process.platform === "linux") {
await window.showInformationMessage(
`${commonText} We recommend updating to the latest version.`);
return;
}
const result = await window.showInformationMessage(
`${commonText} Would you like to update the version? ${
isMacOS ? "(Homebrew is required on macOS)"
: "(This will close ALL pwsh terminals running in this Visual Studio Code session)"
}`, ...options);
// If the user cancels the notification.
if (!result) { return; }
// Yes choice.
switch (result.id) {
// Yes choice.
case 0:
if (isWindows) {
const msiMatcher = arch === "x86" ?
"win-x86.msi" : "win-x64.msi";
const asset = release.assets.filter((a: any) => a.name.indexOf(msiMatcher) >= 0)[0];
const msiDownloadPath = path.join(os.tmpdir(), asset.name);
const res = await fetch(asset.browser_download_url);
if (!res.ok) {
throw new Error("unable to fetch MSI");
}
await window.withProgress({
title: "Downloading PowerShell Installer...",
location: ProgressLocation.Notification,
cancellable: false,
},
async () => {
// Streams the body of the request to a file.
await streamPipeline(res.body, fs.createWriteStream(msiDownloadPath));
});
// Stop the Integrated Console session because Windows likes to hold on to files.
sessionManager.stop();
// Close all terminals with the name "pwsh" in the current VS Code session.
// This will encourage folks to not close the instance of VS Code that spawned
// the MSI process.
for (const terminal of window.terminals) {
if (terminal.name === "pwsh") {
terminal.dispose();
}
}
// Invoke the MSI via cmd.
const msi = spawn("msiexec", ["/i", msiDownloadPath]);
msi.on("close", (code) => {
// Now that the MSI is finished, start the Integrated Console session.
sessionManager.start();
fs.unlinkSync(msiDownloadPath);
});
} else if (isMacOS) {
const script = release.isPreview
? "brew cask upgrade powershell-preview"
: "brew cask upgrade powershell";
await languageServerClient.sendRequest(EvaluateRequestType, {
expression: script,
});
}
break;
// Never choice.
case 2:
await Settings.change("promptToUpdatePowerShell", false, true);
break;
default:
break;
}
}

Просмотреть файл

@ -0,0 +1,29 @@
/*---------------------------------------------------------
* Copyright (C) Microsoft Corporation. All rights reserved.
*--------------------------------------------------------*/
import { window } from "vscode";
import { LanguageClient } from "vscode-languageclient";
export abstract class LanguageClientConsumer {
private _languageClient: LanguageClient;
public setLanguageClient(languageClient: LanguageClient) {
this.languageClient = languageClient;
}
abstract dispose(): void;
public get languageClient(): LanguageClient {
if (!this._languageClient) {
window.showInformationMessage(
"PowerShell extension has not finished starting up yet. Please try again in a few moments.");
}
return this._languageClient;
}
public set languageClient(value: LanguageClient) {
this._languageClient = value;
}
}

Просмотреть файл

@ -0,0 +1,197 @@
/*---------------------------------------------------------
* Copyright (C) Microsoft Corporation. All rights reserved.
*--------------------------------------------------------*/
import fs = require("fs");
import os = require("os");
import path = require("path");
import vscode = require("vscode");
import utils = require("./utils");
export enum LogLevel {
Diagnostic,
Verbose,
Normal,
Warning,
Error,
}
/** Interface for logging operations. New features should use this interface for the "type" of logger.
* This will allow for easy mocking of the logger during unit tests.
*/
export interface ILogger {
write(message: string, ...additionalMessages: string[]);
writeDiagnostic(message: string, ...additionalMessages: string[]);
writeVerbose(message: string, ...additionalMessages: string[]);
writeWarning(message: string, ...additionalMessages: string[]);
writeAndShowWarning(message: string, ...additionalMessages: string[]);
writeError(message: string, ...additionalMessages: string[]);
}
export class Logger implements ILogger {
public logBasePath: string;
public logSessionPath: string;
public MinimumLogLevel: LogLevel = LogLevel.Normal;
private commands: vscode.Disposable[];
private logChannel: vscode.OutputChannel;
private logFilePath: string;
constructor() {
this.logChannel = vscode.window.createOutputChannel("PowerShell Extension Logs");
this.logBasePath = path.resolve(__dirname, "../../logs");
utils.ensurePathExists(this.logBasePath);
this.commands = [
vscode.commands.registerCommand(
"PowerShell.ShowLogs",
() => { this.showLogPanel(); }),
vscode.commands.registerCommand(
"PowerShell.OpenLogFolder",
() => { this.openLogFolder(); }),
];
}
public dispose() {
this.commands.forEach((command) => { command.dispose(); });
this.logChannel.dispose();
}
public getLogFilePath(baseName: string): string {
return path.resolve(this.logSessionPath, `${baseName}.log`);
}
public writeAtLevel(logLevel: LogLevel, message: string, ...additionalMessages: string[]) {
if (logLevel >= this.MinimumLogLevel) {
this.writeLine(message, logLevel);
additionalMessages.forEach((line) => {
this.writeLine(line, logLevel);
});
}
}
public write(message: string, ...additionalMessages: string[]) {
this.writeAtLevel(LogLevel.Normal, message, ...additionalMessages);
}
public writeDiagnostic(message: string, ...additionalMessages: string[]) {
this.writeAtLevel(LogLevel.Diagnostic, message, ...additionalMessages);
}
public writeVerbose(message: string, ...additionalMessages: string[]) {
this.writeAtLevel(LogLevel.Verbose, message, ...additionalMessages);
}
public writeWarning(message: string, ...additionalMessages: string[]) {
this.writeAtLevel(LogLevel.Warning, message, ...additionalMessages);
}
public writeAndShowWarning(message: string, ...additionalMessages: string[]) {
this.writeWarning(message, ...additionalMessages);
vscode.window.showWarningMessage(message, "Show Logs").then((selection) => {
if (selection !== undefined) {
this.showLogPanel();
}
});
}
public writeError(message: string, ...additionalMessages: string[]) {
this.writeAtLevel(LogLevel.Error, message, ...additionalMessages);
}
public writeAndShowError(message: string, ...additionalMessages: string[]) {
this.writeError(message, ...additionalMessages);
vscode.window.showErrorMessage(message, "Show Logs").then((selection) => {
if (selection !== undefined) {
this.showLogPanel();
}
});
}
public async writeAndShowErrorWithActions(
message: string,
actions: { prompt: string; action: () => Promise<void> }[]) {
this.writeError(message);
const fullActions = [
...actions,
{ prompt: "Show Logs", action: async () => { this.showLogPanel(); } },
];
const actionKeys: string[] = fullActions.map((action) => action.prompt);
const choice = await vscode.window.showErrorMessage(message, ...actionKeys);
if (choice) {
for (const action of fullActions) {
if (choice === action.prompt) {
await action.action();
return;
}
}
}
}
public startNewLog(minimumLogLevel: string = "Normal") {
this.MinimumLogLevel = this.logLevelNameToValue(minimumLogLevel.trim());
this.logSessionPath =
path.resolve(
this.logBasePath,
`${Math.floor(Date.now() / 1000)}-${vscode.env.sessionId}`);
this.logFilePath = this.getLogFilePath("vscode-powershell");
utils.ensurePathExists(this.logSessionPath);
}
private logLevelNameToValue(logLevelName: string): LogLevel {
switch (logLevelName.toLowerCase()) {
case "diagnostic": return LogLevel.Diagnostic;
case "verbose": return LogLevel.Verbose;
case "normal": return LogLevel.Normal;
case "warning": return LogLevel.Warning;
case "error": return LogLevel.Error;
default: return LogLevel.Normal;
}
}
private showLogPanel() {
this.logChannel.show();
}
private openLogFolder() {
if (this.logSessionPath) {
// Open the folder in VS Code since there isn't an easy way to
// open the folder in the platform's file browser
vscode.commands.executeCommand(
"vscode.openFolder",
vscode.Uri.file(this.logSessionPath),
true);
}
}
private writeLine(message: string, level: LogLevel = LogLevel.Normal) {
const now = new Date();
const timestampedMessage =
`${now.toLocaleDateString()} ${now.toLocaleTimeString()} [${LogLevel[level].toUpperCase()}] - ${message}`;
this.logChannel.appendLine(timestampedMessage);
if (this.logFilePath) {
fs.appendFile(
this.logFilePath,
timestampedMessage + os.EOL,
(err) => {
if (err) {
// tslint:disable-next-line:no-console
console.log(`Error writing to vscode-powershell log file: ${err}`);
}
});
}
}
}

Просмотреть файл

@ -0,0 +1,498 @@
/*---------------------------------------------------------
* Copyright (C) Microsoft Corporation. All rights reserved.
*--------------------------------------------------------*/
import * as child_process from "child_process";
import * as fs from "fs";
import * as os from "os";
import * as path from "path";
import * as process from "process";
import { IPowerShellAdditionalExePathSettings } from "./settings";
const WindowsPowerShell64BitLabel = "Windows PowerShell (x64)";
const WindowsPowerShell32BitLabel = "Windows PowerShell (x86)";
const LinuxExePath = "/usr/bin/pwsh";
const LinuxPreviewExePath = "/usr/bin/pwsh-preview";
const SnapExePath = "/snap/bin/pwsh";
const SnapPreviewExePath = "/snap/bin/pwsh-preview";
const MacOSExePath = "/usr/local/bin/pwsh";
const MacOSPreviewExePath = "/usr/local/bin/pwsh-preview";
export enum OperatingSystem {
Unknown,
Windows,
MacOS,
Linux,
}
export interface IPlatformDetails {
operatingSystem: OperatingSystem;
isOS64Bit: boolean;
isProcess64Bit: boolean;
}
export interface IPowerShellExeDetails {
readonly displayName: string;
readonly exePath: string;
}
export function getPlatformDetails(): IPlatformDetails {
let operatingSystem = OperatingSystem.Unknown;
if (process.platform === "win32") {
operatingSystem = OperatingSystem.Windows;
} else if (process.platform === "darwin") {
operatingSystem = OperatingSystem.MacOS;
} else if (process.platform === "linux") {
operatingSystem = OperatingSystem.Linux;
}
const isProcess64Bit = process.arch === "x64";
return {
operatingSystem,
isOS64Bit: isProcess64Bit || process.env.hasOwnProperty("PROCESSOR_ARCHITEW6432"),
isProcess64Bit,
};
}
/**
* Class to lazily find installed PowerShell executables on a machine.
* When given a list of additional PowerShell executables,
* this will also surface those at the end of the list.
*/
export class PowerShellExeFinder {
// This is required, since parseInt("7-preview") will return 7.
private static IntRegex: RegExp = /^\d+$/;
private static PwshMsixRegex: RegExp = /^Microsoft.PowerShell_.*/;
private static PwshPreviewMsixRegex: RegExp = /^Microsoft.PowerShellPreview_.*/;
// The platform details descriptor for the platform we're on
private readonly platformDetails: IPlatformDetails;
// Additional configured PowerShells
private readonly additionalPSExeSettings: Iterable<IPowerShellAdditionalExePathSettings>;
private winPS: IPossiblePowerShellExe;
private alternateBitnessWinPS: IPossiblePowerShellExe;
/**
* Create a new PowerShellFinder object to discover PowerShell installations.
* @param platformDetails Information about the machine we are running on.
* @param additionalPowerShellExes Additional PowerShell installations as configured in the settings.
*/
constructor(
platformDetails?: IPlatformDetails,
additionalPowerShellExes?: Iterable<IPowerShellAdditionalExePathSettings>) {
this.platformDetails = platformDetails || getPlatformDetails();
this.additionalPSExeSettings = additionalPowerShellExes || [];
}
/**
* Returns the first available PowerShell executable found in the search order.
*/
public getFirstAvailablePowerShellInstallation(): IPowerShellExeDetails {
for (const pwsh of this.enumeratePowerShellInstallations()) {
return pwsh;
}
}
/**
* Get an array of all PowerShell executables found when searching for PowerShell installations.
*/
public getAllAvailablePowerShellInstallations(): IPowerShellExeDetails[] {
return Array.from(this.enumeratePowerShellInstallations());
}
/**
* Fixes PowerShell paths when Windows PowerShell is set to the non-native bitness.
* @param configuredPowerShellPath the PowerShell path configured by the user.
*/
public fixWindowsPowerShellPath(configuredPowerShellPath: string): string {
const altWinPS = this.findWinPS({ useAlternateBitness: true });
if (!altWinPS) {
return configuredPowerShellPath;
}
const lowerAltWinPSPath = altWinPS.exePath.toLocaleLowerCase();
const lowerConfiguredPath = configuredPowerShellPath.toLocaleLowerCase();
if (lowerConfiguredPath === lowerAltWinPSPath) {
return this.findWinPS().exePath;
}
return configuredPowerShellPath;
}
/**
* Iterates through PowerShell installations on the machine according
* to configuration passed in through the constructor.
* PowerShell items returned by this object are verified
* to exist on the filesystem.
*/
public *enumeratePowerShellInstallations(): Iterable<IPowerShellExeDetails> {
// Get the default PowerShell installations first
for (const defaultPwsh of this.enumerateDefaultPowerShellInstallations()) {
if (defaultPwsh && defaultPwsh.exists()) {
yield defaultPwsh;
}
}
// Also show any additionally configured PowerShells
// These may be duplicates of the default installations, but given a different name.
for (const additionalPwsh of this.enumerateAdditionalPowerShellInstallations()) {
if (additionalPwsh && additionalPwsh.exists()) {
yield additionalPwsh;
}
}
}
/**
* Iterates through all the possible well-known PowerShell installations on a machine.
* Returned values may not exist, but come with an .exists property
* which will check whether the executable exists.
*/
private *enumerateDefaultPowerShellInstallations(): Iterable<IPossiblePowerShellExe> {
// Find PSCore stable first
yield this.findPSCoreStable();
switch (this.platformDetails.operatingSystem) {
case OperatingSystem.Linux:
// On Linux, find the snap
yield this.findPSCoreStableSnap();
break;
case OperatingSystem.Windows:
// Windows may have a 32-bit pwsh.exe
yield this.findPSCoreWindowsInstallation({ useAlternateBitness: true });
// Also look for the MSIX/UWP installation
yield this.findPSCoreMsix();
break;
}
// Look for the .NET global tool
// Some older versions of PowerShell have a bug in this where startup will fail,
// but this is fixed in newer versions
yield this.findPSCoreDotnetGlobalTool();
// Look for PSCore preview
yield this.findPSCorePreview();
switch (this.platformDetails.operatingSystem) {
// On Linux, there might be a preview snap
case OperatingSystem.Linux:
yield this.findPSCorePreviewSnap();
break;
case OperatingSystem.Windows:
// Find a preview MSIX
yield this.findPSCoreMsix({ findPreview: true });
// Look for pwsh-preview with the opposite bitness
yield this.findPSCoreWindowsInstallation({ useAlternateBitness: true, findPreview: true });
// Finally, get Windows PowerShell
// Get the natural Windows PowerShell for the process bitness
yield this.findWinPS();
// Get the alternate bitness Windows PowerShell
yield this.findWinPS({ useAlternateBitness: true });
break;
}
}
/**
* Iterates through the configured additonal PowerShell executable locations,
* without checking for their existence.
*/
private *enumerateAdditionalPowerShellInstallations(): Iterable<IPossiblePowerShellExe> {
for (const additionalPwshSetting of this.additionalPSExeSettings) {
yield new PossiblePowerShellExe(additionalPwshSetting.exePath, additionalPwshSetting.versionName);
}
}
private findPSCoreStable(): IPossiblePowerShellExe {
switch (this.platformDetails.operatingSystem) {
case OperatingSystem.Linux:
return new PossiblePowerShellExe(LinuxExePath, "PowerShell");
case OperatingSystem.MacOS:
return new PossiblePowerShellExe(MacOSExePath, "PowerShell");
case OperatingSystem.Windows:
return this.findPSCoreWindowsInstallation();
}
}
private findPSCorePreview(): IPossiblePowerShellExe {
switch (this.platformDetails.operatingSystem) {
case OperatingSystem.Linux:
return new PossiblePowerShellExe(LinuxPreviewExePath, "PowerShell Preview");
case OperatingSystem.MacOS:
return new PossiblePowerShellExe(MacOSPreviewExePath, "PowerShell Preview");
case OperatingSystem.Windows:
return this.findPSCoreWindowsInstallation({ findPreview: true });
}
}
private findPSCoreDotnetGlobalTool(): IPossiblePowerShellExe {
const exeName: string = this.platformDetails.operatingSystem === OperatingSystem.Windows
? "pwsh.exe"
: "pwsh";
const dotnetGlobalToolExePath: string = path.join(os.homedir(), ".dotnet", "tools", exeName);
return new PossiblePowerShellExe(dotnetGlobalToolExePath, ".NET Core PowerShell Global Tool");
}
private findPSCoreMsix({ findPreview }: { findPreview?: boolean } = {}): IPossiblePowerShellExe {
// We can't proceed if there's no LOCALAPPDATA path
if (!process.env.LOCALAPPDATA) {
return null;
}
// Find the base directory for MSIX application exe shortcuts
const msixAppDir = path.join(process.env.LOCALAPPDATA, "Microsoft", "WindowsApps");
if (!fs.existsSync(msixAppDir)) {
return null;
}
// Define whether we're looking for the preview or the stable
const { pwshMsixDirRegex, pwshMsixName } = findPreview
? { pwshMsixDirRegex: PowerShellExeFinder.PwshPreviewMsixRegex, pwshMsixName: "PowerShell Preview (Store)" }
: { pwshMsixDirRegex: PowerShellExeFinder.PwshMsixRegex, pwshMsixName: "PowerShell (Store)" };
// We should find only one such application, so return on the first one
for (const subdir of fs.readdirSync(msixAppDir)) {
if (pwshMsixDirRegex.test(subdir)) {
const pwshMsixPath = path.join(msixAppDir, subdir, "pwsh.exe");
return new PossiblePowerShellExe(pwshMsixPath, pwshMsixName);
}
}
// If we find nothing, return null
return null;
}
private findPSCoreStableSnap(): IPossiblePowerShellExe {
return new PossiblePowerShellExe(SnapExePath, "PowerShell Snap");
}
private findPSCorePreviewSnap(): IPossiblePowerShellExe {
return new PossiblePowerShellExe(SnapPreviewExePath, "PowerShell Preview Snap");
}
private findPSCoreWindowsInstallation(
{ useAlternateBitness = false, findPreview = false }:
{ useAlternateBitness?: boolean; findPreview?: boolean } = {}): IPossiblePowerShellExe {
const programFilesPath: string = this.getProgramFilesPath({ useAlternateBitness });
if (!programFilesPath) {
return null;
}
const powerShellInstallBaseDir = path.join(programFilesPath, "PowerShell");
// Ensure the base directory exists
if (!(fs.existsSync(powerShellInstallBaseDir) && fs.lstatSync(powerShellInstallBaseDir).isDirectory())) {
return null;
}
let highestSeenVersion: number = -1;
let pwshExePath: string = null;
for (const item of fs.readdirSync(powerShellInstallBaseDir)) {
let currentVersion: number = -1;
if (findPreview) {
// We are looking for something like "7-preview"
// Preview dirs all have dashes in them
const dashIndex = item.indexOf("-");
if (dashIndex < 0) {
continue;
}
// Verify that the part before the dash is an integer
const intPart: string = item.substring(0, dashIndex);
if (!PowerShellExeFinder.IntRegex.test(intPart)) {
continue;
}
// Verify that the part after the dash is "preview"
if (item.substring(dashIndex + 1) !== "preview") {
continue;
}
currentVersion = parseInt(intPart, 10);
} else {
// Search for a directory like "6" or "7"
if (!PowerShellExeFinder.IntRegex.test(item)) {
continue;
}
currentVersion = parseInt(item, 10);
}
// Ensure we haven't already seen a higher version
if (currentVersion <= highestSeenVersion) {
continue;
}
// Now look for the file
const exePath = path.join(powerShellInstallBaseDir, item, "pwsh.exe");
if (!fs.existsSync(exePath)) {
continue;
}
pwshExePath = exePath;
highestSeenVersion = currentVersion;
}
if (!pwshExePath) {
return null;
}
const bitness: string = programFilesPath.includes("x86")
? "(x86)"
: "(x64)";
const preview: string = findPreview ? " Preview" : "";
return new PossiblePowerShellExe(pwshExePath, `PowerShell${preview} ${bitness}`);
}
private findWinPS({ useAlternateBitness = false }: { useAlternateBitness?: boolean } = {}): IPossiblePowerShellExe {
// 32-bit OSes only have one WinPS on them
if (!this.platformDetails.isOS64Bit && useAlternateBitness) {
return null;
}
let winPS = useAlternateBitness ? this.alternateBitnessWinPS : this.winPS;
if (winPS === undefined) {
const systemFolderPath: string = this.getSystem32Path({ useAlternateBitness });
const winPSPath = path.join(systemFolderPath, "WindowsPowerShell", "v1.0", "powershell.exe");
let displayName: string;
if (this.platformDetails.isProcess64Bit) {
displayName = useAlternateBitness
? WindowsPowerShell32BitLabel
: WindowsPowerShell64BitLabel;
} else if (this.platformDetails.isOS64Bit) {
displayName = useAlternateBitness
? WindowsPowerShell64BitLabel
: WindowsPowerShell32BitLabel;
} else {
displayName = WindowsPowerShell32BitLabel;
}
winPS = new PossiblePowerShellExe(winPSPath, displayName, { knownToExist: true });
if (useAlternateBitness) {
this.alternateBitnessWinPS = winPS;
} else {
this.winPS = winPS;
}
}
return winPS;
}
private getProgramFilesPath(
{ useAlternateBitness = false }: { useAlternateBitness?: boolean } = {}): string | null {
if (!useAlternateBitness) {
// Just use the native system bitness
return process.env.ProgramFiles;
}
// We might be a 64-bit process looking for 32-bit program files
if (this.platformDetails.isProcess64Bit) {
return process.env["ProgramFiles(x86)"];
}
// We might be a 32-bit process looking for 64-bit program files
if (this.platformDetails.isOS64Bit) {
return process.env.ProgramW6432;
}
// We're a 32-bit process on 32-bit Windows, there is no other Program Files dir
return null;
}
private getSystem32Path({ useAlternateBitness = false }: { useAlternateBitness?: boolean } = {}): string | null {
const windir: string = process.env.windir;
if (!useAlternateBitness) {
// Just use the native system bitness
return path.join(windir, "System32");
}
// We might be a 64-bit process looking for 32-bit system32
if (this.platformDetails.isProcess64Bit) {
return path.join(windir, "SysWOW64");
}
// We might be a 32-bit process looking for 64-bit system32
if (this.platformDetails.isOS64Bit) {
return path.join(windir, "Sysnative");
}
// We're on a 32-bit Windows, so no alternate bitness
return null;
}
}
export function getWindowsSystemPowerShellPath(systemFolderName: string) {
return path.join(
process.env.windir,
systemFolderName,
"WindowsPowerShell",
"v1.0",
"powershell.exe");
}
interface IPossiblePowerShellExe extends IPowerShellExeDetails {
exists(): boolean;
}
class PossiblePowerShellExe implements IPossiblePowerShellExe {
public readonly exePath: string;
public readonly displayName: string;
private knownToExist: boolean;
constructor(
pathToExe: string,
installationName: string,
{ knownToExist = false }: { knownToExist?: boolean } = {}) {
this.exePath = pathToExe;
this.displayName = installationName;
this.knownToExist = knownToExist || undefined;
}
public exists(): boolean {
if (this.knownToExist === undefined) {
this.knownToExist = fs.existsSync(this.exePath);
}
return this.knownToExist;
}
}

Просмотреть файл

@ -0,0 +1,218 @@
/*---------------------------------------------------------
* Copyright (C) Microsoft Corporation. All rights reserved.
*--------------------------------------------------------*/
import cp = require("child_process");
import fs = require("fs");
import net = require("net");
import os = require("os");
import path = require("path");
import vscode = require("vscode");
import { Logger } from "./logging";
import Settings = require("./settings");
import utils = require("./utils");
export class PowerShellProcess {
public static escapeSingleQuotes(pspath: string): string {
return pspath.replace(new RegExp("'", "g"), "''");
}
// This is used to warn the user that the extension is taking longer than expected to startup.
// After the 15th try we've hit 30 seconds and should warn.
private static warnUserThreshold = 15;
public onExited: vscode.Event<void>;
private onExitedEmitter = new vscode.EventEmitter<void>();
private consoleTerminal: vscode.Terminal = undefined;
private consoleCloseSubscription: vscode.Disposable;
constructor(
public exePath: string,
private bundledModulesPath: string,
private title: string,
private log: Logger,
private startPsesArgs: string,
private sessionFilePath: string,
private sessionSettings: Settings.ISettings) {
this.onExited = this.onExitedEmitter.event;
}
public async start(logFileName: string): Promise<utils.IEditorServicesSessionDetails> {
const editorServicesLogPath = this.log.getLogFilePath(logFileName);
const psesModulePath =
path.resolve(
__dirname,
this.bundledModulesPath,
"PowerShellEditorServices/PowerShellEditorServices.psd1");
const featureFlags =
this.sessionSettings.developer.featureFlags !== undefined
? this.sessionSettings.developer.featureFlags.map((f) => `'${f}'`).join(", ")
: "";
this.startPsesArgs +=
`-LogPath '${PowerShellProcess.escapeSingleQuotes(editorServicesLogPath)}' ` +
`-SessionDetailsPath '${PowerShellProcess.escapeSingleQuotes(this.sessionFilePath)}' ` +
`-FeatureFlags @(${featureFlags}) `;
if (this.sessionSettings.integratedConsole.useLegacyReadLine) {
this.startPsesArgs += "-UseLegacyReadLine";
}
const powerShellArgs = [];
const useLoginShell: boolean =
(utils.isMacOS && this.sessionSettings.startAsLoginShell.osx)
|| (utils.isLinux && this.sessionSettings.startAsLoginShell.linux);
if (useLoginShell && this.isLoginShell(this.exePath)) {
// This MUST be the first argument.
powerShellArgs.push("-Login");
}
powerShellArgs.push("-NoProfile");
powerShellArgs.push("-NonInteractive");
// Only add ExecutionPolicy param on Windows
if (utils.isWindows) {
powerShellArgs.push("-ExecutionPolicy", "Bypass");
}
const startEditorServices = "Import-Module '" +
PowerShellProcess.escapeSingleQuotes(psesModulePath) +
"'; Start-EditorServices " + this.startPsesArgs;
if (utils.isWindows) {
powerShellArgs.push(
"-Command",
startEditorServices);
} else {
// Use -EncodedCommand for better quote support on non-Windows
powerShellArgs.push(
"-EncodedCommand",
Buffer.from(startEditorServices, "utf16le").toString("base64"));
}
this.log.write(
"Language server starting --",
" PowerShell executable: " + this.exePath,
" PowerShell args: " + powerShellArgs.join(" "),
" PowerShell Editor Services args: " + startEditorServices);
// Make sure no old session file exists
utils.deleteSessionFile(this.sessionFilePath);
// Launch PowerShell in the integrated terminal
this.consoleTerminal =
vscode.window.createTerminal({
name: this.title,
shellPath: this.exePath,
shellArgs: powerShellArgs,
hideFromUser: !this.sessionSettings.integratedConsole.showOnStartup,
cwd: this.sessionSettings.cwd
});
const pwshName = path.basename(this.exePath);
this.log.write(`${pwshName} started.`);
if (this.sessionSettings.integratedConsole.showOnStartup) {
// We still need to run this to set the active terminal to the Integrated Console.
this.consoleTerminal.show(true);
}
// Start the language client
this.log.write("Waiting for session file");
const sessionDetails = await this.waitForSessionFile();
// Subscribe a log event for when the terminal closes
this.log.write("Registering terminal close callback");
this.consoleCloseSubscription = vscode.window.onDidCloseTerminal((terminal) => this.onTerminalClose(terminal));
// Log that the PowerShell terminal process has been started
this.log.write("Registering terminal PID log callback");
this.consoleTerminal.processId.then((pid) => this.logTerminalPid(pid, pwshName));
return sessionDetails;
}
public showConsole(preserveFocus: boolean) {
if (this.consoleTerminal) {
this.consoleTerminal.show(preserveFocus);
}
}
public dispose() {
// Clean up the session file
utils.deleteSessionFile(this.sessionFilePath);
if (this.consoleCloseSubscription) {
this.consoleCloseSubscription.dispose();
this.consoleCloseSubscription = undefined;
}
if (this.consoleTerminal) {
this.log.write("Terminating PowerShell process...");
this.consoleTerminal.dispose();
this.consoleTerminal = undefined;
}
}
private logTerminalPid(pid: number, exeName: string) {
this.log.write(`${exeName} PID: ${pid}`);
}
private isLoginShell(pwshPath: string): boolean {
try {
// We can't know what version of PowerShell we have without running it
// So we try to start PowerShell with -Login
// If it exits successfully, we return true
// If it exits unsuccessfully, node throws, we catch, and return false
cp.execFileSync(pwshPath, ["-Login", "-NoProfile", "-NoLogo", "-Command", "exit 0"]);
} catch {
return false;
}
return true;
}
private async waitForSessionFile(): Promise<utils.IEditorServicesSessionDetails> {
// Determine how many tries by dividing by 2000 thus checking every 2 seconds.
const numOfTries = this.sessionSettings.developer.waitForSessionFileTimeoutSeconds / 2;
const warnAt = numOfTries - PowerShellProcess.warnUserThreshold;
// Check every 2 seconds
for (let i = numOfTries; i > 0; i--) {
if (utils.checkIfFileExists(this.sessionFilePath)) {
this.log.write("Session file found");
const sessionDetails = utils.readSessionFile(this.sessionFilePath);
utils.deleteSessionFile(this.sessionFilePath);
return sessionDetails;
}
if (warnAt === i) {
vscode.window.showWarningMessage(`Loading the PowerShell extension is taking longer than expected.
If you're using privilege enforcement software, this can affect start up performance.`);
}
// Wait a bit and try again
await utils.sleep(2000);
}
const err = "Timed out waiting for session file to appear.";
this.log.write(err);
throw new Error(err);
}
private onTerminalClose(terminal: vscode.Terminal) {
if (terminal !== this.consoleTerminal) {
return;
}
this.log.write("powershell.exe terminated or terminal UI was closed");
this.onExitedEmitter.fire();
}
}

Просмотреть файл

@ -0,0 +1,313 @@
/*---------------------------------------------------------------------------------------------
* 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, QuickInputButton, QuickInput, ExtensionContext, QuickInputButtons, Uri } from 'vscode';
// -------------------------------------------------------
// Helper code that wraps the API for the multi-step case.
// -------------------------------------------------------
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>;
}
class InputFlowAction {
static back = new InputFlowAction();
static cancel = new InputFlowAction();
static resume = new InputFlowAction();
}
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());
}
}
}
/**
* 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 setSrcVersionButton = new MyButton({
dark: Uri.file(context.asAbsolutePath('resources/dark/add.svg')),
light: Uri.file(context.asAbsolutePath('resources/light/add.svg')),
}, 'setSrcVersion');
const setTargetVersionButton = new MyButton({
dark: Uri.file(context.asAbsolutePath('resources/dark/add.svg')),
light: Uri.file(context.asAbsolutePath('resources/light/add.svg')),
}, 'setTargetVersion');
const sourceVersionGroup: QuickPickItem[] = ['AzureRM','Az 1.0','Az 2.0','Az 3.0']
.map(label => ({ label }));
const targetVersionGroup: QuickPickItem[] = ['Az 1.0','Az 2.0','Az 3.0','Az 4.0']
.map(label => ({ label }));
interface State {
title: string;
step: number;
totalSteps: number;
srcVersion: QuickPickItem | string;
targetVersion: QuickPickItem | string;
}
async function collectInputs() {
const state = {} as Partial<State>;
await MultiStepInput.run(input => setSourceVersionQuickPick(input, state));
return state as State;
}
const title = 'Set Migration Parameter';
async function setSourceVersionBox(input: MultiStepInput, state: Partial<State>) {
state.srcVersion = await input.showInputBox({
title,
step: 1,
totalSteps: 2,
value: typeof state.srcVersion === 'string' ? state.srcVersion : '',
prompt: 'Set Source Version',
validate: validateNameIsUnique,
shouldResume: shouldResume
});
return (input: MultiStepInput) => setTargetVersionBox(input, state);
}
async function setSourceVersionQuickPick(input: MultiStepInput, state: Partial<State>) {
const pick = await input.showQuickPick({
title,
step: 1,
totalSteps: 2,
placeholder: 'Set Source Version',
items: sourceVersionGroup,
activeItem: typeof state.srcVersion !== 'string' ? state.srcVersion : undefined,
buttons: [setSrcVersionButton],
shouldResume: shouldResume
});
if (pick instanceof MyButton) {
return (input: MultiStepInput) => setSourceVersionBox(input, state);
}
state.srcVersion = pick.label;
return (input: MultiStepInput) => setTargetVersionQuickPick(input, state);
}
async function setTargetVersionBox(input: MultiStepInput, state: Partial<State>) {
state.targetVersion = await input.showInputBox({
title,
step: 2,
totalSteps: 2,
value: typeof state.targetVersion === 'string' ? state.targetVersion : '',
prompt: 'Set Target Version',
validate: validateNameIsUnique,
shouldResume: shouldResume
});
}
async function setTargetVersionQuickPick(input: MultiStepInput, state: Partial<State>) {
const pick = await input.showQuickPick({
title,
step: 2,
totalSteps: 2,
placeholder: 'Set Target Version',
items: targetVersionGroup,
activeItem: typeof state.targetVersion !== 'string' ? state.targetVersion : undefined,
buttons: [setTargetVersionButton],
shouldResume: shouldResume
});
if (pick instanceof MyButton) {
return (input: MultiStepInput) => setTargetVersionBox(input, state);
}
state.targetVersion = pick.label;
}
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' ? 'Version not unique' : undefined;
}
const state = await collectInputs();
window.showInformationMessage(`Translating powershell scripts from '${state.srcVersion}' to '${state.targetVersion}'`);
return [state.srcVersion,state.targetVersion];
}
export async function getSrcVersion(){
return window.showQuickPick(['AzureRM', 'Az1.0', 'Az2.0','Az3.0'],{
placeHolder: 'Select source scripts version'
});
}

Просмотреть файл

@ -0,0 +1,798 @@
/*---------------------------------------------------------
* Copyright (C) Microsoft Corporation. All rights reserved.
*--------------------------------------------------------*/
import fs = require("fs");
import net = require("net");
import path = require("path");
import * as semver from "semver";
import vscode = require("vscode");
import TelemetryReporter from "vscode-extension-telemetry";
import { Message } from "vscode-jsonrpc";
import { Logger } from "./logging";
import { PowerShellProcess } from "./process";
import Settings = require("./settings");
import utils = require("./utils");
import {
CloseAction, DocumentSelector, ErrorAction, LanguageClient, LanguageClientOptions,
Middleware, NotificationType, RequestType0,
ResolveCodeLensSignature, RevealOutputChannelOn, StreamInfo } from "vscode-languageclient";
import { GitHubReleaseInformation, InvokePowerShellUpdateCheck } from "./features/UpdatePowerShell";
import {
getPlatformDetails, IPlatformDetails, IPowerShellExeDetails,
OperatingSystem, PowerShellExeFinder } from "./platform";
import { LanguageClientConsumer } from "./languageClientConsumer";
export enum SessionStatus {
NeverStarted,
NotStarted,
Initializing,
Running,
Stopping,
Failed,
}
export class SessionManager implements Middleware {
public HostName: string;
public HostVersion: string;
public PowerShellExeDetails: IPowerShellExeDetails;
private ShowSessionMenuCommandName = "PowerShell.ShowSessionMenu";
private editorServicesArgs: string;
private sessionStatus: SessionStatus = SessionStatus.NeverStarted;
private suppressRestartPrompt: boolean;
private focusConsoleOnExecute: boolean;
private platformDetails: IPlatformDetails;
private languageClientConsumers: LanguageClientConsumer[] = [];
private statusBarItem: vscode.StatusBarItem;
private languageServerProcess: PowerShellProcess;
private debugSessionProcess: PowerShellProcess;
private versionDetails: IPowerShellVersionDetails;
private registeredCommands: vscode.Disposable[] = [];
private languageServerClient: LanguageClient = undefined;
private sessionSettings: Settings.ISettings = undefined;
private sessionDetails: utils.IEditorServicesSessionDetails;
private bundledModulesPath: string;
private started: boolean = false;
// Initialized by the start() method, since this requires settings
private powershellExeFinder: PowerShellExeFinder;
// When in development mode, VS Code's session ID is a fake
// value of "someValue.machineId". Use that to detect dev
// mode for now until Microsoft/vscode#10272 gets implemented.
public readonly InDevelopmentMode =
vscode.env.sessionId === "someValue.sessionId";
constructor(
private log: Logger,
private documentSelector: DocumentSelector,
hostName: string,
version: string,
private telemetryReporter: TelemetryReporter) {
this.platformDetails = getPlatformDetails();
this.HostName = hostName;
this.HostVersion = version;
const osBitness = this.platformDetails.isOS64Bit ? "64-bit" : "32-bit";
const procBitness = this.platformDetails.isProcess64Bit ? "64-bit" : "32-bit";
this.log.write(
`Visual Studio Code v${vscode.version} ${procBitness}`,
`${this.HostName} Extension v${this.HostVersion}`,
`Operating System: ${OperatingSystem[this.platformDetails.operatingSystem]} ${osBitness}`);
// Fix the host version so that PowerShell can consume it.
// This is needed when the extension uses a prerelease
// version string like 0.9.1-insiders-1234.
this.HostVersion = this.HostVersion.split("-")[0];
this.registerCommands();
}
public dispose(): void {
// Stop the current session
this.stop();
// Dispose of all commands
this.registeredCommands.forEach((command) => { command.dispose(); });
}
public setLanguageClientConsumers(languageClientConsumers: LanguageClientConsumer[]) {
this.languageClientConsumers = languageClientConsumers;
}
public start(exeNameOverride?: string) {
this.sessionSettings = Settings.load();
if (exeNameOverride) {
this.sessionSettings.powerShellDefaultVersion = exeNameOverride;
}
this.log.startNewLog(this.sessionSettings.developer.editorServicesLogLevel);
// Create the PowerShell executable finder now
this.powershellExeFinder = new PowerShellExeFinder(
this.platformDetails,
this.sessionSettings.powerShellAdditionalExePaths);
this.focusConsoleOnExecute = this.sessionSettings.integratedConsole.focusConsoleOnExecute;
this.createStatusBarItem();
this.promptPowerShellExeSettingsCleanup();
this.migrateWhitespaceAroundPipeSetting();
try {
let powerShellExeDetails;
if (this.sessionSettings.powerShellDefaultVersion) {
for (const details of this.powershellExeFinder.enumeratePowerShellInstallations()) {
// Need to compare names case-insensitively, from https://stackoverflow.com/a/2140723
const wantedName = this.sessionSettings.powerShellDefaultVersion;
if (wantedName.localeCompare(details.displayName, undefined, { sensitivity: "accent" }) === 0) {
powerShellExeDetails = details;
break;
}
}
}
this.PowerShellExeDetails = powerShellExeDetails ||
this.powershellExeFinder.getFirstAvailablePowerShellInstallation();
} catch (e) {
this.log.writeError(`Error occurred while searching for a PowerShell executable:\n${e}`);
}
this.suppressRestartPrompt = false;
if (!this.PowerShellExeDetails) {
const message = "Unable to find PowerShell."
+ " Do you have PowerShell installed?"
+ " You can also configure custom PowerShell installations"
+ " with the 'powershell.powerShellAdditionalExePaths' setting.";
this.log.writeAndShowErrorWithActions(message, [
{
prompt: "Get PowerShell",
action: async () => {
const getPSUri = vscode.Uri.parse("https://aka.ms/get-powershell-vscode");
vscode.env.openExternal(getPSUri);
},
},
]);
return;
}
this.bundledModulesPath = path.resolve(__dirname, this.sessionSettings.bundledModulesPath);
if (this.InDevelopmentMode) {
const devBundledModulesPath =
path.resolve(
__dirname,
this.sessionSettings.developer.bundledModulesPath);
// Make sure the module's bin path exists
if (fs.existsSync(path.join(devBundledModulesPath, "PowerShellEditorServices/bin"))) {
this.bundledModulesPath = devBundledModulesPath;
} else {
this.log.write(
"\nWARNING: In development mode but PowerShellEditorServices dev module path cannot be " +
`found (or has not been built yet): ${devBundledModulesPath}\n`);
}
}
this.editorServicesArgs =
`-HostName 'Visual Studio Code Host' ` +
`-HostProfileId 'Microsoft.VSCode' ` +
`-HostVersion '${this.HostVersion}' ` +
`-AdditionalModules @('PowerShellEditorServices.VSCode') ` +
`-BundledModulesPath '${PowerShellProcess.escapeSingleQuotes(this.bundledModulesPath)}' ` +
`-EnableConsoleRepl `;
if (this.sessionSettings.integratedConsole.suppressStartupBanner) {
this.editorServicesArgs += "-StartupBanner '' ";
} else {
const startupBanner = `=====> ${this.HostName} Integrated Console v${this.HostVersion} <=====
`;
this.editorServicesArgs += `-StartupBanner '${startupBanner}' `;
}
if (this.sessionSettings.developer.editorServicesWaitForDebugger) {
this.editorServicesArgs += "-WaitForDebugger ";
}
if (this.sessionSettings.developer.editorServicesLogLevel) {
this.editorServicesArgs += `-LogLevel '${this.sessionSettings.developer.editorServicesLogLevel}' `;
}
this.startPowerShell();
}
public stop() {
// Shut down existing session if there is one
this.log.write("Shutting down language client...");
if (this.sessionStatus === SessionStatus.Failed) {
// Before moving further, clear out the client and process if
// the process is already dead (i.e. it crashed)
this.languageServerClient = undefined;
this.languageServerProcess = undefined;
}
this.sessionStatus = SessionStatus.Stopping;
// Close the language server client
if (this.languageServerClient !== undefined) {
this.languageServerClient.stop();
this.languageServerClient = undefined;
}
// Kill the PowerShell proceses we spawned
if (this.debugSessionProcess) {
this.debugSessionProcess.dispose();
}
if (this.languageServerProcess) {
this.languageServerProcess.dispose();
}
this.sessionStatus = SessionStatus.NotStarted;
}
public restartSession(exeNameOverride?: string) {
this.stop();
this.start(exeNameOverride);
}
public getSessionDetails(): utils.IEditorServicesSessionDetails {
return this.sessionDetails;
}
public getSessionStatus(): SessionStatus {
return this.sessionStatus;
}
public getPowerShellVersionDetails(): IPowerShellVersionDetails {
return this.versionDetails;
}
public createDebugSessionProcess(
sessionPath: string,
sessionSettings: Settings.ISettings): PowerShellProcess {
this.debugSessionProcess =
new PowerShellProcess(
this.PowerShellExeDetails.exePath,
this.bundledModulesPath,
"[TEMP] PowerShell Integrated Console",
this.log,
this.editorServicesArgs + "-DebugServiceOnly ",
sessionPath,
sessionSettings);
return this.debugSessionProcess;
}
public async waitUntilStarted(): Promise<void> {
while(!this.started) {
await utils.sleep(300);
}
}
// ----- LanguageClient middleware methods -----
public resolveCodeLens(
codeLens: vscode.CodeLens,
token: vscode.CancellationToken,
next: ResolveCodeLensSignature): vscode.ProviderResult<vscode.CodeLens> {
const resolvedCodeLens = next(codeLens, token);
const resolveFunc =
(codeLensToFix: vscode.CodeLens): vscode.CodeLens => {
if (codeLensToFix.command?.command === "editor.action.showReferences") {
const oldArgs = codeLensToFix.command.arguments;
// Our JSON objects don't get handled correctly by
// VS Code's built in editor.action.showReferences
// command so we need to convert them into the
// appropriate types to send them as command
// arguments.
codeLensToFix.command.arguments = [
vscode.Uri.parse(oldArgs[0]),
new vscode.Position(oldArgs[1].line, oldArgs[1].character),
oldArgs[2].map((position) => {
return new vscode.Location(
vscode.Uri.parse(position.uri),
new vscode.Range(
position.range.start.line,
position.range.start.character,
position.range.end.line,
position.range.end.character));
}),
];
}
return codeLensToFix;
};
if ((resolvedCodeLens as Thenable<vscode.CodeLens>).then) {
return (resolvedCodeLens as Thenable<vscode.CodeLens>).then(resolveFunc);
} else if (resolvedCodeLens as vscode.CodeLens) {
return resolveFunc(resolvedCodeLens as vscode.CodeLens);
}
return resolvedCodeLens;
}
// Move old setting codeFormatting.whitespaceAroundPipe to new setting codeFormatting.addWhitespaceAroundPipe
private async migrateWhitespaceAroundPipeSetting() {
const configuration = vscode.workspace.getConfiguration(utils.PowerShellLanguageId);
const deprecatedSetting = 'codeFormatting.whitespaceAroundPipe'
const newSetting = 'codeFormatting.addWhitespaceAroundPipe'
const configurationTargetOfNewSetting = await Settings.getEffectiveConfigurationTarget(newSetting);
if (configuration.has(deprecatedSetting) && configurationTargetOfNewSetting === null) {
const configurationTarget = await Settings.getEffectiveConfigurationTarget(deprecatedSetting);
const value = configuration.get(deprecatedSetting, configurationTarget)
await Settings.change(newSetting, value, configurationTarget);
await Settings.change(deprecatedSetting, undefined, configurationTarget);
}
}
private async promptPowerShellExeSettingsCleanup() {
if (this.sessionSettings.powerShellExePath) {
let warningMessage = "The 'powerShell.powerShellExePath' setting is no longer used. ";
warningMessage += this.sessionSettings.powerShellDefaultVersion
? "We can automatically remove it for you."
: "We can remove it from your settings and prompt you for which PowerShell you want to use.";
const choice = await vscode.window.showWarningMessage(warningMessage, "Let's do it!");
if (choice === undefined) {
// They hit the 'x' to close the dialog.
return;
}
this.suppressRestartPrompt = true;
try {
await Settings.change("powerShellExePath", undefined, true);
} finally {
this.suppressRestartPrompt = false;
}
// Show the session menu at the end if they don't have a PowerShellDefaultVersion.
if (!this.sessionSettings.powerShellDefaultVersion) {
await vscode.commands.executeCommand(this.ShowSessionMenuCommandName);
}
}
}
private onConfigurationUpdated() {
const settings = Settings.load();
this.focusConsoleOnExecute = settings.integratedConsole.focusConsoleOnExecute;
// Detect any setting changes that would affect the session
if (!this.suppressRestartPrompt &&
(settings.useX86Host !==
this.sessionSettings.useX86Host ||
settings.powerShellDefaultVersion.toLowerCase() !==
this.sessionSettings.powerShellDefaultVersion.toLowerCase() ||
settings.developer.editorServicesLogLevel.toLowerCase() !==
this.sessionSettings.developer.editorServicesLogLevel.toLowerCase() ||
settings.developer.bundledModulesPath.toLowerCase() !==
this.sessionSettings.developer.bundledModulesPath.toLowerCase() ||
settings.integratedConsole.useLegacyReadLine !==
this.sessionSettings.integratedConsole.useLegacyReadLine)) {
vscode.window.showInformationMessage(
"The PowerShell runtime configuration has changed, would you like to start a new session?",
"Yes", "No")
.then((response) => {
if (response === "Yes") {
this.restartSession();
}
});
}
}
private setStatusBarVersionString(runspaceDetails: IRunspaceDetails) {
const psVersion = runspaceDetails.powerShellVersion;
let versionString =
this.versionDetails.architecture === "x86"
? `${psVersion.displayVersion} (${psVersion.architecture})`
: psVersion.displayVersion;
if (runspaceDetails.runspaceType !== RunspaceType.Local) {
versionString += ` [${runspaceDetails.connectionString}]`;
}
this.setSessionStatus(
versionString,
SessionStatus.Running);
}
private registerCommands(): void {
this.registeredCommands = [
vscode.commands.registerCommand("PowerShell.RestartSession", () => { this.restartSession(); }),
vscode.commands.registerCommand(this.ShowSessionMenuCommandName, () => { this.showSessionMenu(); }),
vscode.workspace.onDidChangeConfiguration(() => this.onConfigurationUpdated()),
vscode.commands.registerCommand(
"PowerShell.ShowSessionConsole", (isExecute?: boolean) => { this.showSessionConsole(isExecute); }),
];
}
private startPowerShell() {
this.setSessionStatus(
"Starting PowerShell...",
SessionStatus.Initializing);
const sessionFilePath =
utils.getSessionFilePath(
Math.floor(100000 + Math.random() * 900000));
this.languageServerProcess =
new PowerShellProcess(
this.PowerShellExeDetails.exePath,
this.bundledModulesPath,
"PowerShell Integrated Console",
this.log,
this.editorServicesArgs,
sessionFilePath,
this.sessionSettings);
this.languageServerProcess.onExited(
() => {
if (this.sessionStatus === SessionStatus.Running) {
this.setSessionStatus("Session exited", SessionStatus.Failed);
this.promptForRestart();
}
});
this.languageServerProcess
.start("EditorServices")
.then(
(sessionDetails) => {
this.sessionDetails = sessionDetails;
if (sessionDetails.status === "started") {
this.log.write("Language server started.");
// Start the language service client
this.startLanguageClient(sessionDetails);
} else if (sessionDetails.status === "failed") {
if (sessionDetails.reason === "unsupported") {
this.setSessionFailure(
"PowerShell language features are only supported on PowerShell version 5.1 and 6.1" +
` and above. The current version is ${sessionDetails.powerShellVersion}.`);
} else if (sessionDetails.reason === "languageMode") {
this.setSessionFailure(
"PowerShell language features are disabled due to an unsupported LanguageMode: " +
`${sessionDetails.detail}`);
} else {
this.setSessionFailure(
`PowerShell could not be started for an unknown reason '${sessionDetails.reason}'`);
}
} else {
// TODO: Handle other response cases
}
},
(error) => {
this.log.write("Language server startup failed.");
this.setSessionFailure("The language service could not be started: ", error);
},
)
.catch((error) => {
this.log.write("Language server startup failed.");
this.setSessionFailure("The language server could not be started: ", error);
});
}
private promptForRestart() {
vscode.window.showErrorMessage(
"The PowerShell session has terminated due to an error, would you like to restart it?",
"Yes", "No")
.then((answer) => { if (answer === "Yes") { this.restartSession(); }});
}
private startLanguageClient(sessionDetails: utils.IEditorServicesSessionDetails) {
// Log the session details object
this.log.write(JSON.stringify(sessionDetails));
try {
this.log.write(`Connecting to language service on pipe ${sessionDetails.languageServicePipeName}...`);
const connectFunc = () => {
return new Promise<StreamInfo>(
(resolve, reject) => {
const socket = net.connect(sessionDetails.languageServicePipeName);
socket.on(
"connect",
() => {
this.log.write("Language service connected.");
resolve({writer: socket, reader: socket});
});
});
};
const clientOptions: LanguageClientOptions = {
documentSelector: this.documentSelector,
synchronize: {
// backend uses "files" and "search" to ignore references.
configurationSection: [ utils.PowerShellLanguageId, "files", "search" ],
// fileEvents: vscode.workspace.createFileSystemWatcher('**/.eslintrc')
},
errorHandler: {
// Override the default error handler to prevent it from
// closing the LanguageClient incorrectly when the socket
// hangs up (ECONNRESET errors).
error: (error: any, message: Message, count: number): ErrorAction => {
// TODO: Is there any error worth terminating on?
return ErrorAction.Continue;
},
closed: () => {
// We have our own restart experience
return CloseAction.DoNotRestart;
},
},
revealOutputChannelOn: RevealOutputChannelOn.Never,
middleware: this,
};
this.languageServerClient =
new LanguageClient(
"PowerShell Editor Services",
connectFunc,
clientOptions);
this.languageServerClient.onReady().then(
() => {
this.languageServerClient
.sendRequest(PowerShellVersionRequestType)
.then(
async (versionDetails) => {
this.versionDetails = versionDetails;
this.started = true;
if (!this.InDevelopmentMode) {
this.telemetryReporter.sendTelemetryEvent("powershellVersionCheck",
{ powershellVersion: versionDetails.version });
}
this.setSessionStatus(
this.versionDetails.architecture === "x86"
? `${this.versionDetails.displayVersion} (${this.versionDetails.architecture})`
: this.versionDetails.displayVersion,
SessionStatus.Running);
// If the user opted to not check for updates, then don't.
if (!this.sessionSettings.promptToUpdatePowerShell) { return; }
try {
const localVersion = semver.parse(this.versionDetails.version);
if (semver.lt(localVersion, "6.0.0")) {
// Skip prompting when using Windows PowerShell for now.
return;
}
// Fetch the latest PowerShell releases from GitHub.
const isPreRelease = !!semver.prerelease(localVersion);
const release: GitHubReleaseInformation =
await GitHubReleaseInformation.FetchLatestRelease(isPreRelease);
await InvokePowerShellUpdateCheck(
this,
this.languageServerClient,
localVersion,
this.versionDetails.architecture,
release);
} catch (e) {
// best effort. This probably failed to fetch the data from GitHub.
this.log.writeWarning(e.message);
}
});
// Send the new LanguageClient to extension features
// so that they can register their message handlers
// before the connection is established.
this.updateLanguageClientConsumers(this.languageServerClient);
this.languageServerClient.onNotification(
RunspaceChangedEventType,
(runspaceDetails) => { this.setStatusBarVersionString(runspaceDetails); });
},
(reason) => {
this.setSessionFailure("Could not start language service: ", reason);
});
this.languageServerClient.start();
} catch (e) {
this.setSessionFailure("The language service could not be started: ", e);
}
}
private updateLanguageClientConsumers(languageClient: LanguageClient) {
this.languageClientConsumers.forEach((feature) => {
feature.setLanguageClient(languageClient);
});
}
private createStatusBarItem() {
if (this.statusBarItem === undefined) {
// Create the status bar item and place it right next
// to the language indicator
this.statusBarItem =
vscode.window.createStatusBarItem(
vscode.StatusBarAlignment.Right,
1);
this.statusBarItem.command = this.ShowSessionMenuCommandName;
this.statusBarItem.tooltip = "Show PowerShell Session Menu";
this.statusBarItem.show();
vscode.window.onDidChangeActiveTextEditor((textEditor) => {
if (textEditor === undefined
|| textEditor.document.languageId !== "powershell") {
this.statusBarItem.hide();
} else {
this.statusBarItem.show();
}
});
}
}
private setSessionStatus(statusText: string, status: SessionStatus): void {
// Set color and icon for 'Running' by default
let statusIconText = "$(terminal) ";
let statusColor = "#affc74";
if (status === SessionStatus.Initializing) {
statusIconText = "$(sync) ";
statusColor = "#f3fc74";
} else if (status === SessionStatus.Failed) {
statusIconText = "$(alert) ";
statusColor = "#fcc174";
}
this.sessionStatus = status;
this.statusBarItem.color = statusColor;
this.statusBarItem.text = statusIconText + statusText;
}
private setSessionFailure(message: string, ...additionalMessages: string[]) {
this.log.writeAndShowError(message, ...additionalMessages);
this.setSessionStatus(
"Initialization Error",
SessionStatus.Failed);
}
private async changePowerShellDefaultVersion(exePath: IPowerShellExeDetails) {
this.suppressRestartPrompt = true;
await Settings.change("powerShellDefaultVersion", exePath.displayName, true);
// We pass in the display name so that we force the extension to use that version
// rather than pull from the settings. The issue we prevent here is when a
// workspace setting is defined which gets priority over user settings which
// is what the change above sets.
this.restartSession(exePath.displayName);
}
private showSessionConsole(isExecute?: boolean) {
if (this.languageServerProcess) {
this.languageServerProcess.showConsole(isExecute && !this.focusConsoleOnExecute);
}
}
private showSessionMenu() {
const availablePowerShellExes = this.powershellExeFinder.getAllAvailablePowerShellInstallations();
let sessionText: string;
switch (this.sessionStatus) {
case SessionStatus.Running:
case SessionStatus.Initializing:
case SessionStatus.NotStarted:
case SessionStatus.NeverStarted:
case SessionStatus.Stopping:
const currentPowerShellExe =
availablePowerShellExes
.find((item) => item.displayName.toLowerCase() === this.PowerShellExeDetails.displayName);
const powerShellSessionName =
currentPowerShellExe ?
currentPowerShellExe.displayName :
`PowerShell ${this.versionDetails.displayVersion} ` +
`(${this.versionDetails.architecture}) ${this.versionDetails.edition} Edition ` +
`[${this.versionDetails.version}]`;
sessionText = `Current session: ${powerShellSessionName}`;
break;
case SessionStatus.Failed:
sessionText = "Session initialization failed, click here to show PowerShell extension logs";
break;
default:
throw new TypeError("Not a valid value for the enum 'SessionStatus'");
}
const powerShellItems =
availablePowerShellExes
.filter((item) => item.displayName !== this.PowerShellExeDetails.displayName)
.map((item) => {
return new SessionMenuItem(
`Switch to: ${item.displayName}`,
() => { this.changePowerShellDefaultVersion(item); });
});
const menuItems: SessionMenuItem[] = [
new SessionMenuItem(
sessionText,
() => { vscode.commands.executeCommand("PowerShell.ShowLogs"); }),
// Add all of the different PowerShell options
...powerShellItems,
new SessionMenuItem(
"Restart Current Session",
() => {
// We pass in the display name so we guarentee that the session
// will be the same PowerShell.
this.restartSession(this.PowerShellExeDetails.displayName);
}),
new SessionMenuItem(
"Open Session Logs Folder",
() => { vscode.commands.executeCommand("PowerShell.OpenLogFolder"); }),
new SessionMenuItem(
"Modify 'powerShell.powerShellAdditionalExePaths' in Settings",
() => { vscode.commands.executeCommand("workbench.action.openSettingsJson"); }),
];
vscode
.window
.showQuickPick<SessionMenuItem>(menuItems)
.then((selectedItem) => { selectedItem.callback(); });
}
}
class SessionMenuItem implements vscode.QuickPickItem {
public description: string;
constructor(
public readonly label: string,
// tslint:disable-next-line:no-empty
public readonly callback: () => void = () => {}) {
}
}
export const PowerShellVersionRequestType =
new RequestType0<IPowerShellVersionDetails, void, void>(
"powerShell/getVersion");
export const RunspaceChangedEventType =
new NotificationType<IRunspaceDetails, void>(
"powerShell/runspaceChanged");
export enum RunspaceType {
Local,
Process,
Remote,
}
export interface IPowerShellVersionDetails {
version: string;
displayVersion: string;
edition: string;
architecture: string;
}
export interface IRunspaceDetails {
powerShellVersion: IPowerShellVersionDetails;
runspaceType: RunspaceType;
connectionString: string;
}

Просмотреть файл

@ -0,0 +1,318 @@
/*---------------------------------------------------------
* Copyright (C) Microsoft Corporation. All rights reserved.
*--------------------------------------------------------*/
"use strict";
import vscode = require("vscode");
import utils = require("./utils");
enum CodeFormattingPreset {
Custom,
Allman,
OTBS,
Stroustrup,
}
enum PipelineIndentationStyle {
IncreaseIndentationForFirstPipeline,
IncreaseIndentationAfterEveryPipeline,
NoIndentation,
None,
}
export enum CommentType {
Disabled = "Disabled",
BlockComment = "BlockComment",
LineComment = "LineComment",
}
export interface IPowerShellAdditionalExePathSettings {
versionName: string;
exePath: string;
}
export interface IBugReportingSettings {
project: string;
}
export interface ICodeFoldingSettings {
enable?: boolean;
showLastLine?: boolean;
}
export interface ICodeFormattingSettings {
autoCorrectAliases: boolean;
preset: CodeFormattingPreset;
openBraceOnSameLine: boolean;
newLineAfterOpenBrace: boolean;
newLineAfterCloseBrace: boolean;
pipelineIndentationStyle: PipelineIndentationStyle;
whitespaceBeforeOpenBrace: boolean;
whitespaceBeforeOpenParen: boolean;
whitespaceAroundOperator: boolean;
whitespaceAfterSeparator: boolean;
whitespaceBetweenParameters: boolean;
whitespaceInsideBrace: boolean;
addWhitespaceAroundPipe: boolean;
trimWhitespaceAroundPipe: boolean;
ignoreOneLineBlock: boolean;
alignPropertyValuePairs: boolean;
useCorrectCasing: boolean;
}
export interface IScriptAnalysisSettings {
enable?: boolean;
settingsPath: string;
}
export interface IDebuggingSettings {
createTemporaryIntegratedConsole?: boolean;
}
export interface IDeveloperSettings {
featureFlags?: string[];
bundledModulesPath?: string;
editorServicesLogLevel?: string;
editorServicesWaitForDebugger?: boolean;
waitForSessionFileTimeoutSeconds?: number;
}
export interface ISettings {
powerShellAdditionalExePaths?: IPowerShellAdditionalExePathSettings[];
powerShellDefaultVersion?: string;
// This setting is no longer used but is here to assist in cleaning up the users settings.
powerShellExePath?: string;
promptToUpdatePowerShell?: boolean;
promptToUpdatePackageManagement?: boolean;
bundledModulesPath?: string;
startAsLoginShell?: IStartAsLoginShellSettings;
startAutomatically?: boolean;
useX86Host?: boolean;
enableProfileLoading?: boolean;
helpCompletion: string;
scriptAnalysis?: IScriptAnalysisSettings;
debugging?: IDebuggingSettings;
developer?: IDeveloperSettings;
codeFolding?: ICodeFoldingSettings;
codeFormatting?: ICodeFormattingSettings;
integratedConsole?: IIntegratedConsoleSettings;
bugReporting?: IBugReportingSettings;
sideBar?: ISideBarSettings;
pester?: IPesterSettings;
buttons?: IButtonSettings;
cwd?: string;
notebooks?: INotebooksSettings;
}
export interface IStartAsLoginShellSettings {
osx?: boolean;
linux?: boolean;
}
export interface IIntegratedConsoleSettings {
showOnStartup?: boolean;
focusConsoleOnExecute?: boolean;
useLegacyReadLine?: boolean;
forceClearScrollbackBuffer?: boolean;
suppressStartupBanner?: boolean;
}
export interface ISideBarSettings {
CommandExplorerVisibility?: boolean;
}
export interface IPesterSettings {
useLegacyCodeLens?: boolean;
outputVerbosity?: string;
debugOutputVerbosity?: string;
}
export interface IButtonSettings {
showRunButtons?: boolean;
showPanelMovementButtons?: boolean;
}
export interface INotebooksSettings {
saveMarkdownCellsAs?: CommentType;
}
export function load(): ISettings {
const configuration: vscode.WorkspaceConfiguration =
vscode.workspace.getConfiguration(
utils.PowerShellLanguageId);
const defaultBugReportingSettings: IBugReportingSettings = {
project: "https://github.com/PowerShell/vscode-powershell",
};
const defaultScriptAnalysisSettings: IScriptAnalysisSettings = {
enable: true,
settingsPath: "PSScriptAnalyzerSettings.psd1",
};
const defaultDebuggingSettings: IDebuggingSettings = {
createTemporaryIntegratedConsole: false,
};
const defaultDeveloperSettings: IDeveloperSettings = {
featureFlags: [],
bundledModulesPath: "../../server/module",
editorServicesLogLevel: "Normal",
editorServicesWaitForDebugger: false,
waitForSessionFileTimeoutSeconds: 240,
};
const defaultCodeFoldingSettings: ICodeFoldingSettings = {
enable: true,
showLastLine: false,
};
const defaultCodeFormattingSettings: ICodeFormattingSettings = {
autoCorrectAliases: false,
preset: CodeFormattingPreset.Custom,
openBraceOnSameLine: true,
newLineAfterOpenBrace: true,
newLineAfterCloseBrace: true,
pipelineIndentationStyle: PipelineIndentationStyle.NoIndentation,
whitespaceBeforeOpenBrace: true,
whitespaceBeforeOpenParen: true,
whitespaceAroundOperator: true,
whitespaceAfterSeparator: true,
whitespaceBetweenParameters: false,
whitespaceInsideBrace: true,
addWhitespaceAroundPipe: true,
trimWhitespaceAroundPipe: false,
ignoreOneLineBlock: true,
alignPropertyValuePairs: true,
useCorrectCasing: false,
};
const defaultStartAsLoginShellSettings: IStartAsLoginShellSettings = {
osx: true,
linux: false,
};
const defaultIntegratedConsoleSettings: IIntegratedConsoleSettings = {
showOnStartup: true,
focusConsoleOnExecute: true,
useLegacyReadLine: false,
forceClearScrollbackBuffer: false,
};
const defaultSideBarSettings: ISideBarSettings = {
CommandExplorerVisibility: true,
};
const defaultButtonSettings: IButtonSettings = {
showRunButtons: true,
showPanelMovementButtons: false
};
const defaultPesterSettings: IPesterSettings = {
useLegacyCodeLens: true,
outputVerbosity: "FromPreference",
debugOutputVerbosity: "Diagnostic",
};
const defaultNotebooksSettings: INotebooksSettings = {
saveMarkdownCellsAs: CommentType.BlockComment,
};
return {
startAutomatically:
configuration.get<boolean>("startAutomatically", true),
powerShellAdditionalExePaths:
configuration.get<IPowerShellAdditionalExePathSettings[]>("powerShellAdditionalExePaths", undefined),
powerShellDefaultVersion:
configuration.get<string>("powerShellDefaultVersion", undefined),
powerShellExePath:
configuration.get<string>("powerShellExePath", undefined),
promptToUpdatePowerShell:
configuration.get<boolean>("promptToUpdatePowerShell", true),
promptToUpdatePackageManagement:
configuration.get<boolean>("promptToUpdatePackageManagement", true),
bundledModulesPath:
"../../PowerShellEditorServices/module",
useX86Host:
configuration.get<boolean>("useX86Host", false),
enableProfileLoading:
configuration.get<boolean>("enableProfileLoading", false),
helpCompletion:
configuration.get<string>("helpCompletion", CommentType.BlockComment),
scriptAnalysis:
configuration.get<IScriptAnalysisSettings>("scriptAnalysis", defaultScriptAnalysisSettings),
debugging:
configuration.get<IDebuggingSettings>("debugging", defaultDebuggingSettings),
developer:
getWorkspaceSettingsWithDefaults<IDeveloperSettings>(configuration, "developer", defaultDeveloperSettings),
codeFolding:
configuration.get<ICodeFoldingSettings>("codeFolding", defaultCodeFoldingSettings),
codeFormatting:
configuration.get<ICodeFormattingSettings>("codeFormatting", defaultCodeFormattingSettings),
integratedConsole:
configuration.get<IIntegratedConsoleSettings>("integratedConsole", defaultIntegratedConsoleSettings),
bugReporting:
configuration.get<IBugReportingSettings>("bugReporting", defaultBugReportingSettings),
sideBar:
configuration.get<ISideBarSettings>("sideBar", defaultSideBarSettings),
pester:
configuration.get<IPesterSettings>("pester", defaultPesterSettings),
buttons:
configuration.get<IButtonSettings>("buttons", defaultButtonSettings),
notebooks:
configuration.get<INotebooksSettings>("notebooks", defaultNotebooksSettings),
startAsLoginShell:
// tslint:disable-next-line
// We follow the same convention as VS Code - https://github.com/microsoft/vscode/blob/ff00badd955d6cfcb8eab5f25f3edc86b762f49f/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts#L105-L107
// "Unlike on Linux, ~/.profile is not sourced when logging into a macOS session. This
// is the reason terminals on macOS typically run login shells by default which set up
// the environment. See http://unix.stackexchange.com/a/119675/115410"
configuration.get<IStartAsLoginShellSettings>("startAsLoginShell", defaultStartAsLoginShellSettings),
cwd:
configuration.get<string>("cwd", null),
};
}
// Get the ConfigurationTarget (read: scope) of where the *effective* setting value comes from
export async function getEffectiveConfigurationTarget(settingName: string): Promise<vscode.ConfigurationTarget> {
const configuration = vscode.workspace.getConfiguration(utils.PowerShellLanguageId);
const detail = configuration.inspect(settingName);
let configurationTarget = null;
if (typeof detail.workspaceFolderValue !== "undefined") {
configurationTarget = vscode.ConfigurationTarget.WorkspaceFolder;
}
else if (typeof detail.workspaceValue !== "undefined") {
configurationTarget = vscode.ConfigurationTarget.Workspace;
}
else if (typeof detail.globalValue !== "undefined") {
configurationTarget = vscode.ConfigurationTarget.Global;
}
return configurationTarget;
}
export async function change(
settingName: string,
newValue: any,
configurationTarget?: vscode.ConfigurationTarget | boolean): Promise<void> {
const configuration = vscode.workspace.getConfiguration(utils.PowerShellLanguageId);
await configuration.update(settingName, newValue, configurationTarget);
}
function getWorkspaceSettingsWithDefaults<TSettings>(
workspaceConfiguration: vscode.WorkspaceConfiguration,
settingName: string,
defaultSettings: TSettings): TSettings {
const importedSettings: TSettings = workspaceConfiguration.get<TSettings>(settingName, defaultSettings);
for (const setting in importedSettings) {
if (importedSettings[setting]) {
defaultSettings[setting] = importedSettings[setting];
}
}
return defaultSettings;
}

Просмотреть файл

@ -0,0 +1,23 @@
import * as path from 'path';
import { runTests } from 'vscode-test';
async function main() {
try {
// The folder containing the Extension Manifest package.json
// Passed to `--extensionDevelopmentPath`
const extensionDevelopmentPath = path.resolve(__dirname, '../../');
// The path to test runner
// 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();

Просмотреть файл

@ -0,0 +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.equal(-1, [1, 2, 3].indexOf(5));
assert.equal(-1, [1, 2, 3].indexOf(0));
});
});

Просмотреть файл

@ -0,0 +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',
color: 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);
}
});
});
}

Просмотреть файл

@ -0,0 +1,105 @@
/*---------------------------------------------------------
* Copyright (C) Microsoft Corporation. All rights reserved.
*--------------------------------------------------------*/
"use strict";
import fs = require("fs");
import os = require("os");
import path = require("path");
export const PowerShellLanguageId = "powershell";
export function ensurePathExists(targetPath: string) {
// Ensure that the path exists
try {
fs.mkdirSync(targetPath);
} catch (e) {
// If the exception isn't to indicate that the folder exists already, rethrow it.
if (e.code !== "EEXIST") {
throw e;
}
}
}
export function getPipePath(pipeName: string) {
if (os.platform() === "win32") {
return "\\\\.\\pipe\\" + pipeName;
} else {
// Windows uses NamedPipes where non-Windows platforms use Unix Domain Sockets.
// This requires connecting to the pipe file in different locations on Windows vs non-Windows.
return path.join(os.tmpdir(), `CoreFxPipe_${pipeName}`);
}
}
export interface IEditorServicesSessionDetails {
status: string;
reason: string;
detail: string;
powerShellVersion: string;
channel: string;
languageServicePort: number;
debugServicePort: number;
languageServicePipeName: string;
debugServicePipeName: string;
}
export type IReadSessionFileCallback = (details: IEditorServicesSessionDetails) => void;
const sessionsFolder = path.resolve(__dirname, "..", "..", "sessions/");
const sessionFilePathPrefix = path.resolve(sessionsFolder, "PSES-VSCode-" + process.env.VSCODE_PID);
// Create the sessions path if it doesn't exist already
ensurePathExists(sessionsFolder);
export function getSessionFilePath(uniqueId: number) {
return `${sessionFilePathPrefix}-${uniqueId}`;
}
export function getDebugSessionFilePath() {
return `${sessionFilePathPrefix}-Debug`;
}
export function writeSessionFile(sessionFilePath: string, sessionDetails: IEditorServicesSessionDetails) {
ensurePathExists(sessionsFolder);
const writeStream = fs.createWriteStream(sessionFilePath);
writeStream.write(JSON.stringify(sessionDetails));
writeStream.close();
}
export function readSessionFile(sessionFilePath: string): IEditorServicesSessionDetails {
const fileContents = fs.readFileSync(sessionFilePath, "utf-8");
return JSON.parse(fileContents);
}
export function deleteSessionFile(sessionFilePath: string) {
try {
fs.unlinkSync(sessionFilePath);
} catch (e) {
// TODO: Be more specific about what we're catching
}
}
export function checkIfFileExists(filePath: string): boolean {
try {
fs.accessSync(filePath, fs.constants.R_OK);
return true;
} catch (e) {
return false;
}
}
export function getTimestampString() {
const time = new Date();
return `[${time.getHours()}:${time.getMinutes()}:${time.getSeconds()}]`;
}
export function sleep(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}
export const isMacOS: boolean = process.platform === "darwin";
export const isWindows: boolean = process.platform === "win32";
export const isLinux: boolean = !isMacOS && !isWindows;

Просмотреть файл

@ -0,0 +1,22 @@
{
"compilerOptions": {
"module": "commonjs",
"target": "es6",
"outDir": "out",
"lib": [
"es2017",
"DOM"
],
"sourceMap": true,
"rootDir": "src"
// "strict": true /* enable all strict type-checking options */
/* Additional Checks */
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
// "noUnusedParameters": true, /* Report errors on unused parameters. */
},
"exclude": [
"node_modules",
".vscode-test"
]
}