Merge branch 'main' into joh/chatWidget

This commit is contained in:
Johannes 2024-03-20 16:06:03 +01:00
commit 15441f4251
No known key found for this signature in database
GPG key ID: 6DEF802A22264FCA
133 changed files with 3012 additions and 2101 deletions

View file

@ -171,7 +171,7 @@ steps:
export VSCODE_NODE_GLIBC="-glibc-2.17"
yarn gulp vscode-reh-linux-$(VSCODE_ARCH)-min-ci
mv ../vscode-reh-linux-$(VSCODE_ARCH) ../vscode-server-linux-$(VSCODE_ARCH) # TODO@joaomoreno
ARCHIVE_PATH=".build/linux/server/vscode-server-linux-$(VSCODE_ARCH).tar.gz"
ARCHIVE_PATH=".build/linux/server/legacy-vscode-server-linux-$(VSCODE_ARCH).tar.gz"
mkdir -p $(dirname $ARCHIVE_PATH)
tar --owner=0 --group=0 -czf $ARCHIVE_PATH -C .. vscode-server-linux-$(VSCODE_ARCH)
echo "##vso[task.setvariable variable=LEGACY_SERVER_PATH]$ARCHIVE_PATH"
@ -184,7 +184,7 @@ steps:
export VSCODE_NODE_GLIBC="-glibc-2.17"
yarn gulp vscode-reh-web-linux-$(VSCODE_ARCH)-min-ci
mv ../vscode-reh-web-linux-$(VSCODE_ARCH) ../vscode-server-linux-$(VSCODE_ARCH)-web # TODO@joaomoreno
ARCHIVE_PATH=".build/linux/web/vscode-server-linux-$(VSCODE_ARCH)-web.tar.gz"
ARCHIVE_PATH=".build/linux/web/legacy-vscode-server-linux-$(VSCODE_ARCH)-web.tar.gz"
mkdir -p $(dirname $ARCHIVE_PATH)
tar --owner=0 --group=0 -czf $ARCHIVE_PATH -C .. vscode-server-linux-$(VSCODE_ARCH)-web
echo "##vso[task.setvariable variable=LEGACY_WEB_PATH]$ARCHIVE_PATH"

View file

@ -6,8 +6,8 @@
mod context;
pub mod args;
pub mod serve_web;
pub mod tunnels;
pub mod update;
pub mod version;
pub mod serve_web;
pub use context::CommandContext;

View file

