Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

feat(vscode): Reuse terminals when appropriate #783

Merged
merged 1 commit into from
Aug 2, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .azure-pipelines/jobs/ci1.linux.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ steps:
- script: npx nps format.and.lint.check
displayName: 'format and lint check'

- script: npx nps prepare.electron
- script: npx nps build.ci.electron
displayName: 'build electron'

- script: npx nps test.affected.origin-master
- script: npx nx affected:test --base=origin/master --ci
displayName: 'affected unit-tests'

- script: npx nps e2e.ci1.fixtures
Expand Down
2 changes: 1 addition & 1 deletion .azure-pipelines/jobs/ci2.linux.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
steps:
- script: npx nps prepare.electron
- script: npx nps build.ci.electron
displayName: build electron

- script: npx nps e2e.ci2.fixtures
Expand Down
5 changes: 4 additions & 1 deletion .azure-pipelines/jobs/windows.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
steps:
- script: npx nps prepare.electron
displayName: build electron
displayName: prepare electron

- script: npx nps package.electronWin
displayName: package electron
2 changes: 1 addition & 1 deletion .azure-pipelines/steps/install-dependencies.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@ steps:
versionSpec: '12.x'

- script: |
yarn install --ignore-engines --frozen-lockfile --ignore-optional
yarn install --frozen-lockfile
displayName: yarn install
5 changes: 2 additions & 3 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,16 @@
"version": "0.2.0",
"configurations": [
{
"name": "Extension",
"name": "Run Extension In Dev Mode",
"type": "extensionHost",
"request": "launch",
"runtimeExecutable": "${execPath}",
"args": [
"--extensionDevelopmentPath=${workspaceFolder}/dist/apps/vscode"
],
"trace": "false",
"sourceMaps": false,
"stopOnEntry": false,
"outFiles": ["${workspaceFolder}/**/*"],
"preLaunchTask": "npm: build-vs-code"
}
]
}
2 changes: 1 addition & 1 deletion .vscode/tasks.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
{
"type": "npm",
"script": "build-vs-code",
"isBackground": false,
"isBackground": true,
"presentation": {
"reveal": "never"
},
Expand Down
13 changes: 2 additions & 11 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,18 +42,9 @@ You can build the electron app by running `nps package.electronMac` or `nps pack

## Building VSCode Plugin

You can build the vscode extension by running `nps prepare.and.package.vscode`.
You can install it by running `code --install-extension dist/apps/vscode/angular-console.vsix`
Reload the vscode window to use the newly installed build of the extension.
You can build the vscode extension and run it in development mode by opening up this repo in Visual Studio code and hitting the f5 function key. This will launch `nps build.dev.vscode` in the background and spawn an extension development host version of VSCode so that you can try out your code.

If you are working on the plugin, run:

- `nps build.dev.vscode` (This builds the client/server in watch mode)
- Wait for both client and server to be built.
- Hit F5

Now you can make changes to your code and the client/server builds will trigger.
When you want to see those reflected in VSCode, click the refresh button or hit F5 again.
When you want to update the extension with a new set of changes, go back to the editor you launched the extension host from and click the refresh button (its green and looks like a browser refresh icon).

## Submitting a PR

Expand Down
5 changes: 1 addition & 4 deletions apps/intellij/src/app/start-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import {
} from '@angular-console/server';
import { NestFactory } from '@nestjs/core';
import { Store } from '@nrwl/angular-console-enterprise-electron';
import { join } from 'path';
import { existsSync, writeFile } from 'fs';
import { join } from 'path';

export interface IntellijTerminal {
onDataWrite(callback: (data: string) => void): void;
Expand Down Expand Up @@ -49,9 +49,6 @@ function wsPseudoTerminalFactory(
},
kill: () => {
terminal.kill();
},
setCols: () => {
// No-op, we defer to vscode so as to match its display
}
};
}
Expand Down
146 changes: 81 additions & 65 deletions apps/vscode/src/app/pseudo-terminal.factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,17 @@ import {
readSettings
} from '@angular-console/server';
import { platform } from 'os';
import { ExtensionContext, Terminal, window } from 'vscode';
import { Disposable, ExtensionContext, Terminal, window } from 'vscode';

import { getStoreForContext } from './get-store-for-context';

let terminalsToReuse: Array<Terminal> = [];
window.onDidCloseTerminal(e => {
terminalsToReuse = terminalsToReuse.filter(t => t.processId !== e.processId);
});

const DISPOSE_MESSAGE = 'Press any key to close this terminal';

export function getPseudoTerminalFactory(
context: ExtensionContext
): PseudoTerminalFactory {
Expand All @@ -18,26 +25,29 @@ export function getPseudoTerminalFactory(
if (platform() === 'win32') {
const isWsl = readSettings(store).isWsl;
if (isWsl) {
return wslPseudoTerminalFactory(context, config);
return wslPseudoTerminalFactory(config);
} else {
return win32PseudoTerminalFactory(context, config);
return win32PseudoTerminalFactory(config);
}
}
return unixPseudoTerminalFactory(context, config);
return unixPseudoTerminalFactory(config);
};
}

function win32PseudoTerminalFactory(
context: ExtensionContext,
{ name, program, args, cwd, displayCommand }: PseudoTerminalConfig
): PseudoTerminal {
function win32PseudoTerminalFactory({
name,
program,
args,
cwd,
displayCommand
}: PseudoTerminalConfig): PseudoTerminal {
const successMessage = 'Process completed #woot';
const failureMessage = 'Process failed #failwhale';
const fullCommand = [
`echo '${displayCommand}\n'; Try {`,
`& '${program}' ${args.join(' ')};`,
`if($?) { echo '\n\r${successMessage}' };`,
`if(!$?) { echo '\n\r${failureMessage}' };`,
`if($?) { echo '\n\n${successMessage}\n\n${DISPOSE_MESSAGE}' };`,
`if(!$?) { echo '\n\n${failureMessage}\n\n${DISPOSE_MESSAGE}' };`,
`} Catch { `,
`echo '\n\r${failureMessage}'`,
`}; $null = $Host.UI.RawUI.ReadKey('NoEcho,IncludeKeyDown');`
Expand All @@ -50,110 +60,119 @@ function win32PseudoTerminalFactory(
shellArgs: `-Sta -NoLogo -NonInteractive -C "& {${fullCommand}}"`
});

return renderVsCodeTerminal(
context,
terminal,
successMessage,
failureMessage
);
return renderVsCodeTerminal(terminal, successMessage, failureMessage);
}

function wslPseudoTerminalFactory(
context: ExtensionContext,
{ name, program, args, cwd, displayCommand }: PseudoTerminalConfig
config: PseudoTerminalConfig
): PseudoTerminal {
const successMessage = 'Process completed #woot';
const failureMessage = 'Process failed #failwhale';
const fullCommand =
`echo "${displayCommand}\n" && ${program} ${args.join(
' '
)} && read -n 1 -s -r -p $"\n\n${successMessage}\n"` +
` || read -n 1 -s -r -p $"\n\n${failureMessage}\n"`;

const terminal = window.createTerminal({
name,
cwd,
cwd: config.cwd,
shellPath: 'C:\\Windows\\System32\\wsl.exe',
shellArgs: ['-e', 'bash', '-l', '-i', '-c', fullCommand]
shellArgs: [
'-e',
'bash',
'-l',
'-c',
getBashScriptForCommand(config, successMessage, failureMessage)
]
});

return renderVsCodeTerminal(
context,
terminal,
successMessage,
failureMessage
);
return renderVsCodeTerminal(terminal, successMessage, failureMessage);
}

function unixPseudoTerminalFactory(
context: ExtensionContext,
{ name, program, args, cwd, displayCommand }: PseudoTerminalConfig
config: PseudoTerminalConfig
): PseudoTerminal {
const successMessage = 'Process completed 🙏';
const failureMessage = 'Process failed 🐳';
const fullCommand =
`echo "${displayCommand}\n" && ${program} ${args.join(
' '
)} && read -n 1 -s -r -p $"\n\n${successMessage}\n"` +
` || read -n 1 -s -r -p $"\n\n${failureMessage}\n"`;

const terminal = window.createTerminal({
name,
cwd,
cwd: config.cwd,
shellPath: '/bin/bash',
shellArgs: ['-l', '-i', '-c', fullCommand]
shellArgs: [
'-l',
'-i',
'-c',
getBashScriptForCommand(config, successMessage, failureMessage)
]
});

return renderVsCodeTerminal(
context,
terminal,
successMessage,
failureMessage
return renderVsCodeTerminal(terminal, successMessage, failureMessage);
}

function getBashScriptForCommand(
config: PseudoTerminalConfig,
successMessage: string,
failureMessage: string
) {
const { displayCommand, program, args } = config;
return (
`echo "${displayCommand}\n" && ${program} ${args.join(
' '
)} && read -n 1 -s -r -p $"\n\n${successMessage}\n\n${DISPOSE_MESSAGE}"` +
` || read -n 1 -s -r -p $"\n\n${failureMessage}\n\n${DISPOSE_MESSAGE}"`
);
}

function renderVsCodeTerminal(
context: ExtensionContext,
terminal: Terminal,
successMessage: string,
failureMessage: string
): PseudoTerminal {
const reusableTerminal = terminalsToReuse.pop();
if (reusableTerminal) {
reusableTerminal.dispose();
}

terminal.show();
context.subscriptions.push(terminal);

let onDidWriteData: ((data: string) => void) | undefined;
let onExit: ((code: number) => void) | undefined;

let disposeOnDidWriteData: Disposable | undefined;
const disposeTerminal = (code: number) => {
onDidWriteData = undefined;
if (onExit) {
onExit(code);
onExit = undefined;
}
if (disposeOnDidWriteData) {
disposeOnDidWriteData.dispose();
}
onExit = undefined;
onDidWriteData = undefined;
disposeOnDidWriteData = undefined;
};

context.subscriptions.push(
(<any>terminal).onDidWriteData((data: string) => {
disposeOnDidWriteData = (<any>terminal).onDidWriteData((data: string) => {
// Screen scrape for successMessage or failureMessage as the signal that the
// process has exited.
const success = data.includes(successMessage);
const failed = data.includes(failureMessage);
if (success || failed) {
if (onDidWriteData) {
onDidWriteData(data);
onDidWriteData(data.replace(DISPOSE_MESSAGE, ''));
}

// Screen scrape for successMessage or failureMessage as the signal that the
// process has exited.
const success = data.includes(successMessage);
const failed = data.includes(failureMessage);
if (success || failed) {
disposeTerminal(success ? 0 : 1);
}
})
);
disposeTerminal(success ? 0 : 1);
} else if (onDidWriteData) {
onDidWriteData(data);
}
});

return {
onDidWriteData: callback => {
onDidWriteData = callback;
},
onExit: callback => {
onExit = callback;
onExit = code => {
terminalsToReuse.push(terminal);
callback(code);
};
},
kill: () => {
if (onDidWriteData) {
Expand All @@ -163,9 +182,6 @@ function renderVsCodeTerminal(
terminal.dispose();
}
disposeTerminal(1);
},
setCols: () => {
// No-op, we defer to vscode so as to match its display
}
};
}
4 changes: 2 additions & 2 deletions azure-pipelines.yml
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
jobs:
- job: linux_shard_1
pool:
vmImage: ubuntu-16.04
vmImage: ubuntu-latest
steps:
- template: .azure-pipelines/steps/install-dependencies.yml
- template: .azure-pipelines/jobs/ci1.linux.yml

- job: linux_shard_2
pool:
vmImage: ubuntu-16.04
vmImage: ubuntu-latest
steps:
- template: .azure-pipelines/steps/install-dependencies.yml
- template: .azure-pipelines/jobs/ci2.linux.yml
Expand Down
2 changes: 1 addition & 1 deletion libs/feature-action-bar/src/lib/action-bar.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<div class="action-bar-container" *ngIf="(commands$ | async) as actions">
<!-- Show tasks toolbar -->
<mat-toolbar
(click)="actionsExpanded.next(!actionsExpanded.value)"
(click)="actionsExpandedSubject.next(!actionsExpandedSubject.value)"
[@growShrink]="(showActionToolbar$ | async) ? 'expand' : 'contract'"
class="action-bar"
matRipple
Expand Down
8 changes: 4 additions & 4 deletions libs/server/src/lib/api/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,13 +71,13 @@ export class Commands {

stopCommands(commands: CommandInformation[]) {
commands.forEach(c => {
if (c.commandRunning) {
c.commandRunning.kill();
c.commandRunning = null;
}
if (c.status === 'in-progress') {
c.status = 'terminated';
c.detailedStatusCalculator.setStatus('terminated');
if (c.commandRunning) {
c.commandRunning.kill();
c.commandRunning = null;
}
}
});
}
Expand Down
2 changes: 1 addition & 1 deletion libs/server/src/lib/api/run-command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ export interface PseudoTerminal {

onExit(callback: (code: number) => void): void;

setCols(cols: number): void;
setCols?(cols: number): void;

kill(): void;
}
Expand Down
Loading