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

Pwsh autocomplete #171648

Merged
merged 27 commits into from
Jan 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
7c010f8
Basic autocomplete and suggest fork mostly working
Tyriar Jan 12, 2023
24fbf5d
Protect against invalid dimensions
Tyriar Jan 12, 2023
fd15772
Add - trigger character
Tyriar Jan 12, 2023
8dabbac
Fix refiltering layout
Tyriar Jan 12, 2023
5fd5374
Add an opt-in setting for suggest
Tyriar Jan 13, 2023
4e08cdf
Allow completions to replace the correct characters
Tyriar Jan 13, 2023
4d8210f
Workaround a race caused by conpty
Tyriar Jan 13, 2023
8ff11c6
Clean up
Tyriar Jan 13, 2023
ca0b438
Merge remote-tracking branch 'origin/main' into tyriar/154662
Tyriar Jan 17, 2023
01f0ea7
Codicon -> ThemeIcon
Tyriar Jan 17, 2023
e5cc944
Make SuggestWidgetStatus generic, move to platform
Tyriar Jan 17, 2023
80006bd
Move SuggestWidgetStatus back to editor
Tyriar Jan 17, 2023
c0ef861
Move from base to workbench
Tyriar Jan 17, 2023
51e056f
Consolidate css files
Tyriar Jan 17, 2023
6e0fc63
Make status bar menu optional
Tyriar Jan 17, 2023
14254d2
Simplify suggest in si script
Tyriar Jan 18, 2023
a9b8617
Fix warnings
Tyriar Jan 18, 2023
3d6ca37
Fix position preference
Tyriar Jan 18, 2023
bee4e07
Clean up
Tyriar Jan 19, 2023
967c78d
More clean up
Tyriar Jan 19, 2023
bbe9e95
Support page up/down
Tyriar Jan 19, 2023
1cf6f3e
Hide on terminal blur
Tyriar Jan 19, 2023
4b1f609
Fix invalid repeat count when completing params
Tyriar Jan 19, 2023
9faf9eb
Merge branch 'main' into tyriar/154662
Tyriar Jan 19, 2023
8ac79be
Fix compile
Tyriar Jan 19, 2023
50dd1d3
Fix test and lint issue
Tyriar Jan 19, 2023
70ed6fa
Add workbench suggest o i18n.resources.json
Tyriar Jan 19, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 4 additions & 0 deletions build/lib/i18n.resources.json
Original file line number Diff line number Diff line change
Expand Up @@ -402,6 +402,10 @@
"name": "vs/workbench/services/search",
"project": "vscode-workbench"
},
{
"name": "vs/workbench/services/suggest",
"project": "vscode-workbench"
},
{
"name": "vs/workbench/services/textfile",
"project": "vscode-workbench"
Expand Down
6 changes: 3 additions & 3 deletions src/vs/editor/contrib/suggest/browser/suggestWidget.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import { isHighContrast } from 'vs/platform/theme/common/theme';
import { IColorTheme, IThemeService } from 'vs/platform/theme/common/themeService';
import { CompletionModel } from './completionModel';
import { ResizableHTMLElement } from 'vs/base/browser/ui/resizable/resizable';
import { CompletionItem, Context as SuggestContext } from './suggest';
import { CompletionItem, Context as SuggestContext, suggestWidgetStatusbarMenu } from './suggest';
import { canExpandCompletionItem, SuggestDetailsOverlay, SuggestDetailsWidget } from './suggestWidgetDetails';
import { getAriaId, ItemRenderer } from './suggestWidgetRenderer';
import { getListStyles } from 'vs/platform/theme/browser/defaultStyles';
Expand All @@ -43,7 +43,7 @@ registerColor('editorSuggestWidget.border', { dark: editorWidgetBorder, light: e
const editorSuggestWidgetForeground = registerColor('editorSuggestWidget.foreground', { dark: editorForeground, light: editorForeground, hcDark: editorForeground, hcLight: editorForeground }, nls.localize('editorSuggestWidgetForeground', 'Foreground color of the suggest widget.'));
registerColor('editorSuggestWidget.selectedForeground', { dark: quickInputListFocusForeground, light: quickInputListFocusForeground, hcDark: quickInputListFocusForeground, hcLight: quickInputListFocusForeground }, nls.localize('editorSuggestWidgetSelectedForeground', 'Foreground color of the selected entry in the suggest widget.'));
registerColor('editorSuggestWidget.selectedIconForeground', { dark: quickInputListFocusIconForeground, light: quickInputListFocusIconForeground, hcDark: quickInputListFocusIconForeground, hcLight: quickInputListFocusIconForeground }, nls.localize('editorSuggestWidgetSelectedIconForeground', 'Icon foreground color of the selected entry in the suggest widget.'));
const editorSuggestWidgetSelectedBackground = registerColor('editorSuggestWidget.selectedBackground', { dark: quickInputListFocusBackground, light: quickInputListFocusBackground, hcDark: quickInputListFocusBackground, hcLight: quickInputListFocusBackground }, nls.localize('editorSuggestWidgetSelectedBackground', 'Background color of the selected entry in the suggest widget.'));
export const editorSuggestWidgetSelectedBackground = registerColor('editorSuggestWidget.selectedBackground', { dark: quickInputListFocusBackground, light: quickInputListFocusBackground, hcDark: quickInputListFocusBackground, hcLight: quickInputListFocusBackground }, nls.localize('editorSuggestWidgetSelectedBackground', 'Background color of the selected entry in the suggest widget.'));
registerColor('editorSuggestWidget.highlightForeground', { dark: listHighlightForeground, light: listHighlightForeground, hcDark: listHighlightForeground, hcLight: listHighlightForeground }, nls.localize('editorSuggestWidgetHighlightForeground', 'Color of the match highlights in the suggest widget.'));
registerColor('editorSuggestWidget.focusHighlightForeground', { dark: listFocusHighlightForeground, light: listFocusHighlightForeground, hcDark: listFocusHighlightForeground, hcLight: listFocusHighlightForeground }, nls.localize('editorSuggestWidgetFocusHighlightForeground', 'Color of the match highlights in the suggest widget when an item is focused.'));
registerColor('editorSuggestWidgetStatus.foreground', { dark: transparent(editorSuggestWidgetForeground, .5), light: transparent(editorSuggestWidgetForeground, .5), hcDark: transparent(editorSuggestWidgetForeground, .5), hcLight: transparent(editorSuggestWidgetForeground, .5) }, nls.localize('editorSuggestWidgetStatusForeground', 'Foreground color of the suggest widget status.'));
Expand Down Expand Up @@ -264,7 +264,7 @@ export class SuggestWidget implements IDisposable {
listInactiveFocusOutline: activeContrastBorder
}));

this._status = instantiationService.createInstance(SuggestWidgetStatus, this.element.domNode);
this._status = instantiationService.createInstance(SuggestWidgetStatus, this.element.domNode, suggestWidgetStatusbarMenu);
const applyStatusBarStyle = () => this.element.domNode.classList.toggle('with-status-bar', this.editor.getOption(EditorOption.suggest).showStatusBar);
applyStatusBarStyle();

Expand Down
8 changes: 4 additions & 4 deletions src/vs/editor/contrib/suggest/browser/suggestWidgetStatus.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,9 @@ import { ActionBar, IActionViewItemProvider } from 'vs/base/browser/ui/actionbar
import { IAction } from 'vs/base/common/actions';
import { ResolvedKeybinding } from 'vs/base/common/keybindings';
import { DisposableStore } from 'vs/base/common/lifecycle';
import { suggestWidgetStatusbarMenu } from 'vs/editor/contrib/suggest/browser/suggest';
import { localize } from 'vs/nls';
import { MenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem';
import { IMenuService, MenuItemAction } from 'vs/platform/actions/common/actions';
import { IMenuService, MenuId, MenuItemAction } from 'vs/platform/actions/common/actions';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';

Expand All @@ -23,7 +22,7 @@ class StatusBarViewItem extends MenuEntryActionViewItem {
return super.updateLabel();
}
if (this.label) {
this.label.textContent = localize('ddd', '{0} ({1})', this._action.label, StatusBarViewItem.symbolPrintEnter(kb));
this.label.textContent = localize({ key: 'content', comment: ['A label', 'A keybinding'] }, '{0} ({1})', this._action.label, StatusBarViewItem.symbolPrintEnter(kb));
}
}

Expand All @@ -42,6 +41,7 @@ export class SuggestWidgetStatus {

constructor(
container: HTMLElement,
private readonly _menuId: MenuId,
@IInstantiationService instantiationService: IInstantiationService,
@IMenuService private _menuService: IMenuService,
@IContextKeyService private _contextKeyService: IContextKeyService,
Expand All @@ -64,7 +64,7 @@ export class SuggestWidgetStatus {
}

show(): void {
const menu = this._menuService.createMenu(suggestWidgetStatusbarMenu, this._contextKeyService);
const menu = this._menuService.createMenu(this._menuId, this._contextKeyService);
const renderMenu = () => {
const left: IAction[] = [];
const right: IAction[] = [];
Expand Down
2 changes: 2 additions & 0 deletions src/vs/platform/terminal/common/terminal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ export const enum TerminalSettingId {
ShellIntegrationShowWelcome = 'terminal.integrated.shellIntegration.showWelcome',
ShellIntegrationDecorationsEnabled = 'terminal.integrated.shellIntegration.decorationsEnabled',
ShellIntegrationCommandHistory = 'terminal.integrated.shellIntegration.history',
ShellIntegrationSuggestEnabled = 'terminal.integrated.shellIntegration.suggestEnabled',
SmoothScrolling = 'terminal.integrated.smoothScrolling'
}

Expand Down Expand Up @@ -597,6 +598,7 @@ export interface IShellLaunchConfigDto {
export interface ITerminalProcessOptions {
shellIntegration: {
enabled: boolean;
suggestEnabled: boolean;
};
windowsEnableConpty: boolean;
environmentVariableCollections: ISerializableEnvironmentVariableCollections | undefined;
Expand Down
6 changes: 6 additions & 0 deletions src/vs/platform/terminal/node/terminalEnvironment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,9 @@ export function getShellIntegrationInjection(
}
newArgs = [...newArgs]; // Shallow clone the array to avoid setting the default array
newArgs[newArgs.length - 1] = format(newArgs[newArgs.length - 1], appRoot, '');
if (options.shellIntegration.suggestEnabled) {
envMixin['VSCODE_SUGGEST'] = '1';
}
return { newArgs, envMixin };
}
logService.warn(`Shell integration cannot be enabled for executable "${shellLaunchConfig.executable}" and args`, shellLaunchConfig.args);
Expand Down Expand Up @@ -182,6 +185,9 @@ export function getShellIntegrationInjection(
if (!newArgs) {
return undefined;
}
if (options.shellIntegration.suggestEnabled) {
envMixin['VSCODE_SUGGEST'] = '1';
}
newArgs = [...newArgs]; // Shallow clone the array to avoid setting the default array
newArgs[newArgs.length - 1] = format(newArgs[newArgs.length - 1], appRoot, '');
return { newArgs, envMixin };
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ import { IProductService } from 'vs/platform/product/common/productService';
import { ITerminalProcessOptions } from 'vs/platform/terminal/common/terminal';
import { getShellIntegrationInjection, IShellIntegrationConfigInjection } from 'vs/platform/terminal/node/terminalEnvironment';

const enabledProcessOptions: ITerminalProcessOptions = { shellIntegration: { enabled: true }, windowsEnableConpty: true, environmentVariableCollections: undefined };
const disabledProcessOptions: ITerminalProcessOptions = { shellIntegration: { enabled: false }, windowsEnableConpty: true, environmentVariableCollections: undefined };
const winptyProcessOptions: ITerminalProcessOptions = { shellIntegration: { enabled: true }, windowsEnableConpty: false, environmentVariableCollections: undefined };
const enabledProcessOptions: ITerminalProcessOptions = { shellIntegration: { enabled: true, suggestEnabled: false }, windowsEnableConpty: true, environmentVariableCollections: undefined };
const disabledProcessOptions: ITerminalProcessOptions = { shellIntegration: { enabled: false, suggestEnabled: false }, windowsEnableConpty: true, environmentVariableCollections: undefined };
const winptyProcessOptions: ITerminalProcessOptions = { shellIntegration: { enabled: true, suggestEnabled: false }, windowsEnableConpty: false, environmentVariableCollections: undefined };
const pwshExe = process.platform === 'win32' ? 'pwsh.exe' : 'pwsh';
const repoRoot = process.platform === 'win32' ? process.cwd()[0].toLowerCase() + process.cwd().substring(1) : process.cwd();
const logService = new NullLogService();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,46 @@ function Set-MappedKeyHandlers {
Set-MappedKeyHandler -Chord Alt+Spacebar -Sequence 'F12,b'
Set-MappedKeyHandler -Chord Shift+Enter -Sequence 'F12,c'
Set-MappedKeyHandler -Chord Shift+End -Sequence 'F12,d'

# Conditionally enable suggestions
if ($env:VSCODE_SUGGEST -eq '1') {
Remove-Item Env:VSCODE_SUGGEST

# VS Code send completions request (may override Ctrl+Spacebar)
Set-PSReadLineKeyHandler -Chord 'F12,e' -ScriptBlock {
Send-Completions
}

# Suggest trigger characters
Set-PSReadLineKeyHandler -Chord "-" -ScriptBlock {
[Microsoft.PowerShell.PSConsoleReadLine]::Insert("-")
Send-Completions
}
}
}

function Send-Completions {
$commandLine = ""
$cursorIndex = 0
# TODO: Since fuzzy matching exists, should completions be provided only for character after the
# last space and then filter on the client side? That would let you trigger ctrl+space
# anywhere on a word and have full completions available
[Microsoft.PowerShell.PSConsoleReadLine]::GetBufferState([ref]$commandLine, [ref]$cursorIndex)
$completionPrefix = $commandLine

# Get completions
$result = "`e]633;Completions"
if ($completionPrefix.Length -gt 0) {
# Get and send completions
$completions = TabExpansion2 -inputScript $completionPrefix -cursorColumn $cursorIndex
if ($null -ne $completions.CompletionMatches) {
$result += ";$($completions.ReplacementIndex);$($completions.ReplacementLength);$($cursorIndex);"
$result += $completions.CompletionMatches | ConvertTo-Json -Compress
}
}
$result += "`a"

Write-Host -NoNewLine $result
}

Set-MappedKeyHandlers
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ import { TerminalQuickAccessProvider } from 'vs/workbench/contrib/terminal/brows
import { registerTerminalConfiguration } from 'vs/workbench/contrib/terminal/common/terminalConfiguration';
import { CONTEXT_ACCESSIBILITY_MODE_ENABLED } from 'vs/platform/accessibility/common/accessibility';
import { terminalViewIcon } from 'vs/workbench/contrib/terminal/browser/terminalIcons';
import { WindowsShellType } from 'vs/platform/terminal/common/terminal';
import { TerminalSettingId, WindowsShellType } from 'vs/platform/terminal/common/terminal';
import { isIOS, isWindows } from 'vs/base/common/platform';
import { setupTerminalMenus } from 'vs/workbench/contrib/terminal/browser/terminalMenus';
import { TerminalInstanceService } from 'vs/workbench/contrib/terminal/browser/terminalInstanceService';
Expand Down Expand Up @@ -204,6 +204,11 @@ registerSendSequenceKeybinding('\x1b[24~d', { // F12,d -> shift+end (SelectLine)
when: ContextKeyExpr.and(TerminalContextKeys.focus, ContextKeyExpr.equals(TerminalContextKeyStrings.ShellType, WindowsShellType.PowerShell), TerminalContextKeys.terminalShellIntegrationEnabled, CONTEXT_ACCESSIBILITY_MODE_ENABLED.negate()),
mac: { primary: KeyMod.Shift | KeyMod.CtrlCmd | KeyCode.RightArrow }
});
registerSendSequenceKeybinding('\x1b[24~e', { // F12,e -> ctrl+space (Native suggest)
when: ContextKeyExpr.and(TerminalContextKeys.focus, ContextKeyExpr.equals(TerminalContextKeyStrings.ShellType, WindowsShellType.PowerShell), TerminalContextKeys.terminalShellIntegrationEnabled, CONTEXT_ACCESSIBILITY_MODE_ENABLED.negate(), ContextKeyExpr.equals(`config.${TerminalSettingId.ShellIntegrationSuggestEnabled}`, true)),
primary: KeyMod.CtrlCmd | KeyCode.Space,
mac: { primary: KeyMod.WinCtrl | KeyCode.Space }
});

// Always on pwsh keybindings
registerSendSequenceKeybinding('\x1b[1;2H', { // Shift+home
Expand Down
45 changes: 45 additions & 0 deletions src/vs/workbench/contrib/terminal/browser/terminal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { ScrollPosition } from 'vs/workbench/contrib/terminal/browser/xterm/mark
import { ITerminalQuickFixAddon } from 'vs/workbench/contrib/terminal/browser/xterm/quickFixAddon';
import { INavigationMode, IRegisterContributedProfileArgs, IRemoteTerminalAttachTarget, IStartExtensionTerminalRequest, ITerminalBackend, ITerminalConfigHelper, ITerminalFont, ITerminalProcessExtHostProxy } from 'vs/workbench/contrib/terminal/common/terminal';
import { EditorGroupColumn } from 'vs/workbench/services/editor/common/editorGroupColumn';
import { ISimpleSelectedSuggestion } from 'vs/workbench/services/suggest/browser/simpleSuggestWidget';
import { IMarker } from 'xterm';

export const ITerminalService = createDecorator<ITerminalService>('terminalService');
Expand Down Expand Up @@ -935,6 +936,36 @@ export interface ITerminalInstance {
* If successful, places commandToRun on the command line
*/
freePortKillProcess(port: string, commandToRun: string): Promise<void>;

/**
* Selects the previous suggestion if the suggest widget is visible.
*/
selectPreviousSuggestion(): void;

/**
* Selects the previous page suggestion if the suggest widget is visible.
*/
selectPreviousPageSuggestion(): void;

/**
* Selects the next suggestion if the suggest widget is visible.
*/
selectNextSuggestion(): void;

/**
* Selects the next page suggestion if the suggest widget is visible.
*/
selectNextPageSuggestion(): void;

/**
* Accepts the current suggestion if the suggest widget is visible.
*/
acceptSelectedSuggestion(): Promise<void>;

/**
* Hides the suggest widget.
*/
hideSuggestWidget(): void;
}


Expand Down Expand Up @@ -1042,3 +1073,17 @@ export const enum LinuxDistro {
export const enum TerminalDataTransfers {
Terminals = 'Terminals'
}

export interface ISuggestController {
selectPreviousSuggestion(): void;
selectPreviousPageSuggestion(): void;
selectNextSuggestion(): void;
selectNextPageSuggestion(): void;
acceptSelectedSuggestion(suggestion?: Pick<ISimpleSelectedSuggestion, 'item' | 'model'>): void;
hideSuggestWidget(): void;
/**
* Handle data written to the terminal outside of xterm.js which has no corresponding
* `Terminal.onData` event.
*/
handleNonXtermData(data: string): void;
}