@ -6,14 +6,13 @@
pub mod code_server;
pub mod dev_tunnels;
pub mod legal;
pub mod local_forwarding;
pub mod paths;
pub mod protocol;
pub mod shutdown_signal;
pub mod singleton_client;
pub mod singleton_server;
pub mod local_forwarding;
mod wsl_detect;
mod challenge;
mod control_server;
mod nosleep;
@ -34,8 +33,9 @@ mod service_macos;
#[cfg(target_os = "windows")]
mod service_windows;
mod socket_signal;
mod wsl_detect;
pub use control_server::{serve, serve_stream, Next, ServeStreamParams, AuthRequired};
pub use control_server::{serve, serve_stream, AuthRequired, Next, ServeStreamParams};
pub use nosleep::SleepInhibitor;
pub use service::{
create_service_manager, ServiceContainer, ServiceManager, SERVICE_LOG_FILE_NAME,

View file

@ -90,6 +90,10 @@ impl ServiceManager for SystemdService {
info!(self.log, "Successfully registered service...");
if let Err(e) = proxy.reload().await {
warning!(self.log, "Error issuing reload(): {}", e);
}
// note: enablement is implicit in recent systemd version, but required for older systems
// https://github.com/microsoft/vscode/issues/167489#issuecomment-1331222826
proxy
@ -257,4 +261,7 @@ trait SystemdManagerDbus {
#[dbus_proxy(name = "StopUnit")]
fn stop_unit(&self, name: String, mode: String) -> zbus::Result<zvariant::OwnedObjectPath>;
#[dbus_proxy(name = "Reload")]
fn reload(&self) -> zbus::Result<()>;
}

View file

@ -241,11 +241,7 @@ mod tests {
let mut rx = tailf(read_file, 32);
assert!(rx.try_recv().is_err());
let mut append_file = OpenOptions::new()
.write(true)
.append(true)
.open(&file_path)
.unwrap();
let mut append_file = OpenOptions::new().append(true).open(&file_path).unwrap();
writeln!(&mut append_file, "some line").unwrap();
let recv = rx.recv().await;
@ -338,11 +334,7 @@ mod tests {
assert!(rx.try_recv().is_err());
let mut append_file = OpenOptions::new()
.write(true)
.append(true)
.open(&file_path)
.unwrap();
let mut append_file = OpenOptions::new().append(true).open(&file_path).unwrap();
writeln!(append_file, " is now complete").unwrap();
let recv = rx.recv().await;

View file

@ -3,7 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
use std::{
use std::{
ffi::OsString,
path::{Path, PathBuf},
time::Duration,

View file

@ -36,6 +36,11 @@ export class SettingsDocument {
return this.provideLanguageCompletionItems(location, position);
}
// workbench.editor.label
if (location.path[0] === 'workbench.editor.label.patterns') {
return this.provideEditorLabelCompletionItems(location, position);
}
// settingsSync.ignoredExtensions
if (location.path[0] === 'settingsSync.ignoredExtensions') {
let ignoredExtensions = [];
@ -126,6 +131,31 @@ export class SettingsDocument {
return completions;
}
private async provideEditorLabelCompletionItems(location: Location, pos: vscode.Position): Promise<vscode.CompletionItem[]> {
const completions: vscode.CompletionItem[] = [];
if (!this.isCompletingPropertyValue(location, pos)) {
return completions;
}
let range = this.document.getWordRangeAtPosition(pos, /\$\{[^"\}]*\}?/);
if (!range || range.start.isEqual(pos) || range.end.isEqual(pos) && this.document.getText(range).endsWith('}')) {
range = new vscode.Range(pos, pos);
}
const getText = (variable: string) => {
const text = '${' + variable + '}';
return location.previousNode ? text : JSON.stringify(text);
};
completions.push(this.newSimpleCompletionItem(getText('dirname'), range, vscode.l10n.t("The parent folder name of the editor (e.g. myFileFolder)")));
completions.push(this.newSimpleCompletionItem(getText('dirname(1)'), range, vscode.l10n.t("The nth parent folder name of the editor")));
completions.push(this.newSimpleCompletionItem(getText('filename'), range, vscode.l10n.t("The file name of the editor without its directory or extension (e.g. myFile)")));
completions.push(this.newSimpleCompletionItem(getText('extname'), range, vscode.l10n.t("The file extension of the editor (e.g. txt)")));
return completions;
}
private async provideFilesAssociationsCompletionItems(location: Location, position: vscode.Position): Promise<vscode.CompletionItem[]> {
const completions: vscode.CompletionItem[] = [];

View file

@ -63,9 +63,14 @@ export class GitFileSystemProvider implements FileSystemProvider {
return;
}
const gitUri = toGitUri(uri, '', { replaceFileExtension: true });
const diffOriginalResourceUri = toGitUri(uri, '~',);
const quickDiffOriginalResourceUri = toGitUri(uri, '', { replaceFileExtension: true });
this.mtime = new Date().getTime();
this._onDidChangeFile.fire([{ type: FileChangeType.Changed, uri: gitUri }]);
this._onDidChangeFile.fire([
{ type: FileChangeType.Changed, uri: diffOriginalResourceUri },
{ type: FileChangeType.Changed, uri: quickDiffOriginalResourceUri }
]);
}
@debounce(1100)

View file

@ -4,6 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import type * as nbformat from '@jupyterlab/nbformat';
import { workspace } from 'vscode';
/**
* Metadata we store in VS Code cell output items.
@ -60,3 +61,7 @@ export interface CellMetadata {
*/
metadata?: Partial<nbformat.ICellMetadata> & { vscode?: { languageId?: string } };
}
export function useCustomPropertyInMetadata() {
return !workspace.getConfiguration('jupyter', undefined).get<boolean>('experimental.dropCustomMetadata', false);
}

View file

@ -5,7 +5,7 @@
import type * as nbformat from '@jupyterlab/nbformat';
import { extensions, NotebookCellData, NotebookCellExecutionSummary, NotebookCellKind, NotebookCellOutput, NotebookCellOutputItem, NotebookData } from 'vscode';
import { CellMetadata, CellOutputMetadata } from './common';
import { CellMetadata, CellOutputMetadata, useCustomPropertyInMetadata } from './common';
const jupyterLanguageToMonacoLanguageMapping = new Map([
['c#', 'csharp'],
@ -154,24 +154,42 @@ function convertJupyterOutputToBuffer(mime: string, value: unknown): NotebookCel
function getNotebookCellMetadata(cell: nbformat.IBaseCell): {
[key: string]: any;
} {
const cellMetadata: { [key: string]: any } = {};
// We put this only for VSC to display in diff view.
// Else we don't use this.
const custom: CellMetadata = {};
if (cell['metadata']) {
custom['metadata'] = JSON.parse(JSON.stringify(cell['metadata']));
}
if (useCustomPropertyInMetadata()) {
const cellMetadata: { [key: string]: any } = {};
// We put this only for VSC to display in diff view.
// Else we don't use this.
const custom: CellMetadata = {};
if (cell['metadata']) {
custom['metadata'] = JSON.parse(JSON.stringify(cell['metadata']));
}
if ('id' in cell && typeof cell.id === 'string') {
custom.id = cell.id;
}
if ('id' in cell && typeof cell.id === 'string') {
custom.id = cell.id;
}
cellMetadata.custom = custom;
cellMetadata.custom = custom;
if (cell['attachments']) {
cellMetadata.attachments = JSON.parse(JSON.stringify(cell['attachments']));
if (cell['attachments']) {
cellMetadata.attachments = JSON.parse(JSON.stringify(cell['attachments']));
}
return cellMetadata;
} else {
// We put this only for VSC to display in diff view.
// Else we don't use this.
const cellMetadata: CellMetadata = {};
if (cell['metadata']) {
cellMetadata['metadata'] = JSON.parse(JSON.stringify(cell['metadata']));
}
if ('id' in cell && typeof cell.id === 'string') {
cellMetadata.id = cell.id;
}
if (cell['attachments']) {
cellMetadata.attachments = JSON.parse(JSON.stringify(cell['attachments']));
}
return cellMetadata;
}
return cellMetadata;
}
function getOutputMetadata(output: nbformat.IOutput): CellOutputMetadata {
@ -364,6 +382,6 @@ export function jupyterNotebookModelToNotebookData(
.filter((item): item is NotebookCellData => !!item);
const notebookData = new NotebookData(cells);
notebookData.metadata = { custom: notebookContentWithoutCells };
notebookData.metadata = useCustomPropertyInMetadata() ? { custom: notebookContentWithoutCells } : notebookContentWithoutCells;
return notebookData;
}

View file

@ -8,6 +8,7 @@ import { NotebookSerializer } from './notebookSerializer';
import { activate as keepNotebookModelStoreInSync } from './notebookModelStoreSync';
import { notebookImagePasteSetup } from './notebookImagePaste';
import { AttachmentCleaner } from './notebookAttachmentCleaner';
import { useCustomPropertyInMetadata } from './common';
// From {nbformat.INotebookMetadata} in @jupyterlab/coreutils
type NotebookMetadata = {
@ -33,10 +34,15 @@ export function activate(context: vscode.ExtensionContext) {
keepNotebookModelStoreInSync(context);
context.subscriptions.push(vscode.workspace.registerNotebookSerializer('jupyter-notebook', serializer, {
transientOutputs: false,
transientCellMetadata: {
transientCellMetadata: useCustomPropertyInMetadata() ? {
breakpointMargin: true,
custom: false,
attachments: false
} : {
breakpointMargin: true,
id: false,
metadata: false,
attachments: false
},
cellContentMetadata: {
attachments: true
@ -45,10 +51,15 @@ export function activate(context: vscode.ExtensionContext) {
context.subscriptions.push(vscode.workspace.registerNotebookSerializer('interactive', serializer, {
transientOutputs: false,
transientCellMetadata: {
transientCellMetadata: useCustomPropertyInMetadata() ? {
breakpointMargin: true,
custom: false,
attachments: false
} : {
breakpointMargin: true,
id: false,
metadata: false,
attachments: false
},
cellContentMetadata: {
attachments: true
@ -73,13 +84,18 @@ export function activate(context: vscode.ExtensionContext) {
const language = 'python';
const cell = new vscode.NotebookCellData(vscode.NotebookCellKind.Code, '', language);
const data = new vscode.NotebookData([cell]);
data.metadata = {
data.metadata = useCustomPropertyInMetadata() ? {
custom: {
cells: [],
metadata: {},
nbformat: 4,
nbformat_minor: 2
}
} : {
cells: [],
metadata: {},
nbformat: 4,
nbformat_minor: 2
};
const doc = await vscode.workspace.openNotebookDocument('jupyter-notebook', data);
await vscode.window.showNotebookDocument(doc);
@ -109,6 +125,9 @@ export function activate(context: vscode.ExtensionContext) {
return {
get dropCustomMetadata() {
return !useCustomPropertyInMetadata();
},
exportNotebook: (notebook: vscode.NotebookData): string => {
return exportNotebook(notebook, serializer);
},
@ -119,16 +138,26 @@ export function activate(context: vscode.ExtensionContext) {
}
const edit = new vscode.WorkspaceEdit();
edit.set(resource, [vscode.NotebookEdit.updateNotebookMetadata({
...document.metadata,
custom: {
...(document.metadata.custom ?? {}),
if (useCustomPropertyInMetadata()) {
edit.set(resource, [vscode.NotebookEdit.updateNotebookMetadata({
...document.metadata,
custom: {
...(document.metadata.custom ?? {}),
metadata: <NotebookMetadata>{
...(document.metadata.custom?.metadata ?? {}),
...metadata
},
}
})]);
} else {
edit.set(resource, [vscode.NotebookEdit.updateNotebookMetadata({
...document.metadata,
metadata: <NotebookMetadata>{
...(document.metadata.custom?.metadata ?? {}),
...(document.metadata.metadata ?? {}),
...metadata
},
}
})]);
})]);
}
return vscode.workspace.applyEdit(edit);
},
};

View file

@ -5,7 +5,7 @@
import { ExtensionContext, NotebookCellKind, NotebookDocument, NotebookDocumentChangeEvent, NotebookEdit, workspace, WorkspaceEdit, type NotebookCell, type NotebookDocumentWillSaveEvent } from 'vscode';
import { getCellMetadata, getVSCodeCellLanguageId, removeVSCodeCellLanguageId, setVSCodeCellLanguageId } from './serializers';
import { CellMetadata } from './common';
import { CellMetadata, useCustomPropertyInMetadata } from './common';
import { getNotebookMetadata } from './notebookSerializer';
import type * as nbformat from '@jupyterlab/nbformat';
@ -57,7 +57,11 @@ function trackAndUpdateCellMetadata(notebook: NotebookDocument, cell: NotebookCe
const pendingUpdates = pendingNotebookCellModelUpdates.get(notebook) ?? new Set<Thenable<void>>();
pendingNotebookCellModelUpdates.set(notebook, pendingUpdates);
const edit = new WorkspaceEdit();
edit.set(cell.notebook.uri, [NotebookEdit.updateCellMetadata(cell.index, { ...(cell.metadata), custom: metadata })]);
if (useCustomPropertyInMetadata()) {
edit.set(cell.notebook.uri, [NotebookEdit.updateCellMetadata(cell.index, { ...(cell.metadata), custom: metadata })]);
} else {
edit.set(cell.notebook.uri, [NotebookEdit.updateCellMetadata(cell.index, { ...cell.metadata, ...metadata })]);
}
const promise = workspace.applyEdit(edit).then(noop, noop);
pendingUpdates.add(promise);
const clean = () => cleanup(notebook, promise);

View file

@ -10,6 +10,7 @@ import { defaultNotebookFormat } from './constants';
import { getPreferredLanguage, jupyterNotebookModelToNotebookData } from './deserializers';
import { createJupyterCellFromNotebookCell, pruneCell, sortObjectPropertiesRecursively } from './serializers';
import * as fnv from '@enonic/fnv-plus';
import { useCustomPropertyInMetadata } from './common';
export class NotebookSerializer implements vscode.NotebookSerializer {
constructor(readonly context: vscode.ExtensionContext) {
@ -99,10 +100,11 @@ export class NotebookSerializer implements vscode.NotebookSerializer {
}
export function getNotebookMetadata(document: vscode.NotebookDocument | vscode.NotebookData) {
const notebookContent: Partial<nbformat.INotebookContent> = document.metadata?.custom || {};
notebookContent.cells = notebookContent.cells || [];
notebookContent.nbformat = notebookContent.nbformat || defaultNotebookFormat.major;
notebookContent.nbformat_minor = notebookContent.nbformat_minor ?? defaultNotebookFormat.minor;
notebookContent.metadata = notebookContent.metadata || {};
const existingContent: Partial<nbformat.INotebookContent> = (useCustomPropertyInMetadata() ? document.metadata?.custom : document.metadata) || {};
const notebookContent: Partial<nbformat.INotebookContent> = {};
notebookContent.cells = existingContent.cells || [];
notebookContent.nbformat = existingContent.nbformat || defaultNotebookFormat.major;
notebookContent.nbformat_minor = existingContent.nbformat_minor ?? defaultNotebookFormat.minor;
notebookContent.metadata = existingContent.metadata || {};
return notebookContent;
}

View file

@ -5,7 +5,7 @@
import type * as nbformat from '@jupyterlab/nbformat';
import { NotebookCell, NotebookCellData, NotebookCellKind, NotebookCellOutput } from 'vscode';
import { CellOutputMetadata, type CellMetadata } from './common';
import { CellOutputMetadata, useCustomPropertyInMetadata, type CellMetadata } from './common';
import { textMimeTypes } from './deserializers';
const textDecoder = new TextDecoder();
@ -55,16 +55,24 @@ export function sortObjectPropertiesRecursively(obj: any): any {
}
export function getCellMetadata(cell: NotebookCell | NotebookCellData): CellMetadata {
if (useCustomPropertyInMetadata()) {
const metadata = {
// it contains the cell id, and the cell metadata, along with other nb cell metadata
...(cell.metadata?.custom ?? {})
};
// promote the cell attachments to the top level
const attachments = cell.metadata?.custom?.attachments ?? cell.metadata?.attachments;
if (attachments) {
metadata.attachments = attachments;
}
return metadata;
}
const metadata = {
// it contains the cell id, and the cell metadata, along with other nb cell metadata
...(cell.metadata?.custom ?? {})
...(cell.metadata ?? {})
};
// promote the cell attachments to the top level
const attachments = cell.metadata?.custom?.attachments ?? cell.metadata?.attachments;
if (attachments) {
metadata.attachments = attachments;
}
return metadata;
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -45,7 +45,6 @@
"terminalDataWriteEvent",
"terminalDimensions",
"tunnels",
"testCoverage",
"testObserver",
"textSearchProvider",
"timeline",

View file

@ -1,7 +1,7 @@
{
"name": "code-oss-dev",
"version": "1.88.0",
"distro": "7ca938298e57ad434ea8807e132707055458a749",
"distro": "ff3bff60edcc6e1f7269509e1673036c00fa62bd",
"author": {
"name": "Microsoft Corporation"
},

View file

@ -397,7 +397,7 @@ export namespace CoreNavigationCommands {
]
);
if (cursorStateChanged && args.revealType !== NavigationCommandRevealType.None) {
viewModel.revealPrimaryCursor(args.source, true, true);
viewModel.revealAllCursors(args.source, true, true);
}
}
}
@ -609,7 +609,7 @@ export namespace CoreNavigationCommands {
CursorChangeReason.Explicit,
CursorMoveImpl._move(viewModel, viewModel.getCursorStates(), args)
);
viewModel.revealPrimaryCursor(source, true);
viewModel.revealAllCursors(source, true);
}
private static _move(viewModel: IViewModel, cursors: CursorState[], args: CursorMove_.ParsedArguments): PartialCursorState[] | null {
@ -678,7 +678,7 @@ export namespace CoreNavigationCommands {
CursorChangeReason.Explicit,
CursorMoveCommands.simpleMove(viewModel, viewModel.getCursorStates(), args.direction, args.select, args.value, args.unit)
);
viewModel.revealPrimaryCursor(dynamicArgs.source, true);
viewModel.revealAllCursors(dynamicArgs.source, true);
}
}
@ -993,7 +993,7 @@ export namespace CoreNavigationCommands {
CursorChangeReason.Explicit,
CursorMoveCommands.moveToBeginningOfLine(viewModel, viewModel.getCursorStates(), this._inSelectionMode)
);
viewModel.revealPrimaryCursor(args.source, true);
viewModel.revealAllCursors(args.source, true);
}
}
@ -1037,7 +1037,7 @@ export namespace CoreNavigationCommands {
CursorChangeReason.Explicit,
this._exec(viewModel.getCursorStates())
);
viewModel.revealPrimaryCursor(args.source, true);
viewModel.revealAllCursors(args.source, true);
}
private _exec(cursors: CursorState[]): PartialCursorState[] {
@ -1095,7 +1095,7 @@ export namespace CoreNavigationCommands {
CursorChangeReason.Explicit,
CursorMoveCommands.moveToEndOfLine(viewModel, viewModel.getCursorStates(), this._inSelectionMode, args.sticky || false)
);
viewModel.revealPrimaryCursor(args.source, true);
viewModel.revealAllCursors(args.source, true);
}
}
@ -1173,7 +1173,7 @@ export namespace CoreNavigationCommands {
CursorChangeReason.Explicit,
this._exec(viewModel, viewModel.getCursorStates())
);
viewModel.revealPrimaryCursor(args.source, true);
viewModel.revealAllCursors(args.source, true);
}
private _exec(viewModel: IViewModel, cursors: CursorState[]): PartialCursorState[] {
@ -1228,7 +1228,7 @@ export namespace CoreNavigationCommands {
CursorChangeReason.Explicit,
CursorMoveCommands.moveToBeginningOfBuffer(viewModel, viewModel.getCursorStates(), this._inSelectionMode)
);
viewModel.revealPrimaryCursor(args.source, true);
viewModel.revealAllCursors(args.source, true);
}
}
@ -1272,7 +1272,7 @@ export namespace CoreNavigationCommands {
CursorChangeReason.Explicit,
CursorMoveCommands.moveToEndOfBuffer(viewModel, viewModel.getCursorStates(), this._inSelectionMode)
);
viewModel.revealPrimaryCursor(args.source, true);
viewModel.revealAllCursors(args.source, true);
}
}
@ -1644,7 +1644,7 @@ export namespace CoreNavigationCommands {
]
);
if (args.revealType !== NavigationCommandRevealType.None) {
viewModel.revealPrimaryCursor(args.source, true, true);
viewModel.revealAllCursors(args.source, true, true);
}
}
}
@ -1710,7 +1710,7 @@ export namespace CoreNavigationCommands {
]
);
if (args.revealType !== NavigationCommandRevealType.None) {
viewModel.revealPrimaryCursor(args.source, false, true);
viewModel.revealAllCursors(args.source, false, true);
}
}
}
@ -1789,7 +1789,7 @@ export namespace CoreNavigationCommands {
CursorMoveCommands.cancelSelection(viewModel, viewModel.getPrimaryCursorState())
]
);
viewModel.revealPrimaryCursor(args.source, true);
viewModel.revealAllCursors(args.source, true);
}
});
@ -1816,7 +1816,7 @@ export namespace CoreNavigationCommands {
viewModel.getPrimaryCursorState()
]
);
viewModel.revealPrimaryCursor(args.source, true);
viewModel.revealAllCursors(args.source, true);
status(nls.localize('removedCursor', "Removed secondary cursors"));
}
});

View file

@ -65,6 +65,7 @@
/* There are view-lines in view-zones. We have to make sure this rule does not apply to them, as they don't set a line height */
.monaco-editor .lines-content > .view-lines > .view-line > span {
top: 0;
bottom: 0;
position: absolute;
}

View file

@ -136,7 +136,7 @@ export class CursorsController extends Disposable {
this._columnSelectData = columnSelectData;
}
public revealPrimary(eventsCollector: ViewModelEventsCollector, source: string | null | undefined, minimalReveal: boolean, verticalType: VerticalRevealType, revealHorizontal: boolean, scrollType: editorCommon.ScrollType): void {
public revealAll(eventsCollector: ViewModelEventsCollector, source: string | null | undefined, minimalReveal: boolean, verticalType: VerticalRevealType, revealHorizontal: boolean, scrollType: editorCommon.ScrollType): void {
const viewPositions = this._cursors.getViewPositions();
let revealViewRange: Range | null = null;
@ -150,6 +150,12 @@ export class CursorsController extends Disposable {
eventsCollector.emitViewEvent(new ViewRevealRangeRequestEvent(source, minimalReveal, revealViewRange, revealViewSelections, verticalType, revealHorizontal, scrollType));
}
public revealPrimary(eventsCollector: ViewModelEventsCollector, source: string | null | undefined, minimalReveal: boolean, verticalType: VerticalRevealType, revealHorizontal: boolean, scrollType: editorCommon.ScrollType): void {
const primaryCursor = this._cursors.getPrimaryCursor();
const revealViewSelections = [primaryCursor.viewState.selection];
eventsCollector.emitViewEvent(new ViewRevealRangeRequestEvent(source, minimalReveal, null, revealViewSelections, verticalType, revealHorizontal, scrollType));
}
public saveState(): editorCommon.ICursorState[] {
const result: editorCommon.ICursorState[] = [];
@ -212,7 +218,7 @@ export class CursorsController extends Disposable {
}
this.setStates(eventsCollector, 'restoreState', CursorChangeReason.NotSet, CursorState.fromModelSelections(desiredSelections));
this.revealPrimary(eventsCollector, 'restoreState', false, VerticalRevealType.Simple, true, editorCommon.ScrollType.Immediate);
this.revealAll(eventsCollector, 'restoreState', false, VerticalRevealType.Simple, true, editorCommon.ScrollType.Immediate);
}
public onModelContentChanged(eventsCollector: ViewModelEventsCollector, event: InternalModelContentChangeEvent | ModelInjectedTextChangedEvent): void {
@ -252,7 +258,7 @@ export class CursorsController extends Disposable {
if (this._hasFocus && e.resultingSelection && e.resultingSelection.length > 0) {
const cursorState = CursorState.fromModelSelections(e.resultingSelection);
if (this.setStates(eventsCollector, 'modelChange', e.isUndoing ? CursorChangeReason.Undo : e.isRedoing ? CursorChangeReason.Redo : CursorChangeReason.RecoverFromMarkers, cursorState)) {
this.revealPrimary(eventsCollector, 'modelChange', false, VerticalRevealType.Simple, true, editorCommon.ScrollType.Smooth);
this.revealAll(eventsCollector, 'modelChange', false, VerticalRevealType.Simple, true, editorCommon.ScrollType.Smooth);
}
} else {
const selectionsFromMarkers = this._cursors.readSelectionFromMarkers();
@ -519,7 +525,7 @@ export class CursorsController extends Disposable {
this._cursors.startTrackingSelections();
this._validateAutoClosedActions();
if (this._emitStateChangedIfNecessary(eventsCollector, source, cursorChangeReason, oldState, false)) {
this.revealPrimary(eventsCollector, source, false, VerticalRevealType.Simple, true, editorCommon.ScrollType.Smooth);
this.revealAll(eventsCollector, source, false, VerticalRevealType.Simple, true, editorCommon.ScrollType.Smooth);
}
}

View file

@ -87,6 +87,7 @@ export interface IViewModel extends ICursorSimpleModel {
setCursorColumnSelectData(columnSelectData: IColumnSelectData): void;
getPrevEditOperationType(): EditOperationType;
setPrevEditOperationType(type: EditOperationType): void;
revealAllCursors(source: string | null | undefined, revealHorizontal: boolean, minimalReveal?: boolean): void;
revealPrimaryCursor(source: string | null | undefined, revealHorizontal: boolean, minimalReveal?: boolean): void;
revealTopMostCursor(source: string | null | undefined): void;
revealBottomMostCursor(source: string | null | undefined): void;

View file

@ -1069,6 +1069,9 @@ export class ViewModel extends Disposable implements IViewModel {
public executeCommands(commands: ICommand[], source?: string | null | undefined): void {
this._executeCursorEdit(eventsCollector => this._cursor.executeCommands(eventsCollector, commands, source));
}
public revealAllCursors(source: string | null | undefined, revealHorizontal: boolean, minimalReveal: boolean = false): void {
this._withViewEventsCollector(eventsCollector => this._cursor.revealAll(eventsCollector, source, minimalReveal, viewEvents.VerticalRevealType.Simple, revealHorizontal, ScrollType.Smooth));
}
public revealPrimaryCursor(source: string | null | undefined, revealHorizontal: boolean, minimalReveal: boolean = false): void {
this._withViewEventsCollector(eventsCollector => this._cursor.revealPrimary(eventsCollector, source, minimalReveal, viewEvents.VerticalRevealType.Simple, revealHorizontal, ScrollType.Smooth));
}

View file

@ -39,7 +39,7 @@ export class ExpandLineSelectionAction extends EditorAction {
CursorChangeReason.Explicit,
CursorMoveCommands.expandLineSelection(viewModel, viewModel.getCursorStates())
);
viewModel.revealPrimaryCursor(args.source, true);
viewModel.revealAllCursors(args.source, true);
}
}

View file

@ -68,10 +68,10 @@ export function isIAction(what: FuzzyAction): what is IAction {
}
export interface IRule {
regex: RegExp;
action: FuzzyAction;
matchOnlyAtLineStart: boolean;
name: string;
resolveRegex(state: string): RegExp;
}
export interface IAction {
@ -175,6 +175,26 @@ export function substituteMatches(lexer: ILexerMin, str: string, id: string, mat
});
}
/**
* substituteMatchesRe is used on lexer regex rules and can substitutes predefined patterns:
* $Sn => n'th part of state
*
*/
export function substituteMatchesRe(lexer: ILexerMin, str: string, state: string): string {
const re = /\$[sS](\d\d?)/g;
let stateMatches: string[] | null = null;
return str.replace(re, function (full, s) {
if (stateMatches === null) { // split state on demand
stateMatches = state.split('.');
stateMatches.unshift(state);
}
if (!empty(s) && s < stateMatches.length) {
return fixCase(lexer, stateMatches[s]); //$Sn
}
return '';
});
}
/**
* Find the tokenizer rules for a specific state (i.e. next action)
*/

View file

@ -85,7 +85,8 @@ function createKeywordMatcher(arr: string[], caseInsensitive: boolean = false):
* @example /@attr/ will be replaced with the value of lexer[attr]
* @example /@@text/ will not be replaced and will become /@text/.
*/
function compileRegExp(lexer: monarchCommon.ILexerMin, str: string): RegExp {
function compileRegExp<S extends true | false>(lexer: monarchCommon.ILexerMin, str: string, handleSn: S): S extends true ? RegExp | DynamicRegExp : RegExp;
function compileRegExp(lexer: monarchCommon.ILexerMin, str: string, handleSn: true | false): RegExp | DynamicRegExp {
// @@ must be interpreted as a literal @, so we replace all occurences of @@ with a placeholder character
str = str.replace(/@@/g, `\x01`);
@ -116,6 +117,24 @@ function compileRegExp(lexer: monarchCommon.ILexerMin, str: string): RegExp {
str = str.replace(/\x01/g, '@');
const flags = (lexer.ignoreCase ? 'i' : '') + (lexer.unicode ? 'u' : '');
// handle $Sn
if (handleSn) {
const match = str.match(/\$[sS](\d\d?)/g);
if (match) {
let lastState: string | null = null;
let lastRegEx: RegExp | null = null;
return (state: string) => {
if (lastRegEx && lastState === state) {
return lastRegEx;
}
lastState = state;
lastRegEx = new RegExp(monarchCommon.substituteMatchesRe(lexer, str, state), flags);
return lastRegEx;
};
}
}
return new RegExp(str, flags);
}
@ -196,12 +215,12 @@ function createGuard(lexer: monarchCommon.ILexerMin, ruleName: string, tkey: str
else if (op === '~' || op === '!~') {
if (pat.indexOf('$') < 0) {
// precompile regular expression
const re = compileRegExp(lexer, '^' + pat + '$');
const re = compileRegExp(lexer, '^' + pat + '$', false);
tester = function (s) { return (op === '~' ? re.test(s) : !re.test(s)); };
}
else {
tester = function (s, id, matches, state) {
const re = compileRegExp(lexer, '^' + monarchCommon.substituteMatches(lexer, pat, id, matches, state) + '$');
const re = compileRegExp(lexer, '^' + monarchCommon.substituteMatches(lexer, pat, id, matches, state) + '$', false);
return re.test(s);
};
}
@ -355,11 +374,13 @@ function compileAction(lexer: monarchCommon.ILexerMin, ruleName: string, action:
}
}
type DynamicRegExp = (state: string) => RegExp;
/**
* Helper class for creating matching rules
*/
class Rule implements monarchCommon.IRule {
public regex: RegExp = new RegExp('');
private regex: RegExp | DynamicRegExp = new RegExp('');
public action: monarchCommon.FuzzyAction = { token: '' };
public matchOnlyAtLineStart: boolean = false;
public name: string = '';
@ -382,12 +403,20 @@ class Rule implements monarchCommon.IRule {
this.matchOnlyAtLineStart = (sregex.length > 0 && sregex[0] === '^');
this.name = this.name + ': ' + sregex;
this.regex = compileRegExp(lexer, '^(?:' + (this.matchOnlyAtLineStart ? sregex.substr(1) : sregex) + ')');
this.regex = compileRegExp(lexer, '^(?:' + (this.matchOnlyAtLineStart ? sregex.substr(1) : sregex) + ')', true);
}
public setAction(lexer: monarchCommon.ILexerMin, act: monarchCommon.IAction) {
this.action = compileAction(lexer, this.name, act);
}
public resolveRegex(state: string): RegExp {
if (this.regex instanceof RegExp) {
return this.regex;
} else {
return this.regex(state);
}
}
}
/**

View file

@ -519,8 +519,8 @@ export class MonarchTokenizer extends Disposable implements languages.ITokenizat
}
hasEmbeddedPopRule = true;
let regex = rule.regex;
const regexSource = rule.regex.source;
let regex = rule.resolveRegex(state.stack.state);
const regexSource = regex.source;
if (regexSource.substr(0, 4) === '^(?:' && regexSource.substr(regexSource.length - 1, 1) === ')') {
const flags = (regex.ignoreCase ? 'i' : '') + (regex.unicode ? 'u' : '');
regex = new RegExp(regexSource.substr(4, regexSource.length - 5), flags);
@ -643,7 +643,7 @@ export class MonarchTokenizer extends Disposable implements languages.ITokenizat
const restOfLine = line.substr(pos);
for (const rule of rules) {
if (pos === 0 || !rule.matchOnlyAtLineStart) {
matches = restOfLine.match(rule.regex);
matches = restOfLine.match(rule.resolveRegex(state));
if (matches) {
matched = matches[0];
action = rule.action;

View file

@ -346,4 +346,53 @@ suite('Monarch', () => {
disposables.dispose();
});
test('microsoft/monaco-editor#3128: allow state access within rules', () => {
const disposables = new DisposableStore();
const configurationService = new StandaloneConfigurationService();
const languageService = disposables.add(new LanguageService());
const tokenizer = disposables.add(createMonarchTokenizer(languageService, 'test', {
ignoreCase: false,
encoding: /u|u8|U|L/,
tokenizer: {
root: [
// C++ 11 Raw String
[/@encoding?R\"(?:([^ ()\\\t]*))\(/, { token: 'string.raw.begin', next: '@raw.$1' }],
],
raw: [
[/.*\)$S2\"/, 'string.raw', '@pop'],
[/.*/, 'string.raw']
],
},
}, configurationService));
const lines = [
`int main(){`,
``,
` auto s = R""""(`,
` Hello World`,
` )"""";`,
``,
` std::cout << "hello";`,
``,
`}`,
];
const actualTokens = getTokens(tokenizer, lines);
assert.deepStrictEqual(actualTokens, [
[new Token(0, 'source.test', 'test')],
[],
[new Token(0, 'source.test', 'test'), new Token(10, 'string.raw.begin.test', 'test')],
[new Token(0, 'string.raw.test', 'test')],
[new Token(0, 'string.raw.test', 'test'), new Token(6, 'source.test', 'test')],
[],
[new Token(0, 'source.test', 'test')],
[],
[new Token(0, 'source.test', 'test')],
]);
disposables.dispose();
});
});

View file

@ -340,6 +340,7 @@ export abstract class AbstractExtensionManagementService extends Disposable impl
} catch (error) {
// rollback installed extensions
if (successResults.length) {
this.logService.info('Rollback: Uninstalling installed extensions', getErrorMessage(error));
await Promise.allSettled(successResults.map(async ({ local, profileLocation }) => {
try {
await this.createUninstallExtensionTask(local, { versionOnly: true, profileLocation }).run();

View file

@ -288,7 +288,7 @@ export class ExtensionManagementService extends AbstractExtensionManagementServi
const key = ExtensionKey.create(extension).toString();
let installExtensionTask = this.installGalleryExtensionsTasks.get(key);
if (!installExtensionTask) {
this.installGalleryExtensionsTasks.set(key, installExtensionTask = new InstallGalleryExtensionTask(manifest, extension, options, this.extensionsDownloader, this.extensionsScanner, this.uriIdentityService, this.userDataProfilesService, this.extensionsScannerService, this.extensionsProfileScannerService, this.logService));
this.installGalleryExtensionsTasks.set(key, installExtensionTask = new InstallGalleryExtensionTask(manifest, extension, options, this.extensionsDownloader, this.extensionsScanner, this.uriIdentityService, this.userDataProfilesService, this.extensionsScannerService, this.extensionsProfileScannerService, this.logService, this.telemetryService));
installExtensionTask.waitUntilTaskIsFinished().finally(() => this.installGalleryExtensionsTasks.delete(key));
}
return installExtensionTask;
@ -875,6 +875,7 @@ export class InstallGalleryExtensionTask extends InstallExtensionTask {
extensionsScannerService: IExtensionsScannerService,
extensionsProfileScannerService: IExtensionsProfileScannerService,
logService: ILogService,
private readonly telemetryService: ITelemetryService,
) {
super(gallery.identifier, gallery, options, extensionsScanner, uriIdentityService, userDataProfilesService, extensionsScannerService, extensionsProfileScannerService, logService);
}
@ -921,6 +922,42 @@ export class InstallGalleryExtensionTask extends InstallExtensionTask {
}
}
try {
return await this.downloadAndInstallExtension(metadata, token);
} catch (error) {
if (error instanceof ExtensionManagementError && (error.code === ExtensionManagementErrorCode.CorruptZip || error.code === ExtensionManagementErrorCode.IncompleteZip)) {
this.logService.info(`Downloaded VSIX is invalid. Trying to download and install again...`, this.gallery.identifier.id);
type RetryInstallingInvalidVSIXClassification = {
owner: 'sandy081';
comment: 'Event reporting the retry of installing an invalid VSIX';
extensionId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Extension Id' };
succeeded?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Success value' };
};
type RetryInstallingInvalidVSIXEvent = {
extensionId: string;
succeeded: boolean;
};
try {
const result = await this.downloadAndInstallExtension(metadata, token);
this.telemetryService.publicLog2<RetryInstallingInvalidVSIXEvent, RetryInstallingInvalidVSIXClassification>('extensiongallery:install:retry', {
extensionId: this.gallery.identifier.id,
succeeded: true
});
return result;
} catch (error) {
this.telemetryService.publicLog2<RetryInstallingInvalidVSIXEvent, RetryInstallingInvalidVSIXClassification>('extensiongallery:install:retry', {
extensionId: this.gallery.identifier.id,
succeeded: false
});
throw error;
}
} else {
throw error;
}
}
}
private async downloadAndInstallExtension(metadata: Metadata, token: CancellationToken): Promise<[ILocalExtension, Metadata]> {
const { location, verificationStatus } = await this.extensionsDownloader.download(this.gallery, this._operation, !this.options.donotVerifySignature);
try {
this._verificationStatus = verificationStatus;

View file

@ -110,6 +110,7 @@ class TestInstallGalleryExtensionTask extends InstallGalleryExtensionTask {
extensionsScannerService,
extensionsProfileScannerService,
logService,
NullTelemetryService
);
}

View file

@ -65,8 +65,6 @@ interface IQuickPickElement extends IQuickInputItemLazyParts {
readonly saneDescription?: string;
readonly saneDetail?: string;
readonly saneTooltip?: string | IMarkdownString | HTMLElement;
readonly onChecked: Event<boolean>;
checked: boolean;
hidden: boolean;
element?: HTMLElement;
labelHighlights?: IMatch[];
@ -92,17 +90,11 @@ interface IQuickInputItemTemplateData {
class BaseQuickPickItemElement implements IQuickPickElement {
private readonly _init: Lazy<IQuickInputItemLazyParts>;
readonly onChecked: Event<boolean>;
constructor(
readonly index: number,
readonly hasCheckbox: boolean,
private _onChecked: Emitter<{ element: IQuickPickElement; checked: boolean }>,
mainItem: QuickPickItem
) {
this.onChecked = hasCheckbox
? Event.map(Event.filter<{ element: IQuickPickElement; checked: boolean }>(this._onChecked.event, e => e.element === this), e => e.checked)
: Event.None;
this._init = new Lazy(() => {
const saneLabel = mainItem.label ?? '';
const saneSortLabel = parseLabelWithIcons(saneLabel).text.trim();
@ -144,7 +136,7 @@ class BaseQuickPickItemElement implements IQuickPickElement {
this._element = value;
}
private _hidden: boolean = false;
private _hidden = false;
get hidden() {
return this._hidden;
}
@ -152,17 +144,6 @@ class BaseQuickPickItemElement implements IQuickPickElement {
this._hidden = value;
}
private _checked: boolean = false;
get checked() {
return this._checked;
}
set checked(value: boolean) {
if (value !== this._checked) {
this._checked = value;
this._onChecked.fire({ element: this, checked: value });
}
}
protected _saneDescription?: string;
get saneDescription() {
return this._saneDescription;
@ -213,15 +194,22 @@ class BaseQuickPickItemElement implements IQuickPickElement {
}
class QuickPickItemElement extends BaseQuickPickItemElement {
readonly onChecked: Event<boolean>;
constructor(
index: number,
hasCheckbox: boolean,
readonly fireButtonTriggered: (event: IQuickPickItemButtonEvent<IQuickPickItem>) => void,
onCheckedEmitter: Emitter<{ element: IQuickPickElement; checked: boolean }>,
private _onChecked: Emitter<{ element: IQuickPickElement; checked: boolean }>,
readonly item: IQuickPickItem,
private _separator: IQuickPickSeparator | undefined,
) {
super(index, hasCheckbox, onCheckedEmitter, item);
super(index, hasCheckbox, item);
this.onChecked = hasCheckbox
? Event.map(Event.filter<{ element: IQuickPickElement; checked: boolean }>(this._onChecked.event, e => e.element === this), e => e.checked)
: Event.None;
this._saneDescription = item.description;
this._saneDetail = item.detail;
this._saneTooltip = this.item.tooltip;
@ -236,6 +224,21 @@ class QuickPickItemElement extends BaseQuickPickItemElement {
set separator(value: IQuickPickSeparator | undefined) {
this._separator = value;
}
private _checked = false;
get checked() {
return this._checked;
}
set checked(value: boolean) {
if (value !== this._checked) {
this._checked = value;
this._onChecked.fire({ element: this, checked: value });
}
}
get checkboxDisabled() {
return !!this.item.disabled;
}
}
enum QuickPickSeparatorFocusReason {
@ -265,11 +268,9 @@ class QuickPickSeparatorElement extends BaseQuickPickItemElement {
constructor(
index: number,
readonly fireSeparatorButtonTriggered: (event: IQuickPickSeparatorButtonEvent) => void,
// TODO: remove this
onCheckedEmitter: Emitter<{ element: IQuickPickElement; checked: boolean }>,
readonly separator: IQuickPickSeparator,
) {
super(index, false, onCheckedEmitter, separator);
super(index, false, separator);
}
}
@ -313,7 +314,7 @@ class QuickInputAccessibilityProvider implements IListAccessibilityProvider<IQui
}
isChecked(element: IQuickPickElement) {
if (!element.hasCheckbox) {
if (!element.hasCheckbox || !(element instanceof QuickPickItemElement)) {
return undefined;
}
@ -347,9 +348,6 @@ abstract class BaseQuickInputListRenderer<T extends IQuickPickElement> implement
}));
data.checkbox = <HTMLInputElement>dom.append(label, $('input.quick-input-list-checkbox'));
data.checkbox.type = 'checkbox';
data.toDisposeTemplate.add(dom.addStandardDisposableListener(data.checkbox, dom.EventType.CHANGE, e => {
data.element.checked = data.checkbox.checked;
}));
// Rows
const rows = dom.append(label, $('.quick-input-list-rows'));
@ -413,6 +411,16 @@ class QuickPickItemElementRenderer extends BaseQuickInputListRenderer<QuickPickI
return QuickPickItemElementRenderer.ID;
}
override renderTemplate(container: HTMLElement): IQuickInputItemTemplateData {
const data = super.renderTemplate(container);
data.toDisposeTemplate.add(dom.addStandardDisposableListener(data.checkbox, dom.EventType.CHANGE, e => {
(data.element as QuickPickItemElement).checked = data.checkbox.checked;
}));
return data;
}
renderElement(node: ITreeNode<QuickPickItemElement, void>, index: number, data: IQuickInputItemTemplateData): void {
const element = node.element;
data.element = element;
@ -421,9 +429,11 @@ class QuickPickItemElementRenderer extends BaseQuickInputListRenderer<QuickPickI
data.checkbox.checked = element.checked;
data.toDisposeElement.add(element.onChecked(checked => data.checkbox.checked = checked));
data.checkbox.disabled = element.checkboxDisabled;
const { labelHighlights, descriptionHighlights, detailHighlights } = element;
// Icon
if (mainItem.iconPath) {
const icon = isDark(this.themeService.getColorTheme().type) ? mainItem.iconPath.dark : (mainItem.iconPath.light ?? mainItem.iconPath.dark);
const iconUrl = URI.revive(icon);
@ -562,6 +572,10 @@ class QuickPickSeparatorElementRenderer extends BaseQuickInputListRenderer<Quick
const { labelHighlights, descriptionHighlights, detailHighlights } = element;
// Icon
data.icon.style.backgroundImage = '';
data.icon.className = '';
// Label
let descriptionTitle: ITooltipMarkdownString | undefined;
// if we have a tooltip, that will be the hover,
@ -1044,7 +1058,7 @@ export class QuickInputTree extends Disposable {
setAllVisibleChecked(checked: boolean) {
this._itemElements.forEach(element => {
if (!element.hidden) {
if (!element.hidden && !element.checkboxDisabled) {
element.checked = checked;
}
});
@ -1067,7 +1081,6 @@ export class QuickInputTree extends Disposable {
currentSeparatorElement = new QuickPickSeparatorElement(
index,
(event: IQuickPickSeparatorButtonEvent) => this.fireSeparatorButtonTriggered(event),
this._elementChecked,
item
);
element = currentSeparatorElement;
@ -1486,10 +1499,12 @@ export class QuickInputTree extends Disposable {
}
toggleCheckbox() {
const elements = this._tree.getFocus().filter((e): e is IQuickPickElement => !!e);
const elements = this._tree.getFocus().filter((e): e is QuickPickItemElement => e instanceof QuickPickItemElement);
const allChecked = this._allVisibleChecked(elements);
for (const element of elements) {
element.checked = !allChecked;
if (!element.checkboxDisabled) {
element.checked = !allChecked;
}
}
this._fireCheckedEvents();
}
@ -1536,7 +1551,7 @@ export class QuickInputTree extends Disposable {
//#region private methods
private _allVisibleChecked(elements: IQuickPickElement[], whenNoneVisible = true) {
private _allVisibleChecked(elements: QuickPickItemElement[], whenNoneVisible = true) {
for (let i = 0, n = elements.length; i < n; i++) {
const element = elements[i];
if (!element.hidden) {

View file

@ -46,6 +46,10 @@ export interface IQuickPickItem {
highlights?: IQuickPickItemHighlights;
buttons?: readonly IQuickInputButton[];
picked?: boolean;
/**
* Used when we're in multi-select mode. Renders a disabled checkbox.
*/
disabled?: boolean;
alwaysShow?: boolean;
}

View file

@ -1653,7 +1653,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
TextSearchCompleteMessageType: TextSearchCompleteMessageType,
DataTransfer: extHostTypes.DataTransfer,
DataTransferItem: extHostTypes.DataTransferItem,
CoveredCount: extHostTypes.CoveredCount,
TestCoverageCount: extHostTypes.TestCoverageCount,
FileCoverage: extHostTypes.FileCoverage,
StatementCoverage: extHostTypes.StatementCoverage,
BranchCoverage: extHostTypes.BranchCoverage,

View file

@ -231,7 +231,7 @@ export class ExtensionsActivator implements IDisposable {
public activateById(extensionId: ExtensionIdentifier, reason: ExtensionActivationReason): Promise<void> {
const desc = this._registry.getExtensionDescription(extensionId);
if (!desc) {
throw new Error(`Extension '${extensionId}' is not known`);
throw new Error(`Extension '${extensionId.value}' is not known`);
}
return this._activateExtensions([{ id: desc.identifier, reason }]);
}

View file

@ -480,6 +480,7 @@ export class ExtHostNotebookKernels implements ExtHostNotebookKernelsShape {
name: result.variable.name,
value: result.variable.value,
type: result.variable.type,
interfaces: result.variable.interfaces,
language: result.variable.language,
expression: result.variable.expression,
hasNamedChildren: result.hasNamedChildren,

View file

@ -28,7 +28,6 @@ import { TestCommandId } from 'vs/workbench/contrib/testing/common/constants';
import { TestId, TestIdPathParts, TestPosition } from 'vs/workbench/contrib/testing/common/testId';
import { InvalidTestItemError } from 'vs/workbench/contrib/testing/common/testItemCollection';
import { AbstractIncrementalTestCollection, CoverageDetails, ICallProfileRunHandler, ISerializedTestResults, IStartControllerTests, IStartControllerTestsResult, ITestErrorMessage, ITestItem, ITestItemContext, ITestMessageMenuArgs, ITestRunProfile, IncrementalChangeCollector, IncrementalTestCollectionItem, InternalTestItem, TestResultState, TestRunProfileBitset, TestsDiff, TestsDiffOp, isStartControllerTests } from 'vs/workbench/contrib/testing/common/testTypes';
import { checkProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions';
import type * as vscode from 'vscode';
interface ControllerInfo {
@ -155,7 +154,7 @@ export class ExtHostTesting extends Disposable implements ExtHostTestingShape {
return new TestItemImpl(controllerId, id, label, uri);
},
createTestRun: (request, name, persist = true) => {
return this.runTracker.createTestRun(extension, controllerId, collection, request, name, persist);
return this.runTracker.createTestRun(controllerId, collection, request, name, persist);
},
invalidateTestResults: items => {
if (items === undefined) {
@ -354,7 +353,7 @@ export class ExtHostTesting extends Disposable implements ExtHostTestingShape {
return {};
}
const { collection, profiles, extension } = lookup;
const { collection, profiles } = lookup;
const profile = profiles.get(req.profileId);
if (!profile) {
return {};
@ -385,7 +384,6 @@ export class ExtHostTesting extends Disposable implements ExtHostTestingShape {
const tracker = isStartControllerTests(req) && this.runTracker.prepareForMainThreadTestRun(
publicReq,
TestRunDto.fromInternal(req, lookup.collection),
extension,
profile,
token,
);
@ -463,7 +461,6 @@ class TestRunTracker extends Disposable {
constructor(
private readonly dto: TestRunDto,
private readonly proxy: MainThreadTestingShape,
private readonly extension: IRelaxedExtensionDescription,
private readonly logService: ILogService,
private readonly profile: vscode.TestRunProfile | undefined,
parentToken?: CancellationToken,
@ -517,7 +514,6 @@ class TestRunTracker extends Disposable {
const runId = this.dto.id;
const ctrlId = this.dto.controllerId;
const taskId = generateUuid();
const extension = this.extension;
const guardTestMutation = <Args extends unknown[]>(fn: (test: vscode.TestItem, ...args: Args) => void) =>
(test: vscode.TestItem, ...args: Args) => {
@ -574,7 +570,6 @@ class TestRunTracker extends Disposable {
},
// todo@connor4312: back compat
set coverageProvider(provider: ICoverageProvider | undefined) {
checkProposedApiEnabled(extension, 'testCoverage');
coverageProvider = provider;
if (provider) {
Promise.resolve(provider.provideFileCoverage(CancellationToken.None)).then(coverage => {
@ -585,10 +580,7 @@ class TestRunTracker extends Disposable {
});
}
},
addCoverage: coverage => {
checkProposedApiEnabled(extension, 'testCoverage');
addCoverage(coverage);
},
addCoverage,
//#region state mutation
enqueued: guardTestMutation(test => {
this.proxy.$updateTestStateInRun(runId, taskId, TestId.fromExtHostTestItem(test, ctrlId).toString(), TestResultState.Queued);
@ -745,8 +737,8 @@ export class TestRunCoordinator {
* `$startedExtensionTestRun` is not invoked. The run must eventually
* be cancelled manually.
*/
public prepareForMainThreadTestRun(req: vscode.TestRunRequest, dto: TestRunDto, extension: Readonly<IRelaxedExtensionDescription>, profile: vscode.TestRunProfile, token: CancellationToken) {
return this.getTracker(req, dto, extension, profile, token);
public prepareForMainThreadTestRun(req: vscode.TestRunRequest, dto: TestRunDto, profile: vscode.TestRunProfile, token: CancellationToken) {
return this.getTracker(req, dto, profile, token);
}
/**
@ -768,7 +760,7 @@ export class TestRunCoordinator {
/**
* Implements the public `createTestRun` API.
*/
public createTestRun(extension: IRelaxedExtensionDescription, controllerId: string, collection: ExtHostTestItemCollection, request: vscode.TestRunRequest, name: string | undefined, persist: boolean): vscode.TestRun {
public createTestRun(controllerId: string, collection: ExtHostTestItemCollection, request: vscode.TestRunRequest, name: string | undefined, persist: boolean): vscode.TestRun {
const existing = this.tracked.get(request);
if (existing) {
return existing.createRun(name);
@ -788,7 +780,7 @@ export class TestRunCoordinator {
persist
});
const tracker = this.getTracker(request, dto, extension, request.profile);
const tracker = this.getTracker(request, dto, request.profile);
Event.once(tracker.onEnd)(() => {
this.proxy.$finishedExtensionTestRun(dto.id);
});
@ -796,8 +788,8 @@ export class TestRunCoordinator {
return tracker.createRun(name);
}
private getTracker(req: vscode.TestRunRequest, dto: TestRunDto, extension: IRelaxedExtensionDescription, profile: vscode.TestRunProfile | undefined, token?: CancellationToken) {
const tracker = new TestRunTracker(dto, this.proxy, extension, this.logService, profile, token);
private getTracker(req: vscode.TestRunRequest, dto: TestRunDto, profile: vscode.TestRunProfile | undefined, token?: CancellationToken) {
const tracker = new TestRunTracker(dto, this.proxy, this.logService, profile, token);
this.tracked.set(req, tracker);
this.trackedById.set(tracker.id, tracker);
return tracker;

View file

@ -48,7 +48,7 @@ import * as notebooks from 'vs/workbench/contrib/notebook/common/notebookCommon'
import { ICellRange } from 'vs/workbench/contrib/notebook/common/notebookRange';
import * as search from 'vs/workbench/contrib/search/common/search';
import { TestId } from 'vs/workbench/contrib/testing/common/testId';
import { CoverageDetails, DetailType, ICoveredCount, IFileCoverage, ISerializedTestResults, ITestErrorMessage, ITestItem, ITestTag, TestMessageType, TestResultItem, denamespaceTestTag, namespaceTestTag } from 'vs/workbench/contrib/testing/common/testTypes';
import { CoverageDetails, DetailType, ICoverageCount, IFileCoverage, ISerializedTestResults, ITestErrorMessage, ITestItem, ITestTag, TestMessageType, TestResultItem, denamespaceTestTag, namespaceTestTag } from 'vs/workbench/contrib/testing/common/testTypes';
import { EditorGroupColumn } from 'vs/workbench/services/editor/common/editorGroupColumn';
import { ACTIVE_GROUP, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService';
import { checkProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions';
@ -2015,7 +2015,7 @@ export namespace TestResults {
}
export namespace TestCoverage {
function fromCoveredCount(count: vscode.CoveredCount): ICoveredCount {
function fromCoverageCount(count: vscode.TestCoverageCount): ICoverageCount {
return { covered: count.covered, total: count.total };
}
@ -2047,9 +2047,9 @@ export namespace TestCoverage {
return {
id,
uri: coverage.uri,
statement: fromCoveredCount(coverage.statementCoverage),
branch: coverage.branchCoverage && fromCoveredCount(coverage.branchCoverage),
declaration: coverage.declarationCoverage && fromCoveredCount(coverage.declarationCoverage),
statement: fromCoverageCount(coverage.statementCoverage),
branch: coverage.branchCoverage && fromCoverageCount(coverage.branchCoverage),
declaration: coverage.declarationCoverage && fromCoverageCount(coverage.declarationCoverage),
};
}
}

View file

@ -4025,12 +4025,12 @@ export class TestTag implements vscode.TestTag {
//#endregion
//#region Test Coverage
export class CoveredCount implements vscode.CoveredCount {
export class TestCoverageCount implements vscode.TestCoverageCount {
constructor(public covered: number, public total: number) {
}
}
const validateCC = (cc?: vscode.CoveredCount) => {
const validateCC = (cc?: vscode.TestCoverageCount) => {
if (cc && cc.covered > cc.total) {
throw new Error(`The total number of covered items (${cc.covered}) cannot be greater than the total (${cc.total})`);
}
@ -4038,9 +4038,9 @@ const validateCC = (cc?: vscode.CoveredCount) => {
export class FileCoverage implements vscode.FileCoverage {
public static fromDetails(uri: vscode.Uri, details: vscode.FileCoverageDetail[]): vscode.FileCoverage {
const statements = new CoveredCount(0, 0);
const branches = new CoveredCount(0, 0);
const decl = new CoveredCount(0, 0);
const statements = new TestCoverageCount(0, 0);
const branches = new TestCoverageCount(0, 0);
const decl = new TestCoverageCount(0, 0);
for (const detail of details) {
if ('branches' in detail) {
@ -4073,9 +4073,9 @@ export class FileCoverage implements vscode.FileCoverage {
constructor(
public readonly uri: vscode.Uri,
public statementCoverage: vscode.CoveredCount,
public branchCoverage?: vscode.CoveredCount,
public declarationCoverage?: vscode.CoveredCount,
public statementCoverage: vscode.TestCoverageCount,
public branchCoverage?: vscode.TestCoverageCount,
public declarationCoverage?: vscode.TestCoverageCount,
) {
validateCC(statementCoverage);
validateCC(branchCoverage);

View file

@ -13,7 +13,7 @@ import { URI } from 'vs/base/common/uri';
import { mock, mockObject, MockObject } from 'vs/base/test/common/mock';
import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils';
import * as editorRange from 'vs/editor/common/core/range';
import { ExtensionIdentifier, IRelaxedExtensionDescription } from 'vs/platform/extensions/common/extensions';
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
import { NullLogService } from 'vs/platform/log/common/log';
import { MainThreadTestingShape } from 'vs/workbench/api/common/extHost.protocol';
import { ExtHostCommands } from 'vs/workbench/api/common/extHostCommands';
@ -603,7 +603,6 @@ suite('ExtHost Testing', () => {
let req: TestRunRequest;
let dto: TestRunDto;
const ext: IRelaxedExtensionDescription = {} as any;
teardown(() => {
for (const { id } of c.trackers) {
@ -637,11 +636,11 @@ suite('ExtHost Testing', () => {
});
test('tracks a run started from a main thread request', () => {
const tracker = ds.add(c.prepareForMainThreadTestRun(req, dto, ext, configuration, cts.token));
const tracker = ds.add(c.prepareForMainThreadTestRun(req, dto, configuration, cts.token));
assert.strictEqual(tracker.hasRunningTasks, false);
const task1 = c.createTestRun(ext, 'ctrl', single, req, 'run1', true);
const task2 = c.createTestRun(ext, 'ctrl', single, req, 'run2', true);
const task1 = c.createTestRun('ctrl', single, req, 'run1', true);
const task2 = c.createTestRun('ctrl', single, req, 'run2', true);
assert.strictEqual(proxy.$startedExtensionTestRun.called, false);
assert.strictEqual(tracker.hasRunningTasks, true);
@ -662,8 +661,8 @@ suite('ExtHost Testing', () => {
test('run cancel force ends after a timeout', () => {
const clock = sinon.useFakeTimers();
try {
const tracker = ds.add(c.prepareForMainThreadTestRun(req, dto, ext, configuration, cts.token));
const task = c.createTestRun(ext, 'ctrl', single, req, 'run1', true);
const tracker = ds.add(c.prepareForMainThreadTestRun(req, dto, configuration, cts.token));
const task = c.createTestRun('ctrl', single, req, 'run1', true);
const onEnded = sinon.stub();
ds.add(tracker.onEnd(onEnded));
@ -687,8 +686,8 @@ suite('ExtHost Testing', () => {
});
test('run cancel force ends on second cancellation request', () => {
const tracker = ds.add(c.prepareForMainThreadTestRun(req, dto, ext, configuration, cts.token));
const task = c.createTestRun(ext, 'ctrl', single, req, 'run1', true);
const tracker = ds.add(c.prepareForMainThreadTestRun(req, dto, configuration, cts.token));
const task = c.createTestRun('ctrl', single, req, 'run1', true);
const onEnded = sinon.stub();
ds.add(tracker.onEnd(onEnded));
@ -706,7 +705,7 @@ suite('ExtHost Testing', () => {
});
test('tracks a run started from an extension request', () => {
const task1 = c.createTestRun(ext, 'ctrl', single, req, 'hello world', false);
const task1 = c.createTestRun('ctrl', single, req, 'hello world', false);
const tracker = Iterable.first(c.trackers)!;
assert.strictEqual(tracker.hasRunningTasks, true);
@ -722,8 +721,8 @@ suite('ExtHost Testing', () => {
}]
]);
const task2 = c.createTestRun(ext, 'ctrl', single, req, 'run2', true);
const task3Detached = c.createTestRun(ext, 'ctrl', single, { ...req }, 'task3Detached', true);
const task2 = c.createTestRun('ctrl', single, req, 'run2', true);
const task3Detached = c.createTestRun('ctrl', single, { ...req }, 'task3Detached', true);
task1.end();
assert.strictEqual(proxy.$finishedExtensionTestRun.called, false);
@ -737,7 +736,7 @@ suite('ExtHost Testing', () => {
});
test('adds tests to run smartly', () => {
const task1 = c.createTestRun(ext, 'ctrlId', single, req, 'hello world', false);
const task1 = c.createTestRun('ctrlId', single, req, 'hello world', false);
const tracker = Iterable.first(c.trackers)!;
const expectedArgs: unknown[][] = [];
assert.deepStrictEqual(proxy.$addTestsToRun.args, expectedArgs);
@ -776,7 +775,7 @@ suite('ExtHost Testing', () => {
const test2 = new TestItemImpl('ctrlId', 'id-d', 'test d', URI.file('/testd.txt'));
test1.range = test2.range = new Range(new Position(0, 0), new Position(1, 0));
single.root.children.replace([test1, test2]);
const task = c.createTestRun(ext, 'ctrlId', single, req, 'hello world', false);
const task = c.createTestRun('ctrlId', single, req, 'hello world', false);
const message1 = new TestMessage('some message');
message1.location = new Location(URI.file('/a.txt'), new Position(0, 0));
@ -817,7 +816,7 @@ suite('ExtHost Testing', () => {
});
test('guards calls after runs are ended', () => {
const task = c.createTestRun(ext, 'ctrl', single, req, 'hello world', false);
const task = c.createTestRun('ctrl', single, req, 'hello world', false);
task.end();
task.failed(single.root, new TestMessage('some message'));
@ -829,7 +828,7 @@ suite('ExtHost Testing', () => {
});
test('excludes tests outside tree or explicitly excluded', () => {
const task = c.createTestRun(ext, 'ctrlId', single, {
const task = c.createTestRun('ctrlId', single, {
profile: configuration,
include: [single.root.children.get('id-a')!],
exclude: [single.root.children.get('id-a')!.children.get('id-aa')!],
@ -858,7 +857,7 @@ suite('ExtHost Testing', () => {
const childB = new TestItemImpl('ctrlId', 'id-child', 'child', undefined);
testB!.children.replace([childB]);
const task1 = c.createTestRun(ext, 'ctrl', single, new TestRunRequestImpl(), 'hello world', false);
const task1 = c.createTestRun('ctrl', single, new TestRunRequestImpl(), 'hello world', false);
const tracker = Iterable.first(c.trackers)!;
task1.passed(childA);

View file

@ -5,7 +5,6 @@
import { onDidChangeFullscreen } from 'vs/base/browser/browser';
import { hide, show } from 'vs/base/browser/dom';
import { mainWindow } from 'vs/base/browser/window';
import { Emitter, Event } from 'vs/base/common/event';
import { DisposableStore } from 'vs/base/common/lifecycle';
import { isNative } from 'vs/base/common/platform';
@ -175,14 +174,14 @@ export class AuxiliaryEditorPart {
disposables.add(auxiliaryWindow.onBeforeUnload(event => {
for (const group of editorPart.groups) {
for (const editor of group.editors) {
const canMove = editor.canMove(mainWindow.vscodeWindowId);
if (typeof canMove === 'string') {
// Closing an auxiliary window with opened editors
// will move the editors to the main window. As such,
// we need to validate that we can move and otherwise
// prevent the window from closing.
// Closing an auxiliary window with opened editors
// will move the editors to the main window. As such,
// we need to validate that we can move and otherwise
// prevent the window from closing.
const canMoveVeto = editor.canMove(group.id, this.editorPartsView.mainPart.activeGroup.id);
if (typeof canMoveVeto === 'string') {
group.openEditor(editor);
event.veto(canMove);
event.veto(canMoveVeto);
break;
}
}

View file

@ -1247,7 +1247,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
//#region moveEditor()
moveEditors(editors: { editor: EditorInput; options?: IEditorOptions }[], target: EditorGroupView): void {
moveEditors(editors: { editor: EditorInput; options?: IEditorOptions }[], target: EditorGroupView): boolean {
// Optimization: knowing that we move many editors, we
// delay the title update to a later point for this group
@ -1258,29 +1258,38 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
skipTitleUpdate: this !== target
};
let moveFailed = false;
const movedEditors = new Set<EditorInput>();
for (const { editor, options } of editors) {
this.moveEditor(editor, target, options, internalOptions);
if (this.moveEditor(editor, target, options, internalOptions)) {
movedEditors.add(editor);
} else {
moveFailed = true;
}
}
// Update the title control all at once with all editors
// in source and target if the title update was skipped
if (internalOptions.skipTitleUpdate) {
const movedEditors = editors.map(({ editor }) => editor);
target.titleControl.openEditors(movedEditors);
this.titleControl.closeEditors(movedEditors);
target.titleControl.openEditors(Array.from(movedEditors));
this.titleControl.closeEditors(Array.from(movedEditors));
}
return !moveFailed;
}
moveEditor(editor: EditorInput, target: EditorGroupView, options?: IEditorOptions, internalOptions?: IInternalMoveCopyOptions): void {
moveEditor(editor: EditorInput, target: EditorGroupView, options?: IEditorOptions, internalOptions?: IInternalMoveCopyOptions): boolean {
// Move within same group
if (this === target) {
this.doMoveEditorInsideGroup(editor, options);
return true;
}
// Move across groups
else {
this.doMoveOrCopyEditorAcrossGroups(editor, target, options, { ...internalOptions, keepCopy: false });
return this.doMoveOrCopyEditorAcrossGroups(editor, target, options, { ...internalOptions, keepCopy: false });
}
}
@ -1321,15 +1330,16 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
}
}
private doMoveOrCopyEditorAcrossGroups(editor: EditorInput, target: EditorGroupView, openOptions?: IEditorOpenOptions, internalOptions?: IInternalMoveCopyOptions): void {
private doMoveOrCopyEditorAcrossGroups(editor: EditorInput, target: EditorGroupView, openOptions?: IEditorOpenOptions, internalOptions?: IInternalMoveCopyOptions): boolean {
const keepCopy = internalOptions?.keepCopy;
// Validate that we can move
if (!keepCopy) {
const canMove = editor.canMove(target.windowId);
if (typeof canMove === 'string') {
this.dialogService.error(canMove);
return;
if (!keepCopy || editor.hasCapability(EditorInputCapabilities.Singleton) /* singleton editors will always move */) {
const canMoveVeto = editor.canMove(this.id, target.id);
if (typeof canMoveVeto === 'string') {
this.dialogService.error(canMoveVeto, localize('moveErrorDetails', "Try saving or reverting the editor first and then try again."));
return false;
}
}
@ -1358,6 +1368,8 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
if (!keepCopy) {
this.doCloseEditor(editor, true /* do not focus next one behind if any */, { ...internalOptions, context: EditorCloseContext.MOVE });
}
return true;
}
//#endregion

View file

@ -675,6 +675,9 @@ export class StatusbarService extends MultiWindowParts<StatusbarPart> implements
readonly mainPart = this._register(this.instantiationService.createInstance(MainStatusbarPart));
private readonly _onDidCreateAuxiliaryStatusbarPart = this._register(new Emitter<AuxiliaryStatusbarPart>());
private readonly onDidCreateAuxiliaryStatusbarPart = this._onDidCreateAuxiliaryStatusbarPart.event;
constructor(
@IInstantiationService private readonly instantiationService: IInstantiationService,
@IStorageService storageService: IStorageService,
@ -688,6 +691,8 @@ export class StatusbarService extends MultiWindowParts<StatusbarPart> implements
//#region Auxiliary Statusbar Parts
createAuxiliaryStatusbarPart(container: HTMLElement): IAuxiliaryStatusbarPart {
// Container
const statusbarPartContainer = document.createElement('footer');
statusbarPartContainer.classList.add('part', 'statusbar');
statusbarPartContainer.setAttribute('role', 'status');
@ -696,6 +701,7 @@ export class StatusbarService extends MultiWindowParts<StatusbarPart> implements
statusbarPartContainer.setAttribute('tabindex', '0');
container.appendChild(statusbarPartContainer);
// Statusbar Part
const statusbarPart = this.instantiationService.createInstance(AuxiliaryStatusbarPart, statusbarPartContainer);
const disposable = this.registerPart(statusbarPart);
@ -703,6 +709,9 @@ export class StatusbarService extends MultiWindowParts<StatusbarPart> implements
Event.once(statusbarPart.onWillDispose)(() => disposable.dispose());
// Emit internal event
this._onDidCreateAuxiliaryStatusbarPart.fire(statusbarPart);
return statusbarPart;
}
@ -717,9 +726,46 @@ export class StatusbarService extends MultiWindowParts<StatusbarPart> implements
readonly onDidChangeEntryVisibility = this.mainPart.onDidChangeEntryVisibility;
addEntry(entry: IStatusbarEntry, id: string, alignment: StatusbarAlignment, priorityOrLocation: number | IStatusbarEntryLocation | IStatusbarEntryPriority = 0): IStatusbarEntryAccessor {
if (entry.showInAllWindows) {
return this.doAddEntryToAllWindows(entry, id, alignment, priorityOrLocation);
}
return this.mainPart.addEntry(entry, id, alignment, priorityOrLocation);
}
private doAddEntryToAllWindows(entry: IStatusbarEntry, id: string, alignment: StatusbarAlignment, priorityOrLocation: number | IStatusbarEntryLocation | IStatusbarEntryPriority = 0): IStatusbarEntryAccessor {
const entryDisposables = new DisposableStore();
const accessors = new Set<IStatusbarEntryAccessor>();
function addEntry(part: StatusbarPart | AuxiliaryStatusbarPart): void {
const partDisposables = new DisposableStore();
partDisposables.add(part.onWillDispose(() => partDisposables.dispose()));
const accessor = partDisposables.add(part.addEntry(entry, id, alignment, priorityOrLocation));
accessors.add(accessor);
partDisposables.add(toDisposable(() => accessors.delete(accessor)));
entryDisposables.add(partDisposables);
partDisposables.add(toDisposable(() => entryDisposables.delete(partDisposables)));
}
for (const part of this.parts) {
addEntry(part);
}
entryDisposables.add(this.onDidCreateAuxiliaryStatusbarPart(part => addEntry(part)));
return {
update: (entry: IStatusbarEntry) => {
for (const update of accessors) {
update.update(entry);
}
},
dispose: () => entryDisposables.dispose()
};
}
isEntryVisible(id: string): boolean {
return this.mainPart.isEntryVisible(id);
}

View file

@ -18,7 +18,7 @@
/* Misc */
.file-icon-themable-tree .monaco-list-row .content .monaco-highlighted-label .highlight,
.monaco-tl-contents .monaco-highlighted-label .highlight {
.pane-body .monaco-tl-contents .monaco-highlighted-label .highlight {
color: unset !important;
background-color: var(--vscode-list-filterMatchBackground);
outline: 1px dotted var(--vscode-list-filterMatchBorder);

View file

@ -12,6 +12,7 @@ import { isStandalone } from 'vs/base/browser/browser';
import { WorkbenchPhase, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions';
import { ActivityBarPosition, EditorActionsLocation, EditorTabsMode, LayoutSettings } from 'vs/workbench/services/layout/browser/layoutService';
import { defaultWindowTitle, defaultWindowTitleSeparator } from 'vs/workbench/browser/parts/titlebar/windowTitle';
import { CustomEditorLabelService } from 'vs/workbench/services/editor/common/customEditorLabelService';
const registry = Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration);
@ -85,6 +86,32 @@ const registry = Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Con
'markdownDescription': localize('decorations.colors', "Controls whether editor file decorations should use colors."),
'default': true
},
[CustomEditorLabelService.SETTING_ID_ENABLED]: {
'type': 'boolean',
'markdownDescription': localize('workbench.editor.label.enabled', "Controls whether the custom workbench editor labels should be applied."),
'default': true,
},
[CustomEditorLabelService.SETTING_ID_PATTERNS]: {
'type': 'object',
'markdownDescription': (() => {
let customEditorLabelDescription = localize('workbench.editor.label.patterns', "Controls the rendering of the editor label. Each __Item__ is a pattern that matches a file path. Both relative and absolute file paths are supported. In case multiple patterns match, the longest matching path will be picked. Each __Value__ is the template for the rendered editor when the __Item__ matches. Variables are substituted based on the context:");
customEditorLabelDescription += '\n- ' + [
localize('workbench.editor.label.dirname', "`${dirname}`: name of the folder in which the file is located (e.g. `root/folder/file.txt -> folder`)."),
localize('workbench.editor.label.nthdirname', "`${dirname(N)}`: name of the nth parent folder in which the file is located (e.g. `N=1: root/folder/file.txt -> root`)."),
localize('workbench.editor.label.filename', "`${filename}`: name of the file without the file extension (e.g. `root/folder/file.txt -> file`)."),
localize('workbench.editor.label.extname', "`${extname}`: the file extension (e.g. `root/folder/file.txt -> txt`)."),
].join('\n- '); // intentionally concatenated to not produce a string that is too long for translations
customEditorLabelDescription += '\n\n' + localize('customEditorLabelDescriptionExample', "Example: `\"**/static/**/*.html\": \"${filename} - ${dirname} (${extname})\"` will render a file `root/static/folder/file.html` as `file - folder (html)`.");
return customEditorLabelDescription;
})(),
additionalProperties:
{
type: 'string',
markdownDescription: localize('workbench.editor.label.template', "The template which should be rendered when the pattern mtches. May include the variables ${dirname}, ${filename} and ${extname}."),
},
'default': {}
},
'workbench.editor.labelFormat': {
'type': 'string',
'enum': ['default', 'short', 'medium', 'long'],

View file

@ -289,16 +289,15 @@ export abstract class EditorInput extends AbstractEditorInput {
}
/**
* Indicates if this editor can be moved to another window. By default
* editors can freely be moved around windows. If an editor cannot be
* Indicates if this editor can be moved to another group. By default
* editors can freely be moved around groups. If an editor cannot be
* moved, a message should be returned to show to the user.
*
* @param targetWindowId the target window to move the editor to.
* @returns `true` if the editor can be moved to the target window, or
* @returns `true` if the editor can be moved to the target group, or
* a string with a message to show to the user if the editor cannot be
* moved.
*/
canMove(targetWindowId: number): true | string {
canMove(sourceGroup: GroupIdentifier, targetGroup: GroupIdentifier): true | string {
return true;
}

View file

@ -13,6 +13,7 @@ import { IFilesConfigurationService } from 'vs/workbench/services/filesConfigura
import { IMarkdownString } from 'vs/base/common/htmlContent';
import { isConfigured } from 'vs/platform/configuration/common/configuration';
import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfiguration';
import { ICustomEditorLabelService } from 'vs/workbench/services/editor/common/customEditorLabelService';
/**
* The base class for all editor inputs that open resources.
@ -46,7 +47,8 @@ export abstract class AbstractResourceEditorInput extends EditorInput implements
@ILabelService protected readonly labelService: ILabelService,
@IFileService protected readonly fileService: IFileService,
@IFilesConfigurationService protected readonly filesConfigurationService: IFilesConfigurationService,
@ITextResourceConfigurationService protected readonly textResourceConfigurationService: ITextResourceConfigurationService
@ITextResourceConfigurationService protected readonly textResourceConfigurationService: ITextResourceConfigurationService,
@ICustomEditorLabelService protected readonly customEditorLabelService: ICustomEditorLabelService
) {
super();
@ -61,6 +63,7 @@ export abstract class AbstractResourceEditorInput extends EditorInput implements
this._register(this.labelService.onDidChangeFormatters(e => this.onLabelEvent(e.scheme)));
this._register(this.fileService.onDidChangeFileSystemProviderRegistrations(e => this.onLabelEvent(e.scheme)));
this._register(this.fileService.onDidChangeFileSystemProviderCapabilities(e => this.onLabelEvent(e.scheme)));
this._register(this.customEditorLabelService.onDidChange(() => this.updateLabel()));
}
private onLabelEvent(scheme: string): void {
@ -95,7 +98,7 @@ export abstract class AbstractResourceEditorInput extends EditorInput implements
private _name: string | undefined = undefined;
override getName(): string {
if (typeof this._name !== 'string') {
this._name = this.labelService.getUriBasenameLabel(this._preferredResource);
this._name = this.customEditorLabelService.getName(this._preferredResource) ?? this.labelService.getUriBasenameLabel(this._preferredResource);
}
return this._name;

View file

@ -19,6 +19,7 @@ import { IReference } from 'vs/base/common/lifecycle';
import { createTextBufferFactory } from 'vs/editor/common/model/textModel';
import { IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService';
import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfiguration';
import { ICustomEditorLabelService } from 'vs/workbench/services/editor/common/customEditorLabelService';
/**
* The base class for all editor inputs that open in text editors.
@ -33,9 +34,10 @@ export abstract class AbstractTextResourceEditorInput extends AbstractResourceEd
@ILabelService labelService: ILabelService,
@IFileService fileService: IFileService,
@IFilesConfigurationService filesConfigurationService: IFilesConfigurationService,
@ITextResourceConfigurationService textResourceConfigurationService: ITextResourceConfigurationService
@ITextResourceConfigurationService textResourceConfigurationService: ITextResourceConfigurationService,
@ICustomEditorLabelService customEditorLabelService: ICustomEditorLabelService
) {
super(resource, preferredResource, labelService, fileService, filesConfigurationService, textResourceConfigurationService);
super(resource, preferredResource, labelService, fileService, filesConfigurationService, textResourceConfigurationService, customEditorLabelService);
}
override save(group: GroupIdentifier, options?: ITextFileSaveOptions): Promise<IUntypedEditorInput | undefined> {
@ -107,9 +109,10 @@ export class TextResourceEditorInput extends AbstractTextResourceEditorInput imp
@IFileService fileService: IFileService,
@ILabelService labelService: ILabelService,
@IFilesConfigurationService filesConfigurationService: IFilesConfigurationService,
@ITextResourceConfigurationService textResourceConfigurationService: ITextResourceConfigurationService
@ITextResourceConfigurationService textResourceConfigurationService: ITextResourceConfigurationService,
@ICustomEditorLabelService customEditorLabelService: ICustomEditorLabelService
) {
super(resource, undefined, editorService, textFileService, labelService, fileService, filesConfigurationService, textResourceConfigurationService);
super(resource, undefined, editorService, textFileService, labelService, fileService, filesConfigurationService, textResourceConfigurationService, customEditorLabelService);
}
override getName(): string {

View file

@ -3,7 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Disposable, DisposableStore, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { Disposable, MutableDisposable } from 'vs/base/common/lifecycle';
import { Event } from 'vs/base/common/event';
import Severity from 'vs/base/common/severity';
import { localize } from 'vs/nls';
@ -13,34 +13,6 @@ import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configur
import { INotificationHandle, INotificationService, NotificationPriority } from 'vs/platform/notification/common/notification';
import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
import { IStatusbarEntryAccessor, IStatusbarService, StatusbarAlignment } from 'vs/workbench/services/statusbar/browser/statusbar';
import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
class ScreenReaderModeStatusEntry extends Disposable {
private readonly screenReaderModeElement = this._register(new MutableDisposable<IStatusbarEntryAccessor>());
constructor(@IStatusbarService private readonly statusbarService: IStatusbarService) {
super();
}
updateScreenReaderModeElement(visible: boolean): void {
if (visible) {
if (!this.screenReaderModeElement.value) {
const text = localize('screenReaderDetected', "Screen Reader Optimized");
this.screenReaderModeElement.value = this.statusbarService.addEntry({
name: localize('status.editor.screenReaderMode', "Screen Reader Mode"),
text,
ariaLabel: text,
command: 'showEditorScreenReaderNotification',
kind: 'prominent'
}, 'status.editor.screenReaderMode', StatusbarAlignment.RIGHT, 100.6);
}
} else {
this.screenReaderModeElement.clear();
}
}
}
export class AccessibilityStatus extends Disposable implements IWorkbenchContribution {
@ -48,40 +20,23 @@ export class AccessibilityStatus extends Disposable implements IWorkbenchContrib
private screenReaderNotification: INotificationHandle | null = null;
private promptedScreenReader: boolean = false;
private readonly screenReaderModeElements = new Set<ScreenReaderModeStatusEntry>();
private readonly screenReaderModeElement = this._register(new MutableDisposable<IStatusbarEntryAccessor>());
constructor(
@IConfigurationService private readonly configurationService: IConfigurationService,
@INotificationService private readonly notificationService: INotificationService,
@IAccessibilityService private readonly accessibilityService: IAccessibilityService,
@IInstantiationService instantiationService: IInstantiationService,
@IEditorGroupsService private readonly editorGroupService: IEditorGroupsService
@IStatusbarService private readonly statusbarService: IStatusbarService
) {
super();
this.createScreenReaderModeElement(instantiationService, this._store);
this.updateScreenReaderModeElements(accessibilityService.isScreenReaderOptimized());
this._register(CommandsRegistry.registerCommand({ id: 'showEditorScreenReaderNotification', handler: () => this.showScreenReaderNotification() }));
CommandsRegistry.registerCommand({ id: 'showEditorScreenReaderNotification', handler: () => this.showScreenReaderNotification() });
this.updateScreenReaderModeElement(this.accessibilityService.isScreenReaderOptimized());
this.registerListeners();
}
private createScreenReaderModeElement(instantiationService: IInstantiationService, disposables: DisposableStore): ScreenReaderModeStatusEntry {
const entry = disposables.add(instantiationService.createInstance(ScreenReaderModeStatusEntry));
this.screenReaderModeElements.add(entry);
disposables.add(toDisposable(() => this.screenReaderModeElements.delete(entry)));
return entry;
}
private updateScreenReaderModeElements(visible: boolean): void {
for (const entry of this.screenReaderModeElements) {
entry.updateScreenReaderModeElement(visible);
}
}
private registerListeners(): void {
this._register(this.accessibilityService.onDidChangeScreenReaderOptimized(() => this.onScreenReaderModeChange()));
@ -90,11 +45,6 @@ export class AccessibilityStatus extends Disposable implements IWorkbenchContrib
this.onScreenReaderModeChange();
}
}));
this._register(this.editorGroupService.onDidCreateAuxiliaryEditorPart(({ instantiationService, disposables }) => {
const entry = this.createScreenReaderModeElement(instantiationService, disposables);
entry.updateScreenReaderModeElement(this.accessibilityService.isScreenReaderOptimized());
}));
}
private showScreenReaderNotification(): void {
@ -120,6 +70,23 @@ export class AccessibilityStatus extends Disposable implements IWorkbenchContrib
Event.once(this.screenReaderNotification.onDidClose)(() => this.screenReaderNotification = null);
}
private updateScreenReaderModeElement(visible: boolean): void {
if (visible) {
if (!this.screenReaderModeElement.value) {
const text = localize('screenReaderDetected', "Screen Reader Optimized");
this.screenReaderModeElement.value = this.statusbarService.addEntry({
name: localize('status.editor.screenReaderMode', "Screen Reader Mode"),
text,
ariaLabel: text,
command: 'showEditorScreenReaderNotification',
kind: 'prominent',
showInAllWindows: true
}, 'status.editor.screenReaderMode', StatusbarAlignment.RIGHT, 100.6);
}
} else {
this.screenReaderModeElement.clear();
}
}
private onScreenReaderModeChange(): void {
@ -138,14 +105,6 @@ export class AccessibilityStatus extends Disposable implements IWorkbenchContrib
if (this.screenReaderNotification) {
this.screenReaderNotification.close();
}
this.updateScreenReaderModeElements(this.accessibilityService.isScreenReaderOptimized());
}
override dispose(): void {
super.dispose();
for (const entry of this.screenReaderModeElements) {
entry.dispose();
}
this.updateScreenReaderModeElement(this.accessibilityService.isScreenReaderOptimized());
}
}

View file

@ -5,7 +5,7 @@
import { fromNow } from 'vs/base/common/date';
import { DisposableStore } from 'vs/base/common/lifecycle';
import { localize } from 'vs/nls';
import { localize, localize2 } from 'vs/nls';
import { Action2 } from 'vs/platform/actions/common/actions';
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
@ -13,28 +13,63 @@ import { IProductService } from 'vs/platform/product/common/productService';
import { IQuickInputService, IQuickPickItem, IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput';
import { IAuthenticationAccessService } from 'vs/workbench/services/authentication/browser/authenticationAccessService';
import { IAuthenticationUsageService } from 'vs/workbench/services/authentication/browser/authenticationUsageService';
import { AllowedExtension } from 'vs/workbench/services/authentication/common/authentication';
import { AllowedExtension, IAuthenticationService } from 'vs/workbench/services/authentication/common/authentication';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
export class ManageTrustedExtensionsForAccountAction extends Action2 {
constructor() {
super({
id: '_manageTrustedExtensionsForAccount',
title: localize('manageTrustedExtensionsForAccount', "Manage Trusted Extensions For Account"),
f1: false
title: localize2('manageTrustedExtensionsForAccount', "Manage Trusted Extensions For Account"),
category: localize2('accounts', "Accounts"),
f1: true
});
}
override async run(accessor: ServicesAccessor, { providerId, accountLabel }: { providerId: string; accountLabel: string }): Promise<void> {
override async run(accessor: ServicesAccessor, options?: { providerId: string; accountLabel: string }): Promise<void> {
const productService = accessor.get(IProductService);
const extensionService = accessor.get(IExtensionService);
const dialogService = accessor.get(IDialogService);
const quickInputService = accessor.get(IQuickInputService);
const authenticationService = accessor.get(IAuthenticationService);
const authenticationUsageService = accessor.get(IAuthenticationUsageService);
const authenticationAccessService = accessor.get(IAuthenticationAccessService);
let providerId = options?.providerId;
let accountLabel = options?.accountLabel;
if (!providerId || !accountLabel) {
throw new Error('Invalid arguments. Expected: { providerId: string; accountLabel: string }');
const accounts = new Array<{ providerId: string; providerLabel: string; accountLabel: string }>();
for (const id of authenticationService.getProviderIds()) {
const providerLabel = authenticationService.getProvider(id).label;
const sessions = await authenticationService.getSessions(id);
const uniqueAccountLabels = new Set<string>();
for (const session of sessions) {
if (!uniqueAccountLabels.has(session.account.label)) {
uniqueAccountLabels.add(session.account.label);
accounts.push({ providerId: id, providerLabel, accountLabel: session.account.label });
}
}
}
const pick = await quickInputService.pick(
accounts.map(account => ({
providerId: account.providerId,
label: account.accountLabel,
description: account.providerLabel
})),
{
placeHolder: localize('pickAccount', "Pick an account to manage trusted extensions for"),
matchOnDescription: true,
}
);
if (pick) {
providerId = pick.providerId;
accountLabel = pick.label;
} else {
return;
}
}
const allowedExtensions = authenticationAccessService.readAllowedExtensions(providerId, accountLabel);
@ -96,20 +131,24 @@ export class ManageTrustedExtensionsForAccountAction extends Action2 {
}
const sortByLastUsed = (a: AllowedExtension, b: AllowedExtension) => (b.lastUsed || 0) - (a.lastUsed || 0);
const toQuickPickItem = function (extension: AllowedExtension) {
const toQuickPickItem = function (extension: AllowedExtension): IQuickPickItem & { extension: AllowedExtension } {
const lastUsed = extension.lastUsed;
const description = lastUsed
? localize({ key: 'accountLastUsedDate', comment: ['The placeholder {0} is a string with time information, such as "3 days ago"'] }, "Last used this account {0}", fromNow(lastUsed, true))
: localize('notUsed', "Has not used this account");
let tooltip: string | undefined;
let disabled: boolean | undefined;
if (extension.trusted) {
tooltip = localize('trustedExtensionTooltip', "This extension is trusted by Microsoft and\nalways has access to this account");
disabled = true;
}
return {
label: extension.name,
extension,
description,
tooltip
tooltip,
disabled,
picked: extension.allowed === undefined || extension.allowed
};
};
const items: Array<TrustedExtensionsQuickPickItem | IQuickPickSeparator> = [
@ -127,33 +166,15 @@ export class ManageTrustedExtensionsForAccountAction extends Action2 {
const updatedAllowedList = quickPick.items
.filter((item): item is TrustedExtensionsQuickPickItem => item.type !== 'separator')
.map(i => i.extension);
const allowedExtensionsSet = new Set(quickPick.selectedItems.map(i => i.extension));
updatedAllowedList.forEach(extension => {
extension.allowed = allowedExtensionsSet.has(extension);
});
authenticationAccessService.updateAllowedExtensions(providerId, accountLabel, updatedAllowedList);
quickPick.hide();
}));
disposableStore.add(quickPick.onDidChangeSelection((changed) => {
const trustedItems = new Set<TrustedExtensionsQuickPickItem>();
quickPick.items.forEach(item => {
const trustItem = item as TrustedExtensionsQuickPickItem;
if (trustItem.extension) {
if (trustItem.extension.trusted) {
trustedItems.add(trustItem);
} else {
trustItem.extension.allowed = false;
}
}
});
changed.forEach((item) => {
item.extension.allowed = true;
trustedItems.delete(item);
});
// reselect trusted items if a user tried to unselect one since quick pick doesn't support forcing selection
if (trustedItems.size) {
quickPick.selectedItems = [...changed, ...trustedItems];
}
}));
disposableStore.add(quickPick.onDidHide(() => {
disposableStore.dispose();
}));

View file

@ -438,10 +438,12 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge
const editorBorder = 2;
const editorPadding = 12;
const executeToolbarWidth = this.cachedToolbarWidth = this.toolbar.getItemsWidth();
const toolbarPadding = 4;
const sideToolbarWidth = this.options.renderStyle === 'compact' ? 20 : 0;
const initialEditorScrollWidth = this._inputEditor.getScrollWidth();
const newDimension = { width: width - inputPartHorizontalPadding - editorBorder - editorPadding - executeToolbarWidth - sideToolbarWidth, height: inputEditorHeight };
const newEditorWidth = width - inputPartHorizontalPadding - editorBorder - editorPadding - executeToolbarWidth - sideToolbarWidth - toolbarPadding;
const newDimension = { width: newEditorWidth, height: inputEditorHeight };
if (!this.previousInputEditorDimension || (this.previousInputEditorDimension.width !== newDimension.width || this.previousInputEditorDimension.height !== newDimension.height)) {
// This layout call has side-effects that are hard to understand. eg if we are calling this inside a onDidChangeContent handler, this can trigger the next onDidChangeContent handler
// to be invoked, and we have a lot of these on this editor. Only doing a layout this when the editor size has actually changed makes it much easier to follow.

View file

@ -20,7 +20,7 @@ import { Codicon } from 'vs/base/common/codicons';
import { Emitter, Event } from 'vs/base/common/event';
import { FuzzyScore } from 'vs/base/common/filters';
import { IMarkdownString, MarkdownString } from 'vs/base/common/htmlContent';
import { Disposable, DisposableStore, IDisposable, IReference, toDisposable } from 'vs/base/common/lifecycle';
import { Disposable, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { ResourceMap } from 'vs/base/common/map';
import { FileAccess, Schemas, matchesSomeScheme } from 'vs/base/common/network';
import { clamp } from 'vs/base/common/numbers';
@ -48,11 +48,11 @@ import { defaultButtonStyles } from 'vs/platform/theme/browser/defaultStyles';
import { ColorScheme } from 'vs/platform/theme/common/theme';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { IResourceLabel, ResourceLabels } from 'vs/workbench/browser/labels';
import { ChatTreeItem, IChatCodeBlockInfo, IChatFileTreeInfo, GeneratingPhrase } from 'vs/workbench/contrib/chat/browser/chat';
import { ChatTreeItem, GeneratingPhrase, IChatCodeBlockInfo, IChatFileTreeInfo } from 'vs/workbench/contrib/chat/browser/chat';
import { ChatFollowups } from 'vs/workbench/contrib/chat/browser/chatFollowups';
import { ChatMarkdownDecorationsRenderer, IMarkdownVulnerability, annotateSpecialMarkdownContent, extractVulnerabilitiesFromText } from 'vs/workbench/contrib/chat/browser/chatMarkdownDecorationsRenderer';
import { ChatMarkdownDecorationsRenderer } from 'vs/workbench/contrib/chat/browser/chatMarkdownDecorationsRenderer';
import { ChatEditorOptions } from 'vs/workbench/contrib/chat/browser/chatOptions';
import { ChatCodeBlockContentProvider, CodeBlockPart, ICodeBlockData, ICodeBlockPart, localFileLanguageId, parseLocalFileData } from 'vs/workbench/contrib/chat/browser/codeBlockPart';
import { ChatCodeBlockContentProvider, CodeBlockPart, ICodeBlockData, localFileLanguageId, parseLocalFileData } from 'vs/workbench/contrib/chat/browser/codeBlockPart';
import { IChatAgentMetadata } from 'vs/workbench/contrib/chat/common/chatAgents';
import { CONTEXT_CHAT_RESPONSE_SUPPORT_ISSUE_REPORTING, CONTEXT_REQUEST, CONTEXT_RESPONSE, CONTEXT_RESPONSE_DETECTED_AGENT_COMMAND, CONTEXT_RESPONSE_FILTERED, CONTEXT_RESPONSE_VOTE } from 'vs/workbench/contrib/chat/common/chatContextKeys';
import { IChatProgressRenderableResponseContent } from 'vs/workbench/contrib/chat/common/chatModel';
@ -62,6 +62,7 @@ import { IChatProgressMessageRenderData, IChatRenderData, IChatResponseMarkdownR
import { IWordCountResult, getNWords } from 'vs/workbench/contrib/chat/common/chatWordCounter';
import { createFileIconThemableTreeContainerScope } from 'vs/workbench/contrib/files/browser/views/explorerView';
import { IFilesConfiguration } from 'vs/workbench/contrib/files/common/files';
import { IMarkdownVulnerability, annotateSpecialMarkdownContent } from '../common/annotations';
import { CodeBlockModelCollection } from '../common/codeBlockModelCollection';
const $ = dom.$;
@ -878,14 +879,14 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer<Ch
fillInIncompleteTokens,
codeBlockRendererSync: (languageId, text) => {
const index = codeBlockIndex++;
let textModel: Promise<IReference<IResolvedTextEditorModel>>;
let textModel: Promise<IResolvedTextEditorModel>;
let range: Range | undefined;
let vulns: readonly IMarkdownVulnerability[] | undefined;
if (equalsIgnoreCase(languageId, localFileLanguageId)) {
try {
const parsedBody = parseLocalFileData(text);
range = parsedBody.range && Range.lift(parsedBody.range);
textModel = this.textModelService.createModelReference(parsedBody.uri);
textModel = this.textModelService.createModelReference(parsedBody.uri).then(ref => ref.object);
} catch (e) {
return $('div');
}
@ -896,16 +897,13 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer<Ch
}
const sessionId = isResponseVM(element) || isRequestVM(element) ? element.sessionId : '';
const blockModel = this.codeBlockModelCollection.get(sessionId, element, index);
if (!blockModel) {
const modelEntry = this.codeBlockModelCollection.get(sessionId, element, index);
if (!modelEntry) {
console.error('Trying to render code block without model', element.id, index);
return $('div');
}
textModel = blockModel;
const extractedVulns = extractVulnerabilitiesFromText(text);
vulns = extractedVulns.vulnerabilities;
textModel.then(ref => ref.object.textEditorModel.setValue(extractedVulns.newText));
vulns = modelEntry.vulns;
textModel = modelEntry.model;
}
const hideToolbar = isResponseVM(element) && element.errorDetails?.responseIsFiltered;
@ -956,7 +954,7 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer<Ch
};
}
private renderCodeBlock(data: ICodeBlockData): IDisposableReference<ICodeBlockPart> {
private renderCodeBlock(data: ICodeBlockData): IDisposableReference<CodeBlockPart> {
const ref = this._editorPool.get();
const editorInfo = ref.object;
editorInfo.render(data, this._currentLayoutWidth, this.rendererOptions.editableCodeBlock);
@ -1026,11 +1024,11 @@ interface IDisposableReference<T> extends IDisposable {
isStale: () => boolean;
}
export class EditorPool extends Disposable {
class EditorPool extends Disposable {
private readonly _pool: ResourcePool<CodeBlockPart>;
public inUse(): Iterable<ICodeBlockPart> {
public inUse(): Iterable<CodeBlockPart> {
return this._pool.inUse;
}
@ -1046,7 +1044,7 @@ export class EditorPool extends Disposable {
}));
}
get(): IDisposableReference<ICodeBlockPart> {
get(): IDisposableReference<CodeBlockPart> {
const codeBlock = this._pool.get();
let stale = false;
return {

View file

@ -5,18 +5,14 @@
import * as dom from 'vs/base/browser/dom';
import { toErrorMessage } from 'vs/base/common/errorMessage';
import { MarkdownString } from 'vs/base/common/htmlContent';
import { revive } from 'vs/base/common/marshalling';
import { basename } from 'vs/base/common/resources';
import { URI } from 'vs/base/common/uri';
import { IRange } from 'vs/editor/common/core/range';
import { Location } from 'vs/editor/common/languages';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { ILabelService } from 'vs/platform/label/common/label';
import { ILogService } from 'vs/platform/log/common/log';
import { IChatProgressRenderableResponseContent, IChatProgressResponseContent } from 'vs/workbench/contrib/chat/common/chatModel';
import { ChatRequestDynamicVariablePart, ChatRequestTextPart, IParsedChatRequest } from 'vs/workbench/contrib/chat/common/chatParserTypes';
import { IChatAgentMarkdownContentWithVulnerability, IChatAgentVulnerabilityDetails, IChatContentInlineReference } from 'vs/workbench/contrib/chat/common/chatService';
import { contentRefUrl } from '../common/annotations';
const variableRefUrl = 'http://_vscodedecoration_';
@ -111,73 +107,3 @@ export class ChatMarkdownDecorationsRenderer {
}
}
export interface IMarkdownVulnerability {
title: string;
description: string;
range: IRange;
}
export function extractVulnerabilitiesFromText(text: string): { newText: string; vulnerabilities: IMarkdownVulnerability[] } {
const vulnerabilities: IMarkdownVulnerability[] = [];
let newText = text;
let match: RegExpExecArray | null;
while ((match = /<vscode_annotation details="(.*?)">(.*?)<\/vscode_annotation>/ms.exec(newText)) !== null) {
const [full, details, content] = match;
const start = match.index;
const textBefore = newText.substring(0, start);
const linesBefore = textBefore.split('\n').length - 1;
const linesInside = content.split('\n').length - 1;
const previousNewlineIdx = textBefore.lastIndexOf('\n');
const startColumn = start - (previousNewlineIdx + 1) + 1;
const endPreviousNewlineIdx = (textBefore + content).lastIndexOf('\n');
const endColumn = start + content.length - (endPreviousNewlineIdx + 1) + 1;
try {
const vulnDetails: IChatAgentVulnerabilityDetails[] = JSON.parse(decodeURIComponent(details));
vulnDetails.forEach(({ title, description }) =>
vulnerabilities.push({
title, description, range:
{ startLineNumber: linesBefore + 1, startColumn, endLineNumber: linesBefore + linesInside + 1, endColumn }
}));
} catch (err) {
// Something went wrong with encoding this text, just ignore it
}
newText = newText.substring(0, start) + content + newText.substring(start + full.length);
}
return { newText, vulnerabilities };
}
const contentRefUrl = 'http://_vscodecontentref_'; // must be lowercase for URI
export function annotateSpecialMarkdownContent(response: ReadonlyArray<IChatProgressResponseContent>): ReadonlyArray<IChatProgressRenderableResponseContent> {
const result: Exclude<IChatProgressResponseContent, IChatContentInlineReference | IChatAgentMarkdownContentWithVulnerability>[] = [];
for (const item of response) {
const previousItem = result[result.length - 1];
if (item.kind === 'inlineReference') {
const location = 'uri' in item.inlineReference ? item.inlineReference : { uri: item.inlineReference };
const printUri = URI.parse(contentRefUrl).with({ fragment: JSON.stringify(location) });
const markdownText = `[${item.name || basename(location.uri)}](${printUri.toString()})`;
if (previousItem?.kind === 'markdownContent') {
result[result.length - 1] = { content: new MarkdownString(previousItem.content.value + markdownText, { isTrusted: previousItem.content.isTrusted }), kind: 'markdownContent' };
} else {
result.push({ content: new MarkdownString(markdownText), kind: 'markdownContent' });
}
} else if (item.kind === 'markdownContent' && previousItem?.kind === 'markdownContent') {
result[result.length - 1] = { content: new MarkdownString(previousItem.content.value + item.content.value, { isTrusted: previousItem.content.isTrusted }), kind: 'markdownContent' };
} else if (item.kind === 'markdownVuln') {
const vulnText = encodeURIComponent(JSON.stringify(item.vulnerabilities));
const markdownText = `<vscode_annotation details="${vulnText}">${item.content.value}</vscode_annotation>`;
if (previousItem?.kind === 'markdownContent') {
result[result.length - 1] = { content: new MarkdownString(previousItem.content.value + markdownText, { isTrusted: previousItem.content.isTrusted }), kind: 'markdownContent' };
} else {
result.push({ content: new MarkdownString(markdownText), kind: 'markdownContent' });
}
} else {
result.push(item);
}
}
return result;
}

View file

@ -8,8 +8,8 @@ import 'vs/css!./codeBlockPart';
import * as dom from 'vs/base/browser/dom';
import { Button } from 'vs/base/browser/ui/button/button';
import { Codicon } from 'vs/base/common/codicons';
import { Emitter, Event } from 'vs/base/common/event';
import { Disposable, IReference } from 'vs/base/common/lifecycle';
import { Emitter } from 'vs/base/common/event';
import { Disposable } from 'vs/base/common/lifecycle';
import { Schemas } from 'vs/base/common/network';
import { URI, UriComponents } from 'vs/base/common/uri';
import { IEditorConstructionOptions } from 'vs/editor/browser/config/editorConfiguration';
@ -38,12 +38,12 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
import { AccessibilityVerbositySettingId } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration';
import { IChatRendererDelegate } from 'vs/workbench/contrib/chat/browser/chatListRenderer';
import { IMarkdownVulnerability } from 'vs/workbench/contrib/chat/browser/chatMarkdownDecorationsRenderer';
import { ChatEditorOptions } from 'vs/workbench/contrib/chat/browser/chatOptions';
import { IChatResponseViewModel, isResponseVM } from 'vs/workbench/contrib/chat/common/chatViewModel';
import { MenuPreventer } from 'vs/workbench/contrib/codeEditor/browser/menuPreventer';
import { SelectionClipboardContributionID } from 'vs/workbench/contrib/codeEditor/browser/selectionClipboard';
import { getSimpleEditorOptions } from 'vs/workbench/contrib/codeEditor/browser/simpleEditorOptions';
import { IMarkdownVulnerability } from '../common/annotations';
const $ = dom.$;
@ -51,7 +51,7 @@ export interface ICodeBlockData {
readonly codeBlockIndex: number;
readonly element: unknown;
readonly textModel: Promise<IReference<IResolvedTextEditorModel>>;
readonly textModel: Promise<IResolvedTextEditorModel>;
readonly languageId: string;
readonly vulns?: readonly IMarkdownVulnerability[];
@ -106,21 +106,8 @@ export interface ICodeBlockActionContext {
element: unknown;
}
export interface ICodeBlockPart {
readonly editor: CodeEditorWidget;
readonly onDidChangeContentHeight: Event<void>;
readonly element: HTMLElement;
readonly uri: URI | undefined;
layout(width: number): void;
render(data: ICodeBlockData, width: number, editable?: boolean): Promise<void>;
focus(): void;
reset(): unknown;
dispose(): void;
}
const defaultCodeblockPadding = 10;
export class CodeBlockPart extends Disposable implements ICodeBlockPart {
export class CodeBlockPart extends Disposable {
protected readonly _onDidChangeContentHeight = this._register(new Emitter<void>());
public readonly onDidChangeContentHeight = this._onDidChangeContentHeight.event;
@ -164,6 +151,7 @@ export class CodeBlockPart extends Disposable implements ICodeBlockPart {
padding: { top: defaultCodeblockPadding, bottom: defaultCodeblockPadding },
mouseWheelZoom: false,
scrollbar: {
vertical: 'hidden',
alwaysConsumeMouseWheel: false
},
definitionLinkOpensInPeek: false,
@ -331,7 +319,7 @@ export class CodeBlockPart extends Disposable implements ICodeBlockPart {
return this.editor.getContentHeight();
}
async render(data: ICodeBlockData, width: number, editable: boolean) {
async render(data: ICodeBlockData, width: number, editable: boolean | undefined) {
if (data.parentContextKeyService) {
this.contextKeyService.updateParent(data.parentContextKeyService);
}
@ -373,7 +361,7 @@ export class CodeBlockPart extends Disposable implements ICodeBlockPart {
}
private async updateEditor(data: ICodeBlockData): Promise<void> {
const textModel = (await data.textModel).object.textEditorModel;
const textModel = (await data.textModel).textEditorModel;
this.editor.setModel(textModel);
if (data.range) {
this.editor.setSelection(data.range);

View file

@ -0,0 +1,80 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { MarkdownString } from 'vs/base/common/htmlContent';
import { basename } from 'vs/base/common/resources';
import { URI } from 'vs/base/common/uri';
import { IRange } from 'vs/editor/common/core/range';
import { IChatProgressRenderableResponseContent, IChatProgressResponseContent } from 'vs/workbench/contrib/chat/common/chatModel';
import { IChatAgentMarkdownContentWithVulnerability, IChatAgentVulnerabilityDetails, IChatContentInlineReference } from 'vs/workbench/contrib/chat/common/chatService';
export const contentRefUrl = 'http://_vscodecontentref_'; // must be lowercase for URI
export function annotateSpecialMarkdownContent(response: ReadonlyArray<IChatProgressResponseContent>): ReadonlyArray<IChatProgressRenderableResponseContent> {
const result: Exclude<IChatProgressResponseContent, IChatContentInlineReference | IChatAgentMarkdownContentWithVulnerability>[] = [];
for (const item of response) {
const previousItem = result[result.length - 1];
if (item.kind === 'inlineReference') {
const location = 'uri' in item.inlineReference ? item.inlineReference : { uri: item.inlineReference };
const printUri = URI.parse(contentRefUrl).with({ fragment: JSON.stringify(location) });
const markdownText = `[${item.name || basename(location.uri)}](${printUri.toString()})`;
if (previousItem?.kind === 'markdownContent') {
result[result.length - 1] = { content: new MarkdownString(previousItem.content.value + markdownText, { isTrusted: previousItem.content.isTrusted }), kind: 'markdownContent' };
} else {
result.push({ content: new MarkdownString(markdownText), kind: 'markdownContent' });
}
} else if (item.kind === 'markdownContent' && previousItem?.kind === 'markdownContent') {
result[result.length - 1] = { content: new MarkdownString(previousItem.content.value + item.content.value, { isTrusted: previousItem.content.isTrusted }), kind: 'markdownContent' };
} else if (item.kind === 'markdownVuln') {
const vulnText = encodeURIComponent(JSON.stringify(item.vulnerabilities));
const markdownText = `<vscode_annotation details='${vulnText}'>${item.content.value}</vscode_annotation>`;
if (previousItem?.kind === 'markdownContent') {
result[result.length - 1] = { content: new MarkdownString(previousItem.content.value + markdownText, { isTrusted: previousItem.content.isTrusted }), kind: 'markdownContent' };
} else {
result.push({ content: new MarkdownString(markdownText), kind: 'markdownContent' });
}
} else {
result.push(item);
}
}
return result;
}
export interface IMarkdownVulnerability {
title: string;
description: string;
range: IRange;
}
export function extractVulnerabilitiesFromText(text: string): { newText: string; vulnerabilities: IMarkdownVulnerability[] } {
const vulnerabilities: IMarkdownVulnerability[] = [];
let newText = text;
let match: RegExpExecArray | null;
while ((match = /<vscode_annotation details="(.*?)">(.*?)<\/vscode_annotation>/ms.exec(newText)) !== null) {
const [full, details, content] = match;
const start = match.index;
const textBefore = newText.substring(0, start);
const linesBefore = textBefore.split('\n').length - 1;
const linesInside = content.split('\n').length - 1;
const previousNewlineIdx = textBefore.lastIndexOf('\n');
const startColumn = start - (previousNewlineIdx + 1) + 1;
const endPreviousNewlineIdx = (textBefore + content).lastIndexOf('\n');
const endColumn = start + content.length - (endPreviousNewlineIdx + 1) + 1;
try {
const vulnDetails: IChatAgentVulnerabilityDetails[] = JSON.parse(decodeURIComponent(details));
vulnDetails.forEach(({ title, description }) => vulnerabilities.push({
title, description, range: { startLineNumber: linesBefore + 1, startColumn, endLineNumber: linesBefore + linesInside + 1, endColumn }
}));
} catch (err) {
// Something went wrong with encoding this text, just ignore it
}
newText = newText.substring(0, start) + content + newText.substring(start + full.length);
}
return { newText, vulnerabilities };
}

View file

@ -219,7 +219,7 @@ export class ChatAgentService extends Disposable implements IChatAgentService {
{
extensionId: p.extensionId,
id: p.name,
metadata: { description: p.description },
metadata: this._agents.has(p.name) ? this._agents.get(p.name)!.data.metadata : { description: p.description },
isDefault: p.isDefault,
defaultImplicitVariables: p.defaultImplicitVariables,
locations: isNonEmptyArray(p.locations) ? p.locations.map(ChatAgentLocation.fromRaw) : [ChatAgentLocation.Panel],

View file

@ -8,9 +8,6 @@ import { Disposable } from 'vs/base/common/lifecycle';
import { marked } from 'vs/base/common/marked/marked';
import { ThemeIcon } from 'vs/base/common/themables';
import { URI } from 'vs/base/common/uri';
import { Range } from 'vs/editor/common/core/range';
import { ILanguageService } from 'vs/editor/common/languages/language';
import { EndOfLinePreference } from 'vs/editor/common/model';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ILogService } from 'vs/platform/log/common/log';
import { IChatAgentCommand, IChatAgentData, IChatAgentResult } from 'vs/workbench/contrib/chat/common/chatAgents';
@ -192,7 +189,6 @@ export class ChatViewModel extends Disposable implements IChatViewModel {
private readonly _model: IChatModel,
public readonly codeBlockModelCollection: CodeBlockModelCollection,
@IInstantiationService private readonly instantiationService: IInstantiationService,
@ILanguageService private readonly languageService: ILanguageService,
) {
super();
@ -270,31 +266,7 @@ export class ChatViewModel extends Disposable implements IChatViewModel {
renderer.code = (value, languageId) => {
languageId ??= '';
const newText = this.fixCodeText(value, languageId);
const textModel = this.codeBlockModelCollection.getOrCreate(this._model.sessionId, model, codeBlockIndex++);
textModel.then(ref => {
const model = ref.object.textEditorModel;
if (languageId) {
const vscodeLanguageId = this.languageService.getLanguageIdByLanguageName(languageId);
if (vscodeLanguageId && vscodeLanguageId !== ref.object.textEditorModel.getLanguageId()) {
ref.object.textEditorModel.setLanguage(vscodeLanguageId);
}
}
const currentText = ref.object.textEditorModel.getValue(EndOfLinePreference.LF);
if (newText === currentText) {
return;
}
if (newText.startsWith(currentText)) {
const text = newText.slice(currentText.length);
const lastLine = model.getLineCount();
const lastCol = model.getLineMaxColumn(lastLine);
model.applyEdits([{ range: new Range(lastLine, lastCol, lastLine, lastCol), text }]);
} else {
// console.log(`Failed to optimize setText`);
model.setValue(newText);
}
});
this.codeBlockModelCollection.update(this._model.sessionId, model, codeBlockIndex++, { text: newText, languageId });
return '';
};

View file

@ -7,15 +7,23 @@ import { Disposable, IReference } from 'vs/base/common/lifecycle';
import { ResourceMap } from 'vs/base/common/map';
import { Schemas } from 'vs/base/common/network';
import { URI } from 'vs/base/common/uri';
import { Range } from 'vs/editor/common/core/range';
import { ILanguageService } from 'vs/editor/common/languages/language';
import { EndOfLinePreference } from 'vs/editor/common/model';
import { IResolvedTextEditorModel, ITextModelService } from 'vs/editor/common/services/resolverService';
import { IChatRequestViewModel, IChatResponseViewModel, isResponseVM } from 'vs/workbench/contrib/chat/common/chatViewModel';
import { extractVulnerabilitiesFromText, IMarkdownVulnerability } from './annotations';
export class CodeBlockModelCollection extends Disposable {
private readonly _models = new ResourceMap<Promise<IReference<IResolvedTextEditorModel>>>();
private readonly _models = new ResourceMap<{
readonly model: Promise<IReference<IResolvedTextEditorModel>>;
vulns: readonly IMarkdownVulnerability[];
}>();
constructor(
@ILanguageService private readonly languageService: ILanguageService,
@ITextModelService private readonly textModelService: ITextModelService
) {
super();
@ -26,12 +34,16 @@ export class CodeBlockModelCollection extends Disposable {
this.clear();
}
get(sessionId: string, chat: IChatRequestViewModel | IChatResponseViewModel, codeBlockIndex: number): Promise<IReference<IResolvedTextEditorModel>> | undefined {
get(sessionId: string, chat: IChatRequestViewModel | IChatResponseViewModel, codeBlockIndex: number): { model: Promise<IResolvedTextEditorModel>; vulns: readonly IMarkdownVulnerability[] } | undefined {
const uri = this.getUri(sessionId, chat, codeBlockIndex);
return this._models.get(uri);
const entries = this._models.get(uri);
if (!entries) {
return;
}
return { model: entries.model.then(ref => ref.object), vulns: entries.vulns };
}
getOrCreate(sessionId: string, chat: IChatRequestViewModel | IChatResponseViewModel, codeBlockIndex: number): Promise<IReference<IResolvedTextEditorModel>> {
getOrCreate(sessionId: string, chat: IChatRequestViewModel | IChatResponseViewModel, codeBlockIndex: number): { model: Promise<IResolvedTextEditorModel>; vulns: readonly IMarkdownVulnerability[] } {
const existing = this.get(sessionId, chat, codeBlockIndex);
if (existing) {
return existing;
@ -39,15 +51,46 @@ export class CodeBlockModelCollection extends Disposable {
const uri = this.getUri(sessionId, chat, codeBlockIndex);
const ref = this.textModelService.createModelReference(uri);
this._models.set(uri, ref);
return ref;
this._models.set(uri, { model: ref, vulns: [] });
return { model: ref.then(ref => ref.object), vulns: [] };
}
clear(): void {
this._models.forEach(async (model) => (await model).dispose());
this._models.forEach(async entry => (await entry.model).dispose());
this._models.clear();
}
async update(sessionId: string, chat: IChatRequestViewModel | IChatResponseViewModel, codeBlockIndex: number, content: { text: string; languageId?: string }) {
const entry = this.getOrCreate(sessionId, chat, codeBlockIndex);
const extractedVulns = extractVulnerabilitiesFromText(content.text);
const newText = extractedVulns.newText;
entry.vulns = extractedVulns.vulnerabilities;
const textModel = (await entry.model).textEditorModel;
if (content.languageId) {
const vscodeLanguageId = this.languageService.getLanguageIdByLanguageName(content.languageId);
if (vscodeLanguageId && vscodeLanguageId !== textModel.getLanguageId()) {
textModel.setLanguage(vscodeLanguageId);
}
}
const currentText = textModel.getValue(EndOfLinePreference.LF);
if (newText === currentText) {
return;
}
if (newText.startsWith(currentText)) {
const text = newText.slice(currentText.length);
const lastLine = textModel.getLineCount();
const lastCol = textModel.getLineMaxColumn(lastLine);
textModel.applyEdits([{ range: new Range(lastLine, lastCol, lastLine, lastCol), text }]);
} else {
// console.log(`Failed to optimize setText`);
textModel.setValue(newText);
}
}
private getUri(sessionId: string, chat: IChatRequestViewModel | IChatResponseViewModel, index: number): URI {
const metadata = this.getUriMetaData(chat);
return URI.from({

View file

@ -42,7 +42,6 @@ import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
import { Registry } from 'vs/platform/registry/common/platform';
import { IConfigurationRegistry, Extensions } from 'vs/platform/configuration/common/configurationRegistry';
import { IStatusbarEntry, IStatusbarEntryAccessor, IStatusbarService, StatusbarAlignment } from 'vs/workbench/services/statusbar/browser/statusbar';
import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
import { IHostService } from 'vs/workbench/services/host/browser/host';
import { getCodeEditor } from 'vs/editor/browser/editorBrowser';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
@ -909,7 +908,6 @@ export class KeywordActivationContribution extends Disposable implements IWorkbe
@ISpeechService private readonly speechService: ISpeechService,
@IConfigurationService private readonly configurationService: IConfigurationService,
@ICommandService private readonly commandService: ICommandService,
@IEditorGroupsService private readonly editorGroupService: IEditorGroupsService,
@IInstantiationService instantiationService: IInstantiationService,
@IEditorService private readonly editorService: IEditorService,
@IHostService private readonly hostService: IHostService,
@ -938,10 +936,6 @@ export class KeywordActivationContribution extends Disposable implements IWorkbe
this.handleKeywordActivation();
}
}));
this._register(this.editorGroupService.onDidCreateAuxiliaryEditorPart(({ instantiationService, disposables }) => {
disposables.add(instantiationService.createInstance(KeywordActivationStatusEntry));
}));
}
private updateConfiguration(): void {
@ -1108,7 +1102,8 @@ class KeywordActivationStatusEntry extends Disposable {
tooltip: this.speechService.hasActiveKeywordRecognition ? KeywordActivationStatusEntry.STATUS_ACTIVE : KeywordActivationStatusEntry.STATUS_INACTIVE,
ariaLabel: this.speechService.hasActiveKeywordRecognition ? KeywordActivationStatusEntry.STATUS_ACTIVE : KeywordActivationStatusEntry.STATUS_INACTIVE,
command: KeywordActivationStatusEntry.STATUS_COMMAND,
kind: 'prominent'
kind: 'prominent',
showInAllWindows: true
};
}

View file

@ -1,15 +0,0 @@
{
newText: "some code\nover\nmultiple lines content with vuln\nand\nnewlinesmore code\nwith newline",
vulnerabilities: [
{
title: "title",
description: "vuln",
range: {
startLineNumber: 3,
startColumn: 16,
endLineNumber: 5,
endColumn: 9
}
}
]
}

View file

@ -1,11 +0,0 @@
[
{
content: {
value: "some code\nover\nmultiple lines <vscode_annotation details=\"%5B%7B%22title%22%3A%22title%22%2C%22description%22%3A%22vuln%22%7D%5D\">content with vuln\nand\nnewlines</vscode_annotation>more code\nwith newline<vscode_annotation details=\"%5B%7B%22title%22%3A%22title%22%2C%22description%22%3A%22vuln%22%7D%5D\">content with vuln\nand\nnewlines</vscode_annotation>",
isTrusted: false,
supportThemeIcons: false,
supportHtml: false
},
kind: "markdownContent"
}
]

View file

@ -1,25 +0,0 @@
{
newText: "some code\nover\nmultiple lines content with vuln\nand\nnewlinesmore code\nwith newlinecontent with vuln\nand\nnewlines",
vulnerabilities: [
{
title: "title",
description: "vuln",
range: {
startLineNumber: 3,
startColumn: 16,
endLineNumber: 5,
endColumn: 9
}
},
{
title: "title",
description: "vuln",
range: {
startLineNumber: 6,
startColumn: 13,
endLineNumber: 8,
endColumn: 9
}
}
]
}

View file

@ -1,11 +0,0 @@
[
{
content: {
value: "some code <vscode_annotation details=\"%5B%7B%22title%22%3A%22title%22%2C%22description%22%3A%22vuln%22%7D%5D\">content with vuln</vscode_annotation> after",
isTrusted: false,
supportThemeIcons: false,
supportHtml: false
},
kind: "markdownContent"
}
]

View file

@ -1,15 +0,0 @@
{
newText: "some code content with vuln after",
vulnerabilities: [
{
title: "title",
description: "vuln",
range: {
startLineNumber: 1,
startColumn: 11,
endLineNumber: 1,
endColumn: 28
}
}
]
}

View file

@ -1,7 +1,7 @@
[
{
content: {
value: "some code\nover\nmultiple lines <vscode_annotation details=\"%5B%7B%22title%22%3A%22title%22%2C%22description%22%3A%22vuln%22%7D%5D\">content with vuln\nand\nnewlines</vscode_annotation>more code\nwith newline",
value: "some code\nover\nmultiple lines <vscode_annotation details='%5B%7B%22title%22%3A%22title%22%2C%22description%22%3A%22vuln%22%7D%5D'>content with vuln\nand\nnewlines</vscode_annotation>more code\nwith newline",
isTrusted: false,
supportThemeIcons: false,
supportHtml: false

View file

@ -0,0 +1,4 @@
{
newText: "some code\nover\nmultiple lines <vscode_annotation details='%5B%7B%22title%22%3A%22title%22%2C%22description%22%3A%22vuln%22%7D%5D'>content with vuln\nand\nnewlines</vscode_annotation>more code\nwith newline",
vulnerabilities: [ ]
}

View file

@ -0,0 +1,11 @@
[
{
content: {
value: "some code\nover\nmultiple lines <vscode_annotation details='%5B%7B%22title%22%3A%22title%22%2C%22description%22%3A%22vuln%22%7D%5D'>content with vuln\nand\nnewlines</vscode_annotation>more code\nwith newline<vscode_annotation details='%5B%7B%22title%22%3A%22title%22%2C%22description%22%3A%22vuln%22%7D%5D'>content with vuln\nand\nnewlines</vscode_annotation>",
isTrusted: false,
supportThemeIcons: false,
supportHtml: false
},
kind: "markdownContent"
}
]

View file

@ -0,0 +1,4 @@
{
newText: "some code\nover\nmultiple lines <vscode_annotation details='%5B%7B%22title%22%3A%22title%22%2C%22description%22%3A%22vuln%22%7D%5D'>content with vuln\nand\nnewlines</vscode_annotation>more code\nwith newline<vscode_annotation details='%5B%7B%22title%22%3A%22title%22%2C%22description%22%3A%22vuln%22%7D%5D'>content with vuln\nand\nnewlines</vscode_annotation>",
vulnerabilities: [ ]
}

View file

@ -0,0 +1,11 @@
[
{
content: {
value: "some code <vscode_annotation details='%5B%7B%22title%22%3A%22title%22%2C%22description%22%3A%22vuln%22%7D%5D'>content with vuln</vscode_annotation> after",
isTrusted: false,
supportThemeIcons: false,
supportHtml: false
},
kind: "markdownContent"
}
]

View file

@ -0,0 +1,4 @@
{
newText: "some code <vscode_annotation details='%5B%7B%22title%22%3A%22title%22%2C%22description%22%3A%22vuln%22%7D%5D'>content with vuln</vscode_annotation> after",
vulnerabilities: [ ]
}

View file

@ -22,7 +22,11 @@
},
agent: {
id: "ChatProviderWithUsedContext",
metadata: { description: undefined }
metadata: {
description: undefined,
requester: { name: "test" },
fullName: "test"
}
}
},
{
@ -54,7 +58,11 @@
value: "nullExtensionDescription",
_lower: "nullextensiondescription"
},
metadata: { description: undefined },
metadata: {
description: undefined,
requester: { name: "test" },
fullName: "test"
},
slashCommands: [ ],
locations: [ "panel" ],
isDefault: undefined

View file

@ -6,14 +6,14 @@
import { MarkdownString } from 'vs/base/common/htmlContent';
import { assertSnapshot } from 'vs/base/test/common/snapshot';
import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils';
import { annotateSpecialMarkdownContent, extractVulnerabilitiesFromText } from 'vs/workbench/contrib/chat/browser/chatMarkdownDecorationsRenderer';
import { IChatMarkdownContent } from 'vs/workbench/contrib/chat/common/chatService';
import { annotateSpecialMarkdownContent, extractVulnerabilitiesFromText } from '../../common/annotations';
function content(str: string): IChatMarkdownContent {
return { kind: 'markdownContent', content: new MarkdownString(str) };
}
suite('ChatMarkdownDecorationsRenderer', function () {
suite('Annotations', function () {
ensureNoDisposablesAreLeakedInTestSuite();
suite('extractVulnerabilitiesFromText', () => {

View file

@ -211,8 +211,11 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget
return;
}
}
const rangeToReveal = this._commentThread.range
? new Range(this._commentThread.range.startLineNumber, this._commentThread.range.startColumn, this._commentThread.range.endLineNumber + 1, 1)
: new Range(1, 1, 1, 1);
this.editor.revealRangeInCenter(this._commentThread.range ?? new Range(1, 1, 1, 1));
this.editor.revealRangeInCenter(rangeToReveal);
if (focus === CommentWidgetFocus.Widget) {
this._commentThreadWidget.focus();
} else if (focus === CommentWidgetFocus.Editor) {

View file

@ -369,7 +369,7 @@ class CommentingRangeDecorator {
}
export function revealCommentThread(commentService: ICommentService, editorService: IEditorService, uriIdentityService: IUriIdentityService,
commentThread: languages.CommentThread<IRange>, comment: languages.Comment, focusReply?: boolean, pinned?: boolean, preserveFocus?: boolean, sideBySide?: boolean): void {
commentThread: languages.CommentThread<IRange>, comment: languages.Comment | undefined, focusReply?: boolean, pinned?: boolean, preserveFocus?: boolean, sideBySide?: boolean): void {
if (!commentThread.resource) {
return;
}
@ -386,7 +386,7 @@ export function revealCommentThread(commentService: ICommentService, editorServi
const currentActiveResources: IEditor[] = isDiffEditor(activeEditor) ? [activeEditor.getOriginalEditor(), activeEditor.getModifiedEditor()]
: (activeEditor ? [activeEditor] : []);
const threadToReveal = commentThread.threadId;
const commentToReveal = comment.uniqueIdInThread;
const commentToReveal = comment?.uniqueIdInThread;
const resource = URI.parse(commentThread.resource);
for (const editor of currentActiveResources) {
@ -683,7 +683,7 @@ export class CommentController implements IEditorContribution {
return editor.getContribution<CommentController>(ID);
}
public revealCommentThread(threadId: string, commentUniqueId: number, fetchOnceIfNotExist: boolean, focus: CommentWidgetFocus): void {
public revealCommentThread(threadId: string, commentUniqueId: number | undefined, fetchOnceIfNotExist: boolean, focus: CommentWidgetFocus): void {
const commentThreadWidget = this._commentWidgets.filter(widget => widget.commentThread.threadId === threadId);
if (commentThreadWidget.length === 1) {
commentThreadWidget[0].reveal(commentUniqueId, focus);

View file

@ -352,7 +352,7 @@ export class CommentsPanel extends FilterViewPane implements ICommentsView {
return;
}
const threadToReveal = element instanceof ResourceWithCommentThreads ? element.commentThreads[0].thread : element.thread;
const commentToReveal = element instanceof ResourceWithCommentThreads ? element.commentThreads[0].comment : element.comment;
const commentToReveal = element instanceof ResourceWithCommentThreads ? element.commentThreads[0].comment : undefined;
return revealCommentThread(this.commentService, this.editorService, this.uriIdentityService, threadToReveal, commentToReveal, false, pinned, preserveFocus, sideBySide);
}

View file

@ -123,7 +123,7 @@ export class SimpleCommentEditor extends CodeEditorWidget {
export function calculateEditorHeight(parentEditor: LayoutableEditor, editor: ICodeEditor, currentHeight: number): number {
const layoutInfo = editor.getLayoutInfo();
const lineHeight = editor.getOption(EditorOption.lineHeight);
const contentHeight = (editor.getModel()?.getLineCount()! * lineHeight) ?? editor.getContentHeight(); // Can't just call getContentHeight() because it returns an incorrect, large, value when the editor is first created.
const contentHeight = (editor._getViewModel()?.getLineCount()! * lineHeight) ?? editor.getContentHeight(); // Can't just call getContentHeight() because it returns an incorrect, large, value when the editor is first created.
if ((contentHeight > layoutInfo.height) ||
(contentHeight < layoutInfo.height && currentHeight > MIN_EDITOR_HEIGHT)) {
const linesToAdd = Math.ceil((contentHeight - layoutInfo.height) / lineHeight);

View file

@ -24,6 +24,7 @@ import { ILabelService } from 'vs/platform/label/common/label';
import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo';
import { EditorInputCapabilities, GroupIdentifier, IMoveResult, IRevertOptions, ISaveOptions, IUntypedEditorInput, Verbosity, createEditorOpenError } from 'vs/workbench/common/editor';
import { EditorInput } from 'vs/workbench/common/editor/editorInput';
import { ICustomEditorLabelService } from 'vs/workbench/services/editor/common/customEditorLabelService';
import { ICustomEditorModel, ICustomEditorService } from 'vs/workbench/contrib/customEditor/common/customEditor';
import { IOverlayWebview, IWebviewService } from 'vs/workbench/contrib/webview/browser/webview';
import { IWebviewWorkbenchService, LazilyResolvedWebviewEditorInput } from 'vs/workbench/contrib/webviewPanel/browser/webviewWorkbenchService';
@ -93,6 +94,7 @@ export class CustomEditorInput extends LazilyResolvedWebviewEditorInput {
@IFilesConfigurationService private readonly filesConfigurationService: IFilesConfigurationService,
@IEditorGroupsService private readonly editorGroupsService: IEditorGroupsService,
@IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService,
@ICustomEditorLabelService private readonly customEditorLabelService: ICustomEditorLabelService,
) {
super({ providedId: init.viewType, viewType: init.viewType, name: '' }, webview, webviewWorkbenchService);
this._editorResource = init.resource;
@ -110,6 +112,7 @@ export class CustomEditorInput extends LazilyResolvedWebviewEditorInput {
this._register(this.labelService.onDidChangeFormatters(e => this.onLabelEvent(e.scheme)));
this._register(this.fileService.onDidChangeFileSystemProviderRegistrations(e => this.onLabelEvent(e.scheme)));
this._register(this.fileService.onDidChangeFileSystemProviderCapabilities(e => this.onLabelEvent(e.scheme)));
this._register(this.customEditorLabelService.onDidChange(() => this.updateLabel()));
}
private onLabelEvent(scheme: string): void {
@ -121,6 +124,7 @@ export class CustomEditorInput extends LazilyResolvedWebviewEditorInput {
private updateLabel(): void {
// Clear any cached labels from before
this._editorName = undefined;
this._shortDescription = undefined;
this._mediumDescription = undefined;
this._longDescription = undefined;
@ -166,8 +170,13 @@ export class CustomEditorInput extends LazilyResolvedWebviewEditorInput {
return capabilities;
}
private _editorName: string | undefined = undefined;
override getName(): string {
return basename(this.labelService.getUriLabel(this.resource));
if (typeof this._editorName !== 'string') {
this._editorName = this.customEditorLabelService.getName(this.resource) ?? basename(this.labelService.getUriLabel(this.resource));
}
return this._editorName;
}
override getDescription(verbosity = Verbosity.MEDIUM): string | undefined {
@ -399,7 +408,7 @@ export class CustomEditorInput extends LazilyResolvedWebviewEditorInput {
}
public override claim(claimant: unknown, targetWindow: CodeWindow, scopedContextKeyService: IContextKeyService | undefined): void {
if (typeof this.canMove(targetWindow.vscodeWindowId) === 'string') {
if (this.doCanMove(targetWindow.vscodeWindowId) !== true) {
throw createEditorOpenError(localize('editorUnsupportedInWindow', "Unable to open the editor in this window, it contains modifications that can only be saved in the original window."), [
toAction({
id: 'openInOriginalWindow',
@ -415,7 +424,19 @@ export class CustomEditorInput extends LazilyResolvedWebviewEditorInput {
return super.claim(claimant, targetWindow, scopedContextKeyService);
}
public override canMove(targetWindowId: number): true | string {
public override canMove(sourceGroup: GroupIdentifier, targetGroup: GroupIdentifier): true | string {
const resolvedTargetGroup = this.editorGroupsService.getGroup(targetGroup);
if (resolvedTargetGroup) {
const canMove = this.doCanMove(resolvedTargetGroup.windowId);
if (typeof canMove === 'string') {
return canMove;
}
}
return super.canMove(sourceGroup, targetGroup);
}
private doCanMove(targetWindowId: number): true | string {
if (this.isModified() && this._modelRef?.object.canHotExit === false) {
const sourceWindowId = getWindow(this.webview.container).vscodeWindowId;
if (sourceWindowId !== targetWindowId) {
@ -426,7 +447,7 @@ export class CustomEditorInput extends LazilyResolvedWebviewEditorInput {
// into another window because that means, we potentally loose the modified
// state and thus trigger data loss.
return localize('editorCannotMove', "Unable to move the editor from this window, it contains modifications that can only be saved in the this window.");
return localize('editorCannotMove', "Unable to move '{0}': The editor contains changes that can only be saved in its current window.", this.getName());
}
}

View file

@ -90,6 +90,7 @@ export const CONTEXT_VARIABLE_EVALUATE_NAME_PRESENT = new RawContextKey<boolean>
export const CONTEXT_VARIABLE_IS_READONLY = new RawContextKey<boolean>('variableIsReadonly', false, { type: 'boolean', description: nls.localize('variableIsReadonly', "True when the focused variable is read-only.") });
export const CONTEXT_VARIABLE_VALUE = new RawContextKey<boolean>('variableValue', false, { type: 'string', description: nls.localize('variableValue', "Value of the variable, present for debug visualization clauses.") });
export const CONTEXT_VARIABLE_TYPE = new RawContextKey<boolean>('variableType', false, { type: 'string', description: nls.localize('variableType', "Type of the variable, present for debug visualization clauses.") });
export const CONTEXT_VARIABLE_INTERFACES = new RawContextKey<boolean>('variableInterfaces', false, { type: 'array', description: nls.localize('variableInterfaces', "Any interfaces or contracts that the variable satisfies, present for debug visualization clauses.") });
export const CONTEXT_VARIABLE_NAME = new RawContextKey<boolean>('variableName', false, { type: 'string', description: nls.localize('variableName', "Name of the variable, present for debug visualization clauses.") });
export const CONTEXT_VARIABLE_LANGUAGE = new RawContextKey<boolean>('variableLanguage', false, { type: 'string', description: nls.localize('variableLanguage', "Language of the variable source, present for debug visualization clauses.") });
export const CONTEXT_VARIABLE_EXTENSIONID = new RawContextKey<boolean>('variableExtensionId', false, { type: 'string', description: nls.localize('variableExtensionId', "Extension ID of the variable source, present for debug visualization clauses.") });

View file

@ -9,16 +9,16 @@ import { Registry } from 'vs/platform/registry/common/platform';
import { MenuRegistry, MenuId, registerAction2, Action2, ISubmenuItem, IMenuItem, IAction2Options } from 'vs/platform/actions/common/actions';
import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { ExtensionsLocalizedLabel, IExtensionManagementService, IExtensionGalleryService, PreferencesLocalizedLabel, InstallOperation } from 'vs/platform/extensionManagement/common/extensionManagement';
import { EnablementState, IExtensionManagementServerService, IWorkbenchExtensionEnablementService, IWorkbenchExtensionManagementService } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
import { EnablementState, IExtensionManagementServerService, IWorkbenchExtensionEnablementService, IWorkbenchExtensionManagementService, extensionsConfigurationNodeBase } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
import { IExtensionIgnoredRecommendationsService, IExtensionRecommendationsService } from 'vs/workbench/services/extensionRecommendations/common/extensionRecommendations';
import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution, registerWorkbenchContribution2, WorkbenchPhase } from 'vs/workbench/common/contributions';
import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from 'vs/workbench/common/contributions';
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
import { VIEWLET_ID, IExtensionsWorkbenchService, IExtensionsViewPaneContainer, TOGGLE_IGNORE_EXTENSION_ACTION_ID, INSTALL_EXTENSION_FROM_VSIX_COMMAND_ID, WORKSPACE_RECOMMENDATIONS_VIEW_ID, IWorkspaceRecommendedExtensionsView, AutoUpdateConfigurationKey, HasOutdatedExtensionsContext, SELECT_INSTALL_VSIX_EXTENSION_COMMAND_ID, LIST_WORKSPACE_UNSUPPORTED_EXTENSIONS_COMMAND_ID, ExtensionEditorTab, THEME_ACTIONS_GROUP, INSTALL_ACTIONS_GROUP, OUTDATED_EXTENSIONS_VIEW_ID, CONTEXT_HAS_GALLERY, IExtension, extensionsSearchActionsMenu, UPDATE_ACTIONS_GROUP } from 'vs/workbench/contrib/extensions/common/extensions';
import { ReinstallAction, InstallSpecificVersionOfExtensionAction, ConfigureWorkspaceRecommendedExtensionsAction, ConfigureWorkspaceFolderRecommendedExtensionsAction, PromptExtensionInstallFailureAction, SearchExtensionsAction, SetColorThemeAction, SetFileIconThemeAction, SetProductIconThemeAction, ClearLanguageAction, ToggleAutoUpdateForExtensionAction, ToggleAutoUpdatesForPublisherAction, TogglePreReleaseExtensionAction } from 'vs/workbench/contrib/extensions/browser/extensionsActions';
import { ExtensionsInput } from 'vs/workbench/contrib/extensions/common/extensionsInput';
import { ExtensionEditor } from 'vs/workbench/contrib/extensions/browser/extensionEditor';
import { StatusUpdater, MaliciousExtensionChecker, ExtensionsViewletViewsContribution, ExtensionsViewPaneContainer, BuiltInExtensionsContext, SearchMarketplaceExtensionsContext, RecommendedExtensionsContext, DefaultViewsContext, ExtensionsSortByContext, SearchHasTextContext } from 'vs/workbench/contrib/extensions/browser/extensionsViewlet';
import { IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope, IConfigurationPropertySchema } from 'vs/platform/configuration/common/configurationRegistry';
import { IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry';
import * as jsonContributionRegistry from 'vs/platform/jsonschemas/common/jsonContributionRegistry';
import { ExtensionsConfigurationSchema, ExtensionsConfigurationSchemaId } from 'vs/workbench/contrib/extensions/common/extensionsFileTemplate';
import { CommandsRegistry, ICommandService } from 'vs/platform/commands/common/commands';
@ -80,7 +80,6 @@ import { IStringDictionary } from 'vs/base/common/collections';
import { CONTEXT_KEYBINDINGS_EDITOR } from 'vs/workbench/contrib/preferences/common/preferences';
import { DeprecatedExtensionsChecker } from 'vs/workbench/contrib/extensions/browser/deprecatedExtensionsChecker';
import { ProgressLocation } from 'vs/platform/progress/common/progress';
import { IProductService } from 'vs/platform/product/common/productService';
// Singletons
registerSingleton(IExtensionsWorkbenchService, ExtensionsWorkbenchService, InstantiationType.Eager /* Auto updates extensions */);
@ -123,25 +122,10 @@ Registry.as<IViewContainersRegistry>(ViewContainerExtensions.ViewContainersRegis
alwaysUseContainerInfo: true,
}, ViewContainerLocation.Sidebar);
class ExtensionsSettingsContributions implements IWorkbenchContribution {
static readonly ID = 'workbench.extensions.settingsContributions';
constructor(
@IProductService private readonly productService: IProductService
) {
Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration)
.registerConfiguration({
id: 'extensions',
order: 30,
title: localize('extensionsConfigurationTitle', "Extensions"),
type: 'object',
properties: this.getExtensionsSettingsProperties()
});
}
private getExtensionsSettingsProperties(): IStringDictionary<IConfigurationPropertySchema> {
const settings: IStringDictionary<IConfigurationPropertySchema> = {
Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration)
.registerConfiguration({
...extensionsConfigurationNodeBase,
properties: {
'extensions.autoUpdate': {
enum: [true, 'onlyEnabledExtensions', 'onlySelectedExtensions', false,],
enumItemLabels: [
@ -273,22 +257,9 @@ class ExtensionsSettingsContributions implements IWorkbenchContribution {
type: 'boolean',
description: localize('extensionsInQuickAccess', "When enabled, extensions can be searched for via Quick Access and report issues from there."),
default: true
},
};
if (this.productService.quality !== 'stable') {
settings['extensions.experimental.supportWorkspaceExtensions'] = {
type: 'boolean',
description: localize('extensions.experimental.supportWorkspaceExtensions', "Enables support for workspace specific local extensions."),
default: false,
scope: ConfigurationScope.APPLICATION
};
}
}
return settings;
}
}
});
const jsonRegistry = <jsonContributionRegistry.IJSONContributionRegistry>Registry.as(jsonContributionRegistry.Extensions.JSONContribution);
jsonRegistry.registerSchema(ExtensionsConfigurationSchemaId, ExtensionsConfigurationSchema);
@ -1764,8 +1735,6 @@ class ExtensionStorageCleaner implements IWorkbenchContribution {
}
}
registerWorkbenchContribution2(ExtensionsSettingsContributions.ID, ExtensionsSettingsContributions, WorkbenchPhase.BlockStartup);
const workbenchRegistry = Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench);
workbenchRegistry.registerWorkbenchContribution(ExtensionsContributions, LifecyclePhase.Restored);
workbenchRegistry.registerWorkbenchContribution(StatusUpdater, LifecyclePhase.Eventually);

View file

@ -1575,6 +1575,7 @@ export class ExtensionRuntimeStateAction extends ExtensionAction {
@IUpdateService private readonly updateService: IUpdateService,
@IExtensionService private readonly extensionService: IExtensionService,
@IProductService private readonly productService: IProductService,
@ITelemetryService private readonly telemetryService: ITelemetryService,
) {
super('extensions.runtimeState', '', ExtensionRuntimeStateAction.DisabledClass, false);
this._register(this.extensionService.onDidChangeExtensions(() => this.update()));
@ -1615,6 +1616,21 @@ export class ExtensionRuntimeStateAction extends ExtensionAction {
override async run(): Promise<any> {
const runtimeState = this.extension?.runtimeState;
if (!runtimeState?.action) {
return;
}
type ExtensionRuntimeStateActionClassification = {
owner: 'sandy081';
comment: 'Extension runtime state action event';
action: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Executed action' };
};
type ExtensionRuntimeStateActionEvent = {
action: string;
};
this.telemetryService.publicLog2<ExtensionRuntimeStateActionEvent, ExtensionRuntimeStateActionClassification>('extensions:runtimestate:action', {
action: runtimeState.action
});
if (runtimeState?.action === ExtensionRuntimeActionType.ReloadWindow) {
return this.hostService.reload();

View file

@ -1942,7 +1942,11 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension
if (installOptions.justification) {
const syncCheck = isUndefined(installOptions.isMachineScoped) && this.userDataSyncEnablementService.isEnabled() && this.userDataSyncEnablementService.isResourceEnabled(SyncResource.Extensions);
const buttons: IPromptButton<boolean>[] = [];
buttons.push({ label: isString(installOptions.justification) ? nls.localize({ key: 'installButtonLabel', comment: ['&& denotes a mnemonic'] }, "&&Install Extension") : nls.localize({ key: 'installButtonLabelWithAction', comment: ['&& denotes a mnemonic'] }, "&&Install Extension and {0}", installOptions.justification.action), run: () => true });
buttons.push({
label: isString(installOptions.justification) || !installOptions.justification.action
? nls.localize({ key: 'installButtonLabel', comment: ['&& denotes a mnemonic'] }, "&&Install Extension")
: nls.localize({ key: 'installButtonLabelWithAction', comment: ['&& denotes a mnemonic'] }, "&&Install Extension and {0}", installOptions.justification.action), run: () => true
});
if (!extension) {
buttons.push({ label: nls.localize('open', "Open Extension"), run: () => { this.open(extension!); return false; } });
}

View file

@ -25,6 +25,7 @@ import { createTextBufferFactory } from 'vs/editor/common/model/textModel';
import { IPathService } from 'vs/workbench/services/path/common/pathService';
import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfiguration';
import { IMarkdownString } from 'vs/base/common/htmlContent';
import { ICustomEditorLabelService } from 'vs/workbench/services/editor/common/customEditorLabelService';
const enum ForceOpenAs {
None,
@ -98,9 +99,10 @@ export class FileEditorInput extends AbstractTextResourceEditorInput implements
@IFilesConfigurationService filesConfigurationService: IFilesConfigurationService,
@IEditorService editorService: IEditorService,
@IPathService private readonly pathService: IPathService,
@ITextResourceConfigurationService textResourceConfigurationService: ITextResourceConfigurationService
@ITextResourceConfigurationService textResourceConfigurationService: ITextResourceConfigurationService,
@ICustomEditorLabelService customEditorLabelService: ICustomEditorLabelService
) {
super(resource, preferredResource, editorService, textFileService, labelService, fileService, filesConfigurationService, textResourceConfigurationService);
super(resource, preferredResource, editorService, textFileService, labelService, fileService, filesConfigurationService, textResourceConfigurationService, customEditorLabelService);
this.model = this.textFileService.files.get(resource);

View file

@ -46,6 +46,7 @@ import { InlineChatContentWidget } from 'vs/workbench/contrib/inlineChat/browser
import { MessageController } from 'vs/editor/contrib/message/browser/messageController';
import { tail } from 'vs/base/common/arrays';
import { IChatRequestModel } from 'vs/workbench/contrib/chat/common/chatModel';
import { InlineChatError } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSessionServiceImpl';
export const enum State {
CREATE_SESSION = 'CREATE_SESSION',
@ -279,6 +280,7 @@ export class InlineChatController implements IEditorContribution {
const widgetPosition = this._showWidget(true, initPosition);
// this._updatePlaceholder();
let errorMessage = localize('create.fail', "Failed to start editor chat");
if (!session) {
const createSessionCts = new CancellationTokenSource();
@ -294,11 +296,18 @@ export class InlineChatController implements IEditorContribution {
}
});
session = await this._inlineChatSessionService.createSession(
this._editor,
{ editMode: this._getMode(), wholeRange: options.initialRange },
createSessionCts.token
);
try {
session = await this._inlineChatSessionService.createSession(
this._editor,
{ editMode: this._getMode(), wholeRange: options.initialRange },
createSessionCts.token
);
} catch (error) {
// Inline chat errors are from the provider and have their error messages shown to the user
if (error instanceof InlineChatError || error?.name === InlineChatError.code) {
errorMessage = error.message;
}
}
createSessionCts.dispose();
msgListener.dispose();
@ -315,7 +324,7 @@ export class InlineChatController implements IEditorContribution {
delete options.existingSession;
if (!session) {
MessageController.get(this._editor)?.showMessage(localize('create.fail', "Failed to start editor chat"), widgetPosition);
MessageController.get(this._editor)?.showMessage(errorMessage, widgetPosition);
this._log('Failed to start editor chat');
return State.CANCEL;
}

View file

@ -308,7 +308,7 @@ export class InlineChatInputWidget {
const newDecorations: IModelDeltaDecoration[] = [];
for (const command of commands) {
const withSlash = `/${command.command}`;
const withSlash = `/${command.command} `;
const firstLine = this._inputModel.getLineContent(1);
if (firstLine.startsWith(withSlash)) {
newDecorations.push({

View file

@ -185,6 +185,14 @@ type SessionData = {
store: IDisposable;
};
export class InlineChatError extends Error {
static readonly code = 'InlineChatError';
constructor(message: string) {
super(message);
this.name = InlineChatError.code;
}
}
export class InlineChatSessionServiceImpl implements IInlineChatSessionService {
declare _serviceBrand: undefined;
@ -329,7 +337,7 @@ export class InlineChatSessionServiceImpl implements IInlineChatSessionService {
} catch (error) {
this._logService.error('[IE] FAILED to prepare session', provider.extensionId);
this._logService.error(error);
return undefined;
throw new InlineChatError((error as Error)?.message || 'Failed to prepare session');
}
if (!rawSession) {
this._logService.trace('[IE] NO session', provider.extensionId);

View file

@ -9,13 +9,13 @@ import { ProgressBar } from 'vs/base/browser/ui/progressbar/progressbar';
import { Emitter, Event, MicrotaskEmitter } from 'vs/base/common/event';
import { IMarkdownString, MarkdownString } from 'vs/base/common/htmlContent';
import { Lazy } from 'vs/base/common/lazy';
import { DisposableStore, IReference, MutableDisposable } from 'vs/base/common/lifecycle';
import { DisposableStore, MutableDisposable } from 'vs/base/common/lifecycle';
import { ISettableObservable, constObservable, derived, observableValue } from 'vs/base/common/observable';
import 'vs/css!./media/inlineChat';
import { ICodeEditor, IDiffEditorConstructionOptions } from 'vs/editor/browser/editorBrowser';
import { EmbeddedCodeEditorWidget } from 'vs/editor/browser/widget/codeEditor/embeddedCodeEditorWidget';
import { AccessibleDiffViewer, IAccessibleDiffViewerModel } from 'vs/editor/browser/widget/diffEditor/components/accessibleDiffViewer';
import { EmbeddedDiffEditorWidget } from 'vs/editor/browser/widget/diffEditor/embeddedDiffEditorWidget';
import { EmbeddedCodeEditorWidget } from 'vs/editor/browser/widget/codeEditor/embeddedCodeEditorWidget';
import { EditorOption, IComputedEditorOptions } from 'vs/editor/common/config/editorOptions';
import { LineRange } from 'vs/editor/common/core/lineRange';
import { Position } from 'vs/editor/common/core/position';
@ -404,14 +404,14 @@ export class InlineChatWidget {
this._onDidChangeHeight.fire();
}
getCodeBlockInfo(codeBlockIndex: number): Promise<IReference<IResolvedTextEditorModel>> | undefined {
async getCodeBlockInfo(codeBlockIndex: number): Promise<IResolvedTextEditorModel | undefined> {
const { viewModel } = this._chatWidget;
if (!viewModel) {
return undefined;
}
for (const item of viewModel.getItems()) {
if (isResponseVM(item)) {
return viewModel.codeBlockModelCollection.get(viewModel.sessionId, item, codeBlockIndex);
return viewModel.codeBlockModelCollection.get(viewModel.sessionId, item, codeBlockIndex)?.model;
}
}
return undefined;

View file

@ -122,12 +122,16 @@ export class IssueQuickAccess extends PickerQuickAccessProvider<IPickerQuickAcce
return undefined;
}
return {
label,
highlights: { label: matchesFuzzy(filter, label, true) ?? undefined },
buttons,
trigger,
accept
};
const highlights = matchesFuzzy(filter, label, true);
if (highlights) {
return {
label,
highlights: { label: highlights },
buttons,
trigger,
accept
};
}
return undefined;
}
}

View file

@ -16,6 +16,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti
import { ILabelService } from 'vs/platform/label/common/label';
import { DEFAULT_EDITOR_ASSOCIATION, EditorInputCapabilities, IResourceMergeEditorInput, IRevertOptions, isResourceMergeEditorInput, IUntypedEditorInput } from 'vs/workbench/common/editor';
import { EditorInput, IEditorCloseHandler } from 'vs/workbench/common/editor/editorInput';
import { ICustomEditorLabelService } from 'vs/workbench/services/editor/common/customEditorLabelService';
import { AbstractTextResourceEditorInput } from 'vs/workbench/common/editor/textResourceEditorInput';
import { IMergeEditorInputModel, TempFileMergeEditorModeFactory, WorkspaceMergeEditorModeFactory } from 'vs/workbench/contrib/mergeEditor/browser/mergeEditorInputModel';
import { MergeEditorTelemetry } from 'vs/workbench/contrib/mergeEditor/browser/telemetry';
@ -62,9 +63,10 @@ export class MergeEditorInput extends AbstractTextResourceEditorInput implements
@IFileService fileService: IFileService,
@IConfigurationService private readonly configurationService: IConfigurationService,
@IFilesConfigurationService filesConfigurationService: IFilesConfigurationService,
@ITextResourceConfigurationService textResourceConfigurationService: ITextResourceConfigurationService
@ITextResourceConfigurationService textResourceConfigurationService: ITextResourceConfigurationService,
@ICustomEditorLabelService customEditorLabelService: ICustomEditorLabelService,
) {
super(result, undefined, editorService, textFileService, labelService, fileService, filesConfigurationService, textResourceConfigurationService);
super(result, undefined, editorService, textFileService, labelService, fileService, filesConfigurationService, textResourceConfigurationService, customEditorLabelService);
}
override dispose(): void {

View file

@ -0,0 +1,102 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Disposable } from 'vs/base/common/lifecycle';
import { IMarkerData, IMarkerService } from 'vs/platform/markers/common/markers';
import { IRange } from 'vs/editor/common/core/range';
import { ICellExecutionError, ICellExecutionStateChangedEvent, IExecutionStateChangedEvent, INotebookExecutionStateService, NotebookExecutionType } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService';
import { IInlineChatService } from 'vs/workbench/contrib/inlineChat/common/inlineChat';
import { Iterable } from 'vs/base/common/iterator';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { NotebookSetting } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { CodeCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel';
export class CellDiagnostics extends Disposable {
static ID: string = 'workbench.notebook.cellDiagnostics';
private enabled = false;
private listening = false;
private errorDetails: ICellExecutionError | undefined = undefined;
public get ErrorDetails() {
return this.errorDetails;
}
constructor(
private readonly cell: CodeCellViewModel,
@INotebookExecutionStateService private readonly notebookExecutionStateService: INotebookExecutionStateService,
@IMarkerService private readonly markerService: IMarkerService,
@IInlineChatService private readonly inlineChatService: IInlineChatService,
@IConfigurationService private readonly configurationService: IConfigurationService
) {
super();
this.updateEnabled();
this._register(inlineChatService.onDidChangeProviders(() => this.updateEnabled()));
this._register(configurationService.onDidChangeConfiguration((e) => {
if (e.affectsConfiguration(NotebookSetting.cellFailureDiagnostics)) {
this.updateEnabled();
}
}));
}
private updateEnabled() {
const settingEnabled = this.configurationService.getValue(NotebookSetting.cellFailureDiagnostics);
if (this.enabled && (!settingEnabled || Iterable.isEmpty(this.inlineChatService.getAllProvider()))) {
this.enabled = false;
this.clearDiagnostics();
} else if (!this.enabled && settingEnabled && !Iterable.isEmpty(this.inlineChatService.getAllProvider())) {
this.enabled = true;
if (!this.listening) {
this.listening = true;
this._register(this.notebookExecutionStateService.onDidChangeExecution((e) => this.handleChangeExecutionState(e)));
}
}
}
private handleChangeExecutionState(e: ICellExecutionStateChangedEvent | IExecutionStateChangedEvent) {
if (this.enabled && e.type === NotebookExecutionType.cell && e.affectsCell(this.cell.uri)) {
if (!!e.changed) {
// cell is running
this.clearDiagnostics();
} else {
this.setDiagnostics();
}
}
}
public clear() {
this.clearDiagnostics();
}
private clearDiagnostics() {
this.markerService.changeOne(CellDiagnostics.ID, this.cell.uri, []);
this.errorDetails = undefined;
}
private setDiagnostics() {
const metadata = this.cell.model.internalMetadata;
if (!metadata.lastRunSuccess && metadata?.error?.location) {
const marker = this.createMarkerData(metadata.error.message, metadata.error.location);
this.markerService.changeOne(CellDiagnostics.ID, this.cell.uri, [marker]);
this.errorDetails = metadata.error;
}
}
private createMarkerData(message: string, location: IRange): IMarkerData {
return {
severity: 8,
message: message,
startLineNumber: location.startLineNumber + 1,
startColumn: location.startColumn + 1,
endLineNumber: location.endLineNumber + 1,
endColumn: location.endColumn + 1,
source: 'Cell Execution Error'
};
}
}

View file

@ -21,6 +21,7 @@ export interface INotebookVariableElement {
readonly name: string;
readonly value: string;
readonly type?: string;
readonly interfaces?: string[];
readonly expression?: string;
readonly language?: string;
readonly indexedChildrenCount: number;

View file

@ -25,7 +25,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { IViewPaneOptions, ViewPane } from 'vs/workbench/browser/parts/views/viewPane';
import { IViewDescriptorService } from 'vs/workbench/common/views';
import { CONTEXT_VARIABLE_EXTENSIONID, CONTEXT_VARIABLE_LANGUAGE, CONTEXT_VARIABLE_NAME, CONTEXT_VARIABLE_TYPE, CONTEXT_VARIABLE_VALUE } from 'vs/workbench/contrib/debug/common/debug';
import { CONTEXT_VARIABLE_EXTENSIONID, CONTEXT_VARIABLE_INTERFACES, CONTEXT_VARIABLE_LANGUAGE, CONTEXT_VARIABLE_NAME, CONTEXT_VARIABLE_TYPE, CONTEXT_VARIABLE_VALUE } from 'vs/workbench/contrib/debug/common/debug';
import { INotebookScope, INotebookVariableElement, NotebookVariableDataSource } from 'vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesDataSource';
import { NotebookVariableAccessibilityProvider, NotebookVariableRenderer, NotebookVariablesDelegate } from 'vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesTree';
import { getNotebookEditorFromEditorPane } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
@ -122,6 +122,7 @@ export class NotebookVariablesView extends ViewPane {
[CONTEXT_VARIABLE_NAME.key, element.name],
[CONTEXT_VARIABLE_VALUE.key, element.value],
[CONTEXT_VARIABLE_TYPE.key, element.type],
[CONTEXT_VARIABLE_INTERFACES.key, element.interfaces],
[CONTEXT_VARIABLE_LANGUAGE.key, element.language],
[CONTEXT_VARIABLE_EXTENSIONID.key, element.extensionId]
]);

View file

@ -1099,6 +1099,11 @@ configurationRegistry.registerConfiguration({
markdownDescription: nls.localize('notebook.VariablesView.description', "Enable the experimental notebook variables view within the debug panel."),
type: 'boolean',
default: false
}
},
[NotebookSetting.cellFailureDiagnostics]: {
markdownDescription: nls.localize('notebook.cellFailureDiagnostics', "Enable experimental diagnostics for cell failures."),
type: 'boolean',
default: false
},
}
});

View file

@ -22,6 +22,8 @@ import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookS
import { BaseCellViewModel } from './baseCellViewModel';
import { NotebookLayoutInfo } from 'vs/workbench/contrib/notebook/browser/notebookViewEvents';
import { ICellExecutionStateChangedEvent } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService';
import { CellDiagnostics } from 'vs/workbench/contrib/notebook/browser/contrib/cellDiagnostics/cellDiagnostics';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
export const outputDisplayLimit = 500;
@ -44,6 +46,8 @@ export class CodeCellViewModel extends BaseCellViewModel implements ICellViewMod
private _outputCollection: number[] = [];
private readonly _cellDiagnostics: CellDiagnostics;
private _outputsTop: PrefixSumComputer | null = null;
protected _pauseableEmitter = this._register(new PauseableEmitter<CodeCellLayoutChangeEvent>());
@ -143,7 +147,8 @@ export class CodeCellViewModel extends BaseCellViewModel implements ICellViewMod
@INotebookService private readonly _notebookService: INotebookService,
@ITextModelService modelService: ITextModelService,
@IUndoRedoService undoRedoService: IUndoRedoService,
@ICodeEditorService codeEditorService: ICodeEditorService
@ICodeEditorService codeEditorService: ICodeEditorService,
@IInstantiationService instantiationService: IInstantiationService
) {
super(viewType, model, UUID.generateUuid(), viewContext, configurationService, modelService, undoRedoService, codeEditorService);
this._outputViewModels = this.model.outputs.map(output => new CellOutputViewModel(this, output, this._notebookService));
@ -166,11 +171,17 @@ export class CodeCellViewModel extends BaseCellViewModel implements ICellViewMod
if (outputLayoutChange) {
this.layoutChange({ outputHeight: true }, 'CodeCellViewModel#model.onDidChangeOutputs');
}
if (this._outputCollection.length === 0 && this._cellDiagnostics.ErrorDetails) {
this._cellDiagnostics.clear();
}
dispose(removedOutputs);
}));
this._outputCollection = new Array(this.model.outputs.length);
this._cellDiagnostics = instantiationService.createInstance(CellDiagnostics, this);
this._register(this._cellDiagnostics);
this._layoutInfo = {
fontInfo: initialNotebookLayoutInfo?.fontInfo || null,
editorHeight: 0,

View file

@ -317,7 +317,7 @@ export function* getMarkdownHeadersInCell(cellContent: string): Iterable<{ reado
if (token.type === 'heading') {
yield {
depth: token.depth,
text: renderMarkdownAsPlaintext({ value: token.text }).trim()
text: renderMarkdownAsPlaintext({ value: token.raw }).trim()
};
}
}

View file

@ -951,7 +951,8 @@ export const NotebookSetting = {
anchorToFocusedCell: 'notebook.scrolling.experimental.anchorToFocusedCell',
cellChat: 'notebook.experimental.cellChat',
notebookVariablesView: 'notebook.experimental.variablesView',
InteractiveWindowPromptToSave: 'interactiveWindow.promptToSaveOnClose'
InteractiveWindowPromptToSave: 'interactiveWindow.promptToSaveOnClose',
cellFailureDiagnostics: 'notebook.experimental.cellFailureDiagnostics',
} as const;
export const enum CellStatusbarAlignment {

View file

@ -30,6 +30,7 @@ import { localize } from 'vs/nls';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IMarkdownString } from 'vs/base/common/htmlContent';
import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfiguration';
import { ICustomEditorLabelService } from 'vs/workbench/services/editor/common/customEditorLabelService';
export interface NotebookEditorInputOptions {
startDirty?: boolean;
@ -69,9 +70,10 @@ export class NotebookEditorInput extends AbstractResourceEditorInput {
@IFilesConfigurationService filesConfigurationService: IFilesConfigurationService,
@IExtensionService extensionService: IExtensionService,
@IEditorService editorService: IEditorService,
@ITextResourceConfigurationService textResourceConfigurationService: ITextResourceConfigurationService
@ITextResourceConfigurationService textResourceConfigurationService: ITextResourceConfigurationService,
@ICustomEditorLabelService customEditorLabelService: ICustomEditorLabelService
) {
super(resource, preferredResource, labelService, fileService, filesConfigurationService, textResourceConfigurationService);
super(resource, preferredResource, labelService, fileService, filesConfigurationService, textResourceConfigurationService, customEditorLabelService);
this._defaultDirtyState = !!options.startDirty;
// Automatically resolve this input when the "wanted" model comes to life via

View file

@ -65,6 +65,8 @@ import { EditorFontLigatures, EditorFontVariations } from 'vs/editor/common/conf
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
import { mainWindow } from 'vs/base/browser/window';
import { TestCodeEditorService } from 'vs/editor/test/browser/editorTestServices';
import { IInlineChatService } from 'vs/workbench/contrib/inlineChat/common/inlineChat';
import { InlineChatServiceImpl } from 'vs/workbench/contrib/inlineChat/common/inlineChatServiceImpl';
export class TestCell extends NotebookCellTextModel {
constructor(
@ -197,7 +199,7 @@ export function setupInstantiationService(disposables: DisposableStore) {
instantiationService.stub(IKeybindingService, new MockKeybindingService());
instantiationService.stub(INotebookCellStatusBarService, disposables.add(new NotebookCellStatusBarService()));
instantiationService.stub(ICodeEditorService, disposables.add(new TestCodeEditorService(testThemeService)));
instantiationService.stub(IInlineChatService, instantiationService.createInstance(InlineChatServiceImpl));
return instantiationService;
}

View file

@ -30,6 +30,7 @@ import * as perf from 'vs/base/common/performance';
import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfiguration';
import { Registry } from 'vs/platform/registry/common/platform';
import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, getWorkbenchContribution } from 'vs/workbench/common/contributions';
import { ICustomEditorLabelService } from 'vs/workbench/services/editor/common/customEditorLabelService';
export class PerfviewContrib {
@ -77,7 +78,8 @@ export class PerfviewInput extends TextResourceEditorInput {
@IFileService fileService: IFileService,
@ILabelService labelService: ILabelService,
@IFilesConfigurationService filesConfigurationService: IFilesConfigurationService,
@ITextResourceConfigurationService textResourceConfigurationService: ITextResourceConfigurationService
@ITextResourceConfigurationService textResourceConfigurationService: ITextResourceConfigurationService,
@ICustomEditorLabelService customEditorLabelService: ICustomEditorLabelService
) {
super(
PerfviewContrib.get().getInputUri(),
@ -91,7 +93,8 @@ export class PerfviewInput extends TextResourceEditorInput {
fileService,
labelService,
filesConfigurationService,
textResourceConfigurationService
textResourceConfigurationService,
customEditorLabelService
);
}
}

View file

@ -49,6 +49,10 @@ import { infoIcon } from 'vs/workbench/contrib/extensions/browser/extensionsIcon
import { IOpenerService } from 'vs/platform/opener/common/opener';
import { URI } from 'vs/base/common/uri';
import { mainWindow } from 'vs/base/browser/window';
import { Registry } from 'vs/platform/registry/common/platform';
import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry';
import { workbenchConfigurationNodeBase } from 'vs/workbench/common/configuration';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
type ActionGroup = [string, Array<MenuItemAction | SubmenuItemAction>];
@ -146,6 +150,7 @@ export class RemoteStatusIndicator extends Disposable implements IWorkbenchContr
@IProductService private readonly productService: IProductService,
@IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService,
@IOpenerService private readonly openerService: IOpenerService,
@IConfigurationService private readonly configurationService: IConfigurationService,
) {
super();
@ -714,7 +719,8 @@ export class RemoteStatusIndicator extends Disposable implements IWorkbenchContr
}
}
if (this.extensionGalleryService.isEnabled() && this.remoteMetadataInitialized) {
const showExtensionRecommendations = this.configurationService.getValue<boolean>('workbench.remoteIndicator.showExtensionRecommendations');
if (showExtensionRecommendations && this.extensionGalleryService.isEnabled() && this.remoteMetadataInitialized) {
const notInstalledItems: QuickPickItem[] = [];
for (const metadata of this.remoteExtensionMetadata) {
@ -828,3 +834,15 @@ export class RemoteStatusIndicator extends Disposable implements IWorkbenchContr
quickPick.show();
}
}
Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration)
.registerConfiguration({
...workbenchConfigurationNodeBase,
properties: {
'workbench.remoteIndicator.showExtensionRecommendations': {
type: 'boolean',
markdownDescription: nls.localize('remote.showExtensionRecommendations', "When enabled, remote extensions recommendations will be shown in the Remote Indicator menu."),
default: true
},
}
});

View file

@ -3,49 +3,18 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Disposable, DisposableStore, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { Disposable, DisposableStore, MutableDisposable } from 'vs/base/common/lifecycle';
import { ServicesAccessor } from 'vs/editor/browser/editorExtensions';
import { localize, localize2 } from 'vs/nls';
import { Categories } from 'vs/platform/action/common/actionCommonCategories';
import { Action2, registerAction2 } from 'vs/platform/actions/common/actions';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { SideBySideEditor } from 'vs/workbench/browser/parts/editor/sideBySideEditor';
import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
import { IEditorPane, IEditorPaneScrollPosition, isEditorPaneWithScrolling } from 'vs/workbench/common/editor';
import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IStatusbarEntryAccessor, IStatusbarService, StatusbarAlignment } from 'vs/workbench/services/statusbar/browser/statusbar';
class SyncScrollStatusEntry extends Disposable {
private readonly syncScrollEntry = this._register(new MutableDisposable<IStatusbarEntryAccessor>());
constructor(@IStatusbarService private readonly statusbarService: IStatusbarService) {
super();
}
updateSyncScroll(visible: boolean): void {
if (visible) {
if (!this.syncScrollEntry.value) {
this.syncScrollEntry.value = this.statusbarService.addEntry({
name: 'Scrolling Locked',
text: 'Scrolling Locked',
tooltip: 'Lock Scrolling enabled',
ariaLabel: 'Scrolling Locked',
command: {
id: 'workbench.action.toggleLockedScrolling',
title: ''
},
kind: 'prominent'
}, 'status.scrollLockingEnabled', StatusbarAlignment.RIGHT, 102);
}
} else {
this.syncScrollEntry.clear();
}
}
}
export class SyncScroll extends Disposable implements IWorkbenchContribution {
static readonly ID = 'workbench.contrib.syncScrolling';
@ -55,14 +24,13 @@ export class SyncScroll extends Disposable implements IWorkbenchContribution {
private readonly syncScrollDispoasbles = this._register(new DisposableStore());
private readonly paneDisposables = new DisposableStore();
private statusBarEntries = new Set<SyncScrollStatusEntry>();
private statusBarEntry = this._register(new MutableDisposable<IStatusbarEntryAccessor>());
private isActive: boolean = false;
constructor(
@IEditorService private readonly editorService: IEditorService,
@IEditorGroupsService private readonly editorGroupsService: IEditorGroupsService,
@IInstantiationService private readonly instantiationService: IInstantiationService
@IStatusbarService private readonly statusbarService: IStatusbarService
) {
super();
@ -175,36 +143,31 @@ export class SyncScroll extends Disposable implements IWorkbenchContribution {
// Actions & Commands
private createStatusBarItem(instantiationService: IInstantiationService, disposables: DisposableStore): SyncScrollStatusEntry {
const entry = disposables.add(instantiationService.createInstance(SyncScrollStatusEntry));
this.statusBarEntries.add(entry);
disposables.add(toDisposable(() => this.statusBarEntries.delete(entry)));
return entry;
}
private registerStatusBarItems() {
const entry = this.createStatusBarItem(this.instantiationService, this._store);
entry.updateSyncScroll(this.isActive);
this._register(this.editorGroupsService.onDidCreateAuxiliaryEditorPart(({ instantiationService, disposables }) => {
const entry = this.createStatusBarItem(instantiationService, disposables);
entry.updateSyncScroll(this.isActive);
}));
}
private toggleStatusbarItem(active: boolean): void {
for (const item of this.statusBarEntries) {
item.updateSyncScroll(active);
if (active) {
if (!this.statusBarEntry.value) {
const text = localize('mouseScrolllingLocked', 'Scrolling Locked');
const tooltip = localize('mouseLockScrollingEnabled', 'Lock Scrolling Enabled');
this.statusBarEntry.value = this.statusbarService.addEntry({
name: text,
text,
tooltip,
ariaLabel: text,
command: {
id: 'workbench.action.toggleLockedScrolling',
title: ''
},
kind: 'prominent',
showInAllWindows: true
}, 'status.scrollLockingEnabled', StatusbarAlignment.RIGHT, 102);
}
} else {
this.statusBarEntry.clear();
}
}
private registerActions() {
const $this = this;
this.registerStatusBarItems();
this._register(registerAction2(class extends Action2 {
constructor() {
super({
@ -253,7 +216,6 @@ export class SyncScroll extends Disposable implements IWorkbenchContribution {
}
override dispose(): void {
this.statusBarEntries.forEach(entry => entry.dispose());
this.deactivate();
super.dispose();
}

Some files were not shown because too many files have changed in this diff Show more