mirror of
https://github.com/Microsoft/vscode
synced 2024-09-18 01:58:27 +00:00
Merge branch 'main' into joh/chatWidget
This commit is contained in:
commit
15441f4251
|
@ -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"
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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<()>;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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[] = [];
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
},
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
@ -45,7 +45,6 @@
|
|||
"terminalDataWriteEvent",
|
||||
"terminalDimensions",
|
||||
"tunnels",
|
||||
"testCoverage",
|
||||
"testObserver",
|
||||
"textSearchProvider",
|
||||
"timeline",
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "code-oss-dev",
|
||||
"version": "1.88.0",
|
||||
"distro": "7ca938298e57ad434ea8807e132707055458a749",
|
||||
"distro": "ff3bff60edcc6e1f7269509e1673036c00fa62bd",
|
||||
"author": {
|
||||
"name": "Microsoft Corporation"
|
||||
},
|
||||
|
|
|
@ -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"));
|
||||
}
|
||||
});
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
*/
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
|
||||
});
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -110,6 +110,7 @@ class TestInstallGalleryExtensionTask extends InstallGalleryExtensionTask {
|
|||
extensionsScannerService,
|
||||
extensionsProfileScannerService,
|
||||
logService,
|
||||
NullTelemetryService
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 }]);
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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'],
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}));
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
80
src/vs/workbench/contrib/chat/common/annotations.ts
Normal file
80
src/vs/workbench/contrib/chat/common/annotations.ts
Normal 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 };
|
||||
}
|
||||
|
|
@ -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],
|
||||
|
|
|
@ -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 '';
|
||||
};
|
||||
|
||||
|
|
|
@ -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({
|
||||
|
|
|
@ -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
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
]
|
|
@ -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
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
]
|
|
@ -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
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
|
@ -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
|
|
@ -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: [ ]
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
]
|
|
@ -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: [ ]
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
]
|
|
@ -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: [ ]
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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', () => {
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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.") });
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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; } });
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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({
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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'
|
||||
};
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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]
|
||||
]);
|
||||
|
|
|
@ -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
|
||||
},
|
||||
}
|
||||
});
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
},
|
||||
}
|
||||
});
|
||||
|
|
|
@ -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
Loading…
Reference in a new issue