Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/main' into tyriar/aliases
Browse files Browse the repository at this point in the history
  • Loading branch information
Tyriar committed Jan 31, 2025
2 parents 9802b18 + 2aaf628 commit 4c3914f
Show file tree
Hide file tree
Showing 21 changed files with 196 additions and 43 deletions.
6 changes: 6 additions & 0 deletions build/npm/preinstall.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ if (process.env['npm_execpath'].includes('yarn')) {
const path = require('path');
const fs = require('fs');
const cp = require('child_process');
const os = require('os');

if (process.platform === 'win32') {
if (!hasSupportedVisualStudioVersion()) {
Expand All @@ -32,6 +33,11 @@ if (process.platform === 'win32') {
installHeaders();
}

if (process.arch !== os.arch()) {
console.error(`\x1b[1;31m*** ARCHITECTURE MISMATCH: The node.js process is ${process.arch}, but your OS architecture is ${os.arch()}. ***\x1b[0;0m`);
console.error(`\x1b[1;31m*** This can greatly increase the build time of vs code. ***\x1b[0;0m`);
}

function hasSupportedVisualStudioVersion() {
const fs = require('fs');
const path = require('path');
Expand Down
37 changes: 10 additions & 27 deletions extensions/terminal-suggest/src/terminalSuggestMain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,10 @@ import { getBashGlobals } from './shell/bash';
import { getZshGlobals } from './shell/zsh';
import { getFishGlobals } from './shell/fish';
import { getPwshGlobals } from './shell/pwsh';
import { getTokenType, TokenType } from './tokens';

// TODO: remove once API is finalized
export enum TerminalShellType {
export const enum TerminalShellType {
Sh = 1,
Bash = 2,
Fish = 3,
Expand Down Expand Up @@ -103,7 +104,8 @@ export async function activate(context: vscode.ExtensionContext) {

const prefix = getPrefix(terminalContext.commandLine, terminalContext.cursorPosition);
const pathSeparator = isWindows ? '\\' : '/';
const result = await getCompletionItemsFromSpecs(availableSpecs, terminalContext, commands, prefix, terminal.shellIntegration?.cwd, token);
const tokenType = getTokenType(terminalContext, shellType);
const result = await getCompletionItemsFromSpecs(availableSpecs, terminalContext, commands, prefix, tokenType, terminal.shellIntegration?.cwd, token);
if (terminal.shellIntegration?.env) {
const homeDirCompletion = result.items.find(i => i.label === '~');
if (homeDirCompletion && terminal.shellIntegration.env.HOME) {
Expand Down Expand Up @@ -288,14 +290,14 @@ export async function getCompletionItemsFromSpecs(
terminalContext: { commandLine: string; cursorPosition: number },
availableCommands: ICompletionResource[],
prefix: string,
tokenType: TokenType,
shellIntegrationCwd?: vscode.Uri,
token?: vscode.CancellationToken
): Promise<{ items: vscode.TerminalCompletionItem[]; filesRequested: boolean; foldersRequested: boolean; cwd?: vscode.Uri }> {
const items: vscode.TerminalCompletionItem[] = [];
let filesRequested = false;
let foldersRequested = false;

const firstCommand = getFirstCommand(terminalContext.commandLine);
const precedingText = terminalContext.commandLine.slice(0, terminalContext.cursorPosition + 1);

for (const spec of specs) {
Expand All @@ -311,17 +313,13 @@ export async function getCompletionItemsFromSpecs(
continue;
}

if (
// If the prompt is empty
!terminalContext.commandLine
// or the first command matches the command
|| !!firstCommand && specLabel.startsWith(firstCommand)
) {
// push it to the completion items
// push it to the completion items
if (tokenType === TokenType.Command) {
items.push(createCompletionItem(terminalContext.cursorPosition, prefix, { label: specLabel }, getDescription(spec), availableCommand.detail));
continue;
}

if (!terminalContext.commandLine.startsWith(specLabel)) {
if (!terminalContext.commandLine.startsWith(`${specLabel} `)) {
// the spec label is not the first word in the command line, so do not provide options or args
continue;
}
Expand Down Expand Up @@ -349,9 +347,7 @@ export async function getCompletionItemsFromSpecs(
!filesRequested &&
!foldersRequested;

const shouldShowCommands = !terminalContext.commandLine.substring(0, terminalContext.cursorPosition).trimStart().includes(' ');

if (shouldShowCommands && !filesRequested && !foldersRequested) {
if (tokenType === TokenType.Command) {
// Include builitin/available commands in the results
const labels = new Set(items.map((i) => i.label));
for (const command of availableCommands) {
Expand Down Expand Up @@ -500,19 +496,6 @@ function getCompletionItemsFromArgs(args: Fig.SingleOrArray<Fig.Arg> | undefined
return { items, filesRequested, foldersRequested };
}



function getFirstCommand(commandLine: string): string | undefined {
const wordsOnLine = commandLine.split(' ');
let firstCommand: string | undefined = wordsOnLine[0];
if (wordsOnLine.length > 1) {
firstCommand = undefined;
} else if (wordsOnLine.length === 0) {
firstCommand = commandLine;
}
return firstCommand;
}

function getFriendlyResourcePath(uri: vscode.Uri, pathSeparator: string, kind?: vscode.TerminalCompletionItemKind): string {
let path = uri.fsPath;
// Ensure drive is capitalized on Windows
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import codeCompletionSpec from '../completions/code';
import codeInsidersCompletionSpec from '../completions/code-insiders';
import type { Uri } from 'vscode';
import { basename } from 'path';
import { getTokenType } from '../tokens';

const fixtureDir = vscode.Uri.joinPath(vscode.Uri.file(__dirname), '../../testWorkspace');
const testCwdParent = vscode.Uri.joinPath(fixtureDir, 'parent');
Expand Down Expand Up @@ -150,7 +151,8 @@ suite('Terminal Suggest', () => {
const prefix = commandLine.slice(0, cursorPosition).split(' ').at(-1) || '';
const filesRequested = testSpec.expectedResourceRequests?.type === 'files' || testSpec.expectedResourceRequests?.type === 'both';
const foldersRequested = testSpec.expectedResourceRequests?.type === 'folders' || testSpec.expectedResourceRequests?.type === 'both';
const result = await getCompletionItemsFromSpecs(completionSpecs, { commandLine, cursorPosition }, availableCommands.map(c => { return { label: c }; }), prefix, testCwd);
const terminalContext = { commandLine, cursorPosition };
const result = await getCompletionItemsFromSpecs(completionSpecs, terminalContext, availableCommands.map(c => { return { label: c }; }), prefix, getTokenType(terminalContext, undefined), testCwd);
deepStrictEqual(result.items.map(i => i.label).sort(), (testSpec.expectedCompletions ?? []).sort());
strictEqual(result.filesRequested, filesRequested);
strictEqual(result.foldersRequested, foldersRequested);
Expand Down
52 changes: 52 additions & 0 deletions extensions/terminal-suggest/src/test/tokens.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import 'mocha';
import { strictEqual } from 'node:assert';
import { getTokenType, TokenType } from '../tokens';
import { TerminalShellType } from '../terminalSuggestMain';

suite('Terminal Suggest', () => {
test('simple command', () => {
strictEqual(getTokenType({ commandLine: 'echo', cursorPosition: 'echo'.length }, undefined), TokenType.Command);
});
test('simple argument', () => {
strictEqual(getTokenType({ commandLine: 'echo hello', cursorPosition: 'echo hello'.length }, undefined), TokenType.Argument);
});
test('simple command, cursor mid text', () => {
strictEqual(getTokenType({ commandLine: 'echo hello', cursorPosition: 'echo'.length }, undefined), TokenType.Command);
});
test('simple argument, cursor mid text', () => {
strictEqual(getTokenType({ commandLine: 'echo hello', cursorPosition: 'echo hel'.length }, undefined), TokenType.Argument);
});
suite('reset to command', () => {
test('|', () => {
strictEqual(getTokenType({ commandLine: 'echo hello | ', cursorPosition: 'echo hello | '.length }, undefined), TokenType.Command);
});
test(';', () => {
strictEqual(getTokenType({ commandLine: 'echo hello; ', cursorPosition: 'echo hello; '.length }, undefined), TokenType.Command);
});
test('&&', () => {
strictEqual(getTokenType({ commandLine: 'echo hello && ', cursorPosition: 'echo hello && '.length }, undefined), TokenType.Command);
});
test('||', () => {
strictEqual(getTokenType({ commandLine: 'echo hello || ', cursorPosition: 'echo hello || '.length }, undefined), TokenType.Command);
});
});
suite('pwsh', () => {
test('simple command', () => {
strictEqual(getTokenType({ commandLine: 'Write-Host', cursorPosition: 'Write-Host'.length }, TerminalShellType.PowerShell), TokenType.Command);
});
test('simple argument', () => {
strictEqual(getTokenType({ commandLine: 'Write-Host hello', cursorPosition: 'Write-Host hello'.length }, TerminalShellType.PowerShell), TokenType.Argument);
});
test('reset char', () => {
strictEqual(getTokenType({ commandLine: `Write-Host hello -and `, cursorPosition: `Write-Host hello -and `.length }, TerminalShellType.PowerShell), TokenType.Command);
});
test('arguments after reset char', () => {
strictEqual(getTokenType({ commandLine: `Write-Host hello -and $true `, cursorPosition: `Write-Host hello -and $true `.length }, TerminalShellType.PowerShell), TokenType.Argument);
});
});
});
33 changes: 33 additions & 0 deletions extensions/terminal-suggest/src/tokens.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { TerminalShellType } from './terminalSuggestMain';

export const enum TokenType {
Command,
Argument,
}

const shellTypeResetChars: { [key: number]: string[] | undefined } = {
[TerminalShellType.Bash]: ['>', '>>', '<', '2>', '2>>', '&>', '&>>', '|', '|&', '&&', '||', '&', ';', '(', '{', '<<'],
[TerminalShellType.Zsh]: ['>', '>>', '<', '2>', '2>>', '&>', '&>>', '<>', '|', '|&', '&&', '||', '&', ';', '(', '{', '<<', '<<<', '<('],
[TerminalShellType.PowerShell]: ['>', '>>', '<', '2>', '2>>', '*>', '*>>', '|', '-and', '-or', '-not', '!', '&', '-eq', '-ne', '-gt', '-lt', '-ge', '-le', '-like', '-notlike', '-match', '-notmatch', '-contains', '-notcontains', '-in', '-notin']
};

const defaultShellTypeResetChars = shellTypeResetChars[TerminalShellType.Bash]!;

export function getTokenType(ctx: { commandLine: string; cursorPosition: number }, shellType: TerminalShellType | undefined): TokenType {
const spaceIndex = ctx.commandLine.substring(0, ctx.cursorPosition).lastIndexOf(' ');
if (spaceIndex === -1) {
return TokenType.Command;
}
const tokenIndex = spaceIndex === -1 ? 0 : spaceIndex + 1;
const previousTokens = ctx.commandLine.substring(0, tokenIndex).trim();
const commandResetChars = shellType === undefined ? defaultShellTypeResetChars : shellTypeResetChars[shellType] ?? defaultShellTypeResetChars;
if (commandResetChars.some(e => previousTokens.endsWith(e))) {
return TokenType.Command;
}
return TokenType.Argument;
}
31 changes: 31 additions & 0 deletions extensions/vscode-api-tests/src/singlefolder-tests/editor.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,37 @@ suite('vscode API - editors', () => {
});
});

/**
* Given :
* This is line 1
* |
*
* Expect :
* This is line 1
* This is line 2
* This is line 3
*
* The 3rd line should not be auto-indented, as the edit already
* contains the necessary adjustment.
*/
test('insert snippet with replacement, avoid adjusting indentation', () => {
const snippetString = new SnippetString()
.appendText('This is line 2\n This is line 3');

return withRandomFileEditor('This is line 1\n ', (editor, doc) => {
editor.selection = new Selection(
new Position(1, 3),
new Position(1, 3)
);

return editor.insertSnippet(snippetString, undefined, { undoStopAfter: false, undoStopBefore: false, keepWhitespace: true }).then(inserted => {
assert.ok(inserted);
assert.strictEqual(doc.getText(), 'This is line 1\n This is line 2\n This is line 3');
assert.ok(doc.isDirty);
});
});
});

test('insert snippet with replacement, selection as argument', () => {
const snippetString = new SnippetString()
.appendText('has been');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1227,6 +1227,27 @@ suite('vscode API - workspace', () => {
assert.deepStrictEqual(edt.selections, [new vscode.Selection(0, 0, 0, 3)]);
});

test('SnippetString in WorkspaceEdit with keepWhitespace', async function (): Promise<any> {
const file = await createRandomFile('This is line 1\n ');

const document = await vscode.workspace.openTextDocument(file);
const edt = await vscode.window.showTextDocument(document);

assert.ok(edt === vscode.window.activeTextEditor);

const snippetText = new vscode.SnippetTextEdit(new vscode.Range(1, 3, 1, 3), new vscode.SnippetString('This is line 2\n This is line 3'));
snippetText.keepWhitespace = true;
const we = new vscode.WorkspaceEdit();
we.set(document.uri, [snippetText]);
const success = await vscode.workspace.applyEdit(we);
if (edt !== vscode.window.activeTextEditor) {
return this.skip();
}

assert.ok(success);
assert.strictEqual(document.getText(), 'This is line 1\n This is line 2\n This is line 3');
});

test('Support creating binary files in a WorkspaceEdit', async function (): Promise<any> {

const fileUri = vscode.Uri.parse(`${testFs.scheme}:/${rndName()}`);
Expand Down
2 changes: 1 addition & 1 deletion src/vs/editor/browser/services/bulkEditService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ export class ResourceTextEdit extends ResourceEdit implements IWorkspaceTextEdit

constructor(
readonly resource: URI,
readonly textEdit: TextEdit & { insertAsSnippet?: boolean },
readonly textEdit: TextEdit & { insertAsSnippet?: boolean; keepWhitespace?: boolean },
readonly versionId: number | undefined = undefined,
metadata?: WorkspaceEditMetadata,
) {
Expand Down
2 changes: 1 addition & 1 deletion src/vs/editor/common/languages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1814,7 +1814,7 @@ export interface IWorkspaceFileEdit {

export interface IWorkspaceTextEdit {
resource: URI;
textEdit: TextEdit & { insertAsSnippet?: boolean };
textEdit: TextEdit & { insertAsSnippet?: boolean; keepWhitespace?: boolean };
versionId: number | undefined;
metadata?: WorkspaceEditMetadata;
}
Expand Down
2 changes: 1 addition & 1 deletion src/vs/editor/common/standaloneStrings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export namespace AccessibilityHelpNLS {
export const debugExecuteSelection = nls.localize('debugConsole.executeSelection', "The Debug: Execute Selection command{0} will execute the selected text in the debug console.", '<keybinding:editor.debug.action.selectionToRepl>');
export const chatEditorModification = nls.localize('chatEditorModification', "The editor contains pending modifications that have been made by chat.");
export const chatEditorRequestInProgress = nls.localize('chatEditorRequestInProgress', "The editor is currently waiting for modifications to be made by chat.");
export const chatEditActions = nls.localize('chatEditing.navigation', 'Navigate between edits in the editor with navigate previous{0} and next{1} and accept{3} and reject the current change{4}.', '<keybinding:chatEditor.action.navigatePrevious>', '<keybinding:chatEditor.action.navigateNext>', '<keybinding:chatEditor.action.acceptHunk>', '<keybinding:chatEditor.action.reject>');
export const chatEditActions = nls.localize('chatEditing.navigation', 'Navigate between edits in the editor with navigate previous{0} and next{1} and accept{2}, reject{3} or view the diff{4} for the current change.', '<keybinding:chatEditor.action.navigatePrevious>', '<keybinding:chatEditor.action.navigateNext>', '<keybinding:chatEditor.action.acceptHunk>', '<keybinding:chatEditor.action.undoHunk>', '<keybinding:chatEditor.action.diffHunk>');
}

export namespace InspectTokensNLS {
Expand Down
5 changes: 3 additions & 2 deletions src/vs/editor/contrib/snippet/browser/snippetSession.ts
Original file line number Diff line number Diff line change
Expand Up @@ -375,6 +375,7 @@ const _defaultOptions: ISnippetSessionInsertOptions = {
export interface ISnippetEdit {
range: Range;
template: string;
keepWhitespace?: boolean;
}

export class SnippetSession {
Expand Down Expand Up @@ -569,7 +570,7 @@ export class SnippetSession {
let offset = 0;
for (let i = 0; i < snippetEdits.length; i++) {

const { range, template } = snippetEdits[i];
const { range, template, keepWhitespace } = snippetEdits[i];

// gaps between snippet edits are appended as text nodes. this
// ensures placeholder-offsets are later correct
Expand All @@ -582,7 +583,7 @@ export class SnippetSession {
}

const newNodes = parser.parseFragment(template, snippet);
SnippetSession.adjustWhitespace(model, range.getStartPosition(), true, snippet, new Set(newNodes));
SnippetSession.adjustWhitespace(model, range.getStartPosition(), keepWhitespace !== undefined ? !keepWhitespace : adjustWhitespace, snippet, new Set(newNodes));
snippet.resolveVariables(resolver);

const snippetText = snippet.toString();
Expand Down
1 change: 1 addition & 0 deletions src/vs/monaco.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7979,6 +7979,7 @@ declare namespace monaco.languages {
resource: Uri;
textEdit: TextEdit & {
insertAsSnippet?: boolean;
keepWhitespace?: boolean;
};
versionId: number | undefined;
metadata?: WorkspaceEditMetadata;
Expand Down
4 changes: 3 additions & 1 deletion src/vs/platform/windows/electron-main/windowsMainService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1164,13 +1164,15 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
this.workspacesHistoryMainService.removeRecentlyOpened([fileUri]);

// assume this is a file that does not yet exist
if (options.ignoreFileNotFound) {
if (options.ignoreFileNotFound && error.code === 'ENOENT') {
return {
fileUri,
type: FileType.File,
exists: false
};
}

this.logService.error(`Invalid path provided: ${path}, ${error.message}`);
}

return undefined;
Expand Down
5 changes: 3 additions & 2 deletions src/vs/workbench/api/browser/mainThreadEditor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { ITextModel, ITextModelUpdateOptions } from '../../../editor/common/mode
import { ISingleEditOperation } from '../../../editor/common/core/editOperation.js';
import { IModelService } from '../../../editor/common/services/model.js';
import { SnippetController2 } from '../../../editor/contrib/snippet/browser/snippetController2.js';
import { IApplyEditsOptions, IEditorPropertiesChangeData, IResolvedTextEditorConfiguration, ITextEditorConfigurationUpdate, IUndoStopOptions, TextEditorRevealType } from '../common/extHost.protocol.js';
import { IApplyEditsOptions, IEditorPropertiesChangeData, IResolvedTextEditorConfiguration, ISnippetOptions, ITextEditorConfigurationUpdate, TextEditorRevealType } from '../common/extHost.protocol.js';
import { IEditorPane } from '../../common/editor.js';
import { equals } from '../../../base/common/arrays.js';
import { CodeEditorStateFlag, EditorState } from '../../../editor/contrib/editorState/browser/editorState.js';
Expand Down Expand Up @@ -509,7 +509,7 @@ export class MainThreadTextEditor {
return true;
}

async insertSnippet(modelVersionId: number, template: string, ranges: readonly IRange[], opts: IUndoStopOptions) {
async insertSnippet(modelVersionId: number, template: string, ranges: readonly IRange[], opts: ISnippetOptions) {

if (!this._codeEditor || !this._codeEditor.hasModel()) {
return false;
Expand Down Expand Up @@ -542,6 +542,7 @@ export class MainThreadTextEditor {
snippetController.apply(edits, {
overwriteBefore: 0, overwriteAfter: 0,
undoStopBefore: opts.undoStopBefore, undoStopAfter: opts.undoStopAfter,
adjustWhitespace: !opts.keepWhitespace,
clipboardText
});

Expand Down
3 changes: 3 additions & 0 deletions src/vs/workbench/api/common/extHost.protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,9 @@ export interface IApplyEditsOptions extends IUndoStopOptions {
setEndOfLine?: EndOfLineSequence;
}

export interface ISnippetOptions extends IUndoStopOptions {
keepWhitespace?: boolean;
}
export interface ITextDocumentShowOptions {
position?: EditorGroupColumn;
preserveFocus?: boolean;
Expand Down
Loading

0 comments on commit 4c3914f

Please sign in to comment.