Skip to content
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
7 changes: 7 additions & 0 deletions extensions/terminal-suggest/src/terminalSuggestMain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -413,6 +413,13 @@ export async function resolveCwdFromCurrentCommandString(currentCommandString: s
}
const relativeFolder = lastSlashIndex === -1 ? '' : prefix.slice(0, lastSlashIndex);

// Don't pre-resolve paths with .. segments - let the completion service handle those
// to avoid double-navigation (e.g., typing ../ would resolve cwd to parent here,
// then completion service would navigate up again from the already-parent cwd)
if (relativeFolder.includes('..')) {
return undefined;
}

// Use vscode.Uri.joinPath for path resolution
const resolvedUri = vscode.Uri.joinPath(currentCwd, relativeFolder);

Expand Down
5 changes: 3 additions & 2 deletions extensions/terminal-suggest/src/test/completions/cd.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ export const cdTestSuiteSpec: ISuiteSpec = {

// Relative directories (changes cwd due to /)
{ input: 'cd child/|', expectedCompletions, expectedResourceRequests: { type: 'folders', cwd: testPaths.cwdChild } },
{ input: 'cd ../|', expectedCompletions, expectedResourceRequests: { type: 'folders', cwd: testPaths.cwdParent } },
{ input: 'cd ../sibling|', expectedCompletions, expectedResourceRequests: { type: 'folders', cwd: testPaths.cwdParent } },
// Paths with .. are handled by the completion service to avoid double-navigation (no cwd resolution)
{ input: 'cd ../|', expectedCompletions, expectedResourceRequests: { type: 'folders' } },
{ input: 'cd ../sibling|', expectedCompletions, expectedResourceRequests: { type: 'folders' } },
]
};
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,9 @@ export const lsTestSuiteSpec: ISuiteSpec = {

// Relative directories (changes cwd due to /)
{ input: 'ls child/|', expectedCompletions: allOptions, expectedResourceRequests: { type: 'both', cwd: testPaths.cwdChild } },
{ input: 'ls ../|', expectedCompletions: allOptions, expectedResourceRequests: { type: 'both', cwd: testPaths.cwdParent } },
{ input: 'ls ../sibling|', expectedCompletions: allOptions, expectedResourceRequests: { type: 'both', cwd: testPaths.cwdParent } },
// Paths with .. are handled by the completion service to avoid double-navigation (no cwd resolution)
{ input: 'ls ../|', expectedCompletions: allOptions, expectedResourceRequests: { type: 'both' } },
{ input: 'ls ../sibling|', expectedCompletions: allOptions, expectedResourceRequests: { type: 'both' } },
]
};

2 changes: 1 addition & 1 deletion extensions/terminal-suggest/src/test/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export interface ITestSpec {
input: string;
expectedResourceRequests?: {
type: 'files' | 'folders' | 'both';
cwd: Uri;
cwd?: Uri;
};
expectedCompletions?: (string | ICompletionResource)[];
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ suite('Terminal Suggest', () => {
let expectedString = testSpec.expectedCompletions ? `[${testSpec.expectedCompletions.map(e => `'${e}'`).join(', ')}]` : '[]';
if (testSpec.expectedResourceRequests) {
expectedString += ` + ${testSpec.expectedResourceRequests.type}`;
if (testSpec.expectedResourceRequests.cwd.fsPath !== testPaths.cwd.fsPath) {
if (testSpec.expectedResourceRequests.cwd && testSpec.expectedResourceRequests.cwd.fsPath !== testPaths.cwd.fsPath) {
expectedString += ` @ ${basename(testSpec.expectedResourceRequests.cwd.fsPath)}/`;
}
}
Expand Down
6 changes: 3 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "code-oss-dev",
"version": "1.109.0",
"distro": "66d6007ba3b8eff60c3026cb216e699981aca7ec",
"distro": "276abacfc6a1d1a9d17ab0d7d7cb4775998082b2",
"author": {
"name": "Microsoft Corporation"
},
Expand Down
83 changes: 83 additions & 0 deletions src/vs/base/common/event.ts
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,89 @@ export namespace Event {
}, delay, undefined, flushOnListenerRemove ?? true, undefined, disposable);
}

/**
* Throttles an event, ensuring the event is fired at most once during the specified delay period.
* Unlike debounce, throttle will fire immediately on the leading edge and/or after the delay on the trailing edge.
*
* *NOTE* that this function returns an `Event` and it MUST be called with a `DisposableStore` whenever the returned
* event is accessible to "third parties", e.g the event is a public property. Otherwise a leaked listener on the
* returned event causes this utility to leak a listener on the original event.
*
* @param event The event source for the new event.
* @param merge An accumulator function that merges events if multiple occur during the throttle period.
* @param delay The number of milliseconds to throttle.
* @param leading Whether to fire on the leading edge (immediately on first event).
* @param trailing Whether to fire on the trailing edge (after delay with the last value).
* @param leakWarningThreshold See {@link EmitterOptions.leakWarningThreshold}.
* @param disposable A disposable store to register the throttle emitter to.
*/
export function throttle<T>(event: Event<T>, merge: (last: T | undefined, event: T) => T, delay?: number | typeof MicrotaskDelay, leading?: boolean, trailing?: boolean, leakWarningThreshold?: number, disposable?: DisposableStore): Event<T>;
export function throttle<I, O>(event: Event<I>, merge: (last: O | undefined, event: I) => O, delay?: number | typeof MicrotaskDelay, leading?: boolean, trailing?: boolean, leakWarningThreshold?: number, disposable?: DisposableStore): Event<O>;
export function throttle<I, O>(event: Event<I>, merge: (last: O | undefined, event: I) => O, delay: number | typeof MicrotaskDelay = 100, leading = true, trailing = true, leakWarningThreshold?: number, disposable?: DisposableStore): Event<O> {
let subscription: IDisposable;
let output: O | undefined = undefined;
let handle: Timeout | undefined = undefined;
let numThrottledCalls = 0;

const options: EmitterOptions | undefined = {
leakWarningThreshold,
onWillAddFirstListener() {
subscription = event(cur => {
numThrottledCalls++;
output = merge(output, cur);

// If not currently throttling, fire immediately if leading is enabled
if (handle === undefined) {
if (leading) {
emitter.fire(output);
output = undefined;
numThrottledCalls = 0;
}

// Set up the throttle period
if (typeof delay === 'number') {
handle = setTimeout(() => {
// Fire on trailing edge if there were calls during throttle period
if (trailing && numThrottledCalls > 0) {
emitter.fire(output!);
}
output = undefined;
handle = undefined;
numThrottledCalls = 0;
}, delay);
} else {
// Use a special marker to indicate microtask is pending
handle = 0 as unknown as Timeout;
queueMicrotask(() => {
// Fire on trailing edge if there were calls during throttle period
if (trailing && numThrottledCalls > 0) {
emitter.fire(output!);
}
output = undefined;
handle = undefined;
numThrottledCalls = 0;
});
}
}
// If already throttling, just accumulate the value for trailing edge
});
},
onDidRemoveLastListener() {
subscription.dispose();
}
};

if (!disposable) {
_addLeakageTraceLogic(options);
}

const emitter = new Emitter<O>(options);

disposable?.add(emitter);

return emitter.event;
}

/**
* Filters an event such that some condition is _not_ met more than once in a row, effectively ensuring duplicate
* event objects from different sources do not fire the same event object.
Expand Down
Loading
Loading