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

refactor: support dynamic terminal prompt detection without regex on windows #211382

Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,7 @@ export interface ICommandDetectionCapability {
readonly onCommandInvalidated: Event<ITerminalCommand[]>;
readonly onCurrentCommandInvalidated: Event<ICommandInvalidationRequest>;
setContinuationPrompt(value: string): void;
setPromptTerminator(value: string): void;
setCwd(value: string): void;
setIsWindowsPty(value: boolean): void;
setIsCommandStorageDisabled(): void;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export class CommandDetectionCapability extends Disposable implements ICommandDe

protected _commands: TerminalCommand[] = [];
private _cwd: string | undefined;
private _promptTerminator: string | undefined;
private _currentCommand: PartialTerminalCommand = new PartialTerminalCommand(this._terminal);
private _commandMarkers: IMarker[] = [];
private _dimensions: ITerminalDimensions;
Expand All @@ -54,6 +55,7 @@ export class CommandDetectionCapability extends Disposable implements ICommandDe
return this._currentCommand;
}
get cwd(): string | undefined { return this._cwd; }
get promptTerminator(): string | undefined { return this._promptTerminator; }
private get _isInputting(): boolean {
return !!(this._currentCommand.commandStartMarker && !this._currentCommand.commandExecutedMarker);
}
Expand Down Expand Up @@ -452,6 +454,11 @@ export class CommandDetectionCapability extends Disposable implements ICommandDe
this._onCommandFinished.fire(newCommand);
}
}

setPromptTerminator(promptTerminator: string) {
this._logService.debug('CommandDetectionCapability#setPromptTerminator', promptTerminator);
this._promptTerminator = promptTerminator;
}
}

/**
Expand Down Expand Up @@ -922,7 +929,6 @@ class WindowsPtyHeuristics extends Disposable {
if (!line) {
return;
}
// TODO: fine tune prompt regex to accomodate for unique configurations.
const lineText = line.translateToString(true);
if (!lineText) {
return;
Expand Down Expand Up @@ -967,6 +973,14 @@ class WindowsPtyHeuristics extends Disposable {
};
}

// Dynamic prompt detection
if (this._capability.promptTerminator && lineText.trim().endsWith(this._capability.promptTerminator)) {
const adjustedPrompt = this._adjustPrompt(lineText, lineText, this._capability.promptTerminator);
if (adjustedPrompt) {
return adjustedPrompt;
}
}

// Command Prompt
const cmdMatch = lineText.match(/^(?<prompt>(\(.+\)\s)?(?:[A-Z]:\\.*>))/);
return cmdMatch?.groups?.prompt ? {
Expand Down
17 changes: 17 additions & 0 deletions src/vs/platform/terminal/common/xterm/shellIntegrationAddon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -394,6 +394,12 @@ export class ShellIntegrationAddon extends Disposable implements IShellIntegrati
this._createOrGetCommandDetection(this._terminal).setIsWindowsPty(value === 'True' ? true : false);
return true;
}
case 'Prompt': {
// Remove escape sequences from the user's prompt
const sanitizedValue = value.replace(/\x1b\[[0-9;]*m/g, '');
this._updatePromptTerminator(sanitizedValue);
return true;
}
case 'Task': {
this._createOrGetBufferMarkDetection(this._terminal);
this.capabilities.get(TerminalCapability.CommandDetection)?.setIsCommandStorageDisabled();
Expand All @@ -418,6 +424,17 @@ export class ShellIntegrationAddon extends Disposable implements IShellIntegrati
this._createOrGetCommandDetection(this._terminal).setContinuationPrompt(value);
}

private _updatePromptTerminator(prompt: string) {
if (!this._terminal) {
return;
}
const lastPromptLine = prompt.substring(prompt.lastIndexOf('\n')).trim();
const promptTerminator = lastPromptLine.substring(lastPromptLine.lastIndexOf(' ')).trim();
if (promptTerminator) {
this._createOrGetCommandDetection(this._terminal).setPromptTerminator(promptTerminator);
}
}

private _updateCwd(value: string) {
value = sanitizeCwd(value);
this._createOrGetCwdDetection().updateCwd(value);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,13 +117,15 @@ __vsc_escape_value() {
for (( i=0; i < "${#str}"; ++i )); do
byte="${str:$i:1}"

# Escape backslashes, semi-colons and newlines
# Escape backslashes, semi-colons, escapes, and newlines
if [ "$byte" = "\\" ]; then
token="\\\\"
elif [ "$byte" = ";" ]; then
token="\\x3b"
elif [ "$byte" = $'\n' ]; then
token="\x0a"
elif [ "$byte" = $'\e' ]; then
token="\\x1b"
else
token="$byte"
fi
Expand Down Expand Up @@ -165,6 +167,14 @@ unset VSCODE_NONCE
# Report continuation prompt
builtin printf "\e]633;P;ContinuationPrompt=$(echo "$PS2" | sed 's/\x1b/\\\\x1b/g')\a"

__vsc_report_prompt() {
# Expand the original PS1 similarly to how bash would normally
# See https://stackoverflow.com/a/37137981 for technique
__vsc_prompt=${__vsc_original_PS1@P}
__vsc_prompt="$(builtin printf "%s" "${__vsc_prompt//[$'\001'$'\002']}")"
builtin printf "\e]633;P;Prompt=%s\a" "$(__vsc_escape_value "${__vsc_prompt}")"
}

__vsc_prompt_start() {
builtin printf '\e]633;A\a'
}
Expand Down Expand Up @@ -229,6 +239,7 @@ __vsc_precmd() {
__vsc_command_complete "$__vsc_status"
__vsc_current_command=""
__vsc_update_prompt
__vsc_report_prompt
__vsc_first_prompt=1
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,13 @@ function Global:Prompt() {
Write-Error "failure" -ea ignore
}
# Run the original prompt
$Result += $Global:__VSCodeOriginalPrompt.Invoke()
$OriginalPrompt += $Global:__VSCodeOriginalPrompt.Invoke()
$Result += $OriginalPrompt

# Prompt
# OSC 633 ; <Property>=<Value> ST
$Result += "$([char]0x1b)]633;P;Prompt=$(__VSCode-Escape-Value $OriginalPrompt)`a"

# Write command started
$Result += "$([char]0x1b)]633;B`a"
$Global:__LastHistoryId = $LastHistoryEntry.Id
Expand Down
Loading