diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 371cc5fc..2511a59f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -118,6 +118,10 @@ Eventually, we want to build a convenient API for creating AI tools. The API wil ⭐ Allow user to X out of their current selection. +# Guidelines + +Please don't make big refactors without speaking with us first. We'd like to keep the codebase similar to vscode so we can periodically rebase, and if we have big changes this gets complicated. + # Submitting a Pull Request When you've made changes and want to submit them, please submit a pull request. diff --git a/README.md b/README.md index c1ca29c9..4f46f618 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ Void is the open-source Cursor alternative. -If you're new, welcome! Feel free to check out our [Project board](https://github.com/orgs/voideditor/projects/2/views/3) for the most pressing Issues to work on, and see [`CONTRIBUTING.md`](https://github.com/voideditor/void/blob/main/CONTRIBUTING.md) to get started with the build environment. +If you're new, welcome! Feel free to check out our [Project board](https://github.com/orgs/voideditor/projects/2/views/3) for the most pressing Issues to work on, and see [`CONTRIBUTING.md`](https://github.com/voideditor/void/blob/main/CONTRIBUTING.md) for instructions on building and running Void. This repo contains the full sourcecode for Void. We have a [waitlist](https://voideditor.com/email) for downloading the official release, but you can build and develop Void right now. diff --git a/extensions/void/src/ApprovalCodeLensProvider.ts b/extensions/void/src/ApprovalCodeLensProvider.ts index ddf2a2a9..80386fa1 100644 --- a/extensions/void/src/ApprovalCodeLensProvider.ts +++ b/extensions/void/src/ApprovalCodeLensProvider.ts @@ -1,32 +1,18 @@ import * as vscode from 'vscode'; +import { SuggestedEdit } from './getDiffedLines'; -export type SuggestedEdit = { - // start/end of current file - startLine: number; - endLine: number; - - // start/end of original file - originalStartLine: number, - originalEndLine: number, - - // original content (originalfile[originalStart...originalEnd]) - originalContent: string; - newContent: string; -} - - -// stored for later use +// each diff on the user's screen right now type DiffType = { - diffid: number, // unique id - range: vscode.Range, // current range - originalCode: string, // original code in case user wants to revert this + diffid: number, lenses: vscode.CodeLens[], + greenRange: vscode.Range, + originalCode: string, // If a revert happens, we replace the greenRange with this content. } // TODO in theory this should be disposed const greenDecoration = vscode.window.createTextEditorDecorationType({ backgroundColor: 'rgba(0 255 51 / 0.2)', - isWholeLine: true, // after: { contentText: ' [original]', color: 'rgba(0 255 60 / 0.5)' } // hoverMessage: originalText // this applies to hovering over after:... + isWholeLine: false, // after: { contentText: ' [original]', color: 'rgba(0 255 60 / 0.5)' } // hoverMessage: originalText // this applies to hovering over after:... }) export class ApprovalCodeLensProvider implements vscode.CodeLensProvider { @@ -49,6 +35,7 @@ export class ApprovalCodeLensProvider implements vscode.CodeLensProvider { return this._computedLensesOfDocument[docUriStr] } + // declared by us, registered with vscode.languages.registerCodeLensProvider() constructor() { // this acts as a useEffect. Every time text changes, clear the diffs in this editor vscode.workspace.onDidChangeTextDocument((e) => { @@ -66,6 +53,13 @@ export class ApprovalCodeLensProvider implements vscode.CodeLensProvider { }) } + // used by us only + private refreshLenses = (editor: vscode.TextEditor, docUriStr: string) => { + editor.setDecorations(greenDecoration, this._diffsOfDocument[docUriStr].map(diff => diff.greenRange)) // refresh highlighting + this._computedLensesOfDocument[docUriStr] = this._diffsOfDocument[docUriStr].flatMap(diff => diff.lenses) // recompute _computedLensesOfDocument (can optimize this later) + this._onDidChangeCodeLenses.fire() // fire event for vscode to refresh lenses + } + // used by us only public async addNewApprovals(editor: vscode.TextEditor, suggestedEdits: SuggestedEdit[]) { @@ -77,62 +71,55 @@ export class ApprovalCodeLensProvider implements vscode.CodeLensProvider { if (!this._computedLensesOfDocument[docUriStr]) this._computedLensesOfDocument[docUriStr] = [] - // 0. create a diff for each suggested edit - const diffs: DiffType[] = [] - for (let suggestedEdit of suggestedEdits) { - // TODO we need to account for this case (deletions) - if (suggestedEdit.startLine > suggestedEdit.endLine) - continue - - const selectedRange = new vscode.Range(suggestedEdit.startLine, 0, suggestedEdit.endLine, Number.MAX_SAFE_INTEGER) - - // if any other codelens intersects with the selection, ignore this edit - for (let { range } of this._diffsOfDocument[docUriStr]) { - if (range.intersection(selectedRange)) { - vscode.window.showWarningMessage(`Changes have already been applied to this location. Please accept/reject them before applying new changes.`) - return // do not make any edits - } - } - - diffs.push({ diffid: this._diffidPool, range: selectedRange, originalCode: suggestedEdit.originalContent, lenses: [] }) - this._diffidPool += 1 - } - - this._diffsOfDocument[docUriStr].push(...diffs); - - // 1. apply each diff to the document + // 1. convert suggested edits (which are described using line numbers) into actual edits (described using vscode.Range, vscode.Uri) // must do this before adding codelenses or highlighting so that codelens and highlights will apply to the fresh code and not the old code // apply changes in reverse order so additions don't push down the line numbers of the next edit let workspaceEdit = new vscode.WorkspaceEdit(); - for (let i = suggestedEdits.length - 1; i > -1; i--) { + for (let i = suggestedEdits.length - 1; i > -1; i -= 1) { let suggestedEdit = suggestedEdits[i] - const originalRange = new vscode.Range(suggestedEdit.originalStartLine, 0, suggestedEdit.originalEndLine, Number.MAX_SAFE_INTEGER) - workspaceEdit.replace(docUri, originalRange, suggestedEdit.newContent); + + let greenRange: vscode.Range + + // INSERTIONS (e.g. {originalStartLine: 0, originalEndLine: -1}) + if (suggestedEdit.originalStartLine > suggestedEdit.originalEndLine) { + const originalPosition = new vscode.Position(suggestedEdit.originalStartLine, 0) + workspaceEdit.insert(docUri, originalPosition, suggestedEdit.newContent + '\n') // add back in the line we deleted when we made the startline->endline range go negative + greenRange = new vscode.Range(suggestedEdit.startLine, 0, suggestedEdit.endLine + 1, 0) + } + // DELETIONS + else if (suggestedEdit.startLine > suggestedEdit.endLine) { + const deleteRange = new vscode.Range(suggestedEdit.originalStartLine, 0, suggestedEdit.originalEndLine + 1, 0) + workspaceEdit.delete(docUri, deleteRange) + greenRange = new vscode.Range(suggestedEdit.startLine, 0, suggestedEdit.startLine, 0) + suggestedEdit.originalContent += '\n' // add back in the line we deleted when we made the startline->endline range go negative + } + // REPLACEMENTS + else { + const originalRange = new vscode.Range(suggestedEdit.originalStartLine, 0, suggestedEdit.originalEndLine, Number.MAX_SAFE_INTEGER) + workspaceEdit.replace(docUri, originalRange, suggestedEdit.newContent) + greenRange = new vscode.Range(suggestedEdit.startLine, 0, suggestedEdit.endLine, Number.MAX_SAFE_INTEGER) + } + + this._diffsOfDocument[docUriStr].push({ + diffid: this._diffidPool, + greenRange: greenRange, + originalCode: suggestedEdit.originalContent, + lenses: [ + new vscode.CodeLens(greenRange, { title: 'Accept', command: 'void.approveDiff', arguments: [{ diffid: this._diffidPool }] }), + new vscode.CodeLens(greenRange, { title: 'Reject', command: 'void.discardDiff', arguments: [{ diffid: this._diffidPool }] }) + ] + }); + this._diffidPool += 1 } + this._weAreEditing = true await vscode.workspace.applyEdit(workspaceEdit) await vscode.workspace.save(docUri) this._weAreEditing = false - // 2. add the Yes/No codelenses - for (let diff of diffs) { - const { range, diffid, lenses: codeLenses } = diff - - let approveLens = new vscode.CodeLens(range, { title: 'Accept', command: 'void.approveDiff', arguments: [{ diffid }] }) - let discardLens = new vscode.CodeLens(range, { title: 'Reject', command: 'void.discardDiff', arguments: [{ diffid }] }) - - codeLenses.push(discardLens, approveLens) - } - - // 3. apply green highlighting for each (+) diff - editor.setDecorations(greenDecoration, this._diffsOfDocument[docUriStr].map(diff => diff.range)) - - // recompute _computedLensesOfDocument (can optimize this later) - this._computedLensesOfDocument[docUriStr] = this._diffsOfDocument[docUriStr].flatMap(diff => diff.lenses) - // refresh - this._onDidChangeCodeLenses.fire() + this.refreshLenses(editor, docUriStr) console.log('diffs after added:', this._diffsOfDocument[docUriStr]) } @@ -156,14 +143,8 @@ export class ApprovalCodeLensProvider implements vscode.CodeLensProvider { // remove this diff from the diffsOfDocument[docStr] (can change this behavior in future if add something like history) this._diffsOfDocument[docUriStr].splice(index, 1) - // clear the decoration in this diff's range - editor.setDecorations(greenDecoration, this._diffsOfDocument[docUriStr].map(diff => diff.range)) - - // recompute _computedLensesOfDocument (can optimize this later) - this._computedLensesOfDocument[docUriStr] = this._diffsOfDocument[docUriStr].flatMap(diff => diff.lenses) - // refresh - this._onDidChangeCodeLenses.fire() + this.refreshLenses(editor, docUriStr) } @@ -183,13 +164,13 @@ export class ApprovalCodeLensProvider implements vscode.CodeLensProvider { return } - const { range, lenses, originalCode } = this._diffsOfDocument[docUriStr][index] // do this before we splice and mess up index + const { greenRange: range, lenses, originalCode } = this._diffsOfDocument[docUriStr][index] // do this before we splice and mess up index // remove this diff from the diffsOfDocument[docStr] (can change this behavior in future if add something like history) this._diffsOfDocument[docUriStr].splice(index, 1) // clear the decoration in this diffs range - editor.setDecorations(greenDecoration, this._diffsOfDocument[docUriStr].map(diff => diff.range)) + editor.setDecorations(greenDecoration, this._diffsOfDocument[docUriStr].map(diff => diff.greenRange)) // REVERT THE CHANGE (this is the only part that's different from approveDiff) let workspaceEdit = new vscode.WorkspaceEdit(); @@ -199,10 +180,7 @@ export class ApprovalCodeLensProvider implements vscode.CodeLensProvider { await vscode.workspace.save(docUri) this._weAreEditing = false - // recompute _computedLensesOfDocument (can optimize this later) - this._computedLensesOfDocument[docUriStr] = this._diffsOfDocument[docUriStr].flatMap(diff => diff.lenses) - // refresh - this._onDidChangeCodeLenses.fire() + this.refreshLenses(editor, docUriStr) } } diff --git a/extensions/void/src/extension.ts b/extensions/void/src/extension.ts index 3eedae28..84fc9cae 100644 --- a/extensions/void/src/extension.ts +++ b/extensions/void/src/extension.ts @@ -2,7 +2,7 @@ import * as vscode from 'vscode'; import { WebviewMessage } from './shared_types'; import { CtrlKCodeLensProvider } from './CtrlKCodeLensProvider'; import { getDiffedLines } from './getDiffedLines'; -import { ApprovalCodeLensProvider, SuggestedEdit } from './ApprovalCodeLensProvider'; +import { ApprovalCodeLensProvider } from './ApprovalCodeLensProvider'; import { SidebarWebviewProvider } from './SidebarWebviewProvider'; import { ApiConfig } from './common/sendLLMMessage'; diff --git a/extensions/void/src/getDiffedLines.ts b/extensions/void/src/getDiffedLines.ts index 5b1fa194..56fa119e 100644 --- a/extensions/void/src/getDiffedLines.ts +++ b/extensions/void/src/getDiffedLines.ts @@ -1,5 +1,18 @@ import { diffLines, Change } from 'diff'; -import { SuggestedEdit } from './ApprovalCodeLensProvider'; + +export type SuggestedEdit = { + // start/end of current file + startLine: number; + endLine: number; + + // start/end of original file + originalStartLine: number, + originalEndLine: number, + + // original content (originalfile[originalStart...originalEnd]) + originalContent: string; + newContent: string; +} export function getDiffedLines(oldStr: string, newStr: string) { // an ordered list of every original line, line added to the new file, and line removed from the old file (order is unambiguous, think about it) diff --git a/src/vs/base/browser/browser.ts b/src/vs/base/browser/browser.ts index f3e6b43c..87db1c57 100644 --- a/src/vs/base/browser/browser.ts +++ b/src/vs/base/browser/browser.ts @@ -136,6 +136,6 @@ export function isWCOEnabled(): boolean { // Returns the bounding rect of the titlebar area if it is supported and defined // See docs at https://developer.mozilla.org/en-US/docs/Web/API/WindowControlsOverlay/getTitlebarAreaRect -export function getWCOTitlebarAreaRect(targetWindow: Window): DOMRect | undefined { - return (targetWindow.navigator as any)?.windowControlsOverlay?.getTitlebarAreaRect(); +export function getWCOBoundingRect(): DOMRect | undefined { + return (navigator as any)?.windowControlsOverlay?.getTitlebarAreaRect(); } diff --git a/src/vs/base/browser/ui/radio/radio.ts b/src/vs/base/browser/ui/radio/radio.ts index 5ab1edc4..2f57e4ac 100644 --- a/src/vs/base/browser/ui/radio/radio.ts +++ b/src/vs/base/browser/ui/radio/radio.ts @@ -11,7 +11,7 @@ import { $ } from 'vs/base/browser/dom'; import { IHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; import { Button } from 'vs/base/browser/ui/button/button'; import { DisposableMap, DisposableStore } from 'vs/base/common/lifecycle'; -import { createInstantHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; +import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; export interface IRadioStyles { readonly activeForeground?: string; @@ -53,7 +53,7 @@ export class Radio extends Widget { constructor(opts: IRadioOptions) { super(); - this.hoverDelegate = opts.hoverDelegate ?? this._register(createInstantHoverDelegate()); + this.hoverDelegate = opts.hoverDelegate ?? getDefaultHoverDelegate('element'); this.domNode = $('.monaco-custom-radio'); this.domNode.setAttribute('role', 'radio'); diff --git a/src/vs/base/parts/sandbox/common/electronTypes.ts b/src/vs/base/parts/sandbox/common/electronTypes.ts index ef8c1026..43fa7507 100644 --- a/src/vs/base/parts/sandbox/common/electronTypes.ts +++ b/src/vs/base/parts/sandbox/common/electronTypes.ts @@ -217,6 +217,24 @@ export interface FileFilter { name: string; } +export interface OpenDevToolsOptions { + /** + * Opens the devtools with specified dock state, can be `left`, `right`, `bottom`, + * `undocked`, `detach`. Defaults to last used dock state. In `undocked` mode it's + * possible to dock back. In `detach` mode it's not. + */ + mode: ('left' | 'right' | 'bottom' | 'undocked' | 'detach'); + /** + * Whether to bring the opened devtools window to the foreground. The default is + * `true`. + */ + activate?: boolean; + /** + * A title for the DevTools window (only in `undocked` or `detach` mode). + */ + title?: string; +} + interface InputEvent { // Docs: https://electronjs.org/docs/api/structures/input-event diff --git a/src/vs/editor/browser/services/hoverService/hoverWidget.ts b/src/vs/editor/browser/services/hoverService/hoverWidget.ts index 2d8e9ae9..ed929bc9 100644 --- a/src/vs/editor/browser/services/hoverService/hoverWidget.ts +++ b/src/vs/editor/browser/services/hoverService/hoverWidget.ts @@ -222,7 +222,7 @@ export class HoverWidget extends Widget implements IHoverWidget { } // Show the hover hint if needed - if (options.appearance?.showHoverHint) { + if (hideOnHover && options.appearance?.showHoverHint) { const statusBarElement = $('div.hover-row.status-bar'); const infoElement = $('div.info'); infoElement.textContent = localize('hoverhint', 'Hold {0} key to mouse over', isMacintosh ? 'Option' : 'Alt'); diff --git a/src/vs/editor/common/languages.ts b/src/vs/editor/common/languages.ts index fbb24ec6..32b937b6 100644 --- a/src/vs/editor/common/languages.ts +++ b/src/vs/editor/common/languages.ts @@ -2019,9 +2019,9 @@ export interface CommentThreadChangedEvent { } export interface CodeLens { - range: IRange; - id?: string; - command?: Command; + range: IRange; // the range of code + id?: string; // no idea what this is for + command?: Command; // command to run when they click the codeLens } export interface CodeLensList { diff --git a/src/vs/editor/contrib/codeAction/browser/codeActionController.ts b/src/vs/editor/contrib/codeAction/browser/codeActionController.ts index 862459df..dbbe9a91 100644 --- a/src/vs/editor/contrib/codeAction/browser/codeActionController.ts +++ b/src/vs/editor/contrib/codeAction/browser/codeActionController.ts @@ -180,12 +180,6 @@ export class CodeActionController extends Disposable implements IEditorContribut return; } - - const selection = this._editor.getSelection(); - if (selection?.startLineNumber !== newState.position.lineNumber) { - return; - } - this._lightBulbWidget.value?.update(actions, newState.trigger, newState.position); if (newState.trigger.type === CodeActionTriggerType.Invoke) { diff --git a/src/vs/editor/contrib/codeAction/browser/lightBulbWidget.ts b/src/vs/editor/contrib/codeAction/browser/lightBulbWidget.ts index bc71291b..0395f421 100644 --- a/src/vs/editor/contrib/codeAction/browser/lightBulbWidget.ts +++ b/src/vs/editor/contrib/codeAction/browser/lightBulbWidget.ts @@ -75,14 +75,6 @@ export class LightBulbWidget extends Disposable implements IContentWidget { private _gutterState: LightBulbState.State = LightBulbState.Hidden; private _iconClasses: string[] = []; - private readonly lightbulbClasses = [ - 'codicon-' + GUTTER_LIGHTBULB_ICON.id, - 'codicon-' + GUTTER_LIGHTBULB_AIFIX_AUTO_FIX_ICON.id, - 'codicon-' + GUTTER_LIGHTBULB_AUTO_FIX_ICON.id, - 'codicon-' + GUTTER_LIGHTBULB_AIFIX_ICON.id, - 'codicon-' + GUTTER_SPARKLE_FILLED_ICON.id - ]; - private _preferredKbLabel?: string; private _quickFixKbLabel?: string; @@ -156,8 +148,15 @@ export class LightBulbWidget extends Disposable implements IContentWidget { })); this._register(this._editor.onMouseDown(async (e: IEditorMouseEvent) => { + const lightbulbClasses = [ + 'codicon-' + GUTTER_LIGHTBULB_ICON.id, + 'codicon-' + GUTTER_LIGHTBULB_AIFIX_AUTO_FIX_ICON.id, + 'codicon-' + GUTTER_LIGHTBULB_AUTO_FIX_ICON.id, + 'codicon-' + GUTTER_LIGHTBULB_AIFIX_ICON.id, + 'codicon-' + GUTTER_SPARKLE_FILLED_ICON.id + ]; - if (!e.target.element || !this.lightbulbClasses.some(cls => e.target.element && e.target.element.classList.contains(cls))) { + if (!e.target.element || !lightbulbClasses.some(cls => e.target.element && e.target.element.classList.contains(cls))) { return; } @@ -248,9 +247,7 @@ export class LightBulbWidget extends Disposable implements IContentWidget { let hasDecoration = false; if (currLineDecorations) { for (const decoration of currLineDecorations) { - const glyphClass = decoration.options.glyphMarginClassName; - - if (glyphClass && !this.lightbulbClasses.some(className => glyphClass.includes(className))) { + if (decoration.options.glyphMarginClassName) { hasDecoration = true; break; } @@ -274,6 +271,7 @@ export class LightBulbWidget extends Disposable implements IContentWidget { const currLineEmptyOrIndented = isLineEmptyOrIndented(lineNumber); const notEmpty = !nextLineEmptyOrIndented && !prevLineEmptyOrIndented; + // check above and below. if both are blocked, display lightbulb in the gutter. if (!nextLineEmptyOrIndented && !prevLineEmptyOrIndented && !hasDecoration) { this.gutterState = new LightBulbState.Showing(actions, trigger, atPosition, { @@ -282,7 +280,7 @@ export class LightBulbWidget extends Disposable implements IContentWidget { }); this.renderGutterLightbub(); return this.hide(); - } else if (prevLineEmptyOrIndented || endLine || (prevLineEmptyOrIndented && !currLineEmptyOrIndented)) { + } else if (prevLineEmptyOrIndented || endLine || (notEmpty && !currLineEmptyOrIndented)) { effectiveLineNumber -= 1; } else if (nextLineEmptyOrIndented || (notEmpty && currLineEmptyOrIndented)) { effectiveLineNumber += 1; diff --git a/src/vs/editor/contrib/codelens/browser/codeLensCache.ts b/src/vs/editor/contrib/codelens/browser/codeLensCache.ts index f7666b8c..5f479695 100644 --- a/src/vs/editor/contrib/codelens/browser/codeLensCache.ts +++ b/src/vs/editor/contrib/codelens/browser/codeLensCache.ts @@ -3,17 +3,17 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Event } from 'vs/base/common/event'; -import { LRUCache } from 'vs/base/common/map'; -import { Range } from 'vs/editor/common/core/range'; -import { ITextModel } from 'vs/editor/common/model'; -import { CodeLens, CodeLensList, CodeLensProvider } from 'vs/editor/common/languages'; -import { CodeLensModel } from 'vs/editor/contrib/codelens/browser/codelens'; -import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { IStorageService, StorageScope, StorageTarget, WillSaveStateReason } from 'vs/platform/storage/common/storage'; -import { mainWindow } from 'vs/base/browser/window'; -import { runWhenWindowIdle } from 'vs/base/browser/dom'; +import { Event } from '../../../../base/common/event.js'; +import { LRUCache } from '../../../../base/common/map.js'; +import { Range } from '../../../common/core/range.js'; +import { ITextModel } from '../../../common/model.js'; +import { CodeLens, CodeLensList, CodeLensProvider } from '../../../common/languages.js'; +import { CodeLensModel } from './codelens.js'; +import { InstantiationType, registerSingleton } from '../../../../platform/instantiation/common/extensions.js'; +import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js'; +import { IStorageService, StorageScope, StorageTarget, WillSaveStateReason } from '../../../../platform/storage/common/storage.js'; +import { mainWindow } from '../../../../base/browser/window.js'; +import { runWhenWindowIdle } from '../../../../base/browser/dom.js'; export const ICodeLensCache = createDecorator('ICodeLensCache'); diff --git a/src/vs/editor/contrib/codelens/browser/codelens.ts b/src/vs/editor/contrib/codelens/browser/codelens.ts index 68253150..0a777339 100644 --- a/src/vs/editor/contrib/codelens/browser/codelens.ts +++ b/src/vs/editor/contrib/codelens/browser/codelens.ts @@ -3,17 +3,17 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { CancellationToken } from 'vs/base/common/cancellation'; -import { illegalArgument, onUnexpectedExternalError } from 'vs/base/common/errors'; -import { DisposableStore } from 'vs/base/common/lifecycle'; -import { assertType } from 'vs/base/common/types'; -import { URI } from 'vs/base/common/uri'; -import { ITextModel } from 'vs/editor/common/model'; -import { CodeLens, CodeLensList, CodeLensProvider } from 'vs/editor/common/languages'; -import { IModelService } from 'vs/editor/common/services/model'; -import { CommandsRegistry } from 'vs/platform/commands/common/commands'; -import { LanguageFeatureRegistry } from 'vs/editor/common/languageFeatureRegistry'; -import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; +import { CancellationToken } from '../../../../base/common/cancellation.js'; +import { illegalArgument, onUnexpectedExternalError } from '../../../../base/common/errors.js'; +import { DisposableStore } from '../../../../base/common/lifecycle.js'; +import { assertType } from '../../../../base/common/types.js'; +import { URI } from '../../../../base/common/uri.js'; +import { ITextModel } from '../../../common/model.js'; +import { CodeLens, CodeLensList, CodeLensProvider } from '../../../common/languages.js'; +import { IModelService } from '../../../common/services/model.js'; +import { CommandsRegistry } from '../../../../platform/commands/common/commands.js'; +import { LanguageFeatureRegistry } from '../../../common/languageFeatureRegistry.js'; +import { ILanguageFeaturesService } from '../../../common/services/languageFeatures.js'; export interface CodeLensItem { symbol: CodeLens; diff --git a/src/vs/editor/contrib/codelens/browser/codelensController.ts b/src/vs/editor/contrib/codelens/browser/codelensController.ts index 49433647..6eca903f 100644 --- a/src/vs/editor/contrib/codelens/browser/codelensController.ts +++ b/src/vs/editor/contrib/codelens/browser/codelensController.ts @@ -4,26 +4,26 @@ *--------------------------------------------------------------------------------------------*/ -import { CancelablePromise, createCancelablePromise, disposableTimeout, RunOnceScheduler } from 'vs/base/common/async'; -import { onUnexpectedError, onUnexpectedExternalError } from 'vs/base/common/errors'; -import { DisposableStore, toDisposable } from 'vs/base/common/lifecycle'; -import { StableEditorScrollState } from 'vs/editor/browser/stableEditorScroll'; -import { IActiveCodeEditor, ICodeEditor, IViewZoneChangeAccessor, MouseTargetType } from 'vs/editor/browser/editorBrowser'; -import { EditorAction, EditorContributionInstantiation, registerEditorAction, registerEditorContribution, ServicesAccessor } from 'vs/editor/browser/editorExtensions'; -import { EditorOption, EDITOR_FONT_DEFAULTS } from 'vs/editor/common/config/editorOptions'; -import { IEditorContribution } from 'vs/editor/common/editorCommon'; -import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; -import { IModelDecorationsChangeAccessor } from 'vs/editor/common/model'; -import { CodeLens, Command } from 'vs/editor/common/languages'; -import { CodeLensItem, CodeLensModel, getCodeLensModel } from 'vs/editor/contrib/codelens/browser/codelens'; -import { ICodeLensCache } from 'vs/editor/contrib/codelens/browser/codeLensCache'; -import { CodeLensHelper, CodeLensWidget } from 'vs/editor/contrib/codelens/browser/codelensWidget'; -import { localize } from 'vs/nls'; -import { ICommandService } from 'vs/platform/commands/common/commands'; -import { INotificationService } from 'vs/platform/notification/common/notification'; -import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; -import { IFeatureDebounceInformation, ILanguageFeatureDebounceService } from 'vs/editor/common/services/languageFeatureDebounce'; -import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; +import { CancelablePromise, createCancelablePromise, disposableTimeout, RunOnceScheduler } from '../../../../base/common/async.js'; +import { onUnexpectedError, onUnexpectedExternalError } from '../../../../base/common/errors.js'; +import { DisposableStore, toDisposable } from '../../../../base/common/lifecycle.js'; +import { StableEditorScrollState } from '../../../browser/stableEditorScroll.js'; +import { IActiveCodeEditor, ICodeEditor, IViewZoneChangeAccessor, MouseTargetType } from '../../../browser/editorBrowser.js'; +import { EditorAction, EditorContributionInstantiation, registerEditorAction, registerEditorContribution, ServicesAccessor } from '../../../browser/editorExtensions.js'; +import { EditorOption, EDITOR_FONT_DEFAULTS } from '../../../common/config/editorOptions.js'; +import { IEditorContribution } from '../../../common/editorCommon.js'; +import { EditorContextKeys } from '../../../common/editorContextKeys.js'; +import { IModelDecorationsChangeAccessor } from '../../../common/model.js'; +import { CodeLens, Command } from '../../../common/languages.js'; +import { CodeLensItem, CodeLensModel, getCodeLensModel } from './codelens.js'; +import { ICodeLensCache } from './codeLensCache.js'; +import { CodeLensHelper, CodeLensWidget } from './codelensWidget.js'; +import { localize } from '../../../../nls.js'; +import { ICommandService } from '../../../../platform/commands/common/commands.js'; +import { INotificationService } from '../../../../platform/notification/common/notification.js'; +import { IQuickInputService } from '../../../../platform/quickinput/common/quickInput.js'; +import { IFeatureDebounceInformation, ILanguageFeatureDebounceService } from '../../../common/services/languageFeatureDebounce.js'; +import { ILanguageFeaturesService } from '../../../common/services/languageFeatures.js'; export class CodeLensContribution implements IEditorContribution { diff --git a/src/vs/editor/contrib/codelens/browser/codelensWidget.ts b/src/vs/editor/contrib/codelens/browser/codelensWidget.ts index c89fc7fd..394c333b 100644 --- a/src/vs/editor/contrib/codelens/browser/codelensWidget.ts +++ b/src/vs/editor/contrib/codelens/browser/codelensWidget.ts @@ -3,16 +3,16 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as dom from 'vs/base/browser/dom'; -import { renderLabelWithIcons } from 'vs/base/browser/ui/iconLabel/iconLabels'; -import { Constants } from 'vs/base/common/uint'; -import 'vs/css!./codelensWidget'; -import { ContentWidgetPositionPreference, IActiveCodeEditor, IContentWidget, IContentWidgetPosition, IViewZone, IViewZoneChangeAccessor } from 'vs/editor/browser/editorBrowser'; -import { Range } from 'vs/editor/common/core/range'; -import { IModelDecorationsChangeAccessor, IModelDeltaDecoration, ITextModel } from 'vs/editor/common/model'; -import { ModelDecorationOptions } from 'vs/editor/common/model/textModel'; -import { CodeLens, Command } from 'vs/editor/common/languages'; -import { CodeLensItem } from 'vs/editor/contrib/codelens/browser/codelens'; +import * as dom from '../../../../base/browser/dom.js'; +import { renderLabelWithIcons } from '../../../../base/browser/ui/iconLabel/iconLabels.js'; +import { Constants } from '../../../../base/common/uint.js'; +import './codelensWidget.css'; +import { ContentWidgetPositionPreference, IActiveCodeEditor, IContentWidget, IContentWidgetPosition, IViewZone, IViewZoneChangeAccessor } from '../../../browser/editorBrowser.js'; +import { Range } from '../../../common/core/range.js'; +import { IModelDecorationsChangeAccessor, IModelDeltaDecoration, ITextModel } from '../../../common/model.js'; +import { ModelDecorationOptions } from '../../../common/model/textModel.js'; +import { CodeLens, Command } from '../../../common/languages.js'; +import { CodeLensItem } from './codelens.js'; class CodeLensViewZone implements IViewZone { diff --git a/src/vs/editor/contrib/rename/browser/renameWidget.ts b/src/vs/editor/contrib/rename/browser/renameWidget.ts index 1f6fcc14..b284cd51 100644 --- a/src/vs/editor/contrib/rename/browser/renameWidget.ts +++ b/src/vs/editor/contrib/rename/browser/renameWidget.ts @@ -318,8 +318,7 @@ export class RenameWidget implements IRenameWidget, IContentWidget, IDisposable } afterRender(position: ContentWidgetPositionPreference | null): void { - // FIXME@ulugbekna: commenting trace log out until we start unmounting the widget from editor properly - https://github.com/microsoft/vscode/issues/226975 - // this._trace('invoking afterRender, position: ', position ? 'not null' : 'null'); + this._trace('invoking afterRender, position: ', position ? 'not null' : 'null'); if (position === null) { // cancel rename when input widget isn't rendered anymore this.cancelInput(true, 'afterRender (because position is null)'); @@ -364,7 +363,7 @@ export class RenameWidget implements IRenameWidget, IContentWidget, IDisposable } cancelInput(focusEditor: boolean, caller: string): void { - // this._trace(`invoking cancelInput, caller: ${caller}, _currentCancelInput: ${this._currentAcceptInput ? 'not undefined' : 'undefined'}`); + this._trace(`invoking cancelInput, caller: ${caller}, _currentCancelInput: ${this._currentAcceptInput ? 'not undefined' : 'undefined'}`); this._currentCancelInput?.(focusEditor); } diff --git a/src/vs/platform/actionWidget/browser/actionList.ts b/src/vs/platform/actionWidget/browser/actionList.ts index 8eeeee2d..98403a2a 100644 --- a/src/vs/platform/actionWidget/browser/actionList.ts +++ b/src/vs/platform/actionWidget/browser/actionList.ts @@ -166,8 +166,8 @@ export class ActionList extends Disposable { private readonly _list: List>; - private readonly _actionLineHeight = 24; - private readonly _headerLineHeight = 26; + private readonly _actionLineHeight = 28; + private readonly _headerLineHeight = 28; private readonly _allMenuItems: readonly IActionListItem[]; diff --git a/src/vs/platform/actionWidget/browser/actionWidget.css b/src/vs/platform/actionWidget/browser/actionWidget.css index d205c7ab..b9ebc031 100644 --- a/src/vs/platform/actionWidget/browser/actionWidget.css +++ b/src/vs/platform/actionWidget/browser/actionWidget.css @@ -132,9 +132,8 @@ /* Action bar */ .action-widget .action-widget-action-bar { - background-color: var(--vscode-editorActionList-background); + background-color: var(--vscode-editorHoverWidget-statusBarBackground); border-top: 1px solid var(--vscode-editorHoverWidget-border); - margin-top: 2px; } .action-widget .action-widget-action-bar::before { @@ -144,7 +143,7 @@ } .action-widget .action-widget-action-bar .actions-container { - padding: 3px 8px 0; + padding: 0 8px; } .action-widget-action-bar .action-label { diff --git a/src/vs/platform/extensionManagement/common/extensionEnablementService.ts b/src/vs/platform/extensionManagement/common/extensionEnablementService.ts index 7637899a..61c8816e 100644 --- a/src/vs/platform/extensionManagement/common/extensionEnablementService.ts +++ b/src/vs/platform/extensionManagement/common/extensionEnablementService.ts @@ -16,15 +16,15 @@ export class GlobalExtensionEnablementService extends Disposable implements IGlo private _onDidChangeEnablement = new Emitter<{ readonly extensions: IExtensionIdentifier[]; readonly source?: string }>(); readonly onDidChangeEnablement: Event<{ readonly extensions: IExtensionIdentifier[]; readonly source?: string }> = this._onDidChangeEnablement.event; - private readonly storageManager: StorageManager; + private readonly storageManger: StorageManager; constructor( @IStorageService storageService: IStorageService, @IExtensionManagementService extensionManagementService: IExtensionManagementService, ) { super(); - this.storageManager = this._register(new StorageManager(storageService)); - this._register(this.storageManager.onDidChange(extensions => this._onDidChangeEnablement.fire({ extensions, source: 'storage' }))); + this.storageManger = this._register(new StorageManager(storageService)); + this._register(this.storageManger.onDidChange(extensions => this._onDidChangeEnablement.fire({ extensions, source: 'storage' }))); this._register(extensionManagementService.onDidInstallExtensions(e => e.forEach(({ local, operation }) => { if (local && operation === InstallOperation.Migrate) { this._removeFromDisabledExtensions(local.identifier); /* Reset migrated extensions */ @@ -84,11 +84,11 @@ export class GlobalExtensionEnablementService extends Disposable implements IGlo } private _getExtensions(storageId: string): IExtensionIdentifier[] { - return this.storageManager.get(storageId, StorageScope.PROFILE); + return this.storageManger.get(storageId, StorageScope.PROFILE); } private _setExtensions(storageId: string, extensions: IExtensionIdentifier[]): void { - this.storageManager.set(storageId, extensions, StorageScope.PROFILE); + this.storageManger.set(storageId, extensions, StorageScope.PROFILE); } } diff --git a/src/vs/platform/native/common/native.ts b/src/vs/platform/native/common/native.ts index a27a17ad..344d4539 100644 --- a/src/vs/platform/native/common/native.ts +++ b/src/vs/platform/native/common/native.ts @@ -6,7 +6,7 @@ import { VSBuffer } from 'vs/base/common/buffer'; import { Event } from 'vs/base/common/event'; import { URI } from 'vs/base/common/uri'; -import { MessageBoxOptions, MessageBoxReturnValue, OpenDialogOptions, OpenDialogReturnValue, SaveDialogOptions, SaveDialogReturnValue } from 'vs/base/parts/sandbox/common/electronTypes'; +import { MessageBoxOptions, MessageBoxReturnValue, OpenDevToolsOptions, OpenDialogOptions, OpenDialogReturnValue, SaveDialogOptions, SaveDialogReturnValue } from 'vs/base/parts/sandbox/common/electronTypes'; import { ISerializableCommandAction } from 'vs/platform/action/common/action'; import { INativeOpenDialogOptions } from 'vs/platform/dialogs/common/dialogs'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; @@ -178,7 +178,7 @@ export interface ICommonNativeHostService { exit(code: number): Promise; // Development - openDevTools(options?: INativeHostOptions): Promise; + openDevTools(options?: Partial & INativeHostOptions): Promise; toggleDevTools(options?: INativeHostOptions): Promise; // Perf Introspection diff --git a/src/vs/platform/native/electron-main/nativeHostMainService.ts b/src/vs/platform/native/electron-main/nativeHostMainService.ts index 4ccaa6b1..6c49504e 100644 --- a/src/vs/platform/native/electron-main/nativeHostMainService.ts +++ b/src/vs/platform/native/electron-main/nativeHostMainService.ts @@ -5,7 +5,7 @@ import * as fs from 'fs'; import { exec } from 'child_process'; -import { app, BrowserWindow, clipboard, Display, Menu, MessageBoxOptions, MessageBoxReturnValue, OpenDialogOptions, OpenDialogReturnValue, powerMonitor, SaveDialogOptions, SaveDialogReturnValue, screen, shell, webContents } from 'electron'; +import { app, BrowserWindow, clipboard, Display, Menu, MessageBoxOptions, MessageBoxReturnValue, OpenDevToolsOptions, OpenDialogOptions, OpenDialogReturnValue, powerMonitor, SaveDialogOptions, SaveDialogReturnValue, screen, shell, webContents } from 'electron'; import { arch, cpus, freemem, loadavg, platform, release, totalmem, type } from 'os'; import { promisify } from 'util'; import { memoize } from 'vs/base/common/decorators'; @@ -33,7 +33,7 @@ import { IProductService } from 'vs/platform/product/common/productService'; import { IPartsSplash } from 'vs/platform/theme/common/themeService'; import { IThemeMainService } from 'vs/platform/theme/electron-main/themeMainService'; import { ICodeWindow } from 'vs/platform/window/electron-main/window'; -import { IColorScheme, IOpenedAuxiliaryWindow, IOpenedMainWindow, IOpenEmptyWindowOptions, IOpenWindowOptions, IPoint, IRectangle, IWindowOpenable, useWindowControlsOverlay } from 'vs/platform/window/common/window'; +import { IColorScheme, IOpenedAuxiliaryWindow, IOpenedMainWindow, IOpenEmptyWindowOptions, IOpenWindowOptions, IPoint, IRectangle, IWindowOpenable } from 'vs/platform/window/common/window'; import { IWindowsMainService, OpenContext } from 'vs/platform/windows/electron-main/windows'; import { isWorkspaceIdentifier, toWorkspaceIdentifier } from 'vs/platform/workspace/common/workspace'; import { IWorkspacesManagementMainService } from 'vs/platform/workspaces/electron-main/workspacesManagementMainService'; @@ -855,28 +855,14 @@ export class NativeHostMainService extends Disposable implements INativeHostMain //#region Development - async openDevTools(windowId: number | undefined, options?: INativeHostOptions): Promise { + async openDevTools(windowId: number | undefined, options?: Partial & INativeHostOptions): Promise { const window = this.windowById(options?.targetWindowId, windowId); - - let mode: 'bottom' | undefined = undefined; - if (isLinux && useWindowControlsOverlay(this.configurationService)) { - mode = 'bottom'; // TODO@bpasero WCO and devtools collide with default option 'right' - } - window?.win?.webContents.openDevTools(mode ? { mode } : undefined); + window?.win?.webContents.openDevTools(options?.mode ? { mode: options.mode, activate: options.activate } : undefined); } async toggleDevTools(windowId: number | undefined, options?: INativeHostOptions): Promise { const window = this.windowById(options?.targetWindowId, windowId); - const webContents = window?.win?.webContents; - if (!webContents) { - return; - } - - if (isLinux && useWindowControlsOverlay(this.configurationService) && !webContents.isDevToolsOpened()) { - webContents.openDevTools({ mode: 'bottom' }); // TODO@bpasero WCO and devtools collide with default option 'right' - } else { - webContents.toggleDevTools(); - } + window?.win?.webContents.toggleDevTools(); } //#endregion diff --git a/src/vs/platform/theme/common/colorUtils.ts b/src/vs/platform/theme/common/colorUtils.ts index a237166f..14ceea88 100644 --- a/src/vs/platform/theme/common/colorUtils.ts +++ b/src/vs/platform/theme/common/colorUtils.ts @@ -159,7 +159,7 @@ class ColorRegistry implements IColorRegistry { public registerColor(id: string, defaults: ColorDefaults | ColorValue | null, description: string, needsTransparency = false, deprecationMessage?: string): ColorIdentifier { const colorContribution: ColorContribution = { id, description, defaults, needsTransparency, deprecationMessage }; this.colorsById[id] = colorContribution; - const propertySchema: IJSONSchemaWithSnippets = { type: 'string', format: 'color-hex', defaultSnippets: [{ body: '${1:#ff0000}' }] }; + const propertySchema: IJSONSchemaWithSnippets = { type: 'string', description, format: 'color-hex', defaultSnippets: [{ body: '${1:#ff0000}' }] }; if (deprecationMessage) { propertySchema.deprecationMessage = deprecationMessage; } @@ -168,7 +168,6 @@ class ColorRegistry implements IColorRegistry { propertySchema.patternErrorMessage = nls.localize('transparecyRequired', 'This color must be transparent or it will obscure content'); } this.colorSchema.properties[id] = { - description, oneOf: [ propertySchema, { type: 'string', const: DEFAULT_COLOR_CONFIG_VALUE, description: nls.localize('useDefault', 'Use the default color.') } diff --git a/src/vs/platform/window/common/window.ts b/src/vs/platform/window/common/window.ts index dc5de9f2..40b1ebf0 100644 --- a/src/vs/platform/window/common/window.ts +++ b/src/vs/platform/window/common/window.ts @@ -14,6 +14,7 @@ import { NativeParsedArgs } from 'vs/platform/environment/common/argv'; import { FileType } from 'vs/platform/files/common/files'; import { ILoggerResource, LogLevel } from 'vs/platform/log/common/log'; import { PolicyDefinition, PolicyValue } from 'vs/platform/policy/common/policy'; +import product from 'vs/platform/product/common/product'; import { IPartsSplash } from 'vs/platform/theme/common/themeService'; import { IUserDataProfile } from 'vs/platform/userDataProfile/common/userDataProfile'; import { IAnyWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier, IWorkspaceIdentifier } from 'vs/platform/workspace/common/workspace'; @@ -239,6 +240,8 @@ export function useWindowControlsOverlay(configurationService: IConfigurationSer if (typeof setting === 'boolean') { return setting; } + + return product.quality !== 'stable'; // disable by default in stable for now (TODO@bpasero TODO@benibenj flip when custom title is default) } // Default to true. diff --git a/src/vs/workbench/api/browser/mainThreadWorkspace.ts b/src/vs/workbench/api/browser/mainThreadWorkspace.ts index 880a6026..12441286 100644 --- a/src/vs/workbench/api/browser/mainThreadWorkspace.ts +++ b/src/vs/workbench/api/browser/mainThreadWorkspace.ts @@ -28,7 +28,6 @@ import { IEditSessionIdentityService } from 'vs/platform/workspace/common/editSe import { EditorResourceAccessor, SaveReason, SideBySideEditor } from 'vs/workbench/common/editor'; import { coalesce, firstOrDefault } from 'vs/base/common/arrays'; import { ICanonicalUriService } from 'vs/platform/workspace/common/canonicalUri'; -import { revive } from 'vs/base/common/marshalling'; @extHostNamedCustomer(MainContext.MainThreadWorkspace) export class MainThreadWorkspace implements MainThreadWorkspaceShape { @@ -147,7 +146,7 @@ export class MainThreadWorkspace implements MainThreadWorkspaceShape { const query = this._queryBuilder.file( includeFolder ? [includeFolder] : workspace.folders, - revive(options) + options ); return this._searchService.fileSearch(query, token).then(result => { @@ -165,7 +164,7 @@ export class MainThreadWorkspace implements MainThreadWorkspaceShape { const workspace = this._contextService.getWorkspace(); const folders = folder ? [folder] : workspace.folders.map(folder => folder.uri); - const query = this._queryBuilder.text(pattern, folders, revive(options)); + const query = this._queryBuilder.text(pattern, folders, options); query._reason = 'startTextSearch'; const onProgress = (p: ISearchProgressItem) => { diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index a9c8d66c..6da7aadc 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -550,6 +550,14 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I registerCodeLensProvider(selector: vscode.DocumentSelector, provider: vscode.CodeLensProvider): vscode.Disposable { return extHostLanguageFeatures.registerCodeLensProvider(extension, checkSelector(selector), provider); }, + + + // VOID added this (I think will need to add this back when add ctrl+K) + // registerVoidCtrlKProvider(selector: vscode.DocumentSelector, provider: vscode.CodeLensProvider): vscode.Disposable { + // return extHostLanguageFeatures.registerCodeLensProvider(extension, checkSelector(selector), provider); + // }, + + registerDefinitionProvider(selector: vscode.DocumentSelector, provider: vscode.DefinitionProvider): vscode.Disposable { return extHostLanguageFeatures.registerDefinitionProvider(extension, checkSelector(selector), provider); }, diff --git a/src/vs/workbench/api/common/extHostApiCommands.ts b/src/vs/workbench/api/common/extHostApiCommands.ts index 6384178b..5e25c778 100644 --- a/src/vs/workbench/api/common/extHostApiCommands.ts +++ b/src/vs/workbench/api/common/extHostApiCommands.ts @@ -304,6 +304,17 @@ const newCommands: ApiCommand[] = [ })(value); }) ), + // // --- Void code lens + // new ApiCommand( + // 'vscode.executeVoidCodeLensProvider', '_executeVoidCodeLensProvider', 'Execute Void code lens provider.', + // [ApiCommandArgument.Uri, ApiCommandArgument.Number.with('itemResolveCount', 'Number of lenses that should be resolved and returned. Will only return resolved lenses, will impact performance)').optional()], + // new ApiCommandResult('A promise that resolves to an array of VoidCodeLens-instances.', (value, _args, converter) => { + // return tryMapWith(item => { + // return new types.CodeLens(typeConverters.Range.to(item.range), item.command && converter.fromInternal(item.command)); + // })(value); + // }) + // ), + // --- code actions new ApiCommand( 'vscode.executeCodeActionProvider', '_executeCodeActionProvider', 'Execute code action provider.', diff --git a/src/vs/workbench/api/common/extHostLanguageFeatures.ts b/src/vs/workbench/api/common/extHostLanguageFeatures.ts index 5bb629a6..50ab3ea6 100644 --- a/src/vs/workbench/api/common/extHostLanguageFeatures.ts +++ b/src/vs/workbench/api/common/extHostLanguageFeatures.ts @@ -2312,15 +2312,15 @@ export class ExtHostLanguageFeatures implements extHostProtocol.ExtHostLanguageF } $provideCodeLenses(handle: number, resource: UriComponents, token: CancellationToken): Promise { - return this._withAdapter(handle, CodeLensAdapter, adapter => adapter.provideCodeLenses(URI.revive(resource), token), undefined, token, resource.scheme === 'output'); + return this._withAdapter(handle, CodeLensAdapter, adapter => adapter.provideCodeLenses(URI.revive(resource), token), undefined, token); } $resolveCodeLens(handle: number, symbol: extHostProtocol.ICodeLensDto, token: CancellationToken): Promise { - return this._withAdapter(handle, CodeLensAdapter, adapter => adapter.resolveCodeLens(symbol, token), undefined, undefined, true); + return this._withAdapter(handle, CodeLensAdapter, adapter => adapter.resolveCodeLens(symbol, token), undefined, undefined); } $releaseCodeLenses(handle: number, cacheId: number): void { - this._withAdapter(handle, CodeLensAdapter, adapter => Promise.resolve(adapter.releaseCodeLenses(cacheId)), undefined, undefined, true); + this._withAdapter(handle, CodeLensAdapter, adapter => Promise.resolve(adapter.releaseCodeLenses(cacheId)), undefined, undefined); } // --- declaration diff --git a/src/vs/workbench/api/common/extHostWorkspace.ts b/src/vs/workbench/api/common/extHostWorkspace.ts index 951c87d9..33e3bafe 100644 --- a/src/vs/workbench/api/common/extHostWorkspace.ts +++ b/src/vs/workbench/api/common/extHostWorkspace.ts @@ -584,7 +584,7 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape, IExtHostWorkspac } const parsedInclude = include ? parseSearchExcludeInclude(GlobPattern.from(include)) : undefined; - const excludePatterns = globsToISearchPatternBuilder(options.exclude); + const excludePatterns = include ? globsToISearchPatternBuilder(options.exclude) : undefined; return { options: { @@ -664,10 +664,7 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape, IExtHostWorkspac async findTextInFilesBase(query: vscode.TextSearchQuery, queryOptions: QueryOptions[] | undefined, callback: (result: ITextSearchResult, uri: URI) => void, token: vscode.CancellationToken = CancellationToken.None): Promise { const requestId = this._requestIdProvider.getNext(); - let isCanceled = false; - token.onCancellationRequested(_ => { - isCanceled = true; - }); + const isCanceled = false; this._activeSearchCallbacks[requestId] = p => { if (isCanceled) { diff --git a/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css b/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css index 9a66a127..15afa9df 100644 --- a/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css +++ b/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css @@ -138,11 +138,11 @@ color: var(--vscode-titleBar-activeForeground); } -.monaco-workbench .part.titlebar.inactive > .titlebar-container > .titlebar-center > .window-title > .command-center > .monaco-toolbar > .monaco-action-bar > .actions-container > .action-item > .action-label { +.monaco-workbench .part.titlebar.inactive > .titlebar-container > .titlebar-center > .window-title > .command-center > .monaco-toolbar > .monaco-action-bar > .actions-container > .action-item > .action-label { color: var(--vscode-titleBar-inactiveForeground); } -.monaco-workbench .part.titlebar > .titlebar-container > .titlebar-center > .window-title > .command-center > .monaco-toolbar > .monaco-action-bar > .actions-container > .action-item > .action-label { +.monaco-workbench .part.titlebar > .titlebar-container > .titlebar-center > .window-title > .command-center > .monaco-toolbar > .monaco-action-bar > .actions-container > .action-item > .action-label { color: inherit; } @@ -182,7 +182,7 @@ text-overflow: ellipsis; } -.monaco-workbench .part.titlebar > .titlebar-container > .titlebar-center > .window-title > .command-center .action-item.command-center-center.multiple { +.monaco-workbench .part.titlebar > .titlebar-container > .titlebar-center > .window-title > .command-center .action-item.command-center-center.multiple { justify-content: flex-start; padding: 0 12px; } @@ -280,7 +280,7 @@ border-left: 1px solid transparent; } -/* Window Controls Container */ +/* Window Controls (Minimize, Max/Restore, Close) */ .monaco-workbench .part.titlebar .window-controls-container { display: flex; flex-grow: 0; @@ -292,12 +292,7 @@ height: 100%; } -.monaco-workbench.fullscreen .part.titlebar .window-controls-container { - display: none; - background-color: transparent; -} - -/* Window Controls Container Web: Apply WCO environment variables (https://developer.mozilla.org/en-US/docs/Web/CSS/env#titlebar-area-x) */ +/* Web WCO Sizing/Ordering */ .monaco-workbench.web .part.titlebar .titlebar-right .window-controls-container { width: calc(100vw - env(titlebar-area-width, 100vw) - env(titlebar-area-x, 0px)); height: env(titlebar-area-height, 35px); @@ -316,31 +311,29 @@ order: 1; } -/* Window Controls Container Desktop: apply zoom friendly size */ -.monaco-workbench:not(.web):not(.mac) .part.titlebar .window-controls-container { +/* Desktop Windows/Linux Window Controls*/ +.monaco-workbench:not(.web):not(.mac) .part.titlebar .window-controls-container.primary { width: calc(138px / var(--zoom-factor, 1)); } -.monaco-workbench:not(.web):not(.mac) .part.titlebar .titlebar-container.counter-zoom .window-controls-container { +.monaco-workbench:not(.web):not(.mac) .part.titlebar .titlebar-container.counter-zoom .window-controls-container.primary { width: 138px; } -.monaco-workbench.linux:not(.web) .part.titlebar .window-controls-container.wco-enabled { - width: calc(var(--title-wco-width, 138px)); -} - -.monaco-workbench.linux:not(.web) .part.titlebar .titlebar-container.counter-zoom .window-controls-container.wco-enabled { - width: var(--title-wco-width, 138px); -} - .monaco-workbench:not(.web):not(.mac) .part.titlebar .titlebar-container:not(.counter-zoom) .window-controls-container * { zoom: calc(1 / var(--zoom-factor, 1)); } -.monaco-workbench:not(.web).mac .part.titlebar .window-controls-container { +/* Desktop macOS Window Controls */ +.monaco-workbench:not(.web).mac .part.titlebar .window-controls-container.primary { width: 70px; } +.monaco-workbench.fullscreen .part.titlebar .window-controls-container { + display: none; + background-color: transparent; +} + /* Window Control Icons */ .monaco-workbench .part.titlebar .window-controls-container > .window-icon { display: flex; @@ -349,11 +342,6 @@ height: 100%; width: 46px; font-size: 16px; - color: var(--vscode-titleBar-activeForeground); -} - -.monaco-workbench .part.titlebar.inactive .window-controls-container > .window-icon { - color: var(--vscode-titleBar-inactiveForeground); } .monaco-workbench .part.titlebar .window-controls-container > .window-icon::before { @@ -388,6 +376,7 @@ z-index: 2500; -webkit-app-region: no-drag; height: 100%; + min-width: 28px; } .monaco-workbench .part.titlebar > .titlebar-container > .titlebar-right > .action-toolbar-container { @@ -466,3 +455,11 @@ border-radius: 16px; text-align: center; } + +.monaco-workbench .part.titlebar .window-controls-container .window-icon { + color: var(--vscode-titleBar-activeForeground); +} + +.monaco-workbench .part.titlebar.inactive .window-controls-container .window-icon { + color: var(--vscode-titleBar-inactiveForeground); +} diff --git a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts index ff858e53..9dd7d555 100644 --- a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts +++ b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts @@ -7,7 +7,7 @@ import 'vs/css!./media/titlebarpart'; import { localize, localize2 } from 'vs/nls'; import { MultiWindowParts, Part } from 'vs/workbench/browser/part'; import { ITitleService } from 'vs/workbench/services/title/browser/titleService'; -import { getWCOTitlebarAreaRect, getZoomFactor, isWCOEnabled, onDidChangeZoomLevel } from 'vs/base/browser/browser'; +import { getWCOBoundingRect, getZoomFactor, isWCOEnabled } from 'vs/base/browser/browser'; import { MenuBarVisibility, getTitleBarStyle, getMenuBarVisibility, TitlebarStyle, hasCustomTitlebar, hasNativeTitlebar, DEFAULT_CUSTOM_TITLEBAR_HEIGHT } from 'vs/platform/window/common/window'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; @@ -228,7 +228,7 @@ export class BrowserTitlebarPart extends Part implements ITitlebarPart { const wcoEnabled = isWeb && isWCOEnabled(); let value = this.isCommandCenterVisible || wcoEnabled ? DEFAULT_CUSTOM_TITLEBAR_HEIGHT : 30; if (wcoEnabled) { - value = Math.max(value, getWCOTitlebarAreaRect(getWindow(this.element))?.height ?? 0); + value = Math.max(value, getWCOBoundingRect()?.height ?? 0); } return value / (this.preventZoom ? getZoomFactor(getWindow(this.element)) : 1); @@ -249,7 +249,7 @@ export class BrowserTitlebarPart extends Part implements ITitlebarPart { //#endregion protected rootContainer!: HTMLElement; - protected windowControlsContainer: HTMLElement | undefined; + protected primaryWindowControls: HTMLElement | undefined; protected dragRegion: HTMLElement | undefined; private title!: HTMLElement; @@ -476,49 +476,21 @@ export class BrowserTitlebarPart extends Part implements ITitlebarPart { this.createActionToolBarMenus(); } - // Window Controls Container + let primaryControlLocation = isMacintosh ? 'left' : 'right'; + if (isMacintosh && isNative) { + + // Check if the locale is RTL, macOS will move traffic lights in RTL locales + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Locale/textInfo + + const localeInfo = new Intl.Locale(platformLocale) as any; + if (localeInfo?.textInfo?.direction === 'rtl') { + primaryControlLocation = 'right'; + } + } + if (!hasNativeTitlebar(this.configurationService, this.titleBarStyle)) { - let primaryWindowControlsLocation = isMacintosh ? 'left' : 'right'; - if (isMacintosh && isNative) { - - // Check if the locale is RTL, macOS will move traffic lights in RTL locales - // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Locale/textInfo - - const localeInfo = new Intl.Locale(platformLocale) as any; - if (localeInfo?.textInfo?.direction === 'rtl') { - primaryWindowControlsLocation = 'right'; - } - } - - if (isMacintosh && isNative && primaryWindowControlsLocation === 'left') { - // macOS native: controls are on the left and the container is not needed to make room - // for something, except for web where a custom menu being supported). not putting the - // container helps with allowing to move the window when clicking very close to the - // window control buttons. - } else { - this.windowControlsContainer = append(primaryWindowControlsLocation === 'left' ? this.leftContent : this.rightContent, $('div.window-controls-container')); - if (isWeb) { - // Web: its possible to have control overlays on both sides, for example on macOS - // with window controls on the left and PWA controls on the right. - append(primaryWindowControlsLocation === 'left' ? this.rightContent : this.leftContent, $('div.window-controls-container')); - } - - if (isWCOEnabled()) { - this.windowControlsContainer.classList.add('wco-enabled'); - - const updateWCOWidthVariable = () => { - const targetWindow = getWindow(this.element); - const wcoTitlebarAreaRect = getWCOTitlebarAreaRect(targetWindow); - if (wcoTitlebarAreaRect) { - const wcoWidth = targetWindow.innerWidth - wcoTitlebarAreaRect.width - wcoTitlebarAreaRect.x; - this.windowControlsContainer?.style.setProperty('--title-wco-width', `${wcoWidth}px`); - } - }; - updateWCOWidthVariable(); - - this._register(onDidChangeZoomLevel(() => setTimeout(() => updateWCOWidthVariable(), 5))); // Somehow it does not get the right size without this timeout :-/ - } - } + this.primaryWindowControls = append(primaryControlLocation === 'left' ? this.leftContent : this.rightContent, $('div.window-controls-container.primary')); + append(primaryControlLocation === 'left' ? this.rightContent : this.leftContent, $('div.window-controls-container.secondary')); } // Context menu over title bar: depending on the OS and the location of the click this will either be diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts index d2e60118..28e759fb 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts @@ -152,7 +152,7 @@ class ChatHistoryAction extends Action2 { let lastDate: string | undefined = undefined; const picks = items.flatMap((i): [IQuickPickSeparator | undefined, IChatPickerItem] => { - const timeAgoStr = fromNowByDay(i.lastMessageDate, true, true); + const timeAgoStr = fromNowByDay(i.lastMessageDate, true); const separator: IQuickPickSeparator | undefined = timeAgoStr !== lastDate ? { type: 'separator', label: timeAgoStr, } : undefined; diff --git a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts index be6c4955..d5cf3c35 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts @@ -36,7 +36,7 @@ import { ChatAccessibilityService } from 'vs/workbench/contrib/chat/browser/chat import { ChatEditor, IChatEditorOptions } from 'vs/workbench/contrib/chat/browser/chatEditor'; import { ChatEditorInput, ChatEditorInputSerializer } from 'vs/workbench/contrib/chat/browser/chatEditorInput'; import { agentSlashCommandToMarkdown, agentToMarkdown } from 'vs/workbench/contrib/chat/browser/chatMarkdownDecorationsRenderer'; -import { ChatCompatibilityNotifier, ChatExtensionPointHandler } from 'vs/workbench/contrib/chat/browser/chatParticipantContributions'; +import { ChatExtensionPointHandler } from 'vs/workbench/contrib/chat/browser/chatParticipantContributions'; import { QuickChatService } from 'vs/workbench/contrib/chat/browser/chatQuick'; import { ChatResponseAccessibleView } from 'vs/workbench/contrib/chat/browser/chatResponseAccessibleView'; import { ChatVariablesService } from 'vs/workbench/contrib/chat/browser/chatVariables'; @@ -261,7 +261,9 @@ workbenchContributionsRegistry.registerWorkbenchContribution(ChatSlashStaticSlas Registry.as(EditorExtensions.EditorFactory).registerEditorSerializer(ChatEditorInput.TypeID, ChatEditorInputSerializer); registerWorkbenchContribution2(ChatExtensionPointHandler.ID, ChatExtensionPointHandler, WorkbenchPhase.BlockStartup); registerWorkbenchContribution2(LanguageModelToolsExtensionPointHandler.ID, LanguageModelToolsExtensionPointHandler, WorkbenchPhase.BlockRestore); -registerWorkbenchContribution2(ChatCompatibilityNotifier.ID, ChatCompatibilityNotifier, WorkbenchPhase.Eventually); + +// Disabled until https://github.com/microsoft/vscode/issues/218646 is fixed +// registerWorkbenchContribution2(ChatCompatibilityNotifier.ID, ChatCompatibilityNotifier, WorkbenchPhase.Eventually); registerChatActions(); registerChatCopyActions(); diff --git a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts index 52d7d73b..8f910bc2 100644 --- a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts @@ -516,7 +516,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge onDidChangeCursorPosition(); } - private initAttachedContext(container: HTMLElement, isLayout = false) { + private initAttachedContext(container: HTMLElement) { const oldHeight = container.offsetHeight; dom.clearNode(container); this.attachedContextDisposables.clear(); @@ -578,7 +578,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge this.attachedContextDisposables.add(disp); }); - if (oldHeight !== container.offsetHeight && !isLayout) { + if (oldHeight !== container.offsetHeight) { this._onDidChangeHeight.fire(); } } @@ -609,7 +609,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge private previousInputEditorDimension: IDimension | undefined; private _layout(height: number, width: number, allowRecurse = true): void { - this.initAttachedContext(this.attachedContextContainer, true); + this.initAttachedContext(this.attachedContextContainer); const data = this.getLayoutData(); diff --git a/src/vs/workbench/contrib/chat/browser/chatParticipantContributions.ts b/src/vs/workbench/contrib/chat/browser/chatParticipantContributions.ts index 2bf50f4a..09a83daf 100644 --- a/src/vs/workbench/contrib/chat/browser/chatParticipantContributions.ts +++ b/src/vs/workbench/contrib/chat/browser/chatParticipantContributions.ts @@ -3,16 +3,17 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { Action } from 'vs/base/common/actions'; import { coalesce, isNonEmptyArray } from 'vs/base/common/arrays'; import { Codicon } from 'vs/base/common/codicons'; -import { DisposableMap, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { Disposable, DisposableMap, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import * as strings from 'vs/base/common/strings'; import { localize, localize2 } from 'vs/nls'; -import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { ICommandService } from 'vs/platform/commands/common/commands'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { ILogService } from 'vs/platform/log/common/log'; -import { Severity } from 'vs/platform/notification/common/notification'; +import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; import { Registry } from 'vs/platform/registry/common/platform'; import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; @@ -20,9 +21,7 @@ import { IViewContainersRegistry, IViewDescriptor, IViewsRegistry, ViewContainer import { CHAT_VIEW_ID } from 'vs/workbench/contrib/chat/browser/chat'; import { CHAT_SIDEBAR_PANEL_ID, ChatViewPane } from 'vs/workbench/contrib/chat/browser/chatViewPane'; import { ChatAgentLocation, IChatAgentData, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; -import { CONTEXT_CHAT_EXTENSION_INVALID, CONTEXT_CHAT_PANEL_PARTICIPANT_REGISTERED } from 'vs/workbench/contrib/chat/common/chatContextKeys'; import { IRawChatParticipantContribution } from 'vs/workbench/contrib/chat/common/chatParticipantContribTypes'; -import { showExtensionsWithIdsCommandId } from 'vs/workbench/contrib/extensions/browser/extensionsActions'; import { IExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/common/extensions'; import { isProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions'; import * as extensionsRegistry from 'vs/workbench/services/extensions/common/extensionsRegistry'; @@ -161,6 +160,35 @@ const chatParticipantExtensionPoint = extensionsRegistry.ExtensionsRegistry.regi }, }); +export class ChatCompatibilityNotifier implements IWorkbenchContribution { + static readonly ID = 'workbench.contrib.chatCompatNotifier'; + + constructor( + @IExtensionsWorkbenchService extensionsWorkbenchService: IExtensionsWorkbenchService, + @INotificationService notificationService: INotificationService, + @ICommandService commandService: ICommandService + ) { + // It may be better to have some generic UI for this, for any extension that is incompatible, + // but this is only enabled for Copilot Chat now and it needs to be obvious. + extensionsWorkbenchService.queryLocal().then(exts => { + const chat = exts.find(ext => ext.identifier.id === 'github.copilot-chat'); + if (chat?.local?.validations.some(v => v[0] === Severity.Error)) { + notificationService.notify({ + severity: Severity.Error, + message: localize('chatFailErrorMessage', "Chat failed to load. Please ensure that the GitHub Copilot Chat extension is up to date."), + actions: { + primary: [ + new Action('showExtension', localize('action.showExtension', "Show Extension"), undefined, true, () => { + return commandService.executeCommand('workbench.extensions.action.showExtensionsWithIds', ['GitHub.copilot-chat']); + }) + ] + } + }); + } + }); + } +} + export class ChatExtensionPointHandler implements IWorkbenchContribution { static readonly ID = 'workbench.contrib.chatExtensionPointHandler'; @@ -170,10 +198,9 @@ export class ChatExtensionPointHandler implements IWorkbenchContribution { constructor( @IChatAgentService private readonly _chatAgentService: IChatAgentService, - @ILogService private readonly logService: ILogService + @ILogService private readonly logService: ILogService, ) { this._viewContainer = this.registerViewContainer(); - this.registerDefaultParticipantView(); this.handleAndRegisterChatExtensions(); } @@ -212,6 +239,11 @@ export class ChatExtensionPointHandler implements IWorkbenchContribution { continue; } + const store = new DisposableStore(); + if (providerDescriptor.isDefault && (!providerDescriptor.locations || providerDescriptor.locations?.includes(ChatAgentLocation.Panel))) { + store.add(this.registerDefaultParticipantView(providerDescriptor)); + } + const participantsAndCommandsDisambiguation: { categoryName: string; description: string; @@ -228,7 +260,6 @@ export class ChatExtensionPointHandler implements IWorkbenchContribution { } } - const store = new DisposableStore(); store.add(this._chatAgentService.registerAgent( providerDescriptor.id, { @@ -287,9 +318,15 @@ export class ChatExtensionPointHandler implements IWorkbenchContribution { return viewContainer; } - private registerDefaultParticipantView(): IDisposable { - // Register View. Name must be hardcoded because we want to show it even when the extension fails to load due to an API version incompatibility. - const name = 'GitHub Copilot'; + private hasRegisteredDefaultParticipantView = false; + private registerDefaultParticipantView(defaultParticipantDescriptor: IRawChatParticipantContribution): IDisposable { + if (this.hasRegisteredDefaultParticipantView) { + this.logService.warn(`Tried to register a second default chat participant view for "${defaultParticipantDescriptor.id}"`); + return Disposable.None; + } + + // Register View + const name = defaultParticipantDescriptor.fullName ?? defaultParticipantDescriptor.name; const viewDescriptor: IViewDescriptor[] = [{ id: CHAT_VIEW_ID, containerIcon: this._viewContainer.icon, @@ -299,11 +336,12 @@ export class ChatExtensionPointHandler implements IWorkbenchContribution { canToggleVisibility: false, canMoveView: true, ctorDescriptor: new SyncDescriptor(ChatViewPane), - when: ContextKeyExpr.or(CONTEXT_CHAT_PANEL_PARTICIPANT_REGISTERED, CONTEXT_CHAT_EXTENSION_INVALID) }]; + this.hasRegisteredDefaultParticipantView = true; Registry.as(ViewExtensions.ViewsRegistry).registerViews(viewDescriptor, this._viewContainer); return toDisposable(() => { + this.hasRegisteredDefaultParticipantView = false; Registry.as(ViewExtensions.ViewsRegistry).deregisterViews(viewDescriptor, this._viewContainer); }); } @@ -312,39 +350,3 @@ export class ChatExtensionPointHandler implements IWorkbenchContribution { function getParticipantKey(extensionId: ExtensionIdentifier, participantName: string): string { return `${extensionId.value}_${participantName}`; } - -export class ChatCompatibilityNotifier implements IWorkbenchContribution { - static readonly ID = 'workbench.contrib.chatCompatNotifier'; - - constructor( - @IExtensionsWorkbenchService extensionsWorkbenchService: IExtensionsWorkbenchService, - @IContextKeyService contextKeyService: IContextKeyService, - @IChatAgentService chatAgentService: IChatAgentService, - ) { - // It may be better to have some generic UI for this, for any extension that is incompatible, - // but this is only enabled for Copilot Chat now and it needs to be obvious. - - const showExtensionLabel = localize('showExtension', "Show Extension"); - const viewsRegistry = Registry.as(ViewExtensions.ViewsRegistry); - viewsRegistry.registerViewWelcomeContent(CHAT_VIEW_ID, { - content: localize('chatFailErrorMessage', "Chat failed to load. Please ensure that the GitHub Copilot Chat extension is up to date.") + `\n\n[${showExtensionLabel}](command:${showExtensionsWithIdsCommandId}?${encodeURIComponent(JSON.stringify([['GitHub.copilot-chat']]))})`, - when: CONTEXT_CHAT_EXTENSION_INVALID, - }); - - const isInvalid = CONTEXT_CHAT_EXTENSION_INVALID.bindTo(contextKeyService); - extensionsWorkbenchService.queryLocal().then(exts => { - const chat = exts.find(ext => ext.identifier.id === 'github.copilot-chat'); - if (chat?.local?.validations.some(v => v[0] === Severity.Error)) { - // This catches vscode starting up with the invalid extension, but the extension may still get updated by vscode after this. - isInvalid.set(true); - } - }); - - const listener = chatAgentService.onDidChangeAgents(() => { - if (chatAgentService.getDefaultAgent(ChatAgentLocation.Panel)) { - isInvalid.set(false); - listener.dispose(); - } - }); - } -} diff --git a/src/vs/workbench/contrib/chat/browser/chatViewPane.ts b/src/vs/workbench/contrib/chat/browser/chatViewPane.ts index 68fb88bd..0cb92990 100644 --- a/src/vs/workbench/contrib/chat/browser/chatViewPane.ts +++ b/src/vs/workbench/contrib/chat/browser/chatViewPane.ts @@ -88,9 +88,8 @@ export class ChatViewPane extends ViewPane { } else if (this._widget?.viewModel?.initState === ChatModelInitState.Initialized) { // Model is initialized, and the default agent disappeared, so show welcome view this.didUnregisterProvider = true; + this._onDidChangeViewWelcomeState.fire(); } - - this._onDidChangeViewWelcomeState.fire(); })); } @@ -115,10 +114,6 @@ export class ChatViewPane extends ViewPane { } override shouldShowWelcome(): boolean { - if (!this.chatAgentService.getContributedDefaultAgent(ChatAgentLocation.Panel)) { - return true; - } - const noPersistedSessions = !this.chatService.hasSessions(); return this.didUnregisterProvider || !this._widget?.viewModel && (noPersistedSessions || this.didProviderRegistrationFail); } diff --git a/src/vs/workbench/contrib/chat/browser/codeBlockPart.ts b/src/vs/workbench/contrib/chat/browser/codeBlockPart.ts index 5db94d51..1b53c429 100644 --- a/src/vs/workbench/contrib/chat/browser/codeBlockPart.ts +++ b/src/vs/workbench/contrib/chat/browser/codeBlockPart.ts @@ -770,11 +770,15 @@ export class CodeCompareBlockPart extends Disposable { }); dom.reset(this.messageElement, message); + } const diffData = await data.diffData; + if (!diffData) { + return; + } - if (!isEditApplied && diffData) { + if (!isEditApplied) { const viewModel = this.diffEditor.createViewModel({ original: diffData.original, modified: diffData.modified @@ -797,7 +801,6 @@ export class CodeCompareBlockPart extends Disposable { } else { this.diffEditor.setModel(null); this._lastDiffEditorViewModel.value = undefined; - this._onDidChangeContentHeight.fire(); } this.toolbar.context = { diff --git a/src/vs/workbench/contrib/chat/common/chatAgents.ts b/src/vs/workbench/contrib/chat/common/chatAgents.ts index 5725b2b0..5cc12623 100644 --- a/src/vs/workbench/contrib/chat/common/chatAgents.ts +++ b/src/vs/workbench/contrib/chat/common/chatAgents.ts @@ -24,7 +24,7 @@ import { ILogService } from 'vs/platform/log/common/log'; import { IProductService } from 'vs/platform/product/common/productService'; import { asJson, IRequestService } from 'vs/platform/request/common/request'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; -import { CONTEXT_CHAT_ENABLED, CONTEXT_CHAT_PANEL_PARTICIPANT_REGISTERED } from 'vs/workbench/contrib/chat/common/chatContextKeys'; +import { CONTEXT_CHAT_ENABLED } from 'vs/workbench/contrib/chat/common/chatContextKeys'; import { IChatProgressResponseContent, IChatRequestVariableData, ISerializableChatAgentData } from 'vs/workbench/contrib/chat/common/chatModel'; import { IRawChatCommandContribution, RawChatParticipantLocation } from 'vs/workbench/contrib/chat/common/chatParticipantContribTypes'; import { IChatFollowup, IChatLocationData, IChatProgress, IChatResponseErrorDetails, IChatTaskDto } from 'vs/workbench/contrib/chat/common/chatService'; @@ -233,13 +233,11 @@ export class ChatAgentService implements IChatAgentService { readonly onDidChangeAgents: Event = this._onDidChangeAgents.event; private readonly _hasDefaultAgent: IContextKey; - private readonly _defaultAgentRegistered: IContextKey; constructor( @IContextKeyService private readonly contextKeyService: IContextKeyService, ) { this._hasDefaultAgent = CONTEXT_CHAT_ENABLED.bindTo(this.contextKeyService); - this._defaultAgentRegistered = CONTEXT_CHAT_PANEL_PARTICIPANT_REGISTERED.bindTo(this.contextKeyService); } registerAgent(id: string, data: IChatAgentData): IDisposable { @@ -248,10 +246,6 @@ export class ChatAgentService implements IChatAgentService { throw new Error(`Agent already registered: ${JSON.stringify(id)}`); } - if (data.isDefault) { - this._defaultAgentRegistered.set(true); - } - const that = this; const commands = data.slashCommands; data = { @@ -262,13 +256,8 @@ export class ChatAgentService implements IChatAgentService { }; const entry = { data }; this._agents.set(id, entry); - this._onDidChangeAgents.fire(undefined); return toDisposable(() => { this._agents.delete(id); - if (data.isDefault) { - this._defaultAgentRegistered.set(false); - } - this._onDidChangeAgents.fire(undefined); }); } @@ -456,7 +445,7 @@ export class ChatAgentService implements IChatAgentService { const participants = this.getAgents().reduce((acc, a) => { acc.push({ participant: a.id, disambiguation: a.disambiguation ?? [] }); for (const command of a.slashCommands) { - acc.push({ participant: a.id, command: command.name, disambiguation: command.disambiguation ?? [] }); + acc.push({ participant: a.id, command: command.name, disambiguation: [] }); } return acc; }, []); diff --git a/src/vs/workbench/contrib/chat/common/chatContextKeys.ts b/src/vs/workbench/contrib/chat/common/chatContextKeys.ts index f651a114..5930a975 100644 --- a/src/vs/workbench/contrib/chat/common/chatContextKeys.ts +++ b/src/vs/workbench/contrib/chat/common/chatContextKeys.ts @@ -25,9 +25,7 @@ export const CONTEXT_CHAT_INPUT_HAS_FOCUS = new RawContextKey('chatInpu export const CONTEXT_IN_CHAT_INPUT = new RawContextKey('inChatInput', false, { type: 'boolean', description: localize('inInteractiveInput', "True when focus is in the chat input, false otherwise.") }); export const CONTEXT_IN_CHAT_SESSION = new RawContextKey('inChat', false, { type: 'boolean', description: localize('inChat', "True when focus is in the chat widget, false otherwise.") }); -export const CONTEXT_CHAT_ENABLED = new RawContextKey('chatIsEnabled', false, { type: 'boolean', description: localize('chatIsEnabled', "True when chat is enabled because a default chat participant is activated with an implementation.") }); -export const CONTEXT_CHAT_PANEL_PARTICIPANT_REGISTERED = new RawContextKey('chatPanelParticipantRegistered', false, { type: 'boolean', description: localize('chatParticipantRegistered', "True when a default chat participant is registered for the panel.") }); -export const CONTEXT_CHAT_EXTENSION_INVALID = new RawContextKey('chatExtensionInvalid', false, { type: 'boolean', description: localize('chatExtensionInvalid', "True when the installed chat extension is invalid and needs to be updated.") }); +export const CONTEXT_CHAT_ENABLED = new RawContextKey('chatIsEnabled', false, { type: 'boolean', description: localize('chatIsEnabled', "True when chat is enabled because a default chat participant is registered.") }); export const CONTEXT_CHAT_INPUT_CURSOR_AT_TOP = new RawContextKey('chatCursorAtTop', false); export const CONTEXT_CHAT_INPUT_HAS_AGENT = new RawContextKey('chatInputHasAgent', false); export const CONTEXT_CHAT_LOCATION = new RawContextKey('chatLocation', undefined); diff --git a/src/vs/workbench/contrib/chat/common/chatModel.ts b/src/vs/workbench/contrib/chat/common/chatModel.ts index b9578bae..14a94c04 100644 --- a/src/vs/workbench/contrib/chat/common/chatModel.ts +++ b/src/vs/workbench/contrib/chat/common/chatModel.ts @@ -616,8 +616,6 @@ export type ISerializableChatDataIn = ISerializableChatData1 | ISerializableChat * TODO- ChatModel#_deserialize and reviveSerializedAgent also still do some normalization and maybe that should be done in here too. */ export function normalizeSerializableChatData(raw: ISerializableChatDataIn): ISerializableChatData { - normalizeOldFields(raw); - if (!('version' in raw)) { return { version: 3, @@ -638,30 +636,6 @@ export function normalizeSerializableChatData(raw: ISerializableChatDataIn): ISe return raw; } -function normalizeOldFields(raw: ISerializableChatDataIn): void { - // Fill in fields that very old chat data may be missing - if (!raw.sessionId) { - raw.sessionId = generateUuid(); - } - - if (!raw.creationDate) { - raw.creationDate = getLastYearDate(); - } - - if ('version' in raw && (raw.version === 2 || raw.version === 3)) { - if (!raw.lastMessageDate) { - // A bug led to not porting creationDate properly, and that was copied to lastMessageDate, so fix that up if missing. - raw.lastMessageDate = getLastYearDate(); - } - } -} - -function getLastYearDate(): number { - const lastYearDate = new Date(); - lastYearDate.setFullYear(lastYearDate.getFullYear() - 1); - return lastYearDate.getTime(); -} - export function isExportableSessionData(obj: unknown): obj is IExportableChatData { const data = obj as IExportableChatData; return typeof data === 'object' && diff --git a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts index 2b6cfac7..3ca4524e 100644 --- a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts @@ -481,8 +481,9 @@ export class ChatService extends Disposable implements IChatService { } async sendRequest(sessionId: string, request: string, options?: IChatSendRequestOptions): Promise { + this.trace('sendRequest', `sessionId: ${sessionId}, message: ${request.substring(0, 20)}${request.length > 20 ? '[...]' : ''}}`); - if (!request.trim() && !options?.slashCommand && !options?.agentId) { + if (!request.trim()) { this.trace('sendRequest', 'Rejected empty message'); return; } @@ -545,7 +546,6 @@ export class ChatService extends Disposable implements IChatService { const agentPart = 'kind' in parsedRequest ? undefined : parsedRequest.parts.find((r): r is ChatRequestAgentPart => r instanceof ChatRequestAgentPart); const agentSlashCommandPart = 'kind' in parsedRequest ? undefined : parsedRequest.parts.find((r): r is ChatRequestAgentSubcommandPart => r instanceof ChatRequestAgentSubcommandPart); const commandPart = 'kind' in parsedRequest ? undefined : parsedRequest.parts.find((r): r is ChatRequestSlashCommandPart => r instanceof ChatRequestSlashCommandPart); - const requests = [...model.getRequests()]; let gotProgress = false; const requestType = commandPart ? 'slashCommand' : 'string'; @@ -639,7 +639,7 @@ export class ChatService extends Disposable implements IChatService { if (this.configurationService.getValue('chat.experimental.detectParticipant.enabled') !== false && this.chatAgentService.hasChatParticipantDetectionProviders() && !agentPart && !commandPart && enableCommandDetection) { // We have no agent or command to scope history with, pass the full history to the participant detection provider - const defaultAgentHistory = this.getHistoryEntriesFromModel(requests, model.sessionId, location, defaultAgent.id); + const defaultAgentHistory = this.getHistoryEntriesFromModel(model, location, defaultAgent.id); // Prepare the request object that we will send to the participant detection provider const chatAgentRequest = await prepareChatAgentRequest(defaultAgent, agentSlashCommandPart?.command, enableCommandDetection, undefined, false); @@ -658,7 +658,7 @@ export class ChatService extends Disposable implements IChatService { await this.extensionService.activateByEvent(`onChatParticipant:${agent.id}`); // Recompute history in case the agent or command changed - const history = this.getHistoryEntriesFromModel(requests, model.sessionId, location, agent.id); + const history = this.getHistoryEntriesFromModel(model, location, agent.id); const requestProps = await prepareChatAgentRequest(agent, command, enableCommandDetection, request /* Reuse the request object if we already created it for participant detection */, !!detectedAgent); const pendingRequest = this._pendingRequests.get(sessionId); if (pendingRequest && !pendingRequest.requestId) { @@ -668,7 +668,7 @@ export class ChatService extends Disposable implements IChatService { const agentResult = await this.chatAgentService.invokeAgent(agent.id, requestProps, progressCallback, history, token); rawResult = agentResult; agentOrCommandFollowups = this.chatAgentService.getFollowups(agent.id, requestProps, agentResult, history, followupsCancelToken); - chatTitlePromise = model.getRequests().length === 1 && !model.customTitle ? this.chatAgentService.getChatTitle(defaultAgent.id, this.getHistoryEntriesFromModel(model.getRequests(), model.sessionId, location, agent.id), CancellationToken.None) : undefined; + chatTitlePromise = model.getRequests().length === 1 && !model.customTitle ? this.chatAgentService.getChatTitle(defaultAgent.id, this.getHistoryEntriesFromModel(model, location, agent.id), CancellationToken.None) : undefined; } else if (commandPart && this.chatSlashCommandService.hasCommand(commandPart.slashCommand.command)) { request = model.addRequest(parsedRequest, { variables: [] }, attempt); completeResponseCreated(); @@ -775,9 +775,9 @@ export class ChatService extends Disposable implements IChatService { }; } - private getHistoryEntriesFromModel(requests: IChatRequestModel[], sessionId: string, location: ChatAgentLocation, forAgentId: string): IChatAgentHistoryEntry[] { + private getHistoryEntriesFromModel(model: IChatModel, location: ChatAgentLocation, forAgentId: string): IChatAgentHistoryEntry[] { const history: IChatAgentHistoryEntry[] = []; - for (const request of requests) { + for (const request of model.getRequests()) { if (!request.response) { continue; } @@ -791,7 +791,7 @@ export class ChatService extends Disposable implements IChatService { const promptTextResult = getPromptText(request.message); const historyRequest: IChatAgentRequest = { - sessionId: sessionId, + sessionId: model.sessionId, requestId: request.id, agentId: request.response.agent?.id ?? '', message: promptTextResult.message, diff --git a/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts b/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts index b0455f49..bffc94d7 100644 --- a/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts +++ b/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts @@ -941,6 +941,12 @@ export class StopReadAloud extends Action2 { when: ScopedChatSynthesisInProgress, group: 'navigation', order: -1 + }, + { + id: MENU_INLINE_CHAT_WIDGET_SECONDARY, + when: ScopedChatSynthesisInProgress, + group: 'navigation', + order: -1 } ] }); @@ -974,15 +980,6 @@ export class StopReadChatItemAloud extends Action2 { CONTEXT_RESPONSE_FILTERED.negate() // but not when response is filtered ), group: 'navigation' - }, - { - id: MENU_INLINE_CHAT_WIDGET_SECONDARY, - when: ContextKeyExpr.and( - ScopedChatSynthesisInProgress, // only when in progress - CONTEXT_RESPONSE, // only for responses - CONTEXT_RESPONSE_FILTERED.negate() // but not when response is filtered - ), - group: 'navigation' } ] }); diff --git a/src/vs/workbench/contrib/chat/test/common/chatModel.test.ts b/src/vs/workbench/contrib/chat/test/common/chatModel.test.ts index c37b05e8..4be9380f 100644 --- a/src/vs/workbench/contrib/chat/test/common/chatModel.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/chatModel.test.ts @@ -17,7 +17,7 @@ import { MockContextKeyService } from 'vs/platform/keybinding/test/common/mockKe import { ILogService, NullLogService } from 'vs/platform/log/common/log'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { ChatAgentLocation, ChatAgentService, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; -import { ChatModel, ISerializableChatData1, ISerializableChatData2, ISerializableChatData3, normalizeSerializableChatData, Response } from 'vs/workbench/contrib/chat/common/chatModel'; +import { ChatModel, ISerializableChatData1, ISerializableChatData2, normalizeSerializableChatData, Response } from 'vs/workbench/contrib/chat/common/chatModel'; import { ChatRequestTextPart } from 'vs/workbench/contrib/chat/common/chatParserTypes'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { TestExtensionService, TestStorageService } from 'vs/workbench/test/common/workbenchTestServices'; @@ -230,53 +230,4 @@ suite('normalizeSerializableChatData', () => { assert.strictEqual(newData.lastMessageDate, v2Data.lastMessageDate); assert.strictEqual(newData.customTitle, v2Data.computedTitle); }); - - test('old bad data', () => { - const v1Data: ISerializableChatData1 = { - // Testing the scenario where these are missing - sessionId: undefined!, - creationDate: undefined!, - - initialLocation: undefined, - isImported: false, - requesterAvatarIconUri: undefined, - requesterUsername: 'me', - requests: [], - responderAvatarIconUri: undefined, - responderUsername: 'bot', - welcomeMessage: [] - }; - - const newData = normalizeSerializableChatData(v1Data); - assert.strictEqual(newData.version, 3); - assert.ok(newData.creationDate > 0); - assert.ok(newData.lastMessageDate > 0); - assert.ok(newData.sessionId); - }); - - test('v3 with bug', () => { - const v3Data: ISerializableChatData3 = { - // Test case where old data was wrongly normalized and these fields were missing - creationDate: undefined!, - lastMessageDate: undefined!, - - version: 3, - initialLocation: undefined, - isImported: false, - requesterAvatarIconUri: undefined, - requesterUsername: 'me', - requests: [], - responderAvatarIconUri: undefined, - responderUsername: 'bot', - sessionId: 'session1', - welcomeMessage: [], - customTitle: 'computed title' - }; - - const newData = normalizeSerializableChatData(v3Data); - assert.strictEqual(newData.version, 3); - assert.ok(newData.creationDate > 0); - assert.ok(newData.lastMessageDate > 0); - assert.ok(newData.sessionId); - }); }); diff --git a/src/vs/workbench/contrib/comments/browser/commentsView.ts b/src/vs/workbench/contrib/comments/browser/commentsView.ts index 75deafec..253f7023 100644 --- a/src/vs/workbench/contrib/comments/browser/commentsView.ts +++ b/src/vs/workbench/contrib/comments/browser/commentsView.ts @@ -172,7 +172,7 @@ export class CommentsPanel extends FilterViewPane implements ICommentsView { this.filters = this._register(new CommentsFilters({ showResolved: this.viewState['showResolved'] !== false, showUnresolved: this.viewState['showUnresolved'] !== false, - sortBy: this.viewState['sortBy'] ?? CommentsSortOrder.ResourceAscending, + sortBy: this.viewState['sortBy'], }, this.contextKeyService)); this.filter = new Filter(new FilterOptions(this.filterWidget.getFilterText(), this.filters.showResolved, this.filters.showUnresolved)); diff --git a/src/vs/workbench/contrib/comments/browser/commentsViewActions.ts b/src/vs/workbench/contrib/comments/browser/commentsViewActions.ts index e2494534..8fedca8e 100644 --- a/src/vs/workbench/contrib/comments/browser/commentsViewActions.ts +++ b/src/vs/workbench/contrib/comments/browser/commentsViewActions.ts @@ -7,7 +7,7 @@ import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { Disposable } from 'vs/base/common/lifecycle'; import { localize } from 'vs/nls'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; -import { ContextKeyExpr, IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; +import { ContextKeyExpr, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { Event, Emitter } from 'vs/base/common/event'; import { CommentsViewFilterFocusContextKey, ICommentsView } from 'vs/workbench/contrib/comments/browser/comments'; import { MenuId, MenuRegistry, registerAction2 } from 'vs/platform/actions/common/actions'; @@ -74,9 +74,9 @@ export class CommentsFilters extends Disposable { } } - private _sortBy: IContextKey = CONTEXT_KEY_SORT_BY.bindTo(this.contextKeyService); + private _sortBy = CONTEXT_KEY_SORT_BY.bindTo(this.contextKeyService); get sortBy(): CommentsSortOrder { - return this._sortBy.get() ?? CommentsSortOrder.ResourceAscending; + return this._sortBy.get()!; } set sortBy(sortBy: CommentsSortOrder) { if (this._sortBy.get() !== sortBy) { @@ -208,7 +208,7 @@ registerAction2(class extends ViewAction { icon: Codicon.history, viewId: COMMENTS_VIEW_ID, toggled: { - condition: ContextKeyExpr.equals(CONTEXT_KEY_SORT_BY.key, CommentsSortOrder.UpdatedAtDescending), + condition: ContextKeyExpr.equals('commentsView.sortBy', CommentsSortOrder.UpdatedAtDescending), title: localize('sorting by updated at', "Updated Time"), }, menu: { @@ -229,13 +229,13 @@ registerAction2(class extends ViewAction { constructor() { super({ id: `workbench.actions.${COMMENTS_VIEW_ID}.toggleSortByResource`, - title: localize('toggle sorting by resource', "Position in File"), + title: localize('toggle sorting by resource', "File"), category: localize('comments', "Comments"), icon: Codicon.history, viewId: COMMENTS_VIEW_ID, toggled: { - condition: ContextKeyExpr.equals(CONTEXT_KEY_SORT_BY.key, CommentsSortOrder.ResourceAscending), - title: localize('sorting by position in file', "Position in File"), + condition: ContextKeyExpr.equals('commentsView.sortBy', CommentsSortOrder.ResourceAscending), + title: localize('sorting by file', "File"), }, menu: { id: commentSortSubmenu, diff --git a/src/vs/workbench/contrib/debug/browser/callStackWidget.ts b/src/vs/workbench/contrib/debug/browser/callStackWidget.ts index d02de4fb..50cfbf0f 100644 --- a/src/vs/workbench/contrib/debug/browser/callStackWidget.ts +++ b/src/vs/workbench/contrib/debug/browser/callStackWidget.ts @@ -12,24 +12,20 @@ import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cance import { Codicon } from 'vs/base/common/codicons'; import { Emitter, Event } from 'vs/base/common/event'; import { Disposable, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; -import { autorun, autorunWithStore, derived, IObservable, ISettableObservable, observableValue, transaction } from 'vs/base/common/observable'; +import { autorun, autorunWithStore, derived, IObservable, ISettableObservable, observableValue } from 'vs/base/common/observable'; import { ThemeIcon } from 'vs/base/common/themables'; import { Constants } from 'vs/base/common/uint'; import { URI } from 'vs/base/common/uri'; import { generateUuid } from 'vs/base/common/uuid'; import 'vs/css!./media/callStackWidget'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; -import { EditorContributionCtor, EditorContributionInstantiation, IEditorContributionDescription } from 'vs/editor/browser/editorExtensions'; +import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditor/codeEditorWidget'; import { EmbeddedCodeEditorWidget } from 'vs/editor/browser/widget/codeEditor/embeddedCodeEditorWidget'; import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; -import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; -import { IWordAtPosition } from 'vs/editor/common/core/wordHelper'; -import { IEditorContribution, IEditorDecorationsCollection } from 'vs/editor/common/editorCommon'; import { Location } from 'vs/editor/common/languages'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; -import { ClickLinkGesture, ClickLinkMouseEvent } from 'vs/editor/contrib/gotoSymbol/browser/link/clickLinkGesture'; import { localize, localize2 } from 'vs/nls'; import { createActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { MenuWorkbenchToolBar } from 'vs/platform/actions/browser/toolbar'; @@ -42,7 +38,7 @@ import { INotificationService } from 'vs/platform/notification/common/notificati import { defaultButtonStyles } from 'vs/platform/theme/browser/defaultStyles'; import { ResourceLabel } from 'vs/workbench/browser/labels'; import { makeStackFrameColumnDecoration, TOP_STACK_FRAME_DECORATION } from 'vs/workbench/contrib/debug/browser/callStackEditorContribution'; -import { IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; export class CallStackFrame { @@ -101,9 +97,6 @@ class WrappedCustomStackFrame implements IFrameLikeItem { constructor(public readonly original: CustomStackFrame) { } } -const isFrameLike = (item: unknown): item is IFrameLikeItem => - item instanceof WrappedCallStackFrame || item instanceof WrappedCustomStackFrame; - type ListItem = WrappedCallStackFrame | SkippedCallFrames | WrappedCustomStackFrame; const WIDGET_CLASS_NAME = 'multiCallStackWidget'; @@ -144,7 +137,6 @@ export class CallStackWidget extends Disposable { multipleSelectionSupport: false, mouseSupport: false, keyboardSupport: false, - setRowLineHeight: false, accessibilityProvider: instantiationService.createInstance(StackAccessibilityProvider), } ) as WorkbenchList); @@ -165,17 +157,6 @@ export class CallStackWidget extends Disposable { this.layoutEmitter.fire(); } - public collapseAll() { - transaction(tx => { - for (let i = 0; i < this.list.length; i++) { - const frame = this.list.element(i); - if (isFrameLike(frame)) { - frame.collapsed.set(true, tx); - } - } - }); - } - private async loadFrame(replacing: SkippedCallFrames): Promise { if (!this.cts) { return; @@ -375,9 +356,9 @@ abstract class AbstractFrameRenderer item.collapsed.set(!item.collapsed.get(), undefined); - elementStore.add(collapse.onDidClick(toggleCollapse)); - elementStore.add(dom.addDisposableListener(elements.title, 'click', toggleCollapse)); + elementStore.add(collapse.onDidClick(() => { + item.collapsed.set(!item.collapsed.get(), undefined); + })); } disposeElement(element: ListItem, index: number, templateData: T, height: number | undefined): void { @@ -401,33 +382,26 @@ class FrameCodeRenderer extends AbstractFrameRenderer { private readonly containingEditor: ICodeEditor | undefined, private readonly onLayout: Event, @ITextModelService private readonly modelService: ITextModelService, + @ICodeEditorService private readonly editorService: ICodeEditorService, @IInstantiationService instantiationService: IInstantiationService, ) { super(instantiationService); } protected override finishRenderTemplate(data: IAbstractFrameRendererTemplateData): IStackTemplateData { - // override default e.g. language contributions, only allow users to click - // on code in the call stack to go to its source location - const contributions: IEditorContributionDescription[] = [{ - id: ClickToLocationContribution.ID, - instantiation: EditorContributionInstantiation.BeforeFirstInteraction, - ctor: ClickToLocationContribution as EditorContributionCtor, - }]; - const editor = this.containingEditor ? this.instantiationService.createInstance( EmbeddedCodeEditorWidget, data.elements.editor, editorOptions, - { isSimpleWidget: true, contributions }, + { isSimpleWidget: true }, this.containingEditor, ) : this.instantiationService.createInstance( CodeEditorWidget, data.elements.editor, editorOptions, - { isSimpleWidget: true, contributions }, + { isSimpleWidget: true }, ); data.templateStore.add(editor); @@ -449,6 +423,20 @@ class FrameCodeRenderer extends AbstractFrameRenderer { const uri = item.source!; template.label.element.setFile(uri); + template.elements.title.role = 'link'; + elementStore.add(dom.addDisposableListener(template.elements.title, 'click', e => { + this.editorService.openCodeEditor({ + resource: uri, + options: { + selection: Range.fromPositions({ + column: item.column ?? 1, + lineNumber: item.line ?? 1, + }), + selectionRevealType: TextEditorSelectionRevealType.CenterIfOutsideViewport, + }, + }, this.containingEditor || null, e.ctrlKey || e.metaKey); + })); + const cts = new CancellationTokenSource(); elementStore.add(toDisposable(() => cts.dispose(true))); this.modelService.createModelReference(uri).then(reference => { @@ -644,73 +632,6 @@ class SkippedRenderer implements IListRenderer { } } -/** A simple contribution that makes all data in the editor clickable to go to the location */ -class ClickToLocationContribution extends Disposable implements IEditorContribution { - public static readonly ID = 'clickToLocation'; - private readonly linkDecorations: IEditorDecorationsCollection; - private current: { line: number; word: IWordAtPosition } | undefined; - - constructor( - private readonly editor: ICodeEditor, - @IEditorService editorService: IEditorService, - ) { - super(); - this.linkDecorations = editor.createDecorationsCollection(); - this._register(toDisposable(() => this.linkDecorations.clear())); - - const clickLinkGesture = this._register(new ClickLinkGesture(editor)); - - this._register(clickLinkGesture.onMouseMoveOrRelevantKeyDown(([mouseEvent, keyboardEvent]) => { - this.onMove(mouseEvent); - })); - this._register(clickLinkGesture.onExecute((e) => { - const model = this.editor.getModel(); - if (!this.current || !model) { - return; - } - - editorService.openEditor({ - resource: model.uri, - options: { - selection: Range.fromPositions(new Position(this.current.line, this.current.word.startColumn)), - selectionRevealType: TextEditorSelectionRevealType.CenterIfOutsideViewport, - }, - }, e.hasSideBySideModifier ? SIDE_GROUP : undefined); - })); - } - - private onMove(mouseEvent: ClickLinkMouseEvent) { - if (!mouseEvent.hasTriggerModifier) { - return this.clear(); - } - - const position = mouseEvent.target.position; - const word = position && this.editor.getModel()?.getWordAtPosition(position); - if (!word) { - return this.clear(); - } - - const prev = this.current?.word; - if (prev && prev.startColumn === word.startColumn && prev.endColumn === word.endColumn && prev.word === word.word) { - return; - } - - this.current = { word, line: position.lineNumber }; - this.linkDecorations.set([{ - range: new Range(position.lineNumber, word.startColumn, position.lineNumber, word.endColumn), - options: { - description: 'call-stack-go-to-file-link', - inlineClassName: 'call-stack-go-to-file-link', - }, - }]); - } - - private clear() { - this.linkDecorations.clear(); - this.current = undefined; - } -} - registerAction2(class extends Action2 { constructor() { super({ diff --git a/src/vs/workbench/contrib/debug/browser/media/callStackWidget.css b/src/vs/workbench/contrib/debug/browser/media/callStackWidget.css index e5e0d56e..60f54930 100644 --- a/src/vs/workbench/contrib/debug/browser/media/callStackWidget.css +++ b/src/vs/workbench/contrib/debug/browser/media/callStackWidget.css @@ -24,10 +24,6 @@ &[role="link"] { cursor: pointer; } - - .monaco-icon-label::before { - height: auto; - } } &.collapsed { @@ -43,7 +39,6 @@ .collapse-button { width: 16px; min-height: 1px; /* show even if empty */ - line-height: 0; a { cursor: pointer; @@ -61,11 +56,6 @@ .multiCallStackWidget { .multiCallStackFrameContainer { background: none !important; + line-height: inherit !important; } } - -.monaco-editor .call-stack-go-to-file-link { - text-decoration: underline; - cursor: pointer; - color: var(--vscode-editorLink-activeForeground) !important; -} diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts b/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts index f2eea7c6..1e8ef29a 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts @@ -79,6 +79,7 @@ class ExtensionsViewState extends Disposable implements IExtensionsViewState { } } + export interface ExtensionsListViewOptions { server?: IExtensionManagementServer; flexibleHeight?: boolean; diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts index 22d6a83f..8119cdd0 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts @@ -11,7 +11,7 @@ import { EmbeddedDiffEditorWidget } from 'vs/editor/browser/widget/diffEditor/em import { EmbeddedCodeEditorWidget } from 'vs/editor/browser/widget/codeEditor/embeddedCodeEditorWidget'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { InlineChatController, InlineChatRunOptions } from 'vs/workbench/contrib/inlineChat/browser/inlineChatController'; -import { ACTION_ACCEPT_CHANGES, CTX_INLINE_CHAT_HAS_AGENT, CTX_INLINE_CHAT_HAS_STASHED_SESSION, CTX_INLINE_CHAT_FOCUSED, CTX_INLINE_CHAT_INNER_CURSOR_FIRST, CTX_INLINE_CHAT_INNER_CURSOR_LAST, CTX_INLINE_CHAT_VISIBLE, CTX_INLINE_CHAT_OUTER_CURSOR_POSITION, CTX_INLINE_CHAT_USER_DID_EDIT, CTX_INLINE_CHAT_DOCUMENT_CHANGED, CTX_INLINE_CHAT_EDIT_MODE, EditMode, MENU_INLINE_CHAT_WIDGET_STATUS, CTX_INLINE_CHAT_REQUEST_IN_PROGRESS, CTX_INLINE_CHAT_RESPONSE_TYPE, InlineChatResponseType, ACTION_REGENERATE_RESPONSE, MENU_INLINE_CHAT_CONTENT_STATUS, ACTION_VIEW_IN_CHAT, ACTION_TOGGLE_DIFF, CTX_INLINE_CHAT_CHANGE_HAS_DIFF, CTX_INLINE_CHAT_CHANGE_SHOWS_DIFF, MENU_INLINE_CHAT_ZONE, ACTION_DISCARD_CHANGES } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; +import { ACTION_ACCEPT_CHANGES, CTX_INLINE_CHAT_HAS_AGENT, CTX_INLINE_CHAT_HAS_STASHED_SESSION, CTX_INLINE_CHAT_FOCUSED, CTX_INLINE_CHAT_INNER_CURSOR_FIRST, CTX_INLINE_CHAT_INNER_CURSOR_LAST, CTX_INLINE_CHAT_VISIBLE, CTX_INLINE_CHAT_OUTER_CURSOR_POSITION, CTX_INLINE_CHAT_USER_DID_EDIT, CTX_INLINE_CHAT_DOCUMENT_CHANGED, CTX_INLINE_CHAT_EDIT_MODE, EditMode, MENU_INLINE_CHAT_WIDGET_STATUS, CTX_INLINE_CHAT_REQUEST_IN_PROGRESS, CTX_INLINE_CHAT_RESPONSE_TYPE, InlineChatResponseType, ACTION_REGENERATE_RESPONSE, MENU_INLINE_CHAT_CONTENT_STATUS, ACTION_VIEW_IN_CHAT, ACTION_TOGGLE_DIFF, CTX_INLINE_CHAT_CHANGE_HAS_DIFF, CTX_INLINE_CHAT_CHANGE_SHOWS_DIFF, MENU_INLINE_CHAT_ZONE } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { localize, localize2 } from 'vs/nls'; import { Action2, IAction2Options } from 'vs/platform/actions/common/actions'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; @@ -287,7 +287,7 @@ export class DiscardHunkAction extends AbstractInlineChatAction { constructor() { super({ - id: ACTION_DISCARD_CHANGES, + id: 'inlineChat.discardHunkChange', title: localize('discard', 'Discard'), icon: Codicon.chromeClose, precondition: CTX_INLINE_CHAT_VISIBLE, diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts index c6d9ae57..29b4dee9 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts @@ -39,7 +39,7 @@ import { ChatAgentLocation } from 'vs/workbench/contrib/chat/common/chatAgents'; import { ChatModel, ChatRequestRemovalReason, IChatRequestModel, IChatTextEditGroup, IChatTextEditGroupState, IResponse } from 'vs/workbench/contrib/chat/common/chatModel'; import { IChatService } from 'vs/workbench/contrib/chat/common/chatService'; import { InlineChatContentWidget } from 'vs/workbench/contrib/inlineChat/browser/inlineChatContentWidget'; -import { HunkInformation, HunkState, Session, StashedSession } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSession'; +import { HunkInformation, Session, StashedSession } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSession'; import { InlineChatError } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSessionServiceImpl'; import { EditModeStrategy, HunkAction, IEditObserver, LiveStrategy, PreviewStrategy, ProgressingEditsOptions } from 'vs/workbench/contrib/inlineChat/browser/inlineChatStrategies'; import { CTX_INLINE_CHAT_EDITING, CTX_INLINE_CHAT_REQUEST_IN_PROGRESS, CTX_INLINE_CHAT_RESPONSE_TYPE, CTX_INLINE_CHAT_USER_DID_EDIT, CTX_INLINE_CHAT_VISIBLE, EditMode, INLINE_CHAT_ID, InlineChatConfigKeys, InlineChatResponseType } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; @@ -622,7 +622,6 @@ export class InlineChatController implements IEditorContribution { return; } if (e.kind === 'move') { - assertType(this._session); const log: typeof this._log = (msg: string, ...args: any[]) => this._log('state=_showRequest) moving inline chat', msg, ...args); log('move was requested', e.target, e.range); @@ -637,13 +636,13 @@ export class InlineChatController implements IEditorContribution { } const newEditor = editorPane.getControl(); - if (!isCodeEditor(newEditor) || !newEditor.hasModel()) { + if (!newEditor || !isCodeEditor(newEditor) || !newEditor.hasModel()) { log('new editor is either missing or not a code editor or does not have a model'); return; } - if (this._inlineChatSessionService.getSession(newEditor, e.target)) { - log('new editor ALREADY has a session'); + if (!this._session) { + log('controller does not have a session'); return; } @@ -742,7 +741,7 @@ export class InlineChatController implements IEditorContribution { await responsePromise.p; await progressiveEditsQueue.whenIdle(); - if (response.result?.errorDetails) { + if (response.isCanceled) { await this._session.undoChangesUntil(response.requestId); } @@ -759,6 +758,7 @@ export class InlineChatController implements IEditorContribution { if (response.result?.errorDetails) { // + await this._session.undoChangesUntil(response.requestId); } else if (response.response.value.length === 0) { // empty -> show message @@ -990,21 +990,8 @@ export class InlineChatController implements IEditorContribution { // ---- controller API showSaveHint(): void { - if (!this._session) { - return; - } - - const status = localize('savehint', "Accept or discard changes to continue saving."); + const status = localize('savehint', "Accept or discard changes to continue saving"); this._ui.value.zone.widget.updateStatus(status, { classes: ['warn'] }); - - if (this._ui.value.zone.position) { - this._editor.revealLineInCenterIfOutsideViewport(this._ui.value.zone.position.lineNumber); - } else { - const hunk = this._session.hunkData.getInfo().find(info => info.getState() === HunkState.Pending); - if (hunk) { - this._editor.revealLineInCenterIfOutsideViewport(hunk.getRangesN()[0].startLineNumber); - } - } } acceptInput() { diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatSession.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatSession.ts index f5b111e5..a3a6a25a 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatSession.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatSession.ts @@ -263,7 +263,7 @@ export class StashedSession { // keep session for a little bit, only release when user continues to work (type, move cursor, etc.) this._session = session; this._ctxHasStashedSession.set(true); - this._listener = Event.once(Event.any(editor.onDidChangeCursorSelection, editor.onDidChangeModelContent, editor.onDidChangeModel, editor.onDidBlurEditorWidget))(() => { + this._listener = Event.once(Event.any(editor.onDidChangeCursorSelection, editor.onDidChangeModelContent, editor.onDidChangeModel))(() => { this._session = undefined; this._sessionService.releaseSession(session); this._ctxHasStashedSession.reset(); @@ -360,30 +360,27 @@ export class HunkData { // mirror textModelN changes to textModel0 execept for those that // overlap with a hunk - type HunkRangePair = { rangeN: Range; range0: Range; markAccepted: () => void }; + type HunkRangePair = { rangeN: Range; range0: Range }; const hunkRanges: HunkRangePair[] = []; const ranges0: Range[] = []; - for (const entry of this._data.values()) { + for (const { textModelNDecorations, textModel0Decorations, state } of this._data.values()) { - if (entry.state === HunkState.Pending) { + if (state === HunkState.Pending) { // pending means the hunk's changes aren't "sync'd" yet - for (let i = 1; i < entry.textModelNDecorations.length; i++) { - const rangeN = this._textModelN.getDecorationRange(entry.textModelNDecorations[i]); - const range0 = this._textModel0.getDecorationRange(entry.textModel0Decorations[i]); + for (let i = 1; i < textModelNDecorations.length; i++) { + const rangeN = this._textModelN.getDecorationRange(textModelNDecorations[i]); + const range0 = this._textModel0.getDecorationRange(textModel0Decorations[i]); if (rangeN && range0) { - hunkRanges.push({ - rangeN, range0, - markAccepted: () => entry.state = HunkState.Accepted - }); + hunkRanges.push({ rangeN, range0 }); } } - } else if (entry.state === HunkState.Accepted) { + } else if (state === HunkState.Accepted) { // accepted means the hunk's changes are also in textModel0 - for (let i = 1; i < entry.textModel0Decorations.length; i++) { - const range = this._textModel0.getDecorationRange(entry.textModel0Decorations[i]); + for (let i = 1; i < textModel0Decorations.length; i++) { + const range = this._textModel0.getDecorationRange(textModel0Decorations[i]); if (range) { ranges0.push(range); } @@ -402,20 +399,16 @@ export class HunkData { let pendingChangesLen = 0; - for (const entry of hunkRanges) { - if (entry.rangeN.getEndPosition().isBefore(Range.getStartPosition(change.range))) { + for (const { rangeN, range0 } of hunkRanges) { + if (rangeN.getEndPosition().isBefore(Range.getStartPosition(change.range))) { // pending hunk _before_ this change. When projecting into textModel0 we need to // subtract that. Because diffing is relaxed it might include changes that are not // actual insertions/deletions. Therefore we need to take the length of the original // range into account. - pendingChangesLen += this._textModelN.getValueLengthInRange(entry.rangeN); - pendingChangesLen -= this._textModel0.getValueLengthInRange(entry.range0); + pendingChangesLen += this._textModelN.getValueLengthInRange(rangeN); + pendingChangesLen -= this._textModel0.getValueLengthInRange(range0); - } else if (Range.areIntersectingOrTouching(entry.rangeN, change.range)) { - // an edit overlaps with a (pending) hunk. We take this as a signal - // to mark the hunk as accepted and to ignore the edit. The range of the hunk - // will be up-to-date because of decorations created for them - entry.markAccepted(); + } else if (Range.areIntersectingOrTouching(rangeN, change.range)) { isOverlapping = true; break; @@ -454,23 +447,24 @@ export class HunkData { diff ??= await this._editorWorkerService.computeDiff(this._textModel0.uri, this._textModelN.uri, { ignoreTrimWhitespace: false, maxComputationTimeMs: Number.MAX_SAFE_INTEGER, computeMoves: false }, 'advanced'); - let mergedChanges: DetailedLineRangeMapping[] = []; + if (!diff || diff.changes.length === 0) { + // return new HunkData([], session); + return; + } - if (diff && diff.changes.length > 0) { - // merge changes neighboring changes - mergedChanges = [diff.changes[0]]; - for (let i = 1; i < diff.changes.length; i++) { - const lastChange = mergedChanges[mergedChanges.length - 1]; - const thisChange = diff.changes[i]; - if (thisChange.modified.startLineNumber - lastChange.modified.endLineNumberExclusive <= HunkData._HUNK_THRESHOLD) { - mergedChanges[mergedChanges.length - 1] = new DetailedLineRangeMapping( - lastChange.original.join(thisChange.original), - lastChange.modified.join(thisChange.modified), - (lastChange.innerChanges ?? []).concat(thisChange.innerChanges ?? []) - ); - } else { - mergedChanges.push(thisChange); - } + // merge changes neighboring changes + const mergedChanges = [diff.changes[0]]; + for (let i = 1; i < diff.changes.length; i++) { + const lastChange = mergedChanges[mergedChanges.length - 1]; + const thisChange = diff.changes[i]; + if (thisChange.modified.startLineNumber - lastChange.modified.endLineNumberExclusive <= HunkData._HUNK_THRESHOLD) { + mergedChanges[mergedChanges.length - 1] = new DetailedLineRangeMapping( + lastChange.original.join(thisChange.original), + lastChange.modified.join(thisChange.modified), + (lastChange.innerChanges ?? []).concat(thisChange.innerChanges ?? []) + ); + } else { + mergedChanges.push(thisChange); } } diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts index 01df414e..c44f6e6e 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts @@ -8,7 +8,7 @@ import { coalesceInPlace } from 'vs/base/common/arrays'; import { CancellationToken } from 'vs/base/common/cancellation'; import { Emitter, Event } from 'vs/base/common/event'; import { DisposableStore } from 'vs/base/common/lifecycle'; -import { themeColorFromId, ThemeIcon } from 'vs/base/common/themables'; +import { themeColorFromId } from 'vs/base/common/themables'; import { ICodeEditor, IOverlayWidget, IOverlayWidgetPosition, IViewZone, IViewZoneChangeAccessor } from 'vs/editor/browser/editorBrowser'; import { StableEditorScrollState } from 'vs/editor/browser/stableEditorScroll'; import { LineSource, RenderOptions, renderLines } from 'vs/editor/browser/widget/diffEditor/components/diffEditorViewZones/renderLines'; @@ -27,7 +27,7 @@ import { SaveReason } from 'vs/workbench/common/editor'; import { countWords } from 'vs/workbench/contrib/chat/common/chatWordCounter'; import { HunkInformation, Session, HunkState } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSession'; import { InlineChatZoneWidget } from './inlineChatZoneWidget'; -import { ACTION_TOGGLE_DIFF, CTX_INLINE_CHAT_CHANGE_HAS_DIFF, CTX_INLINE_CHAT_CHANGE_SHOWS_DIFF, CTX_INLINE_CHAT_DOCUMENT_CHANGED, InlineChatConfigKeys, MENU_INLINE_CHAT_ZONE, minimapInlineChatDiffInserted, overviewRulerInlineChatDiffInserted } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; +import { CTX_INLINE_CHAT_CHANGE_HAS_DIFF, CTX_INLINE_CHAT_CHANGE_SHOWS_DIFF, CTX_INLINE_CHAT_DOCUMENT_CHANGED, InlineChatConfigKeys, MENU_INLINE_CHAT_ZONE, minimapInlineChatDiffInserted, overviewRulerInlineChatDiffInserted } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { assertType } from 'vs/base/common/types'; import { IModelService } from 'vs/editor/common/services/model'; import { performAsyncTextEdit, asProgressiveEdit } from './utils'; @@ -43,9 +43,6 @@ import { generateUuid } from 'vs/base/common/uuid'; import { MenuWorkbenchButtonBar } from 'vs/platform/actions/browser/buttonbar'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; import { Iterable } from 'vs/base/common/iterator'; -import { ConflictActionsFactory, IContentWidgetAction } from 'vs/workbench/contrib/mergeEditor/browser/view/conflictActions'; -import { observableValue } from 'vs/base/common/observable'; -import { IMenuService, MenuItemAction } from 'vs/platform/actions/common/actions'; export interface IEditObserver { start(): void; @@ -219,10 +216,8 @@ type HunkDisplayData = { decorationIds: string[]; - diffViewZoneId: string | undefined; - diffViewZone: IViewZone; - - lensActionsViewZoneIds?: string[]; + viewZoneId: string | undefined; + viewZone: IViewZone; distance: number; position: Position; @@ -262,7 +257,6 @@ export class LiveStrategy extends EditModeStrategy { private readonly _ctxCurrentChangeShowsDiff: IContextKey; private readonly _progressiveEditingDecorations: IEditorDecorationsCollection; - private readonly _lensActionsFactory: ConflictActionsFactory; private _editCount: number = 0; constructor( @@ -274,8 +268,6 @@ export class LiveStrategy extends EditModeStrategy { @IEditorWorkerService protected readonly _editorWorkerService: IEditorWorkerService, @IAccessibilityService private readonly _accessibilityService: IAccessibilityService, @IConfigurationService private readonly _configService: IConfigurationService, - @IMenuService private readonly _menuService: IMenuService, - @IContextKeyService private readonly _contextService: IContextKeyService, @ITextFileService textFileService: ITextFileService, @IInstantiationService instaService: IInstantiationService ) { @@ -284,7 +276,6 @@ export class LiveStrategy extends EditModeStrategy { this._ctxCurrentChangeShowsDiff = CTX_INLINE_CHAT_CHANGE_SHOWS_DIFF.bindTo(contextKeyService); this._progressiveEditingDecorations = this._editor.createDecorationsCollection(); - this._lensActionsFactory = this._store.add(new ConflictActionsFactory(this._editor)); } @@ -496,95 +487,45 @@ export class LiveStrategy extends EditModeStrategy { afterLineNumber: -1, heightInLines: result.heightInLines, domNode, - ordinal: 50000 + 2 // more than https://github.com/microsoft/vscode/blob/bf52a5cfb2c75a7327c9adeaefbddc06d529dcad/src/vs/workbench/contrib/inlineChat/browser/inlineChatZoneWidget.ts#L42 + ordinal: 50000 + 1 // more than https://github.com/microsoft/vscode/blob/bf52a5cfb2c75a7327c9adeaefbddc06d529dcad/src/vs/workbench/contrib/inlineChat/browser/inlineChatZoneWidget.ts#L42 }; const toggleDiff = () => { const scrollState = StableEditorScrollState.capture(this._editor); changeDecorationsAndViewZones(this._editor, (_decorationsAccessor, viewZoneAccessor) => { assertType(data); - if (!data.diffViewZoneId) { + if (!data.viewZoneId) { const [hunkRange] = hunkData.getRangesN(); viewZoneData.afterLineNumber = hunkRange.startLineNumber - 1; - data.diffViewZoneId = viewZoneAccessor.addZone(viewZoneData); + data.viewZoneId = viewZoneAccessor.addZone(viewZoneData); overlay?.updateExtraTop(result.heightInLines); } else { - viewZoneAccessor.removeZone(data.diffViewZoneId!); + viewZoneAccessor.removeZone(data.viewZoneId!); overlay?.updateExtraTop(0); - data.diffViewZoneId = undefined; + data.viewZoneId = undefined; } }); - this._ctxCurrentChangeShowsDiff.set(typeof data?.diffViewZoneId === 'string'); + this._ctxCurrentChangeShowsDiff.set(typeof data?.viewZoneId === 'string'); scrollState.restore(this._editor); }; - const overlay = this._showOverlayToolbar && false + const overlay = this._showOverlayToolbar ? this._instaService.createInstance(InlineChangeOverlay, this._editor, hunkData) : undefined; - - let lensActions: DisposableStore | undefined; - const lensActionsViewZoneIds: string[] = []; - - if (this._showOverlayToolbar && hunkData.getState() === HunkState.Pending) { - - lensActions = new DisposableStore(); - - const menu = this._menuService.createMenu(MENU_INLINE_CHAT_ZONE, this._contextService); - const makeActions = () => { - const actions: IContentWidgetAction[] = []; - const tuples = menu.getActions(); - for (const [, group] of tuples) { - for (const item of group) { - if (item instanceof MenuItemAction) { - - let text = item.label; - - if (item.id === ACTION_TOGGLE_DIFF) { - text = item.checked ? 'Hide Changes' : 'Show Changes'; - } else if (ThemeIcon.isThemeIcon(item.item.icon)) { - text = `$(${item.item.icon.id}) ${text}`; - } - - actions.push({ - text, - tooltip: item.tooltip, - action: async () => item.run(), - }); - } - } - } - return actions; - }; - - const obs = observableValue(this, makeActions()); - lensActions.add(menu.onDidChange(() => obs.set(makeActions(), undefined))); - lensActions.add(menu); - - lensActions.add(this._lensActionsFactory.createWidget(viewZoneAccessor, - hunkRanges[0].startLineNumber - 1, - obs, - lensActionsViewZoneIds - )); - } - const remove = () => { changeDecorationsAndViewZones(this._editor, (decorationsAccessor, viewZoneAccessor) => { assertType(data); for (const decorationId of data.decorationIds) { decorationsAccessor.removeDecoration(decorationId); } - if (data.diffViewZoneId) { - viewZoneAccessor.removeZone(data.diffViewZoneId); + if (data.viewZoneId) { + viewZoneAccessor.removeZone(data.viewZoneId); } data.decorationIds = []; - data.diffViewZoneId = undefined; - - data.lensActionsViewZoneIds?.forEach(viewZoneAccessor.removeZone); - data.lensActionsViewZoneIds = undefined; + data.viewZoneId = undefined; }); - lensActions?.dispose(); overlay?.dispose(); }; @@ -607,9 +548,8 @@ export class LiveStrategy extends EditModeStrategy { data = { hunk: hunkData, decorationIds, - diffViewZoneId: '', - diffViewZone: viewZoneData, - lensActionsViewZoneIds, + viewZoneId: '', + viewZone: viewZoneData, distance: myDistance, position: hunkRanges[0].getStartPosition().delta(-1), acceptHunk, diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts index e0423850..252534e6 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts @@ -96,9 +96,9 @@ export class InlineChatWidget { h('div.accessibleViewer@accessibleViewer'), h('div.status@status', [ h('div.label.info.hidden@infoLabel'), - h('div.actions.hidden@toolbar1'), + h('div.actions.button-style.hidden@toolbar1'), h('div.label.status.hidden@statusLabel'), - h('div.actions.secondary.hidden@toolbar2'), + h('div.actions.button-style.hidden@toolbar2'), ]), ] ); @@ -385,7 +385,7 @@ export class InlineChatWidget { } protected _getExtraHeight(): number { - return 2 /*border*/ + 4 /*shadow*/; + return 4 /* padding */ + 2 /*border*/ + 4 /*shadow*/; } get value(): string { diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatZoneWidget.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatZoneWidget.ts index 42612f49..1ce7b490 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatZoneWidget.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatZoneWidget.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { addDisposableListener, Dimension } from 'vs/base/browser/dom'; import * as aria from 'vs/base/browser/ui/aria/aria'; -import { MutableDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { toDisposable } from 'vs/base/common/lifecycle'; import { assertType } from 'vs/base/common/types'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { EditorLayoutInfo, EditorOption } from 'vs/editor/common/config/editorOptions'; @@ -29,7 +29,6 @@ export class InlineChatZoneWidget extends ZoneWidget { readonly widget: EditorBasedInlineChatWidget; - private readonly _scrollUp = this._disposables.add(new ScrollUpState(this.editor)); private readonly _ctxCursorPosition: IContextKey<'above' | 'below' | ''>; private _dimension?: Dimension; @@ -166,7 +165,6 @@ export class InlineChatZoneWidget extends ZoneWidget { this.widget.focus(); revealZone(); - this._scrollUp.enable(); } override updatePositionAndHeight(position: Position): void { @@ -188,15 +186,14 @@ export class InlineChatZoneWidget extends ZoneWidget { return isResponseVM(candidate) && candidate.response.value.length > 0; }); - if (hasResponse && zoneTop < scrollTop || this._scrollUp.didScrollUp) { + if (hasResponse && zoneTop < scrollTop) { // don't reveal the zone if it is already out of view (unless we are still getting ready) - // or if an outside scroll-up happened (e.g the user scrolled up to see the new content) - return this._scrollUp.runIgnored(() => { + return () => { scrollState.restore(this.editor); - }); + }; } - return this._scrollUp.runIgnored(() => { + return () => { scrollState.restore(this.editor); const scrollTop = this.editor.getScrollTop(); @@ -219,7 +216,7 @@ export class InlineChatZoneWidget extends ZoneWidget { this._logService.trace('[IE] REVEAL zone', { zoneTop, lineTop, lineBottom, scrollTop, newScrollTop, forceScrollTop }); this.editor.setScrollTop(newScrollTop, ScrollType.Immediate); } - }); + }; } protected override revealRange(range: Range, isLastLine: boolean): void { @@ -232,7 +229,6 @@ export class InlineChatZoneWidget extends ZoneWidget { override hide(): void { const scrollState = StableEditorBottomScrollState.capture(this.editor); - this._scrollUp.disable(); this._ctxCursorPosition.reset(); this.widget.reset(); this.widget.chatWidget.setVisible(false); @@ -241,54 +237,3 @@ export class InlineChatZoneWidget extends ZoneWidget { scrollState.restore(this.editor); } } - -class ScrollUpState { - - private _lastScrollTop: number = this._editor.getScrollTop(); - private _didScrollUp?: boolean; - private _ignoreEvents = false; - - private readonly _listener = new MutableDisposable(); - - constructor(private readonly _editor: ICodeEditor) { } - - dispose(): void { - this._listener.dispose(); - } - - enable(): void { - this._didScrollUp = undefined; - this._listener.value = this._editor.onDidScrollChange(e => { - if (!e.scrollTopChanged || this._ignoreEvents) { - return; - } - const currentScrollTop = e.scrollTop; - if (currentScrollTop > this._lastScrollTop) { - this._listener.clear(); - this._didScrollUp = true; - } - this._lastScrollTop = currentScrollTop; - }); - } - - disable(): void { - this._listener.clear(); - this._didScrollUp = undefined; - } - - runIgnored(callback: () => void): () => void { - return () => { - this._ignoreEvents = true; - try { - return callback(); - } finally { - this._ignoreEvents = false; - } - }; - } - - get didScrollUp(): boolean | undefined { - return this._didScrollUp; - } - -} diff --git a/src/vs/workbench/contrib/inlineChat/browser/media/inlineChat.css b/src/vs/workbench/contrib/inlineChat/browser/media/inlineChat.css index d8fac566..f209158f 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/media/inlineChat.css +++ b/src/vs/workbench/contrib/inlineChat/browser/media/inlineChat.css @@ -20,7 +20,7 @@ } .monaco-workbench .inline-chat .chat-widget .interactive-session .interactive-input-part { - padding: 2px 6px 0 6px; + padding: 4px 6px 0 6px; } .monaco-workbench .inline-chat .chat-widget .interactive-session .interactive-input-part .interactive-execute-toolbar { @@ -32,12 +32,6 @@ border-radius: 2px; } - -.monaco-workbench .inline-chat .chat-widget .interactive-session .interactive-input-part .interactive-input-followups .interactive-session-followups { - margin: 2px 0 0 4px; -} - - .monaco-workbench .inline-chat .chat-widget .interactive-session .interactive-list { padding: 4px 0 0 0; } @@ -76,15 +70,7 @@ display: flex; justify-content: space-between; align-items: center; - padding-left: 6px; - padding-right: 6px; -} - -.monaco-workbench .inline-chat > .status { - .label, - .actions { - padding-top: 6px; - } + padding: 6px 6px 0 6px } .monaco-workbench .inline-chat .status .actions.hidden { @@ -106,8 +92,7 @@ .monaco-workbench .inline-chat .status .label.status { margin-left: auto; - padding-right: 6px; - padding-left: 6px; + padding: 0 6px; } .monaco-workbench .inline-chat .status .label.hidden, @@ -171,16 +156,6 @@ gap: 4px; } -.monaco-workbench .inline-chat .status .actions.secondary { - display: none; -} - -.monaco-workbench .inline-chat .status:hover .actions.secondary, -.monaco-workbench .inline-chat:focus .status .actions.secondary, -.monaco-workbench .inline-chat .status:focus-within .actions.secondary { - display: inherit; -} - .monaco-workbench .inline-chat-diff-overlay { .monaco-button { diff --git a/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts b/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts index f4c87af0..d656e6c3 100644 --- a/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts +++ b/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts @@ -116,7 +116,6 @@ export const CTX_INLINE_CHAT_RESPONSE_TYPE = new RawContextKey() { - override get id() { return 'one'; } - }; - session.markModelVersion(fakeRequest); - - assert.strictEqual(editor.getModel().getLineCount(), 15); - - await makeEditAsAi([EditOperation.replace(new Range(7, 1, 7, Number.MAX_SAFE_INTEGER), `error = error.replace( - /See https:\/\/github\.com\/Squirrel\/Squirrel\.Mac\/issues\/182 for more information/, - 'This might mean the application was put on quarantine by macOS. See [this link](https://github.com/microsoft/vscode/issues/7426#issuecomment-425093469) for more information' - );`)]); - - assert.strictEqual(editor.getModel().getLineCount(), 18); - - // called when a response errors out - await session.undoChangesUntil(fakeRequest.id); - await session.hunkData.recompute({ applied: 0, sha1: 'fakeSha1' }, undefined); - - assert.strictEqual(editor.getModel().getValue(), origValue); - - session.hunkData.discardAll(); // called when dimissing the session - assert.strictEqual(editor.getModel().getValue(), origValue); - }); - - test('Apply Code\'s preview should be easier to undo/esc #7537', async function () { - model.setValue(`export function fib(n) { - if (n <= 0) return 0; - if (n === 1) return 0; - if (n === 2) return 1; - return fib(n - 1) + fib(n - 2); -}`); - const session = await inlineChatSessionService.createSession(editor, { editMode: EditMode.Live }, CancellationToken.None); - assertType(session); - - await makeEditAsAi([EditOperation.replace(new Range(5, 1, 6, Number.MAX_SAFE_INTEGER), ` - let a = 0, b = 1, c; - for (let i = 3; i <= n; i++) { - c = a + b; - a = b; - b = c; - } - return b; -}`)]); - - assert.strictEqual(session.hunkData.size, 1); - assert.strictEqual(session.hunkData.pending, 1); - assert.ok(session.hunkData.getInfo().every(d => d.getState() === HunkState.Pending)); - - await assertSnapshot(editor.getModel().getValue(), { name: '1' }); - - await model.undo(); - await assertSnapshot(editor.getModel().getValue(), { name: '2' }); - - // overlapping edits (even UNDO) mark edits as accepted - assert.strictEqual(session.hunkData.size, 1); - assert.strictEqual(session.hunkData.pending, 0); - assert.ok(session.hunkData.getInfo().every(d => d.getState() === HunkState.Accepted)); - - // no further change when discarding - session.hunkData.discardAll(); // CANCEL - await assertSnapshot(editor.getModel().getValue(), { name: '2' }); - }); - }); diff --git a/src/vs/workbench/contrib/mergeEditor/browser/view/conflictActions.ts b/src/vs/workbench/contrib/mergeEditor/browser/view/conflictActions.ts index 3290793d..8d88a891 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/view/conflictActions.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/view/conflictActions.ts @@ -54,8 +54,8 @@ export class ConflictActionsFactory extends Disposable { newStyle += `${this._styleClassName} { font-family: var(${fontFamilyVar}), ${EDITOR_FONT_DEFAULTS.fontFamily}}`; } this._styleElement.textContent = newStyle; - this._editor.getContainerDomNode().style?.setProperty(fontFamilyVar, fontFamily ?? 'inherit'); - this._editor.getContainerDomNode().style?.setProperty(fontFeaturesVar, editorFontInfo.fontFeatureSettings); + this._editor.getContainerDomNode().style.setProperty(fontFamilyVar, fontFamily ?? 'inherit'); + this._editor.getContainerDomNode().style.setProperty(fontFeaturesVar, editorFontInfo.fontFeatureSettings); } private _getLayoutInfo() { diff --git a/src/vs/workbench/contrib/mergeEditor/browser/view/fixedZoneWidget.ts b/src/vs/workbench/contrib/mergeEditor/browser/view/fixedZoneWidget.ts index 27a48ee2..3175a1ca 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/view/fixedZoneWidget.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/view/fixedZoneWidget.ts @@ -33,7 +33,6 @@ export abstract class FixedZoneWidget extends Disposable { domNode: document.createElement('div'), afterLineNumber: afterLineNumber, heightInPx: height, - ordinal: 50000 + 1, onComputedHeight: (height) => { this.widgetDomNode.style.height = `${height}px`; }, diff --git a/src/vs/workbench/contrib/notebook/browser/diff/diffComponents.ts b/src/vs/workbench/contrib/notebook/browser/diff/diffComponents.ts index a8f2c46c..a2adaf3b 100644 --- a/src/vs/workbench/contrib/notebook/browser/diff/diffComponents.ts +++ b/src/vs/workbench/contrib/notebook/browser/diff/diffComponents.ts @@ -1074,7 +1074,7 @@ export class DeletedElement extends SingleSideDiffElement { layout(state: IDiffElementLayoutState) { DOM.scheduleAtNextAnimationFrame(DOM.getWindow(this._diffEditorContainer), () => { - if ((state.editorHeight || state.outerWidth) && this._editor) { + if (state.editorHeight || state.outerWidth) { this._editorContainer.style.height = `${this.cell.layoutInfo.editorHeight}px`; this._editor.layout({ width: this.cell.getComputedCellContainerWidth(this.notebookEditor.getLayoutInfo(), false, false), @@ -1254,7 +1254,7 @@ export class InsertElement extends SingleSideDiffElement { layout(state: IDiffElementLayoutState) { DOM.scheduleAtNextAnimationFrame(DOM.getWindow(this._diffEditorContainer), () => { - if ((state.editorHeight || state.outerWidth) && this._editor) { + if (state.editorHeight || state.outerWidth) { this._editorContainer.style.height = `${this.cell.layoutInfo.editorHeight}px`; this._editor.layout({ width: this.cell.getComputedCellContainerWidth(this.notebookEditor.getLayoutInfo(), false, false), @@ -1644,7 +1644,7 @@ export class ModifiedElement extends AbstractElementRenderer { { updateInfoRendering: () => renderSourceEditor(), checkIfModified: (cell) => { - return cell.modified?.textModel.getTextBufferHash() !== cell.original?.textModel.getTextBufferHash() ? { reason: undefined } : false; + return cell.modified?.textModel.getValue() !== cell.original?.textModel.getValue() ? { reason: undefined } : false; }, getFoldingState: (cell) => cell.cellFoldingState, updateFoldingState: (cell, state) => cell.cellFoldingState = state, @@ -1660,7 +1660,7 @@ export class ModifiedElement extends AbstractElementRenderer { const scopedContextKeyService = this.contextKeyService.createScoped(this.templateData.inputToolbarContainer); this._register(scopedContextKeyService); const inputChanged = NOTEBOOK_DIFF_CELL_INPUT.bindTo(scopedContextKeyService); - inputChanged.set(this.cell.modified.textModel.getTextBufferHash() !== this.cell.original.textModel.getTextBufferHash()); + inputChanged.set(this.cell.modified.textModel.getValue() !== this.cell.original.textModel.getValue()); const ignoreWhitespace = NOTEBOOK_DIFF_CELL_IGNORE_WHITESPACE.bindTo(scopedContextKeyService); const ignore = this.textConfigurationService.getValue(this.cell.modified.uri, 'diffEditor.ignoreTrimWhitespace'); @@ -1675,7 +1675,7 @@ export class ModifiedElement extends AbstractElementRenderer { const refreshToolbar = () => { const ignore = this.textConfigurationService.getValue(this.cell.modified.uri, 'diffEditor.ignoreTrimWhitespace'); ignoreWhitespace.set(ignore); - const hasChanges = this.cell.modified.textModel.getTextBufferHash() !== this.cell.original.textModel.getTextBufferHash(); + const hasChanges = this.cell.modified.textModel.getValue() !== this.cell.original.textModel.getValue(); inputChanged.set(hasChanges); if (hasChanges) { diff --git a/src/vs/workbench/contrib/notebook/browser/diff/diffElementViewModel.ts b/src/vs/workbench/contrib/notebook/browser/diff/diffElementViewModel.ts index ad8e1461..9d0cf11b 100644 --- a/src/vs/workbench/contrib/notebook/browser/diff/diffElementViewModel.ts +++ b/src/vs/workbench/contrib/notebook/browser/diff/diffElementViewModel.ts @@ -222,7 +222,7 @@ export abstract class DiffElementCellViewModelBase extends DiffElementViewModelB layoutState: CellLayoutState.Uninitialized }; - this.cellFoldingState = modified?.getTextBufferHash() !== original?.getTextBufferHash() ? PropertyFoldingState.Expanded : PropertyFoldingState.Collapsed; + this.cellFoldingState = modified?.textModel?.getValue() !== original?.textModel?.getValue() ? PropertyFoldingState.Expanded : PropertyFoldingState.Collapsed; this.metadataFoldingState = PropertyFoldingState.Collapsed; this.outputFoldingState = PropertyFoldingState.Collapsed; diff --git a/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffViewModel.ts b/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffViewModel.ts index dd2b3f12..0c82062b 100644 --- a/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffViewModel.ts +++ b/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffViewModel.ts @@ -441,12 +441,16 @@ function createDiffViewModels(instantiationService: IInstantiationService, confi ); } case 'unchanged': { + const originalCell = originalModel.cells[diff.originalCellIndex]; + const modifiedCell = modifiedModel.cells[diff.modifiedCellIndex]; + const type = (originalCell.textModel?.getValue() !== modifiedCell.textModel?.getValue()) ? 'modified' : 'unchanged'; return new SideBySideDiffElementViewModel( model.modified.notebook, model.original.notebook, - originalModel.cells[diff.originalCellIndex], - modifiedModel.cells[diff.modifiedCellIndex], - 'unchanged', eventDispatcher, + originalCell, + modifiedCell, + type, + eventDispatcher, initData, notebookService ); diff --git a/src/vs/workbench/contrib/notebook/common/model/notebookCellTextModel.ts b/src/vs/workbench/contrib/notebook/common/model/notebookCellTextModel.ts index e83261cf..3d7310c6 100644 --- a/src/vs/workbench/contrib/notebook/common/model/notebookCellTextModel.ts +++ b/src/vs/workbench/contrib/notebook/common/model/notebookCellTextModel.ts @@ -419,10 +419,6 @@ export class NotebookCellTextModel extends Disposable implements ICell { return false; } - if (this.outputs.length !== b.outputs.length) { - return false; - } - if (this.getTextLength() !== b.getTextLength()) { return false; } diff --git a/src/vs/workbench/contrib/scm/browser/scmHistoryViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmHistoryViewPane.ts index 69060d9d..ba24fdfa 100644 --- a/src/vs/workbench/contrib/scm/browser/scmHistoryViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmHistoryViewPane.ts @@ -314,7 +314,7 @@ class HistoryItemRenderer implements ITreeRenderer labels.includes(l.title)); - if (historyItemLabels.length > 0) { + if (historyItemLabels) { const historyItemGroupLocalColor = colorTheme.getColor(historyItemGroupLocal); const historyItemGroupRemoteColor = colorTheme.getColor(historyItemGroupRemote); const historyItemGroupBaseColor = colorTheme.getColor(historyItemGroupBase); diff --git a/src/vs/workbench/contrib/search/browser/notebookSearch/notebookSearchService.ts b/src/vs/workbench/contrib/search/browser/notebookSearch/notebookSearchService.ts index 5265cf2c..4ef1cdae 100644 --- a/src/vs/workbench/contrib/search/browser/notebookSearch/notebookSearchService.ts +++ b/src/vs/workbench/contrib/search/browser/notebookSearch/notebookSearchService.ts @@ -117,21 +117,22 @@ export class NotebookSearchService implements INotebookSearchService { } private async doesFileExist(includes: string[], folderQueries: IFolderQuery[], token: CancellationToken): Promise { - const promises: Promise[] = includes.map(async includePattern => { + const promises: Promise[] = includes.map(async includePattern => { const query = this.queryBuilder.file(folderQueries.map(e => e.folder), { includePattern: includePattern.startsWith('/') ? includePattern : '**/' + includePattern, // todo: find cleaner way to ensure that globs match all appropriate filetypes - exists: true, - onlyFileScheme: true, + exists: true }); return this.searchService.fileSearch( query, token ).then((ret) => { - return !!ret.limitHit; + if (!ret.limitHit) { + throw Error('File not found'); + } }); }); - return Promise.any(promises); + return Promise.any(promises).then(() => true).catch(() => false); } private async getClosedNotebookResults(textQuery: ITextQuery, scannedFiles: ResourceSet, token: CancellationToken): Promise { diff --git a/src/vs/workbench/contrib/terminal/browser/media/shellIntegration-bash.sh b/src/vs/workbench/contrib/terminal/browser/media/shellIntegration-bash.sh index fbeaa22f..92c0e07f 100644 --- a/src/vs/workbench/contrib/terminal/browser/media/shellIntegration-bash.sh +++ b/src/vs/workbench/contrib/terminal/browser/media/shellIntegration-bash.sh @@ -328,10 +328,10 @@ __vsc_restore_exit_code() { __vsc_prompt_cmd_original() { __vsc_status="$?" - builtin local cmd __vsc_restore_exit_code "${__vsc_status}" # Evaluate the original PROMPT_COMMAND similarly to how bash would normally # See https://unix.stackexchange.com/a/672843 for technique + builtin local cmd for cmd in "${__vsc_original_prompt_command[@]}"; do eval "${cmd:-}" done diff --git a/src/vs/workbench/contrib/terminal/browser/media/shellIntegration.ps1 b/src/vs/workbench/contrib/terminal/browser/media/shellIntegration.ps1 index 444df384..c624a5f0 100644 --- a/src/vs/workbench/contrib/terminal/browser/media/shellIntegration.ps1 +++ b/src/vs/workbench/contrib/terminal/browser/media/shellIntegration.ps1 @@ -172,9 +172,9 @@ function Set-MappedKeyHandler { function Get-KeywordCompletionResult( $Keyword, - $Description = $Keyword + $Description = $null ) { - [System.Management.Automation.CompletionResult]::new($Keyword, $Keyword, [System.Management.Automation.CompletionResultType]::Keyword, $Description) + [System.Management.Automation.CompletionResult]::new($Keyword, $Keyword, [System.Management.Automation.CompletionResultType]::Keyword, $null -ne $Description ? $Description : $Keyword) } function Set-MappedKeyHandlers { @@ -360,7 +360,7 @@ function Send-Completions { # completions are consistent regardless of where it was requested elseif ($lastWord -match '[/\\]') { $lastSlashIndex = $completionPrefix.LastIndexOfAny(@('/', '\')) - if ($lastSlashIndex -ne -1 -and $lastSlashIndex -lt $cursorIndex) { + if ($lastSlashIndex -ne -1 && $lastSlashIndex -lt $cursorIndex) { $newCursorIndex = $lastSlashIndex + 1 $completionPrefix = $completionPrefix.Substring(0, $newCursorIndex) $prefixCursorDelta = $cursorIndex - $newCursorIndex @@ -388,9 +388,9 @@ function Send-Completions { if ($completions.CompletionMatches.Count -gt 0 -and $completions.CompletionMatches.Where({ $_.ResultType -eq 3 -or $_.ResultType -eq 4 })) { # Add `../ relative to the top completion $firstCompletion = $completions.CompletionMatches[0] - if ($firstCompletion.CompletionText.StartsWith("..$([System.IO.Path]::DirectorySeparatorChar)")) { - if ($completionPrefix -match "(\.\.\$([System.IO.Path]::DirectorySeparatorChar))+") { - $parentDir = "$($matches[0])..$([System.IO.Path]::DirectorySeparatorChar)" + if ($firstCompletion.CompletionText.StartsWith('../')) { + if ($completionPrefix -match '(\.\.\/)+') { + $parentDir = "$($matches[0])../" $currentPath = Split-Path -Parent $firstCompletion.ToolTip try { $parentDirPath = Split-Path -Parent $currentPath @@ -430,7 +430,7 @@ function Send-Completions { # completions are consistent regardless of where it was requested if ($completionPrefix -match '[/\\]') { $lastSlashIndex = $completionPrefix.LastIndexOfAny(@('/', '\')) - if ($lastSlashIndex -ne -1 -and $lastSlashIndex -lt $cursorIndex) { + if ($lastSlashIndex -ne -1 && $lastSlashIndex -lt $cursorIndex) { $newCursorIndex = $lastSlashIndex + 1 $completionPrefix = $completionPrefix.Substring(0, $newCursorIndex) $prefixCursorDelta = $cursorIndex - $newCursorIndex diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index ff8607f6..f38b9bf8 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -2305,7 +2305,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { async handleMouseEvent(event: MouseEvent, contextMenu: IMenu): Promise<{ cancelContextMenu: boolean } | void> { // Don't handle mouse event if it was on the scroll bar - if (dom.isHTMLElement(event.target) && (event.target.classList.contains('scrollbar') || event.target.classList.contains('slider'))) { + if (dom.isHTMLElement(event.target) && event.target.classList.contains('scrollbar')) { return { cancelContextMenu: true }; } diff --git a/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminal.suggest.contribution.ts b/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminal.suggest.contribution.ts index 433de4ee..9ef44f9b 100644 --- a/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminal.suggest.contribution.ts +++ b/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminal.suggest.contribution.ts @@ -9,7 +9,6 @@ import { AutoOpenBarrier } from 'vs/base/common/async'; import { Event } from 'vs/base/common/event'; import { KeyCode } from 'vs/base/common/keyCodes'; import { DisposableStore, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle'; -import { isWindows } from 'vs/base/common/platform'; import { localize2 } from 'vs/nls'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ContextKeyExpr, IContextKey, IContextKeyService, IReadableSet } from 'vs/platform/contextkey/common/contextkey'; @@ -171,20 +170,16 @@ class TerminalSuggestContribution extends DisposableStore implements ITerminalCo // If completions are requested, pause and queue input events until completions are // received. This fixing some problems in PowerShell, particularly enter not executing - // when typing quickly and some characters being printed twice. On Windows this isn't - // needed because inputs are _not_ echoed when not handled immediately. - // TODO: This should be based on the OS of the pty host, not the client - if (!isWindows) { - let barrier: AutoOpenBarrier | undefined; - this.add(addon.onDidRequestCompletions(() => { - barrier = new AutoOpenBarrier(2000); - this._instance.pauseInputEvents(barrier); - })); - this.add(addon.onDidReceiveCompletions(() => { - barrier?.open(); - barrier = undefined; - })); - } + // when typing quickly and some characters being printed twice. + let barrier: AutoOpenBarrier | undefined; + this.add(addon.onDidRequestCompletions(() => { + barrier = new AutoOpenBarrier(2000); + this._instance.pauseInputEvents(barrier); + })); + this.add(addon.onDidReceiveCompletions(() => { + barrier?.open(); + barrier = undefined; + })); } } } diff --git a/src/vs/workbench/contrib/testing/browser/testResultsView/testMessageStack.ts b/src/vs/workbench/contrib/testing/browser/testResultsView/testMessageStack.ts index c6c396e5..cb133b4b 100644 --- a/src/vs/workbench/contrib/testing/browser/testResultsView/testMessageStack.ts +++ b/src/vs/workbench/contrib/testing/browser/testResultsView/testMessageStack.ts @@ -30,10 +30,6 @@ export class TestResultStackWidget extends Disposable { )); } - public collapseAll() { - this.widget.collapseAll(); - } - public update(messageFrame: AnyStackFrame, stack: ITestMessageStackFrame[]) { this.widget.setFrames([messageFrame, ...stack.map(frame => new CallStackFrame( frame.label, diff --git a/src/vs/workbench/contrib/testing/browser/testResultsView/testResultsSubject.ts b/src/vs/workbench/contrib/testing/browser/testResultsView/testResultsSubject.ts index e61463bc..9fa86330 100644 --- a/src/vs/workbench/contrib/testing/browser/testResultsView/testResultsSubject.ts +++ b/src/vs/workbench/contrib/testing/browser/testResultsView/testResultsSubject.ts @@ -22,9 +22,6 @@ interface ISubjectCommon { controllerId: string; } -export const inspectSubjectHasStack = (subject: InspectSubject | undefined) => - subject instanceof MessageSubject && !!subject.stack?.length; - export class MessageSubject implements ISubjectCommon { public readonly test: ITestItem; public readonly message: ITestMessage; diff --git a/src/vs/workbench/contrib/testing/browser/testResultsView/testResultsTree.ts b/src/vs/workbench/contrib/testing/browser/testResultsView/testResultsTree.ts index 6be1cd3c..a65a1057 100644 --- a/src/vs/workbench/contrib/testing/browser/testResultsView/testResultsTree.ts +++ b/src/vs/workbench/contrib/testing/browser/testResultsView/testResultsTree.ts @@ -838,21 +838,22 @@ class TreeActionsProvider { } if (element instanceof TestMessageElement) { - id = MenuId.TestMessageContext; - contextKeys.push([TestingContextKeys.testMessageContext.key, element.contextValue]); - primary.push(new Action( - 'testing.outputPeek.goToTest', - localize('testing.goToTest', "Go to Test"), + 'testing.outputPeek.goToFile', + localize('testing.goToFile', "Go to Source"), ThemeIcon.asClassName(Codicon.goToFile), undefined, () => this.commandService.executeCommand('vscode.revealTest', element.test.item.extId), )); + } + if (element instanceof TestMessageElement) { + id = MenuId.TestMessageContext; + contextKeys.push([TestingContextKeys.testMessageContext.key, element.contextValue]); if (this.showRevealLocationOnMessages && element.location) { primary.push(new Action( 'testing.outputPeek.goToError', - localize('testing.goToError', "Go to Error"), + localize('testing.goToError', "Go to Source"), ThemeIcon.asClassName(Codicon.goToFile), undefined, () => this.editorService.openEditor({ diff --git a/src/vs/workbench/contrib/testing/browser/testResultsView/testResultsViewContent.ts b/src/vs/workbench/contrib/testing/browser/testResultsView/testResultsViewContent.ts index 67f9f05b..db80e2c6 100644 --- a/src/vs/workbench/contrib/testing/browser/testResultsView/testResultsViewContent.ts +++ b/src/vs/workbench/contrib/testing/browser/testResultsView/testResultsViewContent.ts @@ -322,13 +322,6 @@ export class TestResultsViewContent extends Disposable { }); } - /** - * Collapses all displayed stack frames. - */ - public collapseStack() { - this.callStackWidget.collapseAll(); - } - private getCallFrames(subject: InspectSubject) { if (!(subject instanceof MessageSubject)) { return undefined; diff --git a/src/vs/workbench/contrib/testing/browser/testing.contribution.ts b/src/vs/workbench/contrib/testing/browser/testing.contribution.ts index 8caac4a2..e422256e 100644 --- a/src/vs/workbench/contrib/testing/browser/testing.contribution.ts +++ b/src/vs/workbench/contrib/testing/browser/testing.contribution.ts @@ -25,7 +25,7 @@ import { testingResultsIcon, testingViewIcon } from 'vs/workbench/contrib/testin import { TestCoverageView } from 'vs/workbench/contrib/testing/browser/testCoverageView'; import { TestingDecorationService, TestingDecorations } from 'vs/workbench/contrib/testing/browser/testingDecorations'; import { TestingExplorerView } from 'vs/workbench/contrib/testing/browser/testingExplorerView'; -import { CloseTestPeek, CollapsePeekStack, GoToNextMessageAction, GoToPreviousMessageAction, OpenMessageInEditorAction, TestResultsView, TestingOutputPeekController, TestingPeekOpener, ToggleTestingPeekHistory } from 'vs/workbench/contrib/testing/browser/testingOutputPeek'; +import { CloseTestPeek, GoToNextMessageAction, GoToPreviousMessageAction, OpenMessageInEditorAction, TestResultsView, TestingOutputPeekController, TestingPeekOpener, ToggleTestingPeekHistory } from 'vs/workbench/contrib/testing/browser/testingOutputPeek'; import { TestingProgressTrigger } from 'vs/workbench/contrib/testing/browser/testingProgressUiService'; import { TestingViewPaneContainer } from 'vs/workbench/contrib/testing/browser/testingViewPaneContainer'; import { testingConfiguration } from 'vs/workbench/contrib/testing/common/configuration'; @@ -136,7 +136,6 @@ registerAction2(GoToPreviousMessageAction); registerAction2(GoToNextMessageAction); registerAction2(CloseTestPeek); registerAction2(ToggleTestingPeekHistory); -registerAction2(CollapsePeekStack); Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(TestingContentProvider, LifecyclePhase.Restored); Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(TestingPeekOpener, LifecyclePhase.Eventually); diff --git a/src/vs/workbench/contrib/testing/browser/testingDecorations.ts b/src/vs/workbench/contrib/testing/browser/testingDecorations.ts index 0ff1cc43..7884f77a 100644 --- a/src/vs/workbench/contrib/testing/browser/testingDecorations.ts +++ b/src/vs/workbench/contrib/testing/browser/testingDecorations.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import * as dom from 'vs/base/browser/dom'; -import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { Action, IAction, Separator, SubmenuAction } from 'vs/base/common/actions'; import { equals } from 'vs/base/common/arrays'; import { RunOnceScheduler } from 'vs/base/common/async'; @@ -457,24 +456,11 @@ export class TestingDecorations extends Disposable implements IEditorContributio decorations.syncDecorations(this._currentUri); } })); - - const win = dom.getWindow(editor.getDomNode()); - this._register(dom.addDisposableListener(win, 'keydown', e => { - if (new StandardKeyboardEvent(e).keyCode === KeyCode.Alt && this._currentUri) { - decorations.updateDecorationsAlternateAction(this._currentUri, true); + this._register(this.editor.onKeyDown(e => { + if (e.keyCode === KeyCode.Alt && this._currentUri) { + decorations.updateDecorationsAlternateAction(this._currentUri!, true); } })); - this._register(dom.addDisposableListener(win, 'keyup', e => { - if (new StandardKeyboardEvent(e).keyCode === KeyCode.Alt && this._currentUri) { - decorations.updateDecorationsAlternateAction(this._currentUri, false); - } - })); - this._register(dom.addDisposableListener(win, 'blur', () => { - if (this._currentUri) { - decorations.updateDecorationsAlternateAction(this._currentUri, false); - } - })); - this._register(this.editor.onKeyUp(e => { if (e.keyCode === KeyCode.Alt && this._currentUri) { decorations.updateDecorationsAlternateAction(this._currentUri!, false); diff --git a/src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts b/src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts index 3acce2ec..2bac0cf0 100644 --- a/src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts +++ b/src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts @@ -14,7 +14,6 @@ import { Iterable } from 'vs/base/common/iterator'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { Lazy } from 'vs/base/common/lazy'; import { Disposable, MutableDisposable } from 'vs/base/common/lifecycle'; -import { observableValue } from 'vs/base/common/observable'; import { count } from 'vs/base/common/strings'; import { URI } from 'vs/base/common/uri'; import { ICodeEditor, isCodeEditor } from 'vs/editor/browser/editorBrowser'; @@ -44,7 +43,6 @@ import { ServiceCollection } from 'vs/platform/instantiation/common/serviceColle import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { INotificationService } from 'vs/platform/notification/common/notification'; -import { bindContextKey } from 'vs/platform/observable/common/platformObservableUtils'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; @@ -54,7 +52,7 @@ import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity' import { IViewPaneOptions, ViewPane } from 'vs/workbench/browser/parts/views/viewPane'; import { IViewDescriptorService } from 'vs/workbench/common/views'; import { renderTestMessageAsText } from 'vs/workbench/contrib/testing/browser/testMessageColorizer'; -import { InspectSubject, MessageSubject, TaskSubject, TestOutputSubject, inspectSubjectHasStack, mapFindTestMessage } from 'vs/workbench/contrib/testing/browser/testResultsView/testResultsSubject'; +import { InspectSubject, MessageSubject, TaskSubject, TestOutputSubject, mapFindTestMessage } from 'vs/workbench/contrib/testing/browser/testResultsView/testResultsSubject'; import { TestResultsViewContent } from 'vs/workbench/contrib/testing/browser/testResultsView/testResultsViewContent'; import { testingMessagePeekBorder, testingPeekBorder, testingPeekHeaderBackground, testingPeekMessageHeaderBackground } from 'vs/workbench/contrib/testing/browser/theme'; import { AutoOpenPeekViewWhen, TestingConfigKeys, getTestingConfiguration } from 'vs/workbench/contrib/testing/common/configuration'; @@ -500,13 +498,6 @@ export class TestingOutputPeekController extends Disposable implements IEditorCo this.peek.clear(); } - /** - * Collapses all displayed stack frames. - */ - public collapseStack() { - this.peek.value?.collapseStack(); - } - /** * Shows the next message in the peek, if possible. */ @@ -654,14 +645,10 @@ class TestResultsPeek extends PeekViewWidget { private static lastHeightInLines?: number; private readonly visibilityChange = this._disposables.add(new Emitter()); - private readonly _current = observableValue('testPeekCurrent', undefined); private content!: TestResultsViewContent; private scopedContextKeyService!: IContextKeyService; private dimension?: dom.Dimension; - - public get current() { - return this._current.get(); - } + public current?: InspectSubject; constructor( editor: ICodeEditor, @@ -715,14 +702,7 @@ class TestResultsPeek extends PeekViewWidget { protected override _fillHead(container: HTMLElement): void { super._fillHead(container); - const menuContextKeyService = this._disposables.add(this.contextKeyService.createScoped(container)); - this._disposables.add(bindContextKey( - TestingContextKeys.peekHasStack, - menuContextKeyService, - reader => inspectSubjectHasStack(this._current.read(reader)), - )); - - const menu = this.menuService.createMenu(MenuId.TestPeekTitle, menuContextKeyService); + const menu = this.menuService.createMenu(MenuId.TestPeekTitle, this.contextKeyService); const actionBar = this._actionbarWidget!; this._disposables.add(menu.onDidChange(() => { actions.length = 0; @@ -752,7 +732,7 @@ class TestResultsPeek extends PeekViewWidget { */ public setModel(subject: InspectSubject): Promise { if (subject instanceof TaskSubject || subject instanceof TestOutputSubject) { - this._current.set(subject, undefined); + this.current = subject; return this.showInPlace(subject); } @@ -763,14 +743,14 @@ class TestResultsPeek extends PeekViewWidget { return Promise.resolve(); } - this._current.set(subject, undefined); + this.current = subject; if (!revealLocation) { return this.showInPlace(subject); } // If there is a stack we want to display, ensure the default size is large-ish const peekLines = TestResultsPeek.lastHeightInLines || Math.max( - inspectSubjectHasStack(subject) ? Math.ceil(this.getVisibleEditorLines() / 2) : 0, + subject instanceof MessageSubject && subject.stack?.length ? Math.ceil(this.getVisibleEditorLines() / 2) : 0, hintMessagePeekHeight(message) ); @@ -780,13 +760,6 @@ class TestResultsPeek extends PeekViewWidget { return this.showInPlace(subject); } - /** - * Collapses all displayed stack frames. - */ - public collapseStack() { - this.content.collapseStack(); - } - private getVisibleEditorLines() { // note that we don't use the view ranges because we don't want to get // thrown off by large wrapping lines. Being approximate here is okay. @@ -1052,31 +1025,6 @@ export class GoToPreviousMessageAction extends Action2 { } } -export class CollapsePeekStack extends Action2 { - public static readonly ID = 'testing.collapsePeekStack'; - constructor() { - super({ - id: CollapsePeekStack.ID, - title: localize2('testing.collapsePeekStack', 'Collapse Stack Frames'), - icon: Codicon.collapseAll, - category: Categories.Test, - menu: [{ - id: MenuId.TestPeekTitle, - when: TestingContextKeys.peekHasStack, - group: 'navigation', - order: 4, - }], - }); - } - - public override run(accessor: ServicesAccessor) { - const editor = getPeekedEditorFromFocus(accessor.get(ICodeEditorService)); - if (editor) { - TestingOutputPeekController.get(editor)?.collapseStack(); - } - } -} - export class OpenMessageInEditorAction extends Action2 { public static readonly ID = 'testing.openMessageInEditor'; constructor() { diff --git a/src/vs/workbench/contrib/testing/common/testingContextKeys.ts b/src/vs/workbench/contrib/testing/common/testingContextKeys.ts index 1cfd764d..ac303b48 100644 --- a/src/vs/workbench/contrib/testing/common/testingContextKeys.ts +++ b/src/vs/workbench/contrib/testing/common/testingContextKeys.ts @@ -29,7 +29,6 @@ export namespace TestingContextKeys { export const inlineCoverageEnabled = new RawContextKey('testing.inlineCoverageEnabled', false, { type: 'boolean', description: localize('testing.inlineCoverageEnabled', 'Indicates whether inline coverage is shown') }); export const canGoToRelatedCode = new RawContextKey('testing.canGoToRelatedCode', false, { type: 'boolean', description: localize('testing.canGoToRelatedCode', 'Whether a controller implements a capability to find code related to a test') }); export const canGoToRelatedTest = new RawContextKey('testing.canGoToRelatedTest', false, { type: 'boolean', description: localize('testing.canGoToRelatedTest', 'Whether a controller implements a capability to find tests related to code') }); - export const peekHasStack = new RawContextKey('testing.peekHasStack', false, { type: 'boolean', description: localize('testing.peekHasStack', 'Whether the message shown in a peek view has a stack trace') }); export const capabilityToContextKey: { [K in TestRunProfileBitset]: RawContextKey } = { [TestRunProfileBitset.Run]: hasRunnableTests, diff --git a/src/vs/workbench/contrib/userDataProfile/browser/userDataProfilesEditor.ts b/src/vs/workbench/contrib/userDataProfile/browser/userDataProfilesEditor.ts index 17ddb9cb..c471d251 100644 --- a/src/vs/workbench/contrib/userDataProfile/browser/userDataProfilesEditor.ts +++ b/src/vs/workbench/contrib/userDataProfile/browser/userDataProfilesEditor.ts @@ -92,7 +92,6 @@ export class UserDataProfilesEditor extends EditorPane implements IUserDataProfi private profileWidget: ProfileWidget | undefined; private model: UserDataProfilesEditorModel | undefined; - private templates: readonly IProfileTemplateInfo[] = []; constructor( group: IEditorGroup, @@ -208,7 +207,7 @@ export class UserDataProfilesEditor extends EditorPane implements IUserDataProfi actions: { getActions: () => { const actions: IAction[] = []; - if (this.templates.length) { + if (this.model?.templates.length) { actions.push(new SubmenuAction('from.template', localize('from template', "From Template"), this.getCreateFromTemplateActions())); actions.push(new Separator()); } @@ -226,13 +225,15 @@ export class UserDataProfilesEditor extends EditorPane implements IUserDataProfi } private getCreateFromTemplateActions(): IAction[] { - return this.templates.map(template => - new Action( - `template:${template.url}`, - template.name, - undefined, - true, - () => this.createNewProfile(URI.parse(template.url)))); + return this.model + ? this.model.templates.map(template => + new Action( + `template:${template.url}`, + template.name, + undefined, + true, + () => this.createNewProfile(URI.parse(template.url)))) + : []; } private registerListeners(): void { @@ -342,12 +343,9 @@ export class UserDataProfilesEditor extends EditorPane implements IUserDataProfi override async setInput(input: UserDataProfilesEditorInput, options: IEditorOptions | undefined, context: IEditorOpenContext, token: CancellationToken): Promise { await super.setInput(input, options, context, token); this.model = await input.resolve(); - this.model.getTemplates().then(templates => { - this.templates = templates; - if (this.profileWidget) { - this.profileWidget.templates = templates; - } - }); + if (this.profileWidget) { + this.profileWidget.templates = this.model.templates; + } this.updateProfilesList(); this._register(this.model.onDidChange(element => this.updateProfilesList(element))); @@ -712,6 +710,7 @@ class ProfileTreeDataSource implements IAsyncDataSource()); readonly onDidChange = this._onDidChange.event; - private templates: Promise | undefined; + private _templates: IProfileTemplateInfo[] | undefined; + get templates(): readonly IProfileTemplateInfo[] { return this._templates ?? []; } constructor( @IUserDataProfileService private readonly userDataProfileService: IUserDataProfileService, @@ -760,11 +761,9 @@ export class UserDataProfilesEditorModel extends EditorModel { } } - getTemplates(): Promise { - if (!this.templates) { - this.templates = this.userDataProfileManagementService.getBuiltinProfileTemplates(); - } - return this.templates; + override async resolve(): Promise { + await super.resolve(); + this._templates = await this.userDataProfileManagementService.getBuiltinProfileTemplates(); } private createProfileElement(profile: IUserDataProfile): [UserDataProfileElement, DisposableStore] { @@ -772,7 +771,7 @@ export class UserDataProfilesEditorModel extends EditorModel { const activateAction = disposables.add(new Action( 'userDataProfile.activate', - localize('active', "Use this Profile for Current Window"), + localize('active', "Use for Current Window"), ThemeIcon.asClassName(Codicon.check), true, () => this.userDataProfileManagementService.switchProfile(profileElement.profile) @@ -809,16 +808,25 @@ export class UserDataProfilesEditorModel extends EditorModel { () => this.openWindow(profileElement.profile) )); + const useAsNewWindowProfileAction = disposables.add(new Action( + 'userDataProfile.useAsNewWindowProfile', + localize('use as new window', "Use for New Windows"), + undefined, + true, + () => profileElement.toggleNewWindowProfile() + )); + const primaryActions: IAction[] = []; - primaryActions.push(activateAction); primaryActions.push(newWindowAction); + if (!profile.isDefault) { + primaryActions.push(deleteAction); + } const secondaryActions: IAction[] = []; + secondaryActions.push(activateAction); + secondaryActions.push(useAsNewWindowProfileAction); + secondaryActions.push(new Separator()); secondaryActions.push(copyFromProfileAction); secondaryActions.push(exportAction); - if (!profile.isDefault) { - secondaryActions.push(new Separator()); - secondaryActions.push(deleteAction); - } const profileElement = disposables.add(this.instantiationService.createInstance(UserDataProfileElement, profile, @@ -826,9 +834,16 @@ export class UserDataProfilesEditorModel extends EditorModel { [primaryActions, secondaryActions] )); - activateAction.enabled = this.userDataProfileService.currentProfile.id !== profileElement.profile.id; + activateAction.checked = this.userDataProfileService.currentProfile.id === profileElement.profile.id; disposables.add(this.userDataProfileService.onDidChangeCurrentProfile(() => - activateAction.enabled = this.userDataProfileService.currentProfile.id !== profileElement.profile.id)); + activateAction.checked = this.userDataProfileService.currentProfile.id === profileElement.profile.id)); + + useAsNewWindowProfileAction.checked = profileElement.isNewWindowProfile; + disposables.add(profileElement.onDidChange(e => { + if (e.newWindowProfile) { + useAsNewWindowProfileAction.checked = profileElement.isNewWindowProfile; + } + })); return [profileElement, disposables]; } diff --git a/src/vs/workbench/contrib/welcomeGettingStarted/common/gettingStartedContent.ts b/src/vs/workbench/contrib/welcomeGettingStarted/common/gettingStartedContent.ts index 3ca45752..0b826034 100644 --- a/src/vs/workbench/contrib/welcomeGettingStarted/common/gettingStartedContent.ts +++ b/src/vs/workbench/contrib/welcomeGettingStarted/common/gettingStartedContent.ts @@ -226,7 +226,6 @@ export const walkthroughs: GettingStartedWalkthroughContent = [ 'onSettingChanged:workbench.colorTheme', 'onCommand:workbench.action.selectTheme' ], - when: '!accessibilityModeEnabled', media: { type: 'markdown', path: 'theme_picker', } }, { @@ -400,7 +399,7 @@ export const walkthroughs: GettingStartedWalkthroughContent = [ isFeatured: true, icon: setupIcon, when: CONTEXT_ACCESSIBILITY_MODE_ENABLED.key, - next: 'Setup', + next: 'SetupScreenReaderExtended', content: { type: 'steps', steps: [ @@ -471,6 +470,90 @@ export const walkthroughs: GettingStartedWalkthroughContent = [ ] } }, + { + id: 'SetupScreenReaderExtended', + title: localize('gettingStarted.setupScreenReaderExtended.title', "Learn more about using VS Code with a Screen Reader"), + description: localize('gettingStarted.setupScreenReaderExtended.description', "Customize your editor, learn the basics, and start coding"), + isFeatured: true, + icon: setupIcon, + when: `!isWeb && ${CONTEXT_ACCESSIBILITY_MODE_ENABLED.key}`, + content: { + type: 'steps', + steps: [ + { + id: 'extensionsWeb', + title: localize('gettingStarted.extensions.title', "Code with extensions"), + description: localize('gettingStarted.extensionsWeb.description.interpolated', "Extensions are VS Code's power-ups. A growing number are becoming available in the web.\n{0}", Button(localize('browsePopularWeb', "Browse Popular Web Extensions"), 'command:workbench.extensions.action.showPopularExtensions')), + when: 'workspacePlatform == \'webworker\'', + media: { + type: 'markdown', path: 'empty' + } + }, + { + id: 'findLanguageExtensions', + title: localize('gettingStarted.findLanguageExts.title', "Rich support for all your languages"), + description: localize('gettingStarted.findLanguageExts.description.interpolated', "Code smarter with syntax highlighting, code completion, linting and debugging. While many languages are built-in, many more can be added as extensions.\n{0}", Button(localize('browseLangExts', "Browse Language Extensions"), 'command:workbench.extensions.action.showLanguageExtensions')), + when: 'workspacePlatform != \'webworker\'', + media: { + type: 'markdown', path: 'empty' + } + }, + { + id: 'settings', + title: localize('gettingStarted.settings.title', "Tune your settings"), + description: localize('gettingStarted.settings.description.interpolated', "Customize every aspect of VS Code and your extensions to your liking. Commonly used settings are listed first to get you started.\n{0}", Button(localize('tweakSettings', "Open Settings"), 'command:toSide:workbench.action.openSettings')), + media: { + type: 'markdown', path: 'empty' + } + }, + { + id: 'settingsSync', + title: localize('gettingStarted.settingsSync.title', "Sync settings across devices"), + description: localize('gettingStarted.settingsSync.description.interpolated', "Keep your essential customizations backed up and updated across all your devices.\n{0}", Button(localize('enableSync', "Backup and Sync Settings"), 'command:workbench.userDataSync.actions.turnOn')), + when: 'syncStatus != uninitialized', + completionEvents: ['onEvent:sync-enabled'], + media: { + type: 'markdown', path: 'empty' + } + }, + { + id: 'commandPaletteTask', + title: localize('gettingStarted.commandPalette.title', "Unlock productivity with the Command Palette "), + description: localize('gettingStarted.commandPalette.description.interpolated', "Run commands without reaching for your mouse to accomplish any task in VS Code.\n{0}", Button(localize('commandPalette', "Open Command Palette"), 'command:workbench.action.showCommands')), + media: { + type: 'markdown', path: 'empty' + } + }, + { + id: 'pickAFolderTask-Mac', + title: localize('gettingStarted.setup.OpenFolder.title', "Open up your code"), + description: localize('gettingStarted.setup.OpenFolder.description.interpolated', "You're all set to start coding. Open a project folder to get your files into VS Code.\n{0}", Button(localize('pickFolder', "Pick a Folder"), 'command:workbench.action.files.openFileFolder')), + when: 'isMac && workspaceFolderCount == 0', + media: { + type: 'markdown', path: 'empty' + } + }, + { + id: 'pickAFolderTask-Other', + title: localize('gettingStarted.setup.OpenFolder.title', "Open up your code"), + description: localize('gettingStarted.setup.OpenFolder.description.interpolated', "You're all set to start coding. Open a project folder to get your files into VS Code.\n{0}", Button(localize('pickFolder', "Pick a Folder"), 'command:workbench.action.files.openFolder')), + when: '!isMac && workspaceFolderCount == 0', + media: { + type: 'markdown', path: 'empty' + } + }, + { + id: 'quickOpen', + title: localize('gettingStarted.quickOpen.title', "Quickly navigate between your files"), + description: localize('gettingStarted.quickOpen.description.interpolated', "Navigate between files in an instant with one keystroke. Tip: Open multiple files by pressing the right arrow key.\n{0}", Button(localize('quickOpen', "Quick Open a File"), 'command:toSide:workbench.action.quickOpen')), + when: 'workspaceFolderCount != 0', + media: { + type: 'markdown', path: 'empty' + } + }, + ] + } + }, { id: 'Beginner', isFeatured: false, diff --git a/src/vs/workbench/electron-sandbox/desktop.contribution.ts b/src/vs/workbench/electron-sandbox/desktop.contribution.ts index 69b98abf..c51e033d 100644 --- a/src/vs/workbench/electron-sandbox/desktop.contribution.ts +++ b/src/vs/workbench/electron-sandbox/desktop.contribution.ts @@ -28,6 +28,7 @@ import { NativeWindow } from 'vs/workbench/electron-sandbox/window'; import { ModifierKeyEmitter } from 'vs/base/browser/dom'; import { applicationConfigurationNodeBase, securityConfigurationNodeBase } from 'vs/workbench/common/configuration'; import { MAX_ZOOM_LEVEL, MIN_ZOOM_LEVEL } from 'vs/platform/window/electron-sandbox/window'; +import product from 'vs/platform/product/common/product'; // Actions (function registerActions(): void { @@ -239,7 +240,7 @@ import { MAX_ZOOM_LEVEL, MIN_ZOOM_LEVEL } from 'vs/platform/window/electron-sand 'type': 'boolean', 'included': isLinux, 'markdownDescription': localize('window.experimentalControlOverlay', "Show the native window controls when {0} is set to `custom` (Linux only).", '`#window.titleBarStyle#`'), - 'default': true + 'default': product.quality !== 'stable', // TODO@bpasero disable by default in stable for now (TODO@bpasero TODO@benibenj flip when custom title is default) }, 'window.customTitleBarVisibility': { 'type': 'string', diff --git a/src/vs/workbench/electron-sandbox/parts/titlebar/titlebarPart.ts b/src/vs/workbench/electron-sandbox/parts/titlebar/titlebarPart.ts index 2fa6f984..12609c51 100644 --- a/src/vs/workbench/electron-sandbox/parts/titlebar/titlebarPart.ts +++ b/src/vs/workbench/electron-sandbox/parts/titlebar/titlebarPart.ts @@ -156,17 +156,17 @@ export class NativeTitlebarPart extends BrowserTitlebarPart { }))); } - // Window Controls (Native Linux when WCO is disabled) - if (isLinux && !hasNativeTitlebar(this.configurationService) && !isWCOEnabled() && this.windowControlsContainer) { + // Window Controls (Native Windows/Linux) + if (!isMacintosh && !hasNativeTitlebar(this.configurationService) && !isWCOEnabled() && this.primaryWindowControls) { // Minimize - const minimizeIcon = append(this.windowControlsContainer, $('div.window-icon.window-minimize' + ThemeIcon.asCSSSelector(Codicon.chromeMinimize))); + const minimizeIcon = append(this.primaryWindowControls, $('div.window-icon.window-minimize' + ThemeIcon.asCSSSelector(Codicon.chromeMinimize))); this._register(addDisposableListener(minimizeIcon, EventType.CLICK, () => { this.nativeHostService.minimizeWindow({ targetWindowId }); })); // Restore - this.maxRestoreControl = append(this.windowControlsContainer, $('div.window-icon.window-max-restore')); + this.maxRestoreControl = append(this.primaryWindowControls, $('div.window-icon.window-max-restore')); this._register(addDisposableListener(this.maxRestoreControl, EventType.CLICK, async () => { const maximized = await this.nativeHostService.isMaximized({ targetWindowId }); if (maximized) { @@ -177,7 +177,7 @@ export class NativeTitlebarPart extends BrowserTitlebarPart { })); // Close - const closeIcon = append(this.windowControlsContainer, $('div.window-icon.window-close' + ThemeIcon.asCSSSelector(Codicon.chromeClose))); + const closeIcon = append(this.primaryWindowControls, $('div.window-icon.window-close' + ThemeIcon.asCSSSelector(Codicon.chromeClose))); this._register(addDisposableListener(closeIcon, EventType.CLICK, () => { this.nativeHostService.closeWindow({ targetWindowId }); })); diff --git a/src/vs/workbench/services/extensionManagement/browser/extensionEnablementService.ts b/src/vs/workbench/services/extensionManagement/browser/extensionEnablementService.ts index ec37cfcc..bbbd1d0b 100644 --- a/src/vs/workbench/services/extensionManagement/browser/extensionEnablementService.ts +++ b/src/vs/workbench/services/extensionManagement/browser/extensionEnablementService.ts @@ -41,7 +41,7 @@ export class ExtensionEnablementService extends Disposable implements IWorkbench public readonly onEnablementChanged: Event = this._onEnablementChanged.event; protected readonly extensionsManager: ExtensionsManager; - private readonly storageManager: StorageManager; + private readonly storageManger: StorageManager; constructor( @IStorageService storageService: IStorageService, @@ -63,7 +63,7 @@ export class ExtensionEnablementService extends Disposable implements IWorkbench @IInstantiationService instantiationService: IInstantiationService, ) { super(); - this.storageManager = this._register(new StorageManager(storageService)); + this.storageManger = this._register(new StorageManager(storageService)); const uninstallDisposable = this._register(Event.filter(extensionManagementService.onDidUninstallExtension, e => !e.error)(({ identifier }) => this._reset(identifier))); let isDisposed = false; @@ -610,11 +610,11 @@ export class ExtensionEnablementService extends Disposable implements IWorkbench if (!this.hasWorkspace) { return []; } - return this.storageManager.get(storageId, StorageScope.WORKSPACE); + return this.storageManger.get(storageId, StorageScope.WORKSPACE); } private _setExtensions(storageId: string, extensions: IExtensionIdentifier[]): void { - this.storageManager.set(storageId, extensions, StorageScope.WORKSPACE); + this.storageManger.set(storageId, extensions, StorageScope.WORKSPACE); } private async _onDidChangeGloballyDisabledExtensions(extensionIdentifiers: ReadonlyArray, source?: string): Promise { diff --git a/src/vs/workbench/services/search/common/fileSearchManager.ts b/src/vs/workbench/services/search/common/fileSearchManager.ts index 73505b7a..659217ef 100644 --- a/src/vs/workbench/services/search/common/fileSearchManager.ts +++ b/src/vs/workbench/services/search/common/fileSearchManager.ts @@ -13,8 +13,6 @@ import { URI } from 'vs/base/common/uri'; import { IFileMatch, IFileSearchProviderStats, IFolderQuery, ISearchCompleteStats, IFileQuery, QueryGlobTester, resolvePatternsForProvider, hasSiblingFn, excludeToGlobPattern, DEFAULT_MAX_SEARCH_RESULTS } from 'vs/workbench/services/search/common/search'; import { FileSearchProviderFolderOptions, FileSearchProviderNew, FileSearchProviderOptions } from 'vs/workbench/services/search/common/searchExtTypes'; import { TernarySearchTree } from 'vs/base/common/ternarySearchTree'; -import { Disposable } from 'vs/base/common/lifecycle'; -import { OldFileSearchProviderConverter } from 'vs/workbench/services/search/common/searchExtConversionTypes'; interface IInternalFileMatch { base: URI; @@ -55,7 +53,7 @@ class FileSearchEngine { private globalExcludePattern?: glob.ParsedExpression; - constructor(private config: IFileQuery, private provider: FileSearchProviderNew, private sessionLifecycle?: SessionLifecycle) { + constructor(private config: IFileQuery, private provider: FileSearchProviderNew, private sessionToken?: unknown) { this.filePattern = config.filePattern; this.includePattern = config.includePattern && glob.parse(config.includePattern); this.maxResults = config.maxResults || undefined; @@ -118,11 +116,10 @@ class FileSearchEngine { private async doSearch(fqs: IFolderQuery[], onResult: (match: IInternalFileMatch) => void): Promise { const cancellation = new CancellationTokenSource(); const folderOptions = fqs.map(fq => this.getSearchOptionsForFolder(fq)); - const session = this.provider instanceof OldFileSearchProviderConverter ? this.sessionLifecycle?.tokenSource.token : this.sessionLifecycle?.obj; const options: FileSearchProviderOptions = { folderOptions, maxResults: this.config.maxResults ?? DEFAULT_MAX_SEARCH_RESULTS, - session + session: this.sessionToken }; @@ -304,30 +301,11 @@ interface IInternalSearchComplete { stats?: IFileSearchProviderStats; } -/** - * For backwards compatibility, store both a cancellation token and a session object. The session object is the new implementation, where - */ -class SessionLifecycle extends Disposable { - public readonly obj: object; - public readonly tokenSource: CancellationTokenSource; - - constructor() { - super(); - this.obj = new Object(); - this.tokenSource = new CancellationTokenSource(); - } - - public override dispose(): void { - this.tokenSource.cancel(); - super.dispose(); - } -} - export class FileSearchManager { private static readonly BATCH_SIZE = 512; - private readonly sessions = new Map(); + private readonly sessions = new Map(); fileSearch(config: IFileQuery, provider: FileSearchProviderNew, onBatch: (matches: IFileMatch[]) => void, token: CancellationToken): Promise { const sessionTokenSource = this.getSessionTokenSource(config.cacheKey); @@ -355,19 +333,17 @@ export class FileSearchManager { } clearCache(cacheKey: string): void { - // cancel the token - this.sessions.get(cacheKey)?.dispose(); // with no reference to this, it will be removed from WeakMaps this.sessions.delete(cacheKey); } - private getSessionTokenSource(cacheKey: string | undefined): SessionLifecycle | undefined { + private getSessionTokenSource(cacheKey: string | undefined): unknown { if (!cacheKey) { return undefined; } if (!this.sessions.has(cacheKey)) { - this.sessions.set(cacheKey, new SessionLifecycle()); + this.sessions.set(cacheKey, new Object()); } return this.sessions.get(cacheKey); diff --git a/src/vs/workbench/services/search/common/queryBuilder.ts b/src/vs/workbench/services/search/common/queryBuilder.ts index 53b37e5f..ed1ab661 100644 --- a/src/vs/workbench/services/search/common/queryBuilder.ts +++ b/src/vs/workbench/services/search/common/queryBuilder.ts @@ -92,7 +92,6 @@ interface ICommonQueryBuilderOptions { disregardSearchExcludeSettings?: boolean; ignoreSymlinks?: boolean; onlyOpenEditors?: boolean; - onlyFileScheme?: boolean; } export interface IFileQueryBuilderOptions extends ICommonQueryBuilderOptions { @@ -261,8 +260,7 @@ export class QueryBuilder { excludePattern: excludeSearchPathsInfo.pattern, includePattern: includeSearchPathsInfo.pattern, onlyOpenEditors: options.onlyOpenEditors, - maxResults: options.maxResults, - onlyFileScheme: options.onlyFileScheme + maxResults: options.maxResults }; if (options.onlyOpenEditors) { diff --git a/src/vs/workbench/services/search/common/search.ts b/src/vs/workbench/services/search/common/search.ts index 79b0e06e..5f6283c3 100644 --- a/src/vs/workbench/services/search/common/search.ts +++ b/src/vs/workbench/services/search/common/search.ts @@ -100,7 +100,6 @@ export interface ICommonQueryProps { maxResults?: number; usingSearchPaths?: boolean; - onlyFileScheme?: boolean; } export interface IFileQueryProps extends ICommonQueryProps { diff --git a/src/vs/workbench/services/search/common/searchService.ts b/src/vs/workbench/services/search/common/searchService.ts index b58dc567..f4e35c52 100644 --- a/src/vs/workbench/services/search/common/searchService.ts +++ b/src/vs/workbench/services/search/common/searchService.ts @@ -271,9 +271,6 @@ export class SearchService extends Disposable implements ISearchService { return []; } await Promise.all([...fqs.keys()].map(async scheme => { - if (query.onlyFileScheme && scheme !== Schemas.file) { - return; - } const schemeFQs = fqs.get(scheme)!; let provider = this.getSearchProvider(query.type).get(scheme); diff --git a/src/vs/workbench/test/electron-sandbox/workbenchTestServices.ts b/src/vs/workbench/test/electron-sandbox/workbenchTestServices.ts index c19ad7c8..7bcb272f 100644 --- a/src/vs/workbench/test/electron-sandbox/workbenchTestServices.ts +++ b/src/vs/workbench/test/electron-sandbox/workbenchTestServices.ts @@ -143,7 +143,7 @@ export class TestNativeHostService implements INativeHostService { async closeWindow(): Promise { } async quit(): Promise { } async exit(code: number): Promise { } - async openDevTools(): Promise { } + async openDevTools(options?: Partial & INativeHostOptions | undefined): Promise { } async toggleDevTools(): Promise { } async resolveProxy(url: string): Promise { return undefined; } async lookupAuthorization(authInfo: AuthInfo): Promise { return undefined; } diff --git a/src/vs/workbench/workbench.common.main.ts b/src/vs/workbench/workbench.common.main.ts index 9e01f175..461caaf8 100644 --- a/src/vs/workbench/workbench.common.main.ts +++ b/src/vs/workbench/workbench.common.main.ts @@ -3,6 +3,11 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +//#region --- void +import 'vs/workbench/contrib/void/browser/void.contribution' +//#endregion + + //#region --- editor/workbench core import 'vs/editor/editor.all'; @@ -10,6 +15,7 @@ import 'vs/editor/editor.all'; import 'vs/workbench/api/browser/extensionHost.contribution'; import 'vs/workbench/browser/workbench.contribution'; + //#endregion