mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-04-05 19:40:30 -04:00
Merge from vscode 7653d836944892f83ce9e1f95c1204bafa1aec31
This commit is contained in:
@@ -172,6 +172,20 @@ export class GotoSymbolQuickAccessProvider extends AbstractGotoSymbolQuickAccess
|
||||
updatePickerItems();
|
||||
disposables.add(picker.onDidChangeValue(updatePickerItems));
|
||||
|
||||
let ignoreFirstActiveEvent = true;
|
||||
disposables.add(picker.onDidChangeActive(() => {
|
||||
const [entry] = picker.activeItems;
|
||||
|
||||
if (entry && entries[entry.index]) {
|
||||
if (ignoreFirstActiveEvent) {
|
||||
ignoreFirstActiveEvent = false;
|
||||
return;
|
||||
}
|
||||
|
||||
entries[entry.index]?.reveal();
|
||||
}
|
||||
}));
|
||||
|
||||
}).catch(err => {
|
||||
onUnexpectedError(err);
|
||||
picker.hide();
|
||||
|
||||
@@ -350,7 +350,7 @@ class BreakpointsRenderer implements IListRenderer<IBreakpoint, IBreakpointTempl
|
||||
|
||||
data.filePath = dom.append(data.breakpoint, $('span.file-path'));
|
||||
const lineNumberContainer = dom.append(data.breakpoint, $('.line-number-container'));
|
||||
data.lineNumber = dom.append(lineNumberContainer, $('span.line-number'));
|
||||
data.lineNumber = dom.append(lineNumberContainer, $('span.line-number.monaco-count-badge'));
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
@@ -41,6 +41,8 @@ import { IOpenerService } from 'vs/platform/opener/common/opener';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { attachStylerCallback } from 'vs/platform/theme/common/styler';
|
||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
import { commonSuffixLength } from 'vs/base/common/strings';
|
||||
import { posix } from 'vs/base/common/path';
|
||||
|
||||
const $ = dom.$;
|
||||
|
||||
@@ -78,6 +80,26 @@ export function getContextForContributedActions(element: CallStackItem | null):
|
||||
return '';
|
||||
}
|
||||
|
||||
export function getSpecificSourceName(stackFrame: IStackFrame): string {
|
||||
// To reduce flashing of the path name and the way we fetch stack frames
|
||||
// We need to compute the source name based on the other frames in the stale call stack
|
||||
let callStack = (<Thread>stackFrame.thread).getStaleCallStack();
|
||||
callStack = callStack.length > 0 ? callStack : stackFrame.thread.getCallStack();
|
||||
const otherSources = callStack.map(sf => sf.source).filter(s => s !== stackFrame.source);
|
||||
let suffixLength = 0;
|
||||
otherSources.forEach(s => {
|
||||
if (s.name === stackFrame.source.name) {
|
||||
suffixLength = Math.max(suffixLength, commonSuffixLength(stackFrame.source.uri.path, s.uri.path));
|
||||
}
|
||||
});
|
||||
if (suffixLength === 0) {
|
||||
return stackFrame.source.name;
|
||||
}
|
||||
|
||||
const from = Math.max(0, stackFrame.source.uri.path.lastIndexOf(posix.sep, stackFrame.source.uri.path.length - suffixLength - 1));
|
||||
return (from > 0 ? '...' : '') + stackFrame.source.uri.path.substr(from);
|
||||
}
|
||||
|
||||
export class CallStackView extends ViewPane {
|
||||
private pauseMessage!: HTMLSpanElement;
|
||||
private pauseMessageLabel!: HTMLSpanElement;
|
||||
@@ -453,7 +475,7 @@ class SessionsRenderer implements ITreeRenderer<IDebugSession, FuzzyScore, ISess
|
||||
dom.append(session, $('.codicon.codicon-bug'));
|
||||
const name = dom.append(session, $('.name'));
|
||||
const state = dom.append(session, $('.state'));
|
||||
const stateLabel = dom.append(state, $('span.label'));
|
||||
const stateLabel = dom.append(state, $('span.label.monaco-count-badge.long'));
|
||||
const label = new HighlightedLabel(name, false);
|
||||
const actionBar = new ActionBar(session, {
|
||||
actionViewItemProvider: action => {
|
||||
@@ -562,7 +584,7 @@ class StackFramesRenderer implements ITreeRenderer<IStackFrame, FuzzyScore, ISta
|
||||
const file = dom.append(stackFrame, $('.file'));
|
||||
const fileName = dom.append(file, $('span.file-name'));
|
||||
const wrapper = dom.append(file, $('span.line-number-wrapper'));
|
||||
const lineNumber = dom.append(wrapper, $('span.line-number'));
|
||||
const lineNumber = dom.append(wrapper, $('span.line-number.monaco-count-badge'));
|
||||
const label = new HighlightedLabel(labelDiv, false);
|
||||
const actionBar = new ActionBar(stackFrame);
|
||||
|
||||
@@ -582,7 +604,7 @@ class StackFramesRenderer implements ITreeRenderer<IStackFrame, FuzzyScore, ISta
|
||||
data.file.title += `\n${stackFrame.source.raw.origin}`;
|
||||
}
|
||||
data.label.set(stackFrame.name, createMatches(element.filterData), stackFrame.name);
|
||||
data.fileName.textContent = stackFrame.getSpecificSourceName();
|
||||
data.fileName.textContent = getSpecificSourceName(stackFrame);
|
||||
if (stackFrame.range.startLineNumber !== undefined) {
|
||||
data.lineNumber.textContent = `${stackFrame.range.startLineNumber}`;
|
||||
if (stackFrame.range.startColumn) {
|
||||
@@ -855,7 +877,7 @@ class CallStackAccessibilityProvider implements IListAccessibilityProvider<CallS
|
||||
return nls.localize('threadAriaLabel', "Thread {0}, callstack, debug", (<Thread>element).name);
|
||||
}
|
||||
if (element instanceof StackFrame) {
|
||||
return nls.localize('stackFrameAriaLabel', "Stack Frame {0}, line {1}, {2}, callstack, debug", element.name, element.range.startLineNumber, element.getSpecificSourceName());
|
||||
return nls.localize('stackFrameAriaLabel', "Stack Frame {0}, line {1}, {2}, callstack, debug", element.name, element.range.startLineNumber, getSpecificSourceName(element));
|
||||
}
|
||||
if (isDebugSession(element)) {
|
||||
return nls.localize('sessionLabel', "Debug Session {0}", element.getLabel());
|
||||
|
||||
@@ -229,11 +229,6 @@ configurationRegistry.registerConfiguration({
|
||||
default: 'openOnSessionStart',
|
||||
description: nls.localize('openDebug', "Controls when the debug view should open.")
|
||||
},
|
||||
'debug.enableAllHovers': {
|
||||
type: 'boolean',
|
||||
description: nls.localize({ comment: ['This is the description for a setting'], key: 'enableAllHovers' }, "Controls whether the non-debug hovers should be enabled while debugging. When enabled the hover providers will be called to provide a hover. Regular hovers will not be shown even if this setting is enabled."),
|
||||
default: false
|
||||
},
|
||||
'debug.showSubSessionsInToolBar': {
|
||||
type: 'boolean',
|
||||
description: nls.localize({ comment: ['This is the description for a setting'], key: 'showSubSessionsInToolBar' }, "Controls whether the debug sub-sessions are shown in the debug tool bar. When this setting is false the stop command on a sub-session will also stop the parent session."),
|
||||
|
||||
@@ -24,7 +24,6 @@ import { InputFocusedContext } from 'vs/platform/contextkey/common/contextkeys';
|
||||
import { ServicesAccessor } from 'vs/editor/browser/editorExtensions';
|
||||
import { PanelFocusContext } from 'vs/workbench/common/panel';
|
||||
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
|
||||
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfigurationService';
|
||||
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
@@ -206,7 +205,7 @@ export function registerCommands(): void {
|
||||
weight: KeybindingWeight.WorkbenchContrib,
|
||||
primary: KeyMod.Shift | KeyMod.CtrlCmd | KeyCode.F5,
|
||||
when: CONTEXT_IN_DEBUG_MODE,
|
||||
handler: (accessor: ServicesAccessor, _: string, context: CallStackContext | unknown) => {
|
||||
handler: async (accessor: ServicesAccessor, _: string, context: CallStackContext | unknown) => {
|
||||
const debugService = accessor.get(IDebugService);
|
||||
let session: IDebugSession | undefined;
|
||||
if (isSessionContext(context)) {
|
||||
@@ -217,10 +216,10 @@ export function registerCommands(): void {
|
||||
|
||||
if (!session) {
|
||||
const { launch, name } = debugService.getConfigurationManager().selectedConfiguration;
|
||||
debugService.startDebugging(launch, name, { noDebug: false });
|
||||
await debugService.startDebugging(launch, name, { noDebug: false });
|
||||
} else {
|
||||
session.removeReplExpressions();
|
||||
debugService.restartSession(session).then(undefined, onUnexpectedError);
|
||||
await debugService.restartSession(session);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -268,10 +267,15 @@ export function registerCommands(): void {
|
||||
|
||||
CommandsRegistry.registerCommand({
|
||||
id: DISCONNECT_ID,
|
||||
handler: (accessor: ServicesAccessor, sessionId: string | undefined) => {
|
||||
handler: async (accessor: ServicesAccessor, _: string, context: CallStackContext | unknown) => {
|
||||
const debugService = accessor.get(IDebugService);
|
||||
const session = debugService.getModel().getSession(sessionId) || debugService.getViewModel().focusedSession;
|
||||
debugService.stopSession(session).then(undefined, onUnexpectedError);
|
||||
let session: IDebugSession | undefined;
|
||||
if (isSessionContext(context)) {
|
||||
session = debugService.getModel().getSession(context.sessionId);
|
||||
} else {
|
||||
session = debugService.getViewModel().focusedSession;
|
||||
}
|
||||
await debugService.stopSession(session);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -280,7 +284,7 @@ export function registerCommands(): void {
|
||||
weight: KeybindingWeight.WorkbenchContrib,
|
||||
primary: KeyMod.Shift | KeyCode.F5,
|
||||
when: CONTEXT_IN_DEBUG_MODE,
|
||||
handler: (accessor: ServicesAccessor, _: string, context: CallStackContext | unknown) => {
|
||||
handler: async (accessor: ServicesAccessor, _: string, context: CallStackContext | unknown) => {
|
||||
const debugService = accessor.get(IDebugService);
|
||||
let session: IDebugSession | undefined;
|
||||
if (isSessionContext(context)) {
|
||||
@@ -296,7 +300,7 @@ export function registerCommands(): void {
|
||||
session = session.parentSession;
|
||||
}
|
||||
|
||||
debugService.stopSession(session).then(undefined, onUnexpectedError);
|
||||
await debugService.stopSession(session);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -336,9 +340,9 @@ export function registerCommands(): void {
|
||||
|
||||
CommandsRegistry.registerCommand({
|
||||
id: 'debug.startFromConfig',
|
||||
handler: (accessor, config: IConfig) => {
|
||||
handler: async (accessor, config: IConfig) => {
|
||||
const debugService = accessor.get(IDebugService);
|
||||
debugService.startDebugging(undefined, config).then(undefined, onUnexpectedError);
|
||||
await debugService.startDebugging(undefined, config);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -30,10 +30,8 @@ import { CoreEditingCommands } from 'vs/editor/browser/controller/coreCommands';
|
||||
import { first } from 'vs/base/common/arrays';
|
||||
import { memoize, createMemoizer } from 'vs/base/common/decorators';
|
||||
import { IEditorHoverOptions, EditorOption } from 'vs/editor/common/config/editorOptions';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { DebugHoverWidget } from 'vs/workbench/contrib/debug/browser/debugHover';
|
||||
import { ITextModel } from 'vs/editor/common/model';
|
||||
import { getHover } from 'vs/editor/contrib/hover/getHover';
|
||||
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { EditOperation } from 'vs/editor/common/core/editOperation';
|
||||
import { basename } from 'vs/base/common/path';
|
||||
@@ -170,7 +168,6 @@ class DebugEditorContribution implements IDebugEditorContribution {
|
||||
|
||||
private toDispose: IDisposable[];
|
||||
private hoverWidget: DebugHoverWidget;
|
||||
private nonDebugHoverPosition: Position | undefined;
|
||||
private hoverRange: Range | null = null;
|
||||
private mouseDown = false;
|
||||
private static readonly MEMOIZER = createMemoizer();
|
||||
@@ -204,7 +201,6 @@ class DebugEditorContribution implements IDebugEditorContribution {
|
||||
this.toDispose.push(this.editor.onMouseUp(() => this.mouseDown = false));
|
||||
this.toDispose.push(this.editor.onMouseMove((e: IEditorMouseEvent) => this.onEditorMouseMove(e)));
|
||||
this.toDispose.push(this.editor.onMouseLeave((e: IPartialEditorMouseEvent) => {
|
||||
this.provideNonDebugHoverScheduler.cancel();
|
||||
const hoverDomNode = this.hoverWidget.getDomNode();
|
||||
if (!hoverDomNode) {
|
||||
return;
|
||||
@@ -315,24 +311,11 @@ class DebugEditorContribution implements IDebugEditorContribution {
|
||||
return scheduler;
|
||||
}
|
||||
|
||||
@memoize
|
||||
private get provideNonDebugHoverScheduler(): RunOnceScheduler {
|
||||
const scheduler = new RunOnceScheduler(() => {
|
||||
if (this.editor.hasModel() && this.nonDebugHoverPosition) {
|
||||
getHover(this.editor.getModel(), this.nonDebugHoverPosition, CancellationToken.None);
|
||||
}
|
||||
}, HOVER_DELAY);
|
||||
this.toDispose.push(scheduler);
|
||||
|
||||
return scheduler;
|
||||
}
|
||||
|
||||
private hideHoverWidget(): void {
|
||||
if (!this.hideHoverScheduler.isScheduled() && this.hoverWidget.isVisible()) {
|
||||
this.hideHoverScheduler.schedule();
|
||||
}
|
||||
this.showHoverScheduler.cancel();
|
||||
this.provideNonDebugHoverScheduler.cancel();
|
||||
}
|
||||
|
||||
// hover business
|
||||
@@ -351,10 +334,6 @@ class DebugEditorContribution implements IDebugEditorContribution {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.configurationService.getValue<IDebugConfiguration>('debug').enableAllHovers && mouseEvent.target.position) {
|
||||
this.nonDebugHoverPosition = mouseEvent.target.position;
|
||||
this.provideNonDebugHoverScheduler.schedule();
|
||||
}
|
||||
const targetType = mouseEvent.target.type;
|
||||
const stopKey = env.isMacintosh ? 'metaKey' : 'ctrlKey';
|
||||
|
||||
|
||||
@@ -66,10 +66,8 @@
|
||||
/* Debug viewlet trees */
|
||||
|
||||
.debug-pane .line-number {
|
||||
border-radius: 2px;
|
||||
font-size: 0.9em;
|
||||
padding: 0 3px;
|
||||
line-height: 20px;
|
||||
padding-left: 4px;
|
||||
padding-right: 4px;
|
||||
}
|
||||
|
||||
.debug-pane .disabled {
|
||||
@@ -118,6 +116,8 @@
|
||||
|
||||
.debug-pane .debug-call-stack .thread > .state,
|
||||
.debug-pane .debug-call-stack .session > .state {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
text-align: right;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
@@ -159,9 +159,8 @@
|
||||
|
||||
.debug-pane .debug-call-stack .thread > .state > .label,
|
||||
.debug-pane .debug-call-stack .session > .state > .label {
|
||||
border-radius: 2px;
|
||||
font-size: 0.8em;
|
||||
padding: 0 3px;
|
||||
min-height: auto;
|
||||
}
|
||||
|
||||
.debug-pane .debug-call-stack .stack-frame {
|
||||
|
||||
@@ -318,7 +318,6 @@ export interface IStackFrame extends ITreeElement {
|
||||
readonly source: Source;
|
||||
getScopes(): Promise<IScope[]>;
|
||||
getMostSpecificScopes(range: IRange): Promise<ReadonlyArray<IScope>>;
|
||||
getSpecificSourceName(): string;
|
||||
forgetScopes(): void;
|
||||
restart(): Promise<any>;
|
||||
toString(): string;
|
||||
|
||||
@@ -17,8 +17,6 @@ import {
|
||||
IThread, IRawModelUpdate, IScope, IRawStoppedDetails, IEnablement, IBreakpointData, IExceptionInfo, IBreakpointsChangeEvent, IBreakpointUpdateData, IBaseBreakpoint, State, IDataBreakpoint
|
||||
} from 'vs/workbench/contrib/debug/common/debug';
|
||||
import { Source, UNKNOWN_SOURCE_LABEL, getUriFromSource } from 'vs/workbench/contrib/debug/common/debugSource';
|
||||
import { commonSuffixLength } from 'vs/base/common/strings';
|
||||
import { posix } from 'vs/base/common/path';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
|
||||
import { ITextEditorPane } from 'vs/workbench/common/editor';
|
||||
@@ -325,26 +323,6 @@ export class StackFrame implements IStackFrame {
|
||||
return this.scopes;
|
||||
}
|
||||
|
||||
getSpecificSourceName(): string {
|
||||
// To reduce flashing of the path name and the way we fetch stack frames
|
||||
// We need to compute the source name based on the other frames in the stale call stack
|
||||
let callStack = (<Thread>this.thread).getStaleCallStack();
|
||||
callStack = callStack.length > 0 ? callStack : this.thread.getCallStack();
|
||||
const otherSources = callStack.map(sf => sf.source).filter(s => s !== this.source);
|
||||
let suffixLength = 0;
|
||||
otherSources.forEach(s => {
|
||||
if (s.name === this.source.name) {
|
||||
suffixLength = Math.max(suffixLength, commonSuffixLength(this.source.uri.path, s.uri.path));
|
||||
}
|
||||
});
|
||||
if (suffixLength === 0) {
|
||||
return this.source.name;
|
||||
}
|
||||
|
||||
const from = Math.max(0, this.source.uri.path.lastIndexOf(posix.sep, this.source.uri.path.length - suffixLength - 1));
|
||||
return (from > 0 ? '...' : '') + this.source.uri.path.substr(from);
|
||||
}
|
||||
|
||||
async getMostSpecificScopes(range: IRange): Promise<IScope[]> {
|
||||
const scopes = await this.getScopes();
|
||||
const nonExpensiveScopes = scopes.filter(s => !s.expensive);
|
||||
|
||||
@@ -14,7 +14,7 @@ import { IDebugSessionOptions, State } from 'vs/workbench/contrib/debug/common/d
|
||||
import { NullOpenerService } from 'vs/platform/opener/common/opener';
|
||||
import { createDecorationsForStackFrame } from 'vs/workbench/contrib/debug/browser/callStackEditorContribution';
|
||||
import { Constants } from 'vs/base/common/uint';
|
||||
import { getContext, getContextForContributedActions } from 'vs/workbench/contrib/debug/browser/callStackView';
|
||||
import { getContext, getContextForContributedActions, getSpecificSourceName } from 'vs/workbench/contrib/debug/browser/callStackView';
|
||||
import { getStackFrameThreadAndSessionToFocus } from 'vs/workbench/contrib/debug/browser/debugService';
|
||||
import { generateUuid } from 'vs/base/common/uuid';
|
||||
|
||||
@@ -250,8 +250,8 @@ suite('Debug - CallStack', () => {
|
||||
model.addSession(session);
|
||||
const { firstStackFrame, secondStackFrame } = createTwoStackFrames(session);
|
||||
|
||||
assert.equal(firstStackFrame.getSpecificSourceName(), '.../b/c/d/internalModule.js');
|
||||
assert.equal(secondStackFrame.getSpecificSourceName(), '.../x/c/d/internalModule.js');
|
||||
assert.equal(getSpecificSourceName(firstStackFrame), '.../b/c/d/internalModule.js');
|
||||
assert.equal(getSpecificSourceName(secondStackFrame), '.../x/c/d/internalModule.js');
|
||||
});
|
||||
|
||||
test('stack frame toString()', () => {
|
||||
|
||||
@@ -1629,11 +1629,11 @@ export class ShowInstalledExtensionsAction extends Action {
|
||||
super(id, label, undefined, true);
|
||||
}
|
||||
|
||||
run(): Promise<void> {
|
||||
run(refresh?: boolean): Promise<void> {
|
||||
return this.viewletService.openViewlet(VIEWLET_ID, true)
|
||||
.then(viewlet => viewlet?.getViewPaneContainer() as IExtensionsViewPaneContainer)
|
||||
.then(viewlet => {
|
||||
viewlet.search('@installed ');
|
||||
viewlet.search('@installed ', refresh);
|
||||
viewlet.focus();
|
||||
});
|
||||
}
|
||||
@@ -2983,90 +2983,84 @@ export class InstallVSIXAction extends Action {
|
||||
@IHostService private readonly hostService: IHostService,
|
||||
@IFileDialogService private readonly fileDialogService: IFileDialogService,
|
||||
@IExtensionService private readonly extensionService: IExtensionService,
|
||||
@IInstantiationService private readonly instantiationService: IInstantiationService,
|
||||
@IConfigurationService private readonly configurationService: IConfigurationService, // {{SQL CARBON EDIT}}
|
||||
@IStorageService private storageService: IStorageService
|
||||
) {
|
||||
super(id, label, 'extension-action install-vsix', true);
|
||||
}
|
||||
|
||||
run(): Promise<any> {
|
||||
async run(vsixPaths?: URI[]): Promise<void> {
|
||||
// {{SQL CARBON EDIT}} - Replace run body
|
||||
let extensionPolicy = this.configurationService.getValue<string>(ExtensionsPolicyKey);
|
||||
if (extensionPolicy === ExtensionsPolicy.allowAll) {
|
||||
return Promise.resolve(this.fileDialogService.showOpenDialog({
|
||||
title: localize('installFromVSIX', "Install from VSIX"),
|
||||
filters: [{ name: 'VSIX Extensions', extensions: ['vsix'] }],
|
||||
canSelectFiles: true,
|
||||
openLabel: mnemonicButtonLabel(localize({ key: 'installButton', comment: ['&& denotes a mnemonic'] }, "&&Install"))
|
||||
}).then(result => {
|
||||
if (!result) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
return Promise.all(result.map(vsix => {
|
||||
if (!this.storageService.getBoolean(vsix.fsPath, StorageScope.GLOBAL)) {
|
||||
if (!vsixPaths) {
|
||||
vsixPaths = await this.fileDialogService.showOpenDialog({
|
||||
title: localize('installFromVSIX', "Install from VSIX"),
|
||||
filters: [{ name: 'VSIX Extensions', extensions: ['vsix'] }],
|
||||
canSelectFiles: true,
|
||||
openLabel: mnemonicButtonLabel(localize({ key: 'installButton', comment: ['&& denotes a mnemonic'] }, "&&Install"))
|
||||
});
|
||||
}
|
||||
|
||||
await Promise.all(vsixPaths.map(async vsix => {
|
||||
if (!this.storageService.getBoolean(vsix.fsPath, StorageScope.GLOBAL)) {
|
||||
const accept = await new Promise<boolean>(resolve => {
|
||||
this.notificationService.prompt(
|
||||
Severity.Warning,
|
||||
localize('thirdPartyExtension.vsix', 'This is a third party extension and might involve security risks. Are you sure you want to install this extension?'),
|
||||
[
|
||||
{
|
||||
label: localize('thirdPartExt.yes', 'Yes'),
|
||||
run: () => {
|
||||
this.extensionsWorkbenchService.install(vsix).then(extension => {
|
||||
const requireReload = !(extension.local && this.extensionService.canAddExtension(toExtensionDescription(extension.local)));
|
||||
const message = requireReload ? localize('InstallVSIXAction.successReload', "Please reload Azure Data Studio to complete installing the extension {0}.", extension.identifier.id)
|
||||
: localize('InstallVSIXAction.success', "Completed installing the extension {0}.", extension.identifier.id);
|
||||
const actions = requireReload ? [{
|
||||
label: localize('InstallVSIXAction.reloadNow', "Reload Now"),
|
||||
run: () => this.hostService.reload()
|
||||
}] : [];
|
||||
this.notificationService.prompt(
|
||||
Severity.Info,
|
||||
message,
|
||||
actions,
|
||||
{ sticky: true }
|
||||
);
|
||||
});
|
||||
}
|
||||
run: () => resolve(true)
|
||||
},
|
||||
{
|
||||
label: localize('thirdPartyExt.no', 'No'),
|
||||
run: () => { return Promise.resolve(); }
|
||||
run: () => resolve(false)
|
||||
},
|
||||
{
|
||||
label: localize('thirdPartyExt.dontShowAgain', 'Don\'t Show Again'),
|
||||
isSecondary: true,
|
||||
run: () => {
|
||||
this.storageService.store(vsix.fsPath, true, StorageScope.GLOBAL);
|
||||
return Promise.resolve();
|
||||
resolve(true);
|
||||
}
|
||||
}
|
||||
],
|
||||
{ sticky: true }
|
||||
);
|
||||
} else {
|
||||
this.extensionsWorkbenchService.install(vsix).then(extension => {
|
||||
const requireReload = !(extension.local && this.extensionService.canAddExtension(toExtensionDescription(extension.local)));
|
||||
const message = requireReload ? localize('InstallVSIXAction.successReload', "Please reload Azure Data Studio to complete installing the extension {0}.", extension.identifier.id)
|
||||
: localize('InstallVSIXAction.success', "Completed installing the extension {0}.", extension.identifier.id);
|
||||
const actions = requireReload ? [{
|
||||
label: localize('InstallVSIXAction.reloadNow', "Reload Now"),
|
||||
run: () => this.hostService.reload()
|
||||
}] : [];
|
||||
this.notificationService.prompt(
|
||||
Severity.Info,
|
||||
message,
|
||||
actions,
|
||||
{ sticky: true }
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
if (!accept) {
|
||||
return undefined;
|
||||
}
|
||||
})).then(() => Promise.resolve());
|
||||
}));
|
||||
}
|
||||
|
||||
return this.extensionsWorkbenchService.install(vsix);
|
||||
})).then(async (extensions) => {
|
||||
for (const extension of extensions) {
|
||||
if (!extension) {
|
||||
return;
|
||||
}
|
||||
const requireReload = !(extension.local && this.extensionService.canAddExtension(toExtensionDescription(extension.local)));
|
||||
const message = requireReload ? localize('InstallVSIXAction.successReload', "Please reload Visual Studio Code to complete installing the extension {0}.", extension.displayName || extension.name)
|
||||
: localize('InstallVSIXAction.success', "Completed installing the extension {0}.", extension.displayName || extension.name);
|
||||
const actions = requireReload ? [{
|
||||
label: localize('InstallVSIXAction.reloadNow', "Reload Now"),
|
||||
run: () => this.hostService.reload()
|
||||
}] : [];
|
||||
this.notificationService.prompt(
|
||||
Severity.Info,
|
||||
message,
|
||||
actions,
|
||||
{ sticky: true }
|
||||
);
|
||||
}
|
||||
await this.instantiationService.createInstance(ShowInstalledExtensionsAction, ShowInstalledExtensionsAction.ID, ShowInstalledExtensionsAction.LABEL).run(true);
|
||||
});
|
||||
} else {
|
||||
this.notificationService.error(localize('InstallVSIXAction.allowNone', 'Your extension policy does not allow downloading extensions. Please change your extension policy and try again.'));
|
||||
return Promise.resolve();
|
||||
}
|
||||
// {{SQL CARBON EDIT}} - End
|
||||
}
|
||||
|
||||
get enabled(): boolean { // {{SQL CARBON EDIT}} add enabled logic
|
||||
|
||||
@@ -14,7 +14,7 @@ import { IAction, Action } from 'vs/base/common/actions';
|
||||
import { Separator } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import { IViewlet } from 'vs/workbench/common/viewlet';
|
||||
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
|
||||
import { append, $, addClass, toggleClass, Dimension } from 'vs/base/browser/dom';
|
||||
import { append, $, addClass, toggleClass, Dimension, hide, show } from 'vs/base/browser/dom';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
|
||||
@@ -57,6 +57,9 @@ import { ILabelService } from 'vs/platform/label/common/label';
|
||||
import { MementoObject } from 'vs/workbench/common/memento';
|
||||
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
|
||||
import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences';
|
||||
import { DragAndDropObserver } from 'vs/workbench/browser/dnd';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { SIDE_BAR_DRAG_AND_DROP_BACKGROUND } from 'vs/workbench/common/theme';
|
||||
|
||||
const NonEmptyWorkspaceContext = new RawContextKey<boolean>('nonEmptyWorkspace', false);
|
||||
const DefaultViewsContext = new RawContextKey<boolean>('defaultExtensionViews', true);
|
||||
@@ -400,8 +403,12 @@ export class ExtensionsViewPaneContainer extends ViewPaneContainer implements IE
|
||||
addClass(parent, 'extensions-viewlet');
|
||||
this.root = parent;
|
||||
|
||||
const header = append(this.root, $('.header'));
|
||||
const overlay = append(this.root, $('.overlay'));
|
||||
const overlayBackgroundColor = this.getColor(SIDE_BAR_DRAG_AND_DROP_BACKGROUND) ?? '';
|
||||
overlay.style.backgroundColor = overlayBackgroundColor;
|
||||
hide(overlay);
|
||||
|
||||
const header = append(this.root, $('.header'));
|
||||
const placeholder = localize('searchExtensions', "Search Extensions in Marketplace");
|
||||
const searchValue = this.searchViewletState['query.value'] ? this.searchViewletState['query.value'] : '';
|
||||
|
||||
@@ -435,6 +442,49 @@ export class ExtensionsViewPaneContainer extends ViewPaneContainer implements IE
|
||||
}
|
||||
}));
|
||||
|
||||
// Register DragAndDrop support
|
||||
this._register(new DragAndDropObserver(this.root, {
|
||||
onDragEnd: (e: DragEvent) => undefined,
|
||||
onDragEnter: (e: DragEvent) => {
|
||||
if (this.isSupportedDragElement(e)) {
|
||||
show(overlay);
|
||||
}
|
||||
},
|
||||
onDragLeave: (e: DragEvent) => {
|
||||
if (this.isSupportedDragElement(e)) {
|
||||
hide(overlay);
|
||||
}
|
||||
},
|
||||
onDragOver: (e: DragEvent) => {
|
||||
if (e.dataTransfer) {
|
||||
e.dataTransfer.dropEffect = this.isSupportedDragElement(e) ? 'copy' : 'none';
|
||||
}
|
||||
},
|
||||
onDrop: async (e: DragEvent) => {
|
||||
if (this.isSupportedDragElement(e)) {
|
||||
hide(overlay);
|
||||
|
||||
if (e.dataTransfer && e.dataTransfer.files.length > 0) {
|
||||
let vsixPaths: URI[] = [];
|
||||
for (let index = 0; index < e.dataTransfer.files.length; index++) {
|
||||
const path = e.dataTransfer.files.item(index)!.path;
|
||||
if (path.indexOf('.vsix') !== -1) {
|
||||
vsixPaths.push(URI.parse(path));
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
// Attempt to install the extension(s)
|
||||
await this.instantiationService.createInstance(InstallVSIXAction, InstallVSIXAction.ID, InstallVSIXAction.LABEL).run(vsixPaths);
|
||||
}
|
||||
catch (err) {
|
||||
this.notificationService.error(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}));
|
||||
|
||||
super.create(append(this.root, $('.extensions')));
|
||||
}
|
||||
|
||||
@@ -622,6 +672,15 @@ export class ExtensionsViewPaneContainer extends ViewPaneContainer implements IE
|
||||
|
||||
this.notificationService.error(err);
|
||||
}
|
||||
|
||||
private isSupportedDragElement(e: DragEvent): boolean {
|
||||
if (e.dataTransfer) {
|
||||
const typesLowerCase = e.dataTransfer.types.map(t => t.toLocaleLowerCase());
|
||||
return typesLowerCase.indexOf('files') !== -1;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export class StatusUpdater extends Disposable implements IWorkbenchContribution {
|
||||
|
||||
@@ -4,9 +4,19 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
.extensions-viewlet {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.extensions-viewlet > .overlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.extensions-viewlet > .header {
|
||||
height: 41px;
|
||||
box-sizing: border-box;
|
||||
|
||||
@@ -60,10 +60,9 @@
|
||||
}
|
||||
|
||||
.dirty-count.monaco-count-badge {
|
||||
padding: 1px 6px 2px;
|
||||
padding: 2px 4px;
|
||||
margin-left: 6px;
|
||||
min-height: auto;
|
||||
border-radius: 0; /* goes better when ellipsis shows up on narrow sidebar */
|
||||
}
|
||||
|
||||
.explorer-viewlet .explorer-item.nonexistent-root {
|
||||
|
||||
@@ -890,7 +890,7 @@ export class FileDragAndDrop implements ITreeDragAndDrop<ExplorerItem> {
|
||||
const items = FileDragAndDrop.getStatsFromDragAndDropData(data as ElementsDragAndDropData<ExplorerItem, ExplorerItem[]>, originalEvent);
|
||||
if (items && items.length && originalEvent.dataTransfer) {
|
||||
// Apply some datatransfer types to allow for dragging the element outside of the application
|
||||
this.instantiationService.invokeFunction(fillResourceDataTransfers, items, originalEvent);
|
||||
this.instantiationService.invokeFunction(fillResourceDataTransfers, items, undefined, originalEvent);
|
||||
|
||||
// The only custom data transfer we set from the explorer is a file transfer
|
||||
// to be able to DND between multiple code file explorers across windows
|
||||
@@ -965,7 +965,7 @@ export class FileDragAndDrop implements ITreeDragAndDrop<ExplorerItem> {
|
||||
private async handleExternalDrop(data: DesktopDragAndDropData, target: ExplorerItem, originalEvent: DragEvent): Promise<void> {
|
||||
const droppedResources = extractResources(originalEvent, true);
|
||||
// Check for dropped external files to be folders
|
||||
const result = await this.fileService.resolveAll(droppedResources);
|
||||
const result = await this.fileService.resolveAll(droppedResources.map(droppedResource => ({ resource: droppedResource.resource })));
|
||||
|
||||
// Pass focus to window
|
||||
this.hostService.focus();
|
||||
|
||||
@@ -184,7 +184,7 @@ export class OpenEditorsView extends ViewPane {
|
||||
super.renderHeaderTitle(container, this.title);
|
||||
|
||||
const count = dom.append(container, $('.count'));
|
||||
this.dirtyCountElement = dom.append(count, $('.dirty-count.monaco-count-badge'));
|
||||
this.dirtyCountElement = dom.append(count, $('.dirty-count.monaco-count-badge.long'));
|
||||
|
||||
this._register((attachStylerCallback(this.themeService, { badgeBackground, badgeForeground, contrastBorder }, colors => {
|
||||
const background = colors.badgeBackground ? colors.badgeBackground.toString() : '';
|
||||
@@ -660,7 +660,7 @@ class OpenEditorsDragAndDrop implements IListDragAndDrop<OpenEditor | IEditorGro
|
||||
|
||||
if (resources.length) {
|
||||
// Apply some datatransfer types to allow for dragging the element outside of the application
|
||||
this.instantiationService.invokeFunction(fillResourceDataTransfers, resources, originalEvent);
|
||||
this.instantiationService.invokeFunction(fillResourceDataTransfers, resources, undefined, originalEvent);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -849,7 +849,7 @@ export class ResourceDragAndDrop implements ITreeDragAndDrop<TreeElement> {
|
||||
|
||||
if (resources.length) {
|
||||
// Apply some datatransfer types to allow for dragging the element outside of the application
|
||||
this.instantiationService.invokeFunction(fillResourceDataTransfers, resources, originalEvent);
|
||||
this.instantiationService.invokeFunction(fillResourceDataTransfers, resources, undefined, originalEvent);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -540,7 +540,12 @@ registerAction2(class extends InsertCellCommand {
|
||||
id: INSERT_CODE_CELL_ABOVE_COMMAND_ID,
|
||||
title: localize('notebookActions.insertCodeCellAbove', "Insert Code Cell Above"),
|
||||
category: NOTEBOOK_ACTIONS_CATEGORY,
|
||||
f1: true
|
||||
f1: true,
|
||||
keybinding: {
|
||||
primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.Enter,
|
||||
when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, InputFocusedContext.toNegated()),
|
||||
weight: KeybindingWeight.WorkbenchContrib
|
||||
}
|
||||
},
|
||||
CellKind.Code,
|
||||
'above');
|
||||
@@ -572,7 +577,12 @@ registerAction2(class extends InsertCellCommand {
|
||||
title: localize('notebookActions.insertCodeCellBelow', "Insert Code Cell Below"),
|
||||
category: NOTEBOOK_ACTIONS_CATEGORY,
|
||||
icon: { id: 'codicon/add' },
|
||||
f1: true
|
||||
f1: true,
|
||||
keybinding: {
|
||||
primary: KeyMod.CtrlCmd | KeyCode.Enter,
|
||||
when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, InputFocusedContext.toNegated()),
|
||||
weight: KeybindingWeight.WorkbenchContrib
|
||||
}
|
||||
},
|
||||
CellKind.Code,
|
||||
'below');
|
||||
@@ -770,7 +780,12 @@ registerAction2(class extends Action2 {
|
||||
title: localize('notebookActions.moveCellUp', "Move Cell Up"),
|
||||
category: NOTEBOOK_ACTIONS_CATEGORY,
|
||||
icon: { id: 'codicon/arrow-up' },
|
||||
f1: true
|
||||
f1: true,
|
||||
keybinding: {
|
||||
primary: KeyMod.Alt | KeyCode.UpArrow,
|
||||
when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, InputFocusedContext.toNegated()),
|
||||
weight: KeybindingWeight.WorkbenchContrib
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -794,7 +809,12 @@ registerAction2(class extends Action2 {
|
||||
title: localize('notebookActions.moveCellDown', "Move Cell Down"),
|
||||
category: NOTEBOOK_ACTIONS_CATEGORY,
|
||||
icon: { id: 'codicon/arrow-down' },
|
||||
f1: true
|
||||
f1: true,
|
||||
keybinding: {
|
||||
primary: KeyMod.Alt | KeyCode.DownArrow,
|
||||
when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, InputFocusedContext.toNegated()),
|
||||
weight: KeybindingWeight.WorkbenchContrib
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -969,7 +989,12 @@ registerAction2(class extends Action2 {
|
||||
id: COPY_CELL_UP_COMMAND_ID,
|
||||
title: localize('notebookActions.copyCellUp', "Copy Cell Up"),
|
||||
category: NOTEBOOK_ACTIONS_CATEGORY,
|
||||
f1: true
|
||||
f1: true,
|
||||
keybinding: {
|
||||
primary: KeyMod.Alt | KeyMod.Shift | KeyCode.UpArrow,
|
||||
when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, InputFocusedContext.toNegated()),
|
||||
weight: KeybindingWeight.WorkbenchContrib
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -992,7 +1017,12 @@ registerAction2(class extends Action2 {
|
||||
id: COPY_CELL_DOWN_COMMAND_ID,
|
||||
title: localize('notebookActions.copyCellDown', "Copy Cell Down"),
|
||||
category: NOTEBOOK_ACTIONS_CATEGORY,
|
||||
f1: true
|
||||
f1: true,
|
||||
keybinding: {
|
||||
primary: KeyMod.Alt | KeyMod.Shift | KeyCode.DownArrow,
|
||||
when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, InputFocusedContext.toNegated()),
|
||||
weight: KeybindingWeight.WorkbenchContrib
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,10 @@
|
||||
overflow: visible !important;
|
||||
} */
|
||||
|
||||
.monaco-workbench .part.editor > .content .notebook-editor .simple-fr-find-part-wrapper.visible {
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .notebook-editor .cell-list-container .overflowingContentWidgets > div {
|
||||
z-index: 600 !important;
|
||||
/* @rebornix: larger than the editor title bar */
|
||||
|
||||
@@ -110,7 +110,6 @@ export interface ICellViewModel {
|
||||
resolveTextModel(): Promise<ITextModel>;
|
||||
getEvaluatedMetadata(documentMetadata: NotebookDocumentMetadata | undefined): NotebookCellMetadata;
|
||||
getSelectionsStartPosition(): IPosition[] | undefined;
|
||||
getLinesContent(): string[];
|
||||
}
|
||||
|
||||
export interface IEditableCellViewModel extends ICellViewModel {
|
||||
|
||||
@@ -47,6 +47,7 @@ import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor
|
||||
import { NotebookEditorExtensionsRegistry } from 'vs/workbench/contrib/notebook/browser/notebookEditorExtensions';
|
||||
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
import { IPosition, Position } from 'vs/editor/common/core/position';
|
||||
import { IReadonlyTextBuffer } from 'vs/editor/common/model';
|
||||
|
||||
const $ = DOM.$;
|
||||
const NOTEBOOK_EDITOR_VIEW_STATE_PREFERENCE_KEY = 'NotebookEditorViewState';
|
||||
@@ -170,19 +171,20 @@ export class NotebookEditor extends BaseEditor implements INotebookEditor {
|
||||
return true;
|
||||
}
|
||||
|
||||
private updateEditorFocus() {
|
||||
// Note - focus going to the webview will fire 'blur', but the webview element will be
|
||||
// a descendent of the notebook editor root.
|
||||
this.editorFocus?.set(DOM.isAncestor(document.activeElement, this.getDomNode()));
|
||||
}
|
||||
|
||||
protected createEditor(parent: HTMLElement): void {
|
||||
this._rootElement = DOM.append(parent, $('.notebook-editor'));
|
||||
this.createBody(this._rootElement);
|
||||
this.generateFontInfo();
|
||||
this.editorFocus = NOTEBOOK_EDITOR_FOCUSED.bindTo(this.contextKeyService);
|
||||
this.editorFocus.set(true);
|
||||
this._register(this.onDidFocus(() => {
|
||||
this.editorFocus?.set(true);
|
||||
}));
|
||||
|
||||
this._register(this.onDidBlur(() => {
|
||||
this.editorFocus?.set(false);
|
||||
}));
|
||||
this._register(this.onDidFocus(() => this.updateEditorFocus()));
|
||||
this._register(this.onDidBlur(() => this.updateEditorFocus()));
|
||||
|
||||
this.editorEditable = NOTEBOOK_EDITOR_EDITABLE.bindTo(this.contextKeyService);
|
||||
this.editorEditable.set(true);
|
||||
@@ -307,6 +309,8 @@ export class NotebookEditor extends BaseEditor implements INotebookEditor {
|
||||
|
||||
this.control = new NotebookCodeEditors(this.list, this.renderedEditors);
|
||||
this.webview = this.instantiationService.createInstance(BackLayerWebView, this);
|
||||
this.webview.webview.onDidBlur(() => this.updateEditorFocus());
|
||||
this.webview.webview.onDidFocus(() => this.updateEditorFocus());
|
||||
this._register(this.webview.onMessage(message => {
|
||||
if (this.viewModel) {
|
||||
this.notebookService.onDidReceiveMessage(this.viewModel.viewType, this.viewModel.uri, message);
|
||||
@@ -778,11 +782,6 @@ export class NotebookEditor extends BaseEditor implements INotebookEditor {
|
||||
return newCell;
|
||||
}
|
||||
|
||||
private isAtEOL(p: IPosition, lines: string[]) {
|
||||
const line = lines[p.lineNumber - 1];
|
||||
return line.length + 1 === p.column;
|
||||
}
|
||||
|
||||
private pushIfAbsent(positions: IPosition[], p: IPosition) {
|
||||
const last = positions.length > 0 ? positions[positions.length - 1] : undefined;
|
||||
if (!last || last.lineNumber !== p.lineNumber || last.column !== p.column) {
|
||||
@@ -795,8 +794,12 @@ export class NotebookEditor extends BaseEditor implements INotebookEditor {
|
||||
* Move end of line split points to the beginning of the next line;
|
||||
* Avoid duplicate split points
|
||||
*/
|
||||
private splitPointsToBoundaries(splitPoints: IPosition[], lines: string[]): IPosition[] | null {
|
||||
private splitPointsToBoundaries(splitPoints: IPosition[], textBuffer: IReadonlyTextBuffer): IPosition[] | null {
|
||||
const boundaries: IPosition[] = [];
|
||||
const lineCnt = textBuffer.getLineCount();
|
||||
const getLineLen = (lineNumber: number) => {
|
||||
return textBuffer.getLineLength(lineNumber);
|
||||
};
|
||||
|
||||
// split points need to be sorted
|
||||
splitPoints = splitPoints.sort((l, r) => {
|
||||
@@ -809,22 +812,21 @@ export class NotebookEditor extends BaseEditor implements INotebookEditor {
|
||||
this.pushIfAbsent(boundaries, new Position(1, 1));
|
||||
|
||||
for (let sp of splitPoints) {
|
||||
if (this.isAtEOL(sp, lines) && sp.lineNumber < lines.length) {
|
||||
if (getLineLen(sp.lineNumber) + 1 === sp.column && sp.lineNumber < lineCnt) {
|
||||
sp = new Position(sp.lineNumber + 1, 1);
|
||||
}
|
||||
this.pushIfAbsent(boundaries, sp);
|
||||
}
|
||||
|
||||
// eat-up any split point at the beginning, i.e. we ignore the split point at the very end
|
||||
this.pushIfAbsent(boundaries, new Position(lines.length, lines[lines.length - 1].length + 1));
|
||||
this.pushIfAbsent(boundaries, new Position(lineCnt, getLineLen(lineCnt) + 1));
|
||||
|
||||
// if we only have two then they describe the whole range and nothing needs to be split
|
||||
return boundaries.length > 2 ? boundaries : null;
|
||||
}
|
||||
|
||||
private computeCellLinesContents(cell: IEditableCellViewModel, splitPoints: IPosition[]): string[] | null {
|
||||
const lines = cell.getLinesContent();
|
||||
const rangeBoundaries = this.splitPointsToBoundaries(splitPoints, lines);
|
||||
const rangeBoundaries = this.splitPointsToBoundaries(splitPoints, cell.textBuffer);
|
||||
if (!rangeBoundaries) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -3,13 +3,14 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { EditorInput, IEditorInput, GroupIdentifier, ISaveOptions } from 'vs/workbench/common/editor';
|
||||
import { EditorInput, IEditorInput, GroupIdentifier, ISaveOptions, IMoveResult, IRevertOptions } from 'vs/workbench/common/editor';
|
||||
import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { isEqual } from 'vs/base/common/resources';
|
||||
import { isEqual, basename } from 'vs/base/common/resources';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IFilesConfigurationService, AutoSaveMode } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService';
|
||||
import { NotebookEditorModel } from 'vs/workbench/contrib/notebook/common/notebookEditorModel';
|
||||
import { IFileDialogService } from 'vs/platform/dialogs/common/dialogs';
|
||||
|
||||
export class NotebookEditorInput extends EditorInput {
|
||||
|
||||
@@ -39,7 +40,10 @@ export class NotebookEditorInput extends EditorInput {
|
||||
public name: string,
|
||||
public readonly viewType: string | undefined,
|
||||
@INotebookService private readonly notebookService: INotebookService,
|
||||
@IFilesConfigurationService private readonly filesConfigurationService: IFilesConfigurationService
|
||||
@IFilesConfigurationService private readonly filesConfigurationService: IFilesConfigurationService,
|
||||
@IFileDialogService private readonly fileDialogService: IFileDialogService,
|
||||
// @IEditorService private readonly editorService: IEditorService,
|
||||
@IInstantiationService private readonly instantiationService: IInstantiationService
|
||||
) {
|
||||
super();
|
||||
}
|
||||
@@ -85,6 +89,48 @@ export class NotebookEditorInput extends EditorInput {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
async saveAs(group: GroupIdentifier, options?: ISaveOptions): Promise<IEditorInput | undefined> {
|
||||
if (!this.textModel) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const dialogPath = this.textModel.resource;
|
||||
const target = await this.fileDialogService.pickFileToSave(dialogPath, options?.availableFileSystems);
|
||||
if (!target) {
|
||||
return undefined; // save cancelled
|
||||
}
|
||||
|
||||
if (!await this.textModel.saveAs(target)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return this._move(group, target)?.editor;
|
||||
}
|
||||
|
||||
move(group: GroupIdentifier, target: URI): IMoveResult | undefined {
|
||||
if (this.textModel) {
|
||||
const contributedNotebookProviders = this.notebookService.getContributedNotebookProviders(target);
|
||||
|
||||
if (contributedNotebookProviders.find(provider => provider.id === this.textModel!.viewType)) {
|
||||
return this._move(group, target);
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
_move(group: GroupIdentifier, newResource: URI): { editor: IEditorInput } | undefined {
|
||||
const editorInput = NotebookEditorInput.getOrCreate(this.instantiationService, newResource, basename(newResource), this.viewType);
|
||||
return { editor: editorInput };
|
||||
}
|
||||
|
||||
async revert(group: GroupIdentifier, options?: IRevertOptions): Promise<void> {
|
||||
if (this.textModel) {
|
||||
await this.textModel.revert(options);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
async resolve(): Promise<NotebookEditorModel> {
|
||||
if (!await this.notebookService.canResolve(this.viewType!)) {
|
||||
throw new Error(`Cannot open notebook of type '${this.viewType}'`);
|
||||
@@ -96,6 +142,10 @@ export class NotebookEditorInput extends EditorInput {
|
||||
this._onDidChangeDirty.fire();
|
||||
}));
|
||||
|
||||
if (this.textModel.isDirty()) {
|
||||
this._onDidChangeDirty.fire();
|
||||
}
|
||||
|
||||
return this.textModel;
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ import { notebookProviderExtensionPoint, notebookRendererExtensionPoint } from '
|
||||
import { NotebookProviderInfo } from 'vs/workbench/contrib/notebook/common/notebookProvider';
|
||||
import { NotebookExtensionDescription } from 'vs/workbench/api/common/extHost.protocol';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { INotebookTextModel, INotebookMimeTypeSelector, INotebookRendererInfo } from 'vs/workbench/contrib/notebook/common/notebookCommon';
|
||||
import { INotebookTextModel, INotebookMimeTypeSelector, INotebookRendererInfo, NotebookDocumentMetadata, CellEditType, ICellDto2 } from 'vs/workbench/contrib/notebook/common/notebookCommon';
|
||||
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import { NotebookOutputRendererInfo } from 'vs/workbench/contrib/notebook/common/notebookOutputRenderer';
|
||||
import { Iterable } from 'vs/base/common/iterator';
|
||||
@@ -202,13 +202,13 @@ export class NotebookService extends Disposable implements INotebookService, ICu
|
||||
return undefined; // {{SQL CARBON EDIT}} strict-null-check
|
||||
}
|
||||
|
||||
async resolveNotebook(viewType: string, uri: URI): Promise<NotebookTextModel | undefined> {
|
||||
async createNotebookFromBackup(viewType: string, uri: URI, metadata: NotebookDocumentMetadata, languages: string[], cells: ICellDto2[]): Promise<NotebookTextModel | undefined> {
|
||||
const provider = this._notebookProviders.get(viewType);
|
||||
if (!provider) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const notebookModel = await provider.controller.resolveNotebook(viewType, uri);
|
||||
const notebookModel = await provider.controller.createNotebook(viewType, uri, true, false);
|
||||
if (!notebookModel) {
|
||||
return undefined;
|
||||
}
|
||||
@@ -219,6 +219,39 @@ export class NotebookService extends Disposable implements INotebookService, ICu
|
||||
notebookModel,
|
||||
(model) => this._onWillDispose(model),
|
||||
);
|
||||
this._models[modelId] = modelData;
|
||||
|
||||
notebookModel.metadata = metadata;
|
||||
notebookModel.languages = languages;
|
||||
|
||||
notebookModel.applyEdit(notebookModel.versionId, [
|
||||
{
|
||||
editType: CellEditType.Insert,
|
||||
index: 0,
|
||||
cells: cells
|
||||
}
|
||||
]);
|
||||
|
||||
return modelData.model;
|
||||
}
|
||||
|
||||
async resolveNotebook(viewType: string, uri: URI, forceReload: boolean): Promise<NotebookTextModel | undefined> {
|
||||
const provider = this._notebookProviders.get(viewType);
|
||||
if (!provider) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
let notebookModel: NotebookTextModel | undefined;
|
||||
|
||||
notebookModel = await provider.controller.createNotebook(viewType, uri, false, forceReload);
|
||||
|
||||
// new notebook model created
|
||||
const modelId = MODEL_ID(uri);
|
||||
const modelData = new ModelData(
|
||||
notebookModel!,
|
||||
(model) => this._onWillDispose(model),
|
||||
);
|
||||
|
||||
this._models[modelId] = modelData;
|
||||
return modelData.model;
|
||||
}
|
||||
@@ -265,7 +298,7 @@ export class NotebookService extends Disposable implements INotebookService, ICu
|
||||
let provider = this._notebookProviders.get(viewType);
|
||||
|
||||
if (provider) {
|
||||
provider.controller.destoryNotebookDocument(notebook);
|
||||
provider.controller.removeNotebookDocument(notebook);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -291,6 +324,16 @@ export class NotebookService extends Disposable implements INotebookService, ICu
|
||||
return false;
|
||||
}
|
||||
|
||||
async saveAs(viewType: string, resource: URI, target: URI, token: CancellationToken): Promise<boolean> {
|
||||
let provider = this._notebookProviders.get(viewType);
|
||||
|
||||
if (provider) {
|
||||
return provider.controller.saveAs(resource, target, token);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
onDidReceiveMessage(viewType: string, uri: URI, message: any): void {
|
||||
let provider = this._notebookProviders.get(viewType);
|
||||
|
||||
|
||||
@@ -157,7 +157,7 @@ export class NotebookCellList extends WorkbenchList<CellViewModel> implements ID
|
||||
|
||||
for (let i = diff.start; i < diff.start + diff.deleteCount; i++) {
|
||||
const cell = this.element(i);
|
||||
if (this._viewModel!.hasCell(cell)) {
|
||||
if (this._viewModel!.hasCell(cell.handle)) {
|
||||
hideOutputs.push(...cell?.model.outputs);
|
||||
} else {
|
||||
deletedOutputs.push(...cell?.model.outputs);
|
||||
@@ -177,7 +177,7 @@ export class NotebookCellList extends WorkbenchList<CellViewModel> implements ID
|
||||
|
||||
for (let i = diff.start; i < diff.start + diff.deleteCount; i++) {
|
||||
const cell = this.element(i);
|
||||
if (this._viewModel!.hasCell(cell)) {
|
||||
if (this._viewModel!.hasCell(cell.handle)) {
|
||||
hideOutputs.push(...cell?.model.outputs);
|
||||
} else {
|
||||
deletedOutputs.push(...cell?.model.outputs);
|
||||
@@ -299,7 +299,7 @@ export class NotebookCellList extends WorkbenchList<CellViewModel> implements ID
|
||||
|
||||
for (let i = diff.start; i < diff.start + diff.deleteCount; i++) {
|
||||
const cell = this.element(i);
|
||||
if (this._viewModel!.hasCell(cell)) {
|
||||
if (this._viewModel!.hasCell(cell.handle)) {
|
||||
hideOutputs.push(...cell?.model.outputs);
|
||||
} else {
|
||||
deletedOutputs.push(...cell?.model.outputs);
|
||||
@@ -316,6 +316,18 @@ export class NotebookCellList extends WorkbenchList<CellViewModel> implements ID
|
||||
splice2(start: number, deleteCount: number, elements: CellViewModel[] = []): void {
|
||||
// we need to convert start and delete count based on hidden ranges
|
||||
super.splice(start, deleteCount, elements);
|
||||
|
||||
const selectionsLeft = [];
|
||||
this._viewModel!.selectionHandles.forEach(handle => {
|
||||
if (this._viewModel!.hasCell(handle)) {
|
||||
selectionsLeft.push(handle);
|
||||
}
|
||||
});
|
||||
|
||||
if (!selectionsLeft.length && this._viewModel!.viewCells) {
|
||||
// after splice, the selected cells are deleted
|
||||
this._viewModel!.selectionHandles = [this._viewModel!.viewCells[0].handle];
|
||||
}
|
||||
}
|
||||
|
||||
getViewIndex(cell: ICellViewModel) {
|
||||
|
||||
@@ -250,7 +250,6 @@ export class StatefullMarkdownCell extends Disposable {
|
||||
bindEditorListeners(model: ITextModel, dimension?: IDimension) {
|
||||
this.localDisposables.add(model.onDidChangeContent(() => {
|
||||
// we don't need to update view cell text anymore as the textbuffer is shared
|
||||
// this.viewCell.setText(model.getLinesContent());
|
||||
this.viewCell.clearHTML();
|
||||
let clientHeight = this.markdownContainer.clientHeight;
|
||||
this.markdownContainer.innerHTML = '';
|
||||
|
||||
@@ -205,25 +205,6 @@ export abstract class BaseCellViewModel extends Disposable {
|
||||
return this.model.getValue();
|
||||
}
|
||||
|
||||
getLinesContent(): string[] {
|
||||
if (this._textModel) {
|
||||
return this._textModel.getLinesContent();
|
||||
}
|
||||
|
||||
return this.model.textBuffer.getLinesContent();
|
||||
}
|
||||
|
||||
// setLinesContent(value: string[]) {
|
||||
// if (this._textModel) {
|
||||
// // TODO @rebornix we should avoid creating a new string here
|
||||
// return this._textModel.setValue(value.join('\n'));
|
||||
// } else {
|
||||
// const range = this.model.getFullModelRange();
|
||||
// this.model.textBuffer.
|
||||
// this.model.source = value;
|
||||
// }
|
||||
// }
|
||||
|
||||
private saveViewState(): void {
|
||||
if (!this._textEditor) {
|
||||
return;
|
||||
|
||||
@@ -268,7 +268,12 @@ export class NotebookViewModel extends Disposable implements EditorFoldingStateD
|
||||
});
|
||||
|
||||
diffs.reverse().forEach(diff => {
|
||||
this._viewCells.splice(diff[0], diff[1], ...diff[2]);
|
||||
const deletedCells = this._viewCells.splice(diff[0], diff[1], ...diff[2]);
|
||||
|
||||
deletedCells.forEach(cell => {
|
||||
this._handleToViewCellMapping.delete(cell.handle);
|
||||
});
|
||||
|
||||
diff[2].forEach(cell => {
|
||||
this._handleToViewCellMapping.set(cell.handle, cell);
|
||||
this._localStore.add(cell);
|
||||
@@ -456,8 +461,8 @@ export class NotebookViewModel extends Disposable implements EditorFoldingStateD
|
||||
return index + 1;
|
||||
}
|
||||
|
||||
hasCell(cell: ICellViewModel) {
|
||||
return this._handleToViewCellMapping.has(cell.handle);
|
||||
hasCell(handle: number) {
|
||||
return this._handleToViewCellMapping.has(handle);
|
||||
}
|
||||
|
||||
getVersionId() {
|
||||
@@ -586,7 +591,7 @@ export class NotebookViewModel extends Disposable implements EditorFoldingStateD
|
||||
this._viewCells.splice(deleteIndex, 1);
|
||||
this._handleToViewCellMapping.delete(deleteCell.handle);
|
||||
|
||||
this._notebook.removeCell(deleteIndex);
|
||||
this._notebook.removeCell(deleteIndex, 1);
|
||||
this._onDidChangeViewCells.fire({ synchronous: true, splices: [[deleteIndex, 1, []]] });
|
||||
}
|
||||
|
||||
@@ -638,7 +643,7 @@ export class NotebookViewModel extends Disposable implements EditorFoldingStateD
|
||||
this._viewCells.splice(index, 1);
|
||||
this._handleToViewCellMapping.delete(viewCell.handle);
|
||||
|
||||
this._notebook.removeCell(index);
|
||||
this._notebook.removeCell(index, 1);
|
||||
|
||||
let endSelections: number[] = [];
|
||||
if (this.selectionHandles.length) {
|
||||
|
||||
@@ -7,7 +7,8 @@ import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel';
|
||||
import { INotebookTextModel, NotebookCellOutputsSplice, NotebookCellTextModelSplice, NotebookDocumentMetadata, NotebookCellMetadata, ICellEditOperation, CellEditType, CellUri, ICellInsertEdit, NotebookCellsChangedEvent, CellKind, IOutput, notebookDocumentMetadataDefaults, diff, ICellDeleteEdit, NotebookCellsChangeType } from 'vs/workbench/contrib/notebook/common/notebookCommon';
|
||||
import { INotebookTextModel, NotebookCellOutputsSplice, NotebookCellTextModelSplice, NotebookDocumentMetadata, NotebookCellMetadata, ICellEditOperation, CellEditType, CellUri, ICellInsertEdit, NotebookCellsChangedEvent, CellKind, IOutput, notebookDocumentMetadataDefaults, diff, ICellDeleteEdit, NotebookCellsChangeType, ICellDto2 } from 'vs/workbench/contrib/notebook/common/notebookCommon';
|
||||
import { ITextSnapshot } from 'vs/editor/common/model';
|
||||
|
||||
function compareRangesUsingEnds(a: [number, number], b: [number, number]): number {
|
||||
if (a[1] === b[1]) {
|
||||
@@ -17,6 +18,49 @@ function compareRangesUsingEnds(a: [number, number], b: [number, number]): numbe
|
||||
return a[1] - b[1];
|
||||
}
|
||||
|
||||
export class NotebookTextModelSnapshot implements ITextSnapshot {
|
||||
// private readonly _pieces: Ce[] = [];
|
||||
private _index: number = -1;
|
||||
|
||||
constructor(private _model: NotebookTextModel) {
|
||||
// for (let i = 0; i < this._model.cells.length; i++) {
|
||||
// const cell = this._model.cells[i];
|
||||
// this._pieces.push(this._model.cells[i].textBuffer.createSnapshot(true));
|
||||
// }
|
||||
}
|
||||
|
||||
read(): string | null {
|
||||
|
||||
if (this._index === -1) {
|
||||
this._index++;
|
||||
return `{ "metadata": ${JSON.stringify(this._model.metadata)}, "languages": ${JSON.stringify(this._model.languages)}, "cells": [`;
|
||||
}
|
||||
|
||||
if (this._index < this._model.cells.length) {
|
||||
const cell = this._model.cells[this._index];
|
||||
|
||||
const data = {
|
||||
source: cell.getValue(),
|
||||
metadata: cell.metadata,
|
||||
cellKind: cell.cellKind,
|
||||
language: cell.language
|
||||
};
|
||||
|
||||
const rawStr = JSON.stringify(data);
|
||||
const isLastCell = this._index === this._model.cells.length - 1;
|
||||
|
||||
this._index++;
|
||||
return isLastCell ? rawStr : (rawStr + ',');
|
||||
} else if (this._index === this._model.cells.length) {
|
||||
this._index++;
|
||||
return `]}`;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class NotebookTextModel extends Disposable implements INotebookTextModel {
|
||||
private static _cellhandlePool: number = 0;
|
||||
|
||||
@@ -77,6 +121,18 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel
|
||||
return new NotebookCellTextModel(cellUri, cellHandle, source, language, cellKind, outputs || [], metadata);
|
||||
}
|
||||
|
||||
initialize(cells: ICellDto2[]) {
|
||||
this.cells = [];
|
||||
this._versionId = 0;
|
||||
|
||||
const mainCells = cells.map(cell => {
|
||||
const cellHandle = NotebookTextModel._cellhandlePool++;
|
||||
const cellUri = CellUri.generate(this.uri, cellHandle);
|
||||
return new NotebookCellTextModel(cellUri, cellHandle, cell.source, cell.language, cell.cellKind, cell.outputs || [], cell.metadata);
|
||||
});
|
||||
this.insertNewCell(0, mainCells);
|
||||
}
|
||||
|
||||
applyEdit(modelVersionId: number, rawEdits: ICellEditOperation[]): boolean {
|
||||
if (modelVersionId !== this._versionId) {
|
||||
return false;
|
||||
@@ -127,7 +183,7 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel
|
||||
this.insertNewCell(insertEdit.index, mainCells);
|
||||
break;
|
||||
case CellEditType.Delete:
|
||||
this.removeCell(operations[i].index);
|
||||
this.removeCell(operations[i].index, operations[i].end - operations[i].start);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -142,6 +198,10 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel
|
||||
return true;
|
||||
}
|
||||
|
||||
createSnapshot(preserveBOM?: boolean): ITextSnapshot {
|
||||
return new NotebookTextModelSnapshot(this);
|
||||
}
|
||||
|
||||
private _increaseVersionId(): void {
|
||||
this._versionId = this._versionId + 1;
|
||||
}
|
||||
@@ -250,17 +310,19 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel
|
||||
return;
|
||||
}
|
||||
|
||||
removeCell(index: number) {
|
||||
removeCell(index: number, count: number) {
|
||||
this._isUntitled = false;
|
||||
|
||||
let cell = this.cells[index];
|
||||
this._cellListeners.get(cell.handle)?.dispose();
|
||||
this._cellListeners.delete(cell.handle);
|
||||
this.cells.splice(index, 1);
|
||||
for (let i = index; i < index + count; i++) {
|
||||
let cell = this.cells[i];
|
||||
this._cellListeners.get(cell.handle)?.dispose();
|
||||
this._cellListeners.delete(cell.handle);
|
||||
}
|
||||
this.cells.splice(index, count);
|
||||
this._onDidChangeContent.fire();
|
||||
|
||||
this._increaseVersionId();
|
||||
this._onDidModelChangeProxy.fire({ kind: NotebookCellsChangeType.ModelChange, versionId: this._versionId, changes: [[index, 1, []]] });
|
||||
this._onDidModelChangeProxy.fire({ kind: NotebookCellsChangeType.ModelChange, versionId: this._versionId, changes: [[index, count, []]] });
|
||||
}
|
||||
|
||||
moveCellToIdx(index: number, newIdx: number) {
|
||||
|
||||
@@ -273,7 +273,7 @@ export enum CellEditType {
|
||||
}
|
||||
|
||||
export interface ICellDto2 {
|
||||
source: string[];
|
||||
source: string | string[];
|
||||
language: string;
|
||||
cellKind: CellKind;
|
||||
outputs: IOutput[];
|
||||
@@ -300,6 +300,13 @@ export interface INotebookEditData {
|
||||
renderers: number[];
|
||||
}
|
||||
|
||||
export interface NotebookDataDto {
|
||||
readonly cells: ICellDto2[];
|
||||
readonly languages: string[];
|
||||
readonly metadata: NotebookDocumentMetadata;
|
||||
}
|
||||
|
||||
|
||||
export namespace CellUri {
|
||||
|
||||
export const scheme = 'vscode-notebook';
|
||||
|
||||
@@ -15,6 +15,8 @@ import { URI } from 'vs/base/common/uri';
|
||||
import { IWorkingCopyService, IWorkingCopy, IWorkingCopyBackup } from 'vs/workbench/services/workingCopy/common/workingCopyService';
|
||||
import { basename } from 'vs/base/common/resources';
|
||||
import { CancellationTokenSource } from 'vs/base/common/cancellation';
|
||||
import { IBackupFileService } from 'vs/workbench/services/backup/common/backup';
|
||||
import { DefaultEndOfLine, ITextBuffer, EndOfLinePreference } from 'vs/editor/common/model';
|
||||
|
||||
export interface INotebookEditorModelManager {
|
||||
models: NotebookEditorModel[];
|
||||
@@ -24,6 +26,13 @@ export interface INotebookEditorModelManager {
|
||||
get(resource: URI): NotebookEditorModel | undefined;
|
||||
}
|
||||
|
||||
export interface INotebookRevertOptions {
|
||||
/**
|
||||
* Go to disk bypassing any cache of the model if any.
|
||||
*/
|
||||
forceReadFromDisk?: boolean;
|
||||
}
|
||||
|
||||
|
||||
export class NotebookEditorModel extends EditorModel implements IWorkingCopy, INotebookEditorModel {
|
||||
private _dirty = false;
|
||||
@@ -47,7 +56,8 @@ export class NotebookEditorModel extends EditorModel implements IWorkingCopy, IN
|
||||
public readonly resource: URI,
|
||||
public readonly viewType: string,
|
||||
@INotebookService private readonly notebookService: INotebookService,
|
||||
@IWorkingCopyService private readonly workingCopyService: IWorkingCopyService
|
||||
@IWorkingCopyService private readonly workingCopyService: IWorkingCopyService,
|
||||
@IBackupFileService private readonly backupFileService: IBackupFileService
|
||||
) {
|
||||
super();
|
||||
this._register(this.workingCopyService.registerWorkingCopy(this));
|
||||
@@ -56,28 +66,91 @@ export class NotebookEditorModel extends EditorModel implements IWorkingCopy, IN
|
||||
capabilities = 0;
|
||||
|
||||
async backup(): Promise<IWorkingCopyBackup> {
|
||||
return {};
|
||||
return { content: this._notebook.createSnapshot(true) };
|
||||
}
|
||||
|
||||
async revert(options?: IRevertOptions | undefined): Promise<void> {
|
||||
if (options?.soft) {
|
||||
await this.backupFileService.discardBackup(this.resource);
|
||||
return;
|
||||
}
|
||||
|
||||
await this.load({ forceReadFromDisk: true });
|
||||
this._dirty = false;
|
||||
this._onDidChangeDirty.fire();
|
||||
return;
|
||||
}
|
||||
|
||||
async load(): Promise<NotebookEditorModel> {
|
||||
const notebook = await this.notebookService.resolveNotebook(this.viewType!, this.resource);
|
||||
async load(options?: INotebookRevertOptions): Promise<NotebookEditorModel> {
|
||||
if (options?.forceReadFromDisk) {
|
||||
return this.loadFromProvider(true);
|
||||
}
|
||||
if (this.isResolved()) {
|
||||
return this;
|
||||
}
|
||||
|
||||
const backup = await this.backupFileService.resolve(this.resource);
|
||||
|
||||
if (this.isResolved()) {
|
||||
return this; // Make sure meanwhile someone else did not succeed in loading
|
||||
}
|
||||
|
||||
if (backup) {
|
||||
try {
|
||||
return await this.loadFromBackup(backup.value.create(DefaultEndOfLine.LF));
|
||||
} catch (error) {
|
||||
// this.logService.error('[text file model] load() from backup', error); // ignore error and continue to load as file below
|
||||
}
|
||||
}
|
||||
|
||||
return this.loadFromProvider(false);
|
||||
}
|
||||
|
||||
private async loadFromBackup(content: ITextBuffer): Promise<NotebookEditorModel> {
|
||||
const fullRange = content.getRangeAt(0, content.getLength());
|
||||
const data = JSON.parse(content.getValueInRange(fullRange, EndOfLinePreference.LF));
|
||||
|
||||
const notebook = await this.notebookService.createNotebookFromBackup(this.viewType!, this.resource, data.metadata, data.languages, data.cells);
|
||||
this._notebook = notebook!;
|
||||
|
||||
this._name = basename(this._notebook!.uri);
|
||||
|
||||
this._register(this._notebook.onDidChangeContent(() => {
|
||||
this._dirty = true;
|
||||
this._onDidChangeDirty.fire();
|
||||
this.setDirty(true);
|
||||
this._onDidChangeContent.fire();
|
||||
}));
|
||||
|
||||
await this.backupFileService.discardBackup(this.resource);
|
||||
this.setDirty(true);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
private async loadFromProvider(forceReloadFromDisk: boolean) {
|
||||
const notebook = await this.notebookService.resolveNotebook(this.viewType!, this.resource, forceReloadFromDisk);
|
||||
this._notebook = notebook!;
|
||||
|
||||
this._name = basename(this._notebook!.uri);
|
||||
|
||||
this._register(this._notebook.onDidChangeContent(() => {
|
||||
this.setDirty(true);
|
||||
this._onDidChangeContent.fire();
|
||||
}));
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
isResolved(): boolean {
|
||||
return !!this._notebook;
|
||||
}
|
||||
|
||||
setDirty(newState: boolean) {
|
||||
if (this._dirty !== newState) {
|
||||
this._dirty = newState;
|
||||
this._onDidChangeDirty.fire();
|
||||
}
|
||||
}
|
||||
|
||||
isDirty() {
|
||||
return this._dirty;
|
||||
}
|
||||
@@ -89,6 +162,14 @@ export class NotebookEditorModel extends EditorModel implements IWorkingCopy, IN
|
||||
this._onDidChangeDirty.fire();
|
||||
return true;
|
||||
}
|
||||
|
||||
async saveAs(targetResource: URI): Promise<boolean> {
|
||||
const tokenSource = new CancellationTokenSource();
|
||||
await this.notebookService.saveAs(this.notebook.viewType, this.notebook.uri, targetResource, tokenSource.token);
|
||||
this._dirty = false;
|
||||
this._onDidChangeDirty.fire();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
export class NotebookEditorModelManager extends Disposable implements INotebookEditorModelManager {
|
||||
|
||||
@@ -8,7 +8,7 @@ import { URI } from 'vs/base/common/uri';
|
||||
import { NotebookProviderInfo } from 'vs/workbench/contrib/notebook/common/notebookProvider';
|
||||
import { NotebookExtensionDescription } from 'vs/workbench/api/common/extHost.protocol';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { INotebookTextModel, INotebookMimeTypeSelector, INotebookRendererInfo } from 'vs/workbench/contrib/notebook/common/notebookCommon';
|
||||
import { INotebookTextModel, INotebookMimeTypeSelector, INotebookRendererInfo, NotebookDocumentMetadata, ICellDto2 } from 'vs/workbench/contrib/notebook/common/notebookCommon';
|
||||
import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel';
|
||||
@@ -17,12 +17,13 @@ import { INotebookEditorModelManager } from 'vs/workbench/contrib/notebook/commo
|
||||
export const INotebookService = createDecorator<INotebookService>('notebookService');
|
||||
|
||||
export interface IMainNotebookController {
|
||||
resolveNotebook(viewType: string, uri: URI): Promise<NotebookTextModel | undefined>;
|
||||
createNotebook(viewType: string, uri: URI, forBackup: boolean, forceReload: boolean): Promise<NotebookTextModel | undefined>;
|
||||
executeNotebook(viewType: string, uri: URI, token: CancellationToken): Promise<void>;
|
||||
onDidReceiveMessage(uri: URI, message: any): void;
|
||||
executeNotebookCell(uri: URI, handle: number, token: CancellationToken): Promise<void>;
|
||||
destoryNotebookDocument(notebook: INotebookTextModel): Promise<void>;
|
||||
removeNotebookDocument(notebook: INotebookTextModel): Promise<void>;
|
||||
save(uri: URI, token: CancellationToken): Promise<boolean>;
|
||||
saveAs(uri: URI, target: URI, token: CancellationToken): Promise<boolean>;
|
||||
}
|
||||
|
||||
export interface INotebookService {
|
||||
@@ -35,7 +36,8 @@ export interface INotebookService {
|
||||
registerNotebookRenderer(handle: number, extensionData: NotebookExtensionDescription, type: string, selectors: INotebookMimeTypeSelector, preloads: URI[]): void;
|
||||
unregisterNotebookRenderer(handle: number): void;
|
||||
getRendererInfo(handle: number): INotebookRendererInfo | undefined;
|
||||
resolveNotebook(viewType: string, uri: URI): Promise<NotebookTextModel | undefined>;
|
||||
resolveNotebook(viewType: string, uri: URI, forceReload: boolean): Promise<NotebookTextModel | undefined>;
|
||||
createNotebookFromBackup(viewType: string, uri: URI, metadata: NotebookDocumentMetadata, languages: string[], cells: ICellDto2[]): Promise<NotebookTextModel | undefined>;
|
||||
executeNotebook(viewType: string, uri: URI): Promise<void>;
|
||||
executeNotebookCell(viewType: string, uri: URI, handle: number, token: CancellationToken): Promise<void>;
|
||||
|
||||
@@ -45,6 +47,7 @@ export interface INotebookService {
|
||||
destoryNotebookDocument(viewType: string, notebook: INotebookTextModel): void;
|
||||
updateActiveNotebookDocument(viewType: string, resource: URI): void;
|
||||
save(viewType: string, resource: URI, token: CancellationToken): Promise<boolean>;
|
||||
saveAs(viewType: string, resource: URI, target: URI, token: CancellationToken): Promise<boolean>;
|
||||
onDidReceiveMessage(viewType: string, uri: URI, message: any): void;
|
||||
setToCopy(items: NotebookCellTextModel[]): void;
|
||||
getToCopy(): NotebookCellTextModel[] | undefined;
|
||||
|
||||
@@ -47,6 +47,7 @@ import { InputBox, MessageType } from 'vs/base/browser/ui/inputbox/inputBox';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { MenuRegistry, MenuId, isIMenuItem } from 'vs/platform/actions/common/actions';
|
||||
import { IListAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget';
|
||||
import { preferencesEditIcon } from 'vs/workbench/contrib/preferences/browser/preferencesWidgets';
|
||||
|
||||
const $ = DOM.$;
|
||||
|
||||
@@ -400,7 +401,7 @@ export class KeybindingsEditor extends BaseEditor implements IKeybindingsEditorP
|
||||
}
|
||||
|
||||
private createRecordingBadge(container: HTMLElement): HTMLElement {
|
||||
const recordingBadge = DOM.append(container, DOM.$('.recording-badge.disabled'));
|
||||
const recordingBadge = DOM.append(container, DOM.$('.recording-badge.monaco-count-badge.long.disabled'));
|
||||
recordingBadge.textContent = localize('recording', "Recording Keys");
|
||||
this._register(attachStylerCallback(this.themeService, { badgeBackground, contrastBorder, badgeForeground }, colors => {
|
||||
const background = colors.badgeBackground ? colors.badgeBackground.toString() : '';
|
||||
@@ -891,7 +892,7 @@ class ActionsColumn extends Column {
|
||||
private createEditAction(keybindingItemEntry: IKeybindingItemEntry): IAction {
|
||||
const keybinding = this.keybindingsService.lookupKeybinding(KEYBINDINGS_EDITOR_COMMAND_DEFINE);
|
||||
return <IAction>{
|
||||
class: 'codicon-edit',
|
||||
class: preferencesEditIcon.classNames,
|
||||
enabled: true,
|
||||
id: 'editKeybinding',
|
||||
tooltip: keybinding ? localize('editKeybindingLabelWithKey', "Change Keybinding {0}", `(${keybinding.getLabel()})`) : localize('editKeybindingLabel', "Change Keybinding"),
|
||||
|
||||
@@ -27,8 +27,6 @@
|
||||
|
||||
.keybindings-editor > .keybindings-header > .search-container > .keybindings-search-actions-container > .recording-badge {
|
||||
margin-right: 8px;
|
||||
padding: 0px 8px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.keybindings-editor > .keybindings-header.small > .search-container > .keybindings-search-actions-container > .recording-badge,
|
||||
|
||||
@@ -40,12 +40,9 @@
|
||||
}
|
||||
|
||||
.settings-editor > .settings-header > .search-container > .settings-count-widget {
|
||||
margin: 6px 0px;
|
||||
padding: 0px 8px;
|
||||
border-radius: 2px;
|
||||
position: absolute;
|
||||
right: 35px;
|
||||
top: 0;
|
||||
top: 6px;
|
||||
}
|
||||
|
||||
.settings-editor > .settings-header > .search-container > .settings-clear-widget {
|
||||
|
||||
@@ -26,7 +26,7 @@ import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
|
||||
import { RangeHighlightDecorations } from 'vs/workbench/browser/parts/editor/rangeDecorations';
|
||||
import { DefaultSettingsHeaderWidget, EditPreferenceWidget, SettingsGroupTitleWidget, SettingsHeaderWidget } from 'vs/workbench/contrib/preferences/browser/preferencesWidgets';
|
||||
import { DefaultSettingsHeaderWidget, EditPreferenceWidget, SettingsGroupTitleWidget, SettingsHeaderWidget, preferencesEditIcon } from 'vs/workbench/contrib/preferences/browser/preferencesWidgets';
|
||||
import { IFilterResult, IPreferencesEditorModel, IPreferencesService, ISetting, ISettingsEditorModel, ISettingsGroup } from 'vs/workbench/services/preferences/common/preferences';
|
||||
import { DefaultSettingsEditorModel, SettingsEditorModel, WorkspaceConfigurationEditorModel } from 'vs/workbench/services/preferences/common/preferencesModels';
|
||||
import { IMarkerService, IMarkerData, MarkerSeverity, MarkerTag } from 'vs/platform/markers/common/markers';
|
||||
@@ -748,7 +748,7 @@ class EditSettingRenderer extends Disposable {
|
||||
const decorations = this.editor.getLineDecorations(line);
|
||||
if (decorations) {
|
||||
for (const { options } of decorations) {
|
||||
if (options.glyphMarginClassName && options.glyphMarginClassName.indexOf(EditPreferenceWidget.GLYPH_MARGIN_CLASS_NAME) === -1) {
|
||||
if (options.glyphMarginClassName && options.glyphMarginClassName.indexOf(preferencesEditIcon.classNames) === -1) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,6 +35,7 @@ import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/
|
||||
import { ISettingsGroup } from 'vs/workbench/services/preferences/common/preferences';
|
||||
import { EditorOption } from 'vs/editor/common/config/editorOptions';
|
||||
import { isEqual } from 'vs/base/common/resources';
|
||||
import { registerIcon, Codicon } from 'vs/base/common/codicons';
|
||||
|
||||
export class SettingsHeaderWidget extends Widget implements IViewZone {
|
||||
|
||||
@@ -736,9 +737,9 @@ export class SearchWidget extends Widget {
|
||||
}
|
||||
}
|
||||
|
||||
export class EditPreferenceWidget<T> extends Disposable {
|
||||
export const preferencesEditIcon = registerIcon('preferences-edit', Codicon.edit, localize('preferencesEditIcon', 'Icon for the edit action in preferences.'));
|
||||
|
||||
static readonly GLYPH_MARGIN_CLASS_NAME = 'codicon codicon-edit';
|
||||
export class EditPreferenceWidget<T> extends Disposable {
|
||||
|
||||
private _line: number = -1;
|
||||
private _preferences: T[] = [];
|
||||
@@ -775,7 +776,7 @@ export class EditPreferenceWidget<T> extends Disposable {
|
||||
this._line = line;
|
||||
newDecoration.push({
|
||||
options: {
|
||||
glyphMarginClassName: EditPreferenceWidget.GLYPH_MARGIN_CLASS_NAME,
|
||||
glyphMarginClassName: preferencesEditIcon.classNames,
|
||||
glyphMarginHoverMessage: new MarkdownString().appendText(hoverMessage),
|
||||
stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges,
|
||||
},
|
||||
|
||||
@@ -450,7 +450,7 @@ export class SettingsEditor2 extends BaseEditor {
|
||||
inputBorder: settingsTextInputBorder
|
||||
}));
|
||||
|
||||
this.countElement = DOM.append(searchContainer, DOM.$('.settings-count-widget'));
|
||||
this.countElement = DOM.append(searchContainer, DOM.$('.settings-count-widget.monaco-count-badge.long'));
|
||||
this._register(attachStylerCallback(this.themeService, { badgeBackground, contrastBorder, badgeForeground }, colors => {
|
||||
const background = colors.badgeBackground ? colors.badgeBackground.toString() : '';
|
||||
const border = colors.contrastBorder ? colors.contrastBorder.toString() : '';
|
||||
|
||||
@@ -21,6 +21,7 @@ import { attachButtonStyler, attachInputBoxStyler } from 'vs/platform/theme/comm
|
||||
import { ICssStyleCollector, IColorTheme, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService';
|
||||
import { disposableTimeout } from 'vs/base/common/async';
|
||||
import { isUndefinedOrNull } from 'vs/base/common/types';
|
||||
import { preferencesEditIcon } from 'vs/workbench/contrib/preferences/browser/preferencesWidgets';
|
||||
|
||||
const $ = DOM.$;
|
||||
export const settingsHeaderForeground = registerColor('settings.headerForeground', { light: '#444444', dark: '#e7e7e7', hc: '#ffffff' }, localize('headerForeground', "The foreground color for a section header or active title."));
|
||||
@@ -367,7 +368,7 @@ export class ListSettingWidget extends Disposable {
|
||||
|
||||
private createEditAction(idx: number): IAction {
|
||||
return <IAction>{
|
||||
class: 'codicon-edit',
|
||||
class: preferencesEditIcon.classNames,
|
||||
enabled: true,
|
||||
id: 'workbench.action.editListItem',
|
||||
tooltip: this.getLocalizedStrings().editActionTooltip,
|
||||
|
||||
@@ -152,9 +152,7 @@
|
||||
}
|
||||
|
||||
.search-view .message {
|
||||
padding-left: 22px;
|
||||
padding-right: 22px;
|
||||
padding-top: 0px;
|
||||
padding: 0 22px 8px;
|
||||
}
|
||||
|
||||
.search-view .message p:first-child {
|
||||
|
||||
@@ -826,6 +826,11 @@ configurationRegistry.registerConfiguration({
|
||||
],
|
||||
markdownDescription: nls.localize('search.searchEditor.doubleClickBehaviour', "Configure effect of double clicking a result in a search editor.")
|
||||
},
|
||||
'search.searchEditor.reusePriorSearchConfiguration': {
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
markdownDescription: nls.localize('search.searchEditor.reusePriorSearchConfiguration', "When enabled, new Search Editors will reuse the includes, excludes, and flags of the previously opened Search Editor")
|
||||
},
|
||||
'search.sortOrder': {
|
||||
'type': 'string',
|
||||
'enum': [SearchSortOrder.Default, SearchSortOrder.FileNames, SearchSortOrder.Type, SearchSortOrder.Modified, SearchSortOrder.CountDescending, SearchSortOrder.CountAscending],
|
||||
|
||||
@@ -378,7 +378,7 @@ export class SearchDND implements ITreeDragAndDrop<RenderableMatch> {
|
||||
|
||||
if (resources.length) {
|
||||
// Apply some datatransfer types to allow for dragging the element outside of the application
|
||||
this.instantiationService.invokeFunction(fillResourceDataTransfers, resources, originalEvent);
|
||||
this.instantiationService.invokeFunction(fillResourceDataTransfers, resources, undefined, originalEvent);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1022,6 +1022,7 @@ export class SearchView extends ViewPane {
|
||||
}
|
||||
this.viewModel.cancelSearch();
|
||||
this.updateActions();
|
||||
this.tree.ariaLabel = nls.localize('emptySearch', "Empty Search");
|
||||
|
||||
aria.status(nls.localize('ariaSearchResultsClearStatus', "The search results have been cleared"));
|
||||
}
|
||||
@@ -1557,9 +1558,12 @@ export class SearchView extends ViewPane {
|
||||
this.hasSearchResultsKey.set(fileCount > 0);
|
||||
|
||||
const msgWasHidden = this.messagesElement.style.display === 'none';
|
||||
|
||||
const messageEl = this.clearMessage();
|
||||
let resultMsg = this.buildResultCountMessage(this.viewModel.searchResult.count(), fileCount);
|
||||
this.tree.ariaLabel = resultMsg + nls.localize('forTerm', " - Search: {0}", this.searchResult.query?.contentPattern.pattern ?? '');
|
||||
|
||||
if (fileCount > 0) {
|
||||
const messageEl = this.clearMessage();
|
||||
let resultMsg = this.buildResultCountMessage(this.viewModel.searchResult.count(), fileCount);
|
||||
if (disregardExcludesAndIgnores) {
|
||||
resultMsg += nls.localize('useIgnoresAndExcludesDisabled', " - exclude settings and ignore files are disabled");
|
||||
}
|
||||
|
||||
@@ -103,7 +103,7 @@ export function getOutOfWorkspaceEditorResources(accessor: ServicesAccessor): UR
|
||||
}
|
||||
|
||||
// Supports patterns of <path><#|:|(><line><#|:|,><col?>
|
||||
const LINE_COLON_PATTERN = /\s?[#:\(](\d*)([#:,](\d*))?\)?\s*$/;
|
||||
const LINE_COLON_PATTERN = /\s?[#:\(](?:line )?(\d*)(?:[#:,](\d*))?\)?\s*$/;
|
||||
|
||||
export interface IFilterAndRange {
|
||||
filter: string;
|
||||
@@ -119,8 +119,9 @@ export function extractRangeFromFilter(filter: string, unless?: string[]): IFilt
|
||||
|
||||
// Find Line/Column number from search value using RegExp
|
||||
const patternMatch = LINE_COLON_PATTERN.exec(filter);
|
||||
if (patternMatch && patternMatch.length > 1) {
|
||||
const startLineNumber = parseInt(patternMatch[1], 10);
|
||||
|
||||
if (patternMatch) {
|
||||
const startLineNumber = parseInt(patternMatch[1] ?? '', 10);
|
||||
|
||||
// Line Number
|
||||
if (isNumber(startLineNumber)) {
|
||||
@@ -132,16 +133,14 @@ export function extractRangeFromFilter(filter: string, unless?: string[]): IFilt
|
||||
};
|
||||
|
||||
// Column Number
|
||||
if (patternMatch.length > 3) {
|
||||
const startColumn = parseInt(patternMatch[3], 10);
|
||||
if (isNumber(startColumn)) {
|
||||
range = {
|
||||
startLineNumber: range.startLineNumber,
|
||||
startColumn: startColumn,
|
||||
endLineNumber: range.endLineNumber,
|
||||
endColumn: startColumn
|
||||
};
|
||||
}
|
||||
const startColumn = parseInt(patternMatch[2] ?? '', 10);
|
||||
if (isNumber(startColumn)) {
|
||||
range = {
|
||||
startLineNumber: range.startLineNumber,
|
||||
startColumn: startColumn,
|
||||
endLineNumber: range.endLineNumber,
|
||||
endColumn: startColumn
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ suite('extractRangeFromFilter', () => {
|
||||
assert.ok(!extractRangeFromFilter('/some/path'));
|
||||
assert.ok(!extractRangeFromFilter('/some/path/file.txt'));
|
||||
|
||||
for (const lineSep of [':', '#', '(']) {
|
||||
for (const lineSep of [':', '#', '(', ':line ']) {
|
||||
for (const colSep of [':', '#', ',']) {
|
||||
const base = '/some/path/file.txt';
|
||||
|
||||
|
||||
@@ -5,27 +5,12 @@
|
||||
|
||||
import { RawContextKey } from 'vs/platform/contextkey/common/contextkey';
|
||||
|
||||
export const OpenInEditorCommandId = 'search.action.openInEditor';
|
||||
export const OpenNewEditorCommandId = 'search.action.openNewEditor';
|
||||
export const OpenNewEditorToSideCommandId = 'search.action.openNewEditorToSide';
|
||||
export const FocusQueryEditorWidgetCommandId = 'search.action.focusQueryEditorWidget';
|
||||
|
||||
export const ToggleSearchEditorCaseSensitiveCommandId = 'toggleSearchEditorCaseSensitive';
|
||||
export const ToggleSearchEditorWholeWordCommandId = 'toggleSearchEditorWholeWord';
|
||||
export const ToggleSearchEditorRegexCommandId = 'toggleSearchEditorRegex';
|
||||
export const ToggleSearchEditorContextLinesCommandId = 'toggleSearchEditorContextLines';
|
||||
export const IncreaseSearchEditorContextLinesCommandId = 'increaseSearchEditorContextLines';
|
||||
export const DecreaseSearchEditorContextLinesCommandId = 'decreaseSearchEditorContextLines';
|
||||
|
||||
export const RerunSearchEditorSearchCommandId = 'rerunSearchEditorSearch';
|
||||
export const CleanSearchEditorStateCommandId = 'cleanSearchEditorState';
|
||||
export const SelectAllSearchEditorMatchesCommandId = 'selectAllSearchEditorMatches';
|
||||
|
||||
export const InSearchEditor = new RawContextKey<boolean>('inSearchEditor', false);
|
||||
|
||||
export const SearchEditorScheme = 'search-editor';
|
||||
export const SearchEditorBodyScheme = 'search-editor-body';
|
||||
|
||||
export const SearchEditorFindMatchClass = 'seaarchEditorFindMatch';
|
||||
|
||||
export const SearchEditorID = 'workbench.editor.searchEditor';
|
||||
|
||||
export const OpenNewEditorCommandId = 'search.action.openNewEditor';
|
||||
|
||||
@@ -8,9 +8,10 @@ import * as objects from 'vs/base/common/objects';
|
||||
import { endsWith } from 'vs/base/common/strings';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { ServicesAccessor } from 'vs/editor/browser/editorExtensions';
|
||||
import { Range } from 'vs/editor/common/core/range';
|
||||
import { ToggleCaseSensitiveKeybinding, ToggleRegexKeybinding, ToggleWholeWordKeybinding } from 'vs/editor/contrib/find/findModel';
|
||||
import { localize } from 'vs/nls';
|
||||
import { MenuId, SyncActionDescriptor, registerAction2, Action2 } from 'vs/platform/actions/common/actions';
|
||||
import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/actions';
|
||||
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
|
||||
import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
|
||||
@@ -20,20 +21,36 @@ import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { EditorDescriptor, Extensions as EditorExtensions, IEditorRegistry } from 'vs/workbench/browser/editor';
|
||||
import { Extensions as ActionExtensions, IWorkbenchActionRegistry } from 'vs/workbench/common/actions';
|
||||
import { Extensions as WorkbenchExtensions, IWorkbenchContribution, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions';
|
||||
import { Extensions as EditorInputExtensions, IEditorInputFactory, IEditorInputFactoryRegistry, ActiveEditorContext } from 'vs/workbench/common/editor';
|
||||
import { ActiveEditorContext, Extensions as EditorInputExtensions, IEditorInputFactory, IEditorInputFactoryRegistry } from 'vs/workbench/common/editor';
|
||||
import { IViewsService } from 'vs/workbench/common/views';
|
||||
import { getSearchView } from 'vs/workbench/contrib/search/browser/searchActions';
|
||||
import { searchRefreshIcon } from 'vs/workbench/contrib/search/browser/searchIcons';
|
||||
import * as SearchConstants from 'vs/workbench/contrib/search/common/constants';
|
||||
import * as SearchEditorConstants from 'vs/workbench/contrib/searchEditor/browser/constants';
|
||||
import { SearchEditor } from 'vs/workbench/contrib/searchEditor/browser/searchEditor';
|
||||
import { modifySearchEditorContextLinesCommand, OpenSearchEditorAction, selectAllSearchEditorMatchesCommand, toggleSearchEditorCaseSensitiveCommand, toggleSearchEditorContextLinesCommand, toggleSearchEditorRegexCommand, toggleSearchEditorWholeWordCommand, createEditorFromSearchResult, openNewSearchEditor } from 'vs/workbench/contrib/searchEditor/browser/searchEditorActions';
|
||||
import { getOrMakeSearchEditorInput, SearchEditorInput, SearchConfiguration } from 'vs/workbench/contrib/searchEditor/browser/searchEditorInput';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { createEditorFromSearchResult, modifySearchEditorContextLinesCommand, openNewSearchEditor, selectAllSearchEditorMatchesCommand, toggleSearchEditorCaseSensitiveCommand, toggleSearchEditorContextLinesCommand, toggleSearchEditorRegexCommand, toggleSearchEditorWholeWordCommand } from 'vs/workbench/contrib/searchEditor/browser/searchEditorActions';
|
||||
import { getOrMakeSearchEditorInput, SearchConfiguration, SearchEditorInput } from 'vs/workbench/contrib/searchEditor/browser/searchEditorInput';
|
||||
import { parseSavedSearchEditor } from 'vs/workbench/contrib/searchEditor/browser/searchEditorSerialization';
|
||||
import { Range } from 'vs/editor/common/core/range';
|
||||
import { searchRefreshIcon } from 'vs/workbench/contrib/search/browser/searchIcons';
|
||||
import { IViewsService } from 'vs/workbench/common/views';
|
||||
import { getSearchView } from 'vs/workbench/contrib/search/browser/searchActions';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
|
||||
|
||||
const OpenInEditorCommandId = 'search.action.openInEditor';
|
||||
const OpenNewEditorToSideCommandId = 'search.action.openNewEditorToSide';
|
||||
const FocusQueryEditorWidgetCommandId = 'search.action.focusQueryEditorWidget';
|
||||
|
||||
const ToggleSearchEditorCaseSensitiveCommandId = 'toggleSearchEditorCaseSensitive';
|
||||
const ToggleSearchEditorWholeWordCommandId = 'toggleSearchEditorWholeWord';
|
||||
const ToggleSearchEditorRegexCommandId = 'toggleSearchEditorRegex';
|
||||
const ToggleSearchEditorContextLinesCommandId = 'toggleSearchEditorContextLines';
|
||||
const IncreaseSearchEditorContextLinesCommandId = 'increaseSearchEditorContextLines';
|
||||
const DecreaseSearchEditorContextLinesCommandId = 'decreaseSearchEditorContextLines';
|
||||
|
||||
const RerunSearchEditorSearchCommandId = 'rerunSearchEditorSearch';
|
||||
const CleanSearchEditorStateCommandId = 'cleanSearchEditorState';
|
||||
const SelectAllSearchEditorMatchesCommandId = 'selectAllSearchEditorMatches';
|
||||
|
||||
|
||||
|
||||
//#region Editor Descriptior
|
||||
Registry.as<IEditorRegistry>(EditorExtensions.Editors).registerEditor(
|
||||
@@ -128,28 +145,28 @@ Registry.as<IEditorInputFactoryRegistry>(EditorInputExtensions.EditorInputFactor
|
||||
|
||||
//#region Commands
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule(objects.assign({
|
||||
id: SearchEditorConstants.ToggleSearchEditorCaseSensitiveCommandId,
|
||||
id: ToggleSearchEditorCaseSensitiveCommandId,
|
||||
weight: KeybindingWeight.WorkbenchContrib,
|
||||
when: ContextKeyExpr.and(SearchEditorConstants.InSearchEditor, SearchConstants.SearchInputBoxFocusedKey),
|
||||
handler: toggleSearchEditorCaseSensitiveCommand
|
||||
}, ToggleCaseSensitiveKeybinding));
|
||||
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule(objects.assign({
|
||||
id: SearchEditorConstants.ToggleSearchEditorWholeWordCommandId,
|
||||
id: ToggleSearchEditorWholeWordCommandId,
|
||||
weight: KeybindingWeight.WorkbenchContrib,
|
||||
when: ContextKeyExpr.and(SearchEditorConstants.InSearchEditor, SearchConstants.SearchInputBoxFocusedKey),
|
||||
handler: toggleSearchEditorWholeWordCommand
|
||||
}, ToggleWholeWordKeybinding));
|
||||
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule(objects.assign({
|
||||
id: SearchEditorConstants.ToggleSearchEditorRegexCommandId,
|
||||
id: ToggleSearchEditorRegexCommandId,
|
||||
weight: KeybindingWeight.WorkbenchContrib,
|
||||
when: ContextKeyExpr.and(SearchEditorConstants.InSearchEditor, SearchConstants.SearchInputBoxFocusedKey),
|
||||
handler: toggleSearchEditorRegexCommand
|
||||
}, ToggleRegexKeybinding));
|
||||
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: SearchEditorConstants.ToggleSearchEditorContextLinesCommandId,
|
||||
id: ToggleSearchEditorContextLinesCommandId,
|
||||
weight: KeybindingWeight.WorkbenchContrib,
|
||||
when: ContextKeyExpr.and(SearchEditorConstants.InSearchEditor),
|
||||
handler: toggleSearchEditorContextLinesCommand,
|
||||
@@ -158,7 +175,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
});
|
||||
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: SearchEditorConstants.IncreaseSearchEditorContextLinesCommandId,
|
||||
id: IncreaseSearchEditorContextLinesCommandId,
|
||||
weight: KeybindingWeight.WorkbenchContrib,
|
||||
when: ContextKeyExpr.and(SearchEditorConstants.InSearchEditor),
|
||||
handler: (accessor: ServicesAccessor) => modifySearchEditorContextLinesCommand(accessor, true),
|
||||
@@ -166,7 +183,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
});
|
||||
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: SearchEditorConstants.DecreaseSearchEditorContextLinesCommandId,
|
||||
id: DecreaseSearchEditorContextLinesCommandId,
|
||||
weight: KeybindingWeight.WorkbenchContrib,
|
||||
when: ContextKeyExpr.and(SearchEditorConstants.InSearchEditor),
|
||||
handler: (accessor: ServicesAccessor) => modifySearchEditorContextLinesCommand(accessor, false),
|
||||
@@ -174,7 +191,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
});
|
||||
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: SearchEditorConstants.SelectAllSearchEditorMatchesCommandId,
|
||||
id: SelectAllSearchEditorMatchesCommandId,
|
||||
weight: KeybindingWeight.WorkbenchContrib,
|
||||
when: ContextKeyExpr.and(SearchEditorConstants.InSearchEditor),
|
||||
primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_L,
|
||||
@@ -182,7 +199,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
});
|
||||
|
||||
CommandsRegistry.registerCommand(
|
||||
SearchEditorConstants.CleanSearchEditorStateCommandId,
|
||||
CleanSearchEditorStateCommandId,
|
||||
(accessor: ServicesAccessor) => {
|
||||
const activeEditorPane = accessor.get(IEditorService).activeEditorPane;
|
||||
if (activeEditorPane instanceof SearchEditor) {
|
||||
@@ -192,18 +209,40 @@ CommandsRegistry.registerCommand(
|
||||
//#endregion
|
||||
|
||||
//#region Actions
|
||||
const registry = Registry.as<IWorkbenchActionRegistry>(ActionExtensions.WorkbenchActions);
|
||||
const category = localize('search', "Search Editor");
|
||||
|
||||
// TODO: Not an action2 becuase used in view pane container action bar, which uses actions
|
||||
registry.registerWorkbenchAction(
|
||||
SyncActionDescriptor.from(OpenSearchEditorAction),
|
||||
'Search Editor: Open New Search Editor', category);
|
||||
|
||||
registerAction2(class extends Action2 {
|
||||
constructor() {
|
||||
super({
|
||||
id: SearchEditorConstants.OpenInEditorCommandId,
|
||||
id: SearchEditorConstants.OpenNewEditorCommandId,
|
||||
title: localize('search.openNewSearchEditor', "Open new Search Editor"),
|
||||
category,
|
||||
f1: true,
|
||||
});
|
||||
}
|
||||
async run(accessor: ServicesAccessor, args: Partial<SearchConfiguration>) {
|
||||
await accessor.get(IInstantiationService).invokeFunction(openNewSearchEditor, args);
|
||||
}
|
||||
});
|
||||
|
||||
registerAction2(class extends Action2 {
|
||||
constructor() {
|
||||
super({
|
||||
id: OpenNewEditorToSideCommandId,
|
||||
title: localize('search.openNewEditorToSide', "Open new Search Editor to the Side"),
|
||||
category,
|
||||
f1: true,
|
||||
});
|
||||
}
|
||||
async run(accessor: ServicesAccessor, args: Partial<SearchConfiguration>) {
|
||||
await accessor.get(IInstantiationService).invokeFunction(openNewSearchEditor, args, true);
|
||||
}
|
||||
});
|
||||
|
||||
registerAction2(class extends Action2 {
|
||||
constructor() {
|
||||
super({
|
||||
id: OpenInEditorCommandId,
|
||||
title: localize('search.openResultsInEditor', "Open Results in Editor"),
|
||||
category,
|
||||
f1: true,
|
||||
@@ -227,22 +266,7 @@ registerAction2(class extends Action2 {
|
||||
registerAction2(class extends Action2 {
|
||||
constructor() {
|
||||
super({
|
||||
id: SearchEditorConstants.OpenNewEditorToSideCommandId,
|
||||
title: localize('search.openNewEditorToSide', "Open New Search Editor to Side"),
|
||||
category,
|
||||
f1: true,
|
||||
});
|
||||
}
|
||||
async run(accessor: ServicesAccessor) {
|
||||
const instantiationService = accessor.get(IInstantiationService);
|
||||
await instantiationService.invokeFunction(openNewSearchEditor, true);
|
||||
}
|
||||
});
|
||||
|
||||
registerAction2(class extends Action2 {
|
||||
constructor() {
|
||||
super({
|
||||
id: SearchEditorConstants.RerunSearchEditorSearchCommandId,
|
||||
id: RerunSearchEditorSearchCommandId,
|
||||
title: localize('search.rerunSearchInEditor', "Search Again"),
|
||||
category,
|
||||
keybinding: {
|
||||
@@ -274,7 +298,7 @@ registerAction2(class extends Action2 {
|
||||
registerAction2(class extends Action2 {
|
||||
constructor() {
|
||||
super({
|
||||
id: SearchEditorConstants.FocusQueryEditorWidgetCommandId,
|
||||
id: FocusQueryEditorWidgetCommandId,
|
||||
title: localize('search.action.focusQueryEditorWidget', "Focus Search Editor Input"),
|
||||
category,
|
||||
menu: {
|
||||
|
||||
@@ -13,13 +13,18 @@ import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiati
|
||||
import { ILabelService } from 'vs/platform/label/common/label';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { SearchResult } from 'vs/workbench/contrib/search/common/searchModel';
|
||||
import * as Constants from 'vs/workbench/contrib/searchEditor/browser/constants';
|
||||
import { SearchEditor } from 'vs/workbench/contrib/searchEditor/browser/searchEditor';
|
||||
import { getOrMakeSearchEditorInput, SearchEditorInput } from 'vs/workbench/contrib/searchEditor/browser/searchEditorInput';
|
||||
import { getOrMakeSearchEditorInput, SearchEditorInput, SearchConfiguration } from 'vs/workbench/contrib/searchEditor/browser/searchEditorInput';
|
||||
import { serializeSearchResultForEditor } from 'vs/workbench/contrib/searchEditor/browser/searchEditorSerialization';
|
||||
import { IEditorService, SIDE_GROUP, ACTIVE_GROUP } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { ISearchConfigurationProperties } from 'vs/workbench/services/search/common/search';
|
||||
import { searchNewEditorIcon } from 'vs/workbench/contrib/search/browser/searchIcons';
|
||||
import { IConfigurationResolverService } from 'vs/workbench/services/configurationResolver/common/configurationResolver';
|
||||
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
|
||||
import { IHistoryService } from 'vs/workbench/services/history/common/history';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { withNullAsUndefined } from 'vs/base/common/types';
|
||||
import { OpenNewEditorCommandId } from 'vs/workbench/contrib/searchEditor/browser/constants';
|
||||
|
||||
export const toggleSearchEditorCaseSensitiveCommand = (accessor: ServicesAccessor) => {
|
||||
const editorService = accessor.get(IEditorService);
|
||||
@@ -71,7 +76,7 @@ export const selectAllSearchEditorMatchesCommand = (accessor: ServicesAccessor)
|
||||
|
||||
export class OpenSearchEditorAction extends Action {
|
||||
|
||||
static readonly ID: string = Constants.OpenNewEditorCommandId;
|
||||
static readonly ID: string = OpenNewEditorCommandId;
|
||||
static readonly LABEL = localize('search.openNewEditor', "Open New Search Editor");
|
||||
|
||||
constructor(id: string, label: string,
|
||||
@@ -94,12 +99,23 @@ export class OpenSearchEditorAction extends Action {
|
||||
}
|
||||
|
||||
export const openNewSearchEditor =
|
||||
async (accessor: ServicesAccessor, toSide = false) => {
|
||||
async (accessor: ServicesAccessor, args: Partial<SearchConfiguration> = {}, toSide = false) => {
|
||||
const editorService = accessor.get(IEditorService);
|
||||
const telemetryService = accessor.get(ITelemetryService);
|
||||
const instantiationService = accessor.get(IInstantiationService);
|
||||
const configurationService = accessor.get(IConfigurationService);
|
||||
|
||||
const configurationResolverService = accessor.get(IConfigurationResolverService);
|
||||
const workspaceContextService = accessor.get(IWorkspaceContextService);
|
||||
const historyService = accessor.get(IHistoryService);
|
||||
const activeWorkspaceRootUri = historyService.getLastActiveWorkspaceRoot(Schemas.file);
|
||||
const lastActiveWorkspaceRoot = activeWorkspaceRootUri ? withNullAsUndefined(workspaceContextService.getWorkspaceFolder(activeWorkspaceRootUri)) : undefined;
|
||||
|
||||
const resolvedArgs: Record<string, any> = {};
|
||||
Object.entries(args).forEach(([name, value]) => {
|
||||
resolvedArgs[name as any] = (typeof value === 'string') ? configurationResolverService.resolve(lastActiveWorkspaceRoot, value) : value;
|
||||
});
|
||||
|
||||
const activeEditorControl = editorService.activeTextEditorControl;
|
||||
let activeModel: ICodeEditor | undefined;
|
||||
let selected = '';
|
||||
@@ -124,7 +140,7 @@ export const openNewSearchEditor =
|
||||
|
||||
telemetryService.publicLog2('searchEditor/openNewSearchEditor');
|
||||
|
||||
const input = instantiationService.invokeFunction(getOrMakeSearchEditorInput, { config: { query: selected }, text: '' });
|
||||
const input = instantiationService.invokeFunction(getOrMakeSearchEditorInput, { config: { query: selected, ...resolvedArgs }, text: '' });
|
||||
const editor = await editorService.openEditor(input, { pinned: true }, toSide ? SIDE_GROUP : ACTIVE_GROUP) as SearchEditor;
|
||||
|
||||
if (selected && configurationService.getValue<ISearchConfigurationProperties>('search').searchOnType) {
|
||||
|
||||
@@ -36,7 +36,7 @@ import { IWorkingCopy, IWorkingCopyBackup, IWorkingCopyService, WorkingCopyCapab
|
||||
export type SearchConfiguration = {
|
||||
query: string,
|
||||
includes: string,
|
||||
excludes: string
|
||||
excludes: string,
|
||||
contextLines: number,
|
||||
wholeWord: boolean,
|
||||
caseSensitive: boolean,
|
||||
@@ -304,7 +304,7 @@ export const getOrMakeSearchEditorInput = (
|
||||
const storageService = accessor.get(IStorageService);
|
||||
const configurationService = accessor.get(IConfigurationService);
|
||||
|
||||
const reuseOldSettings = configurationService.getValue<ISearchConfigurationProperties>('search').searchEditor?.experimental?.reusePriorSearchConfiguration;
|
||||
const reuseOldSettings = configurationService.getValue<ISearchConfigurationProperties>('search').searchEditor?.reusePriorSearchConfiguration;
|
||||
const priorConfig: SearchConfiguration = reuseOldSettings ? new Memento(SearchEditorInput.ID, storageService).getMemento(StorageScope.WORKSPACE).searchConfig : {};
|
||||
const defaultConfig = defaultSearchConfig();
|
||||
let config = { ...defaultConfig, ...priorConfig, ...existingData.config };
|
||||
|
||||
@@ -114,7 +114,7 @@ const contentPatternToSearchConfiguration = (pattern: ITextQuery, includes: stri
|
||||
wholeWord: !!pattern.contentPattern.isWordMatch,
|
||||
excludes, includes,
|
||||
showIncludesExcludes: !!(includes || excludes || pattern?.userDisabledExcludesAndIgnoreFiles),
|
||||
useIgnores: !!(pattern?.userDisabledExcludesAndIgnoreFiles === undefined ? undefined : !pattern.userDisabledExcludesAndIgnoreFiles),
|
||||
useIgnores: (pattern?.userDisabledExcludesAndIgnoreFiles === undefined ? true : !pattern.userDisabledExcludesAndIgnoreFiles),
|
||||
contextLines,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -791,7 +791,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer
|
||||
if (quickOpenHistoryLimit === 0) {
|
||||
return;
|
||||
}
|
||||
let keys = [...this._recentlyUsedTasks.keys()];
|
||||
let keys = this._recentlyUsedTasks.keys();
|
||||
if (keys.length > quickOpenHistoryLimit) {
|
||||
keys = keys.slice(0, quickOpenHistoryLimit);
|
||||
}
|
||||
@@ -1579,7 +1579,9 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer
|
||||
for (const task of taskSet.tasks) {
|
||||
if (task.type !== this._providerTypes.get(handle)) {
|
||||
this._outputChannel.append(nls.localize('unexpectedTaskType', "The task provider for \"{0}\" tasks unexpectedly provided a task of type \"{1}\".\n", this._providerTypes.get(handle), task.type));
|
||||
this.showOutput();
|
||||
if ((task.type !== 'shell') && (task.type !== 'process')) {
|
||||
this.showOutput();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -2323,7 +2325,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer
|
||||
taskMap[key] = task;
|
||||
}
|
||||
});
|
||||
const reversed = [...recentlyUsedTasks.keys()].reverse();
|
||||
const reversed = recentlyUsedTasks.keys().reverse();
|
||||
for (const key in reversed) {
|
||||
let task = taskMap[key];
|
||||
if (task) {
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ILinkProvider, ILink } from 'xterm';
|
||||
import { TerminalLink } from 'vs/workbench/contrib/terminal/browser/links/terminalLink';
|
||||
|
||||
export abstract class TerminalBaseLinkProvider implements ILinkProvider {
|
||||
private _activeLinks: TerminalLink[] | undefined;
|
||||
|
||||
async provideLinks(bufferLineNumber: number, callback: (links: ILink[] | undefined) => void): Promise<void> {
|
||||
this._activeLinks?.forEach(l => l.dispose);
|
||||
this._activeLinks = await this._provideLinks(bufferLineNumber);
|
||||
callback(this._activeLinks);
|
||||
}
|
||||
|
||||
protected abstract _provideLinks(bufferLineNumber: number): Promise<TerminalLink[]> | TerminalLink[];
|
||||
}
|
||||
@@ -20,6 +20,9 @@ export const FOLDER_NOT_IN_WORKSPACE_LABEL = localize('openFolder', 'Open folder
|
||||
export class TerminalLink extends DisposableStore implements ILink {
|
||||
decorations: ILinkDecorations;
|
||||
|
||||
private _tooltipScheduler: RunOnceScheduler | undefined;
|
||||
private _hoverListeners: DisposableStore | undefined;
|
||||
|
||||
private readonly _onLeave = new Emitter<void>();
|
||||
public get onLeave(): Event<void> { return this._onLeave.event; }
|
||||
|
||||
@@ -40,6 +43,14 @@ export class TerminalLink extends DisposableStore implements ILink {
|
||||
};
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
super.dispose();
|
||||
this._hoverListeners?.dispose();
|
||||
this._hoverListeners = undefined;
|
||||
this._tooltipScheduler?.dispose();
|
||||
this._tooltipScheduler = undefined;
|
||||
}
|
||||
|
||||
activate(event: MouseEvent | undefined, text: string): void {
|
||||
this._activateCallback(event, text);
|
||||
}
|
||||
@@ -58,20 +69,23 @@ export class TerminalLink extends DisposableStore implements ILink {
|
||||
}));
|
||||
|
||||
const timeout = this._configurationService.getValue<number>('editor.hover.delay');
|
||||
const scheduler = new RunOnceScheduler(() => {
|
||||
this._tooltipScheduler = new RunOnceScheduler(() => {
|
||||
this._tooltipCallback(
|
||||
this,
|
||||
convertBufferRangeToViewport(this.range, this._viewportY),
|
||||
this._isHighConfidenceLink ? () => this._enableDecorations() : undefined,
|
||||
this._isHighConfidenceLink ? () => this._disableDecorations() : undefined
|
||||
);
|
||||
this.dispose();
|
||||
// Clear out scheduler until next hover event
|
||||
this._tooltipScheduler?.dispose();
|
||||
this._tooltipScheduler = undefined;
|
||||
}, timeout);
|
||||
this.add(scheduler);
|
||||
scheduler.schedule();
|
||||
this.add(this._tooltipScheduler);
|
||||
this._tooltipScheduler.schedule();
|
||||
|
||||
const origin = { x: event.pageX, y: event.pageY };
|
||||
this.add(dom.addDisposableListener(document, dom.EventType.MOUSE_MOVE, e => {
|
||||
this._hoverListeners = new DisposableStore();
|
||||
this._hoverListeners.add(dom.addDisposableListener(document, dom.EventType.MOUSE_MOVE, e => {
|
||||
// Update decorations
|
||||
if (this._isModifierDown(e)) {
|
||||
this._enableDecorations();
|
||||
@@ -83,14 +97,17 @@ export class TerminalLink extends DisposableStore implements ILink {
|
||||
if (Math.abs(e.pageX - origin.x) > window.devicePixelRatio * 2 || Math.abs(e.pageY - origin.y) > window.devicePixelRatio * 2) {
|
||||
origin.x = e.pageX;
|
||||
origin.y = e.pageY;
|
||||
scheduler.schedule();
|
||||
this._tooltipScheduler?.schedule();
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
leave(): void {
|
||||
this._hoverListeners?.dispose();
|
||||
this._hoverListeners = undefined;
|
||||
this._tooltipScheduler?.dispose();
|
||||
this._tooltipScheduler = undefined;
|
||||
this._onLeave.fire();
|
||||
this.dispose();
|
||||
}
|
||||
|
||||
private _enableDecorations(): void {
|
||||
|
||||
@@ -3,14 +3,15 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Terminal, IViewportRange, ILinkProvider, IBufferCellPosition, ILink, IBufferLine } from 'xterm';
|
||||
import { Terminal, IViewportRange, IBufferLine } from 'xterm';
|
||||
import { ILinkComputerTarget, LinkComputer } from 'vs/editor/common/modes/linkComputer';
|
||||
import { getXtermLineContent, convertLinkRangeToBuffer, positionIsInRange } from 'vs/workbench/contrib/terminal/browser/links/terminalLinkHelpers';
|
||||
import { getXtermLineContent, convertLinkRangeToBuffer } from 'vs/workbench/contrib/terminal/browser/links/terminalLinkHelpers';
|
||||
import { TerminalLink, OPEN_FILE_LABEL } from 'vs/workbench/contrib/terminal/browser/links/terminalLink';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { TerminalBaseLinkProvider } from 'vs/workbench/contrib/terminal/browser/links/terminalBaseLinkProvider';
|
||||
|
||||
export class TerminalProtocolLinkProvider implements ILinkProvider {
|
||||
export class TerminalProtocolLinkProvider extends TerminalBaseLinkProvider {
|
||||
private _linkComputerTarget: ILinkComputerTarget | undefined;
|
||||
|
||||
constructor(
|
||||
@@ -19,10 +20,11 @@ export class TerminalProtocolLinkProvider implements ILinkProvider {
|
||||
private readonly _tooltipCallback: (link: TerminalLink, viewportRange: IViewportRange, modifierDownCallback?: () => void, modifierUpCallback?: () => void) => void,
|
||||
@IInstantiationService private readonly _instantiationService: IInstantiationService
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
public provideLink(position: IBufferCellPosition, callback: (link: ILink | undefined) => void): void {
|
||||
let startLine = position.y - 1;
|
||||
protected _provideLinks(y: number): TerminalLink[] {
|
||||
let startLine = y - 1;
|
||||
let endLine = startLine;
|
||||
|
||||
const lines: IBufferLine[] = [
|
||||
@@ -42,24 +44,16 @@ export class TerminalProtocolLinkProvider implements ILinkProvider {
|
||||
this._linkComputerTarget = new TerminalLinkAdapter(this._xterm, startLine, endLine);
|
||||
const links = LinkComputer.computeLinks(this._linkComputerTarget);
|
||||
|
||||
let found = false;
|
||||
links.forEach(link => {
|
||||
return links.map(link => {
|
||||
const range = convertLinkRangeToBuffer(lines, this._xterm.cols, link.range, startLine);
|
||||
|
||||
// Check if the link if within the mouse position
|
||||
if (positionIsInRange(position, range)) {
|
||||
found = true;
|
||||
const uri = link.url
|
||||
? (typeof link.url === 'string' ? URI.parse(link.url) : link.url)
|
||||
: undefined;
|
||||
const label = (uri?.scheme === 'file') ? OPEN_FILE_LABEL : undefined;
|
||||
callback(this._instantiationService.createInstance(TerminalLink, range, link.url?.toString() || '', this._xterm.buffer.active.viewportY, this._activateCallback, this._tooltipCallback, true, label));
|
||||
}
|
||||
const uri = link.url
|
||||
? (typeof link.url === 'string' ? URI.parse(link.url) : link.url)
|
||||
: undefined;
|
||||
const label = (uri?.scheme === 'file') ? OPEN_FILE_LABEL : undefined;
|
||||
return this._instantiationService.createInstance(TerminalLink, range, link.url?.toString() || '', this._xterm.buffer.active.viewportY, this._activateCallback, this._tooltipCallback, true, label);
|
||||
});
|
||||
|
||||
if (!found) {
|
||||
callback(undefined);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Terminal, ILinkProvider, IViewportRange, IBufferCellPosition, ILink, IBufferLine } from 'xterm';
|
||||
import { getXtermLineContent, convertLinkRangeToBuffer, positionIsInRange } from 'vs/workbench/contrib/terminal/browser/links/terminalLinkHelpers';
|
||||
import { Terminal, IViewportRange, IBufferLine } from 'xterm';
|
||||
import { getXtermLineContent, convertLinkRangeToBuffer } from 'vs/workbench/contrib/terminal/browser/links/terminalLinkHelpers';
|
||||
import { OperatingSystem } from 'vs/base/common/platform';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { TerminalLink, OPEN_FILE_LABEL, FOLDER_IN_WORKSPACE_LABEL, FOLDER_NOT_IN_WORKSPACE_LABEL } from 'vs/workbench/contrib/terminal/browser/links/terminalLink';
|
||||
@@ -14,6 +14,7 @@ import { ICommandService } from 'vs/platform/commands/common/commands';
|
||||
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
|
||||
import { IHostService } from 'vs/workbench/services/host/browser/host';
|
||||
import { XtermLinkMatcherHandler } from 'vs/workbench/contrib/terminal/browser/links/terminalLinkManager';
|
||||
import { TerminalBaseLinkProvider } from 'vs/workbench/contrib/terminal/browser/links/terminalBaseLinkProvider';
|
||||
|
||||
const pathPrefix = '(\\.\\.?|\\~)';
|
||||
const pathSeparatorClause = '\\/';
|
||||
@@ -41,7 +42,7 @@ const lineAndColumnClause = [
|
||||
'(([^:\\s\\(\\)<>\'\"\\[\\]]*)(:(\\d+))?(:(\\d+))?)' // (file path):336, (file path):336:9
|
||||
].join('|').replace(/ /g, `[${'\u00A0'} ]`);
|
||||
|
||||
export class TerminalValidatedLocalLinkProvider implements ILinkProvider {
|
||||
export class TerminalValidatedLocalLinkProvider extends TerminalBaseLinkProvider {
|
||||
constructor(
|
||||
private readonly _xterm: Terminal,
|
||||
private readonly _processOperatingSystem: OperatingSystem,
|
||||
@@ -54,10 +55,12 @@ export class TerminalValidatedLocalLinkProvider implements ILinkProvider {
|
||||
@IWorkspaceContextService private readonly _workspaceContextService: IWorkspaceContextService,
|
||||
@IHostService private readonly _hostService: IHostService
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
async provideLink(position: IBufferCellPosition, callback: (link: ILink | undefined) => void) {
|
||||
let startLine = position.y - 1;
|
||||
protected async _provideLinks(y: number): Promise<TerminalLink[]> {
|
||||
const result: TerminalLink[] = [];
|
||||
let startLine = y - 1;
|
||||
let endLine = startLine;
|
||||
|
||||
const lines: IBufferLine[] = [
|
||||
@@ -121,34 +124,31 @@ export class TerminalValidatedLocalLinkProvider implements ILinkProvider {
|
||||
endLineNumber: 1
|
||||
}, startLine);
|
||||
|
||||
if (positionIsInRange(position, bufferRange)) {
|
||||
const validatedLink = await new Promise<ILink | undefined>(r => {
|
||||
this._validationCallback(link, (result) => {
|
||||
if (result) {
|
||||
const label = result.isDirectory
|
||||
? (this._isDirectoryInsideWorkspace(result.uri) ? FOLDER_IN_WORKSPACE_LABEL : FOLDER_NOT_IN_WORKSPACE_LABEL)
|
||||
: OPEN_FILE_LABEL;
|
||||
const activateCallback = this._wrapLinkHandler((event: MouseEvent | undefined, text: string) => {
|
||||
if (result.isDirectory) {
|
||||
this._handleLocalFolderLink(result.uri);
|
||||
} else {
|
||||
this._activateFileCallback(event, text);
|
||||
}
|
||||
});
|
||||
r(this._instantiationService.createInstance(TerminalLink, bufferRange, link, this._xterm.buffer.active.viewportY, activateCallback, this._tooltipCallback, true, label));
|
||||
} else {
|
||||
r(undefined);
|
||||
}
|
||||
});
|
||||
const validatedLink = await new Promise<TerminalLink | undefined>(r => {
|
||||
this._validationCallback(link, (result) => {
|
||||
if (result) {
|
||||
const label = result.isDirectory
|
||||
? (this._isDirectoryInsideWorkspace(result.uri) ? FOLDER_IN_WORKSPACE_LABEL : FOLDER_NOT_IN_WORKSPACE_LABEL)
|
||||
: OPEN_FILE_LABEL;
|
||||
const activateCallback = this._wrapLinkHandler((event: MouseEvent | undefined, text: string) => {
|
||||
if (result.isDirectory) {
|
||||
this._handleLocalFolderLink(result.uri);
|
||||
} else {
|
||||
this._activateFileCallback(event, text);
|
||||
}
|
||||
});
|
||||
r(this._instantiationService.createInstance(TerminalLink, bufferRange, link, this._xterm.buffer.active.viewportY, activateCallback, this._tooltipCallback, true, label));
|
||||
} else {
|
||||
r(undefined);
|
||||
}
|
||||
});
|
||||
if (validatedLink) {
|
||||
callback(validatedLink);
|
||||
return;
|
||||
}
|
||||
});
|
||||
if (validatedLink) {
|
||||
result.push(validatedLink);
|
||||
}
|
||||
}
|
||||
|
||||
callback(undefined);
|
||||
return result;
|
||||
}
|
||||
|
||||
protected get _localLinkRegex(): RegExp {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Terminal, ILinkProvider, IViewportRange, IBufferCellPosition, ILink } from 'xterm';
|
||||
import { Terminal, IViewportRange } from 'xterm';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { ITerminalConfiguration, TERMINAL_CONFIG_SECTION } from 'vs/workbench/contrib/terminal/common/terminal';
|
||||
import { TerminalLink } from 'vs/workbench/contrib/terminal/browser/links/terminalLink';
|
||||
@@ -15,8 +15,9 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic
|
||||
import { QueryBuilder } from 'vs/workbench/contrib/search/common/queryBuilder';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { XtermLinkMatcherHandler } from 'vs/workbench/contrib/terminal/browser/links/terminalLinkManager';
|
||||
import { TerminalBaseLinkProvider } from 'vs/workbench/contrib/terminal/browser/links/terminalBaseLinkProvider';
|
||||
|
||||
export class TerminalWordLinkProvider implements ILinkProvider {
|
||||
export class TerminalWordLinkProvider extends TerminalBaseLinkProvider {
|
||||
private readonly _fileQueryBuilder = this._instantiationService.createInstance(QueryBuilder);
|
||||
|
||||
constructor(
|
||||
@@ -30,54 +31,49 @@ export class TerminalWordLinkProvider implements ILinkProvider {
|
||||
@ISearchService private readonly _searchService: ISearchService,
|
||||
@IEditorService private readonly _editorService: IEditorService
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
public provideLink(position: IBufferCellPosition, callback: (link: ILink | undefined) => void): void {
|
||||
const start: IBufferCellPosition = { x: position.x, y: position.y };
|
||||
const end: IBufferCellPosition = { x: position.x, y: position.y };
|
||||
|
||||
protected _provideLinks(y: number): TerminalLink[] {
|
||||
// TODO: Support wrapping
|
||||
// Expand to the left until a word separator is hit
|
||||
const line = this._xterm.buffer.active.getLine(position.y - 1)!;
|
||||
let text = '';
|
||||
start.x++; // The hovered cell is considered first
|
||||
for (let x = position.x; x > 0; x--) {
|
||||
const cell = line.getCell(x - 1);
|
||||
if (!cell) {
|
||||
break;
|
||||
}
|
||||
const char = cell.getChars();
|
||||
const config = this._configurationService.getValue<ITerminalConfiguration>(TERMINAL_CONFIG_SECTION);
|
||||
if (cell.getWidth() !== 0 && config.wordSeparators.indexOf(char) >= 0) {
|
||||
break;
|
||||
}
|
||||
start.x = x;
|
||||
text = char + text;
|
||||
}
|
||||
|
||||
// No links were found (the hovered cell is whitespace)
|
||||
if (text.length === 0) {
|
||||
callback(undefined);
|
||||
return;
|
||||
}
|
||||
|
||||
// Expand to the right until a word separator is hit
|
||||
for (let x = position.x + 1; x <= line.length; x++) {
|
||||
const cell = line.getCell(x - 1);
|
||||
if (!cell) {
|
||||
break;
|
||||
}
|
||||
const char = cell.getChars();
|
||||
const config = this._configurationService.getValue<ITerminalConfiguration>(TERMINAL_CONFIG_SECTION);
|
||||
if (cell.getWidth() !== 0 && config.wordSeparators.indexOf(char) >= 0) {
|
||||
break;
|
||||
}
|
||||
end.x = x;
|
||||
text += char;
|
||||
}
|
||||
|
||||
// Dispose of all old links if new links are provides, links are only cached for the current line
|
||||
const result: TerminalLink[] = [];
|
||||
const wordSeparators = this._configurationService.getValue<ITerminalConfiguration>(TERMINAL_CONFIG_SECTION).wordSeparators;
|
||||
const activateCallback = this._wrapLinkHandler((_, link) => this._activate(link));
|
||||
callback(new TerminalLink({ start, end }, text, this._xterm.buffer.active.viewportY, activateCallback, this._tooltipCallback, false, localize('searchWorkspace', 'Search workspace'), this._configurationService));
|
||||
|
||||
const line = this._xterm.buffer.active.getLine(y - 1)!;
|
||||
let text = '';
|
||||
let startX = -1;
|
||||
const cellData = line.getCell(0)!;
|
||||
for (let x = 0; x < line.length; x++) {
|
||||
line.getCell(x, cellData);
|
||||
const chars = cellData.getChars();
|
||||
const width = cellData.getWidth();
|
||||
|
||||
// Add a link if this is a separator
|
||||
if (width !== 0 && wordSeparators.indexOf(chars) >= 0) {
|
||||
if (startX !== -1) {
|
||||
result.push(new TerminalLink({ start: { x: startX + 1, y }, end: { x, y } }, text, this._xterm.buffer.active.viewportY, activateCallback, this._tooltipCallback, false, localize('searchWorkspace', 'Search workspace'), this._configurationService));
|
||||
text = '';
|
||||
startX = -1;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// Mark the start of a link if it hasn't started yet
|
||||
if (startX === -1) {
|
||||
startX = x;
|
||||
}
|
||||
|
||||
text += chars;
|
||||
}
|
||||
|
||||
// Add the final link if there is one
|
||||
if (startX !== -1) {
|
||||
result.push(new TerminalLink({ start: { x: startX + 1, y }, end: { x: line.length, y } }, text, this._xterm.buffer.active.viewportY, activateCallback, this._tooltipCallback, false, localize('searchWorkspace', 'Search workspace'), this._configurationService));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private async _activate(link: string) {
|
||||
|
||||
@@ -34,7 +34,7 @@ import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
|
||||
import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer';
|
||||
import { IQuickAccessRegistry, Extensions as QuickAccessExtensions } from 'vs/platform/quickinput/common/quickAccess';
|
||||
import { TerminalQuickAccessProvider } from 'vs/workbench/contrib/terminal/browser/terminalQuickAccess';
|
||||
import { terminalConfiguration, getTerminalShellConfiguration } from 'vs/workbench/contrib/terminal/common/terminalConfiguration';
|
||||
import { terminalConfiguration } from 'vs/workbench/contrib/terminal/common/terminalConfiguration';
|
||||
import { CONTEXT_ACCESSIBILITY_MODE_ENABLED } from 'vs/platform/accessibility/common/accessibility';
|
||||
|
||||
// Register services
|
||||
@@ -58,11 +58,6 @@ CommandsRegistry.registerCommand({ id: quickAccessNavigatePreviousInTerminalPick
|
||||
// Register configurations
|
||||
const configurationRegistry = Registry.as<IConfigurationRegistry>(Extensions.Configuration);
|
||||
configurationRegistry.registerConfiguration(terminalConfiguration);
|
||||
if (platform.isWeb) {
|
||||
// Desktop shell configuration are registered in electron-browser as their default values rely
|
||||
// on process.env
|
||||
configurationRegistry.registerConfiguration(getTerminalShellConfiguration());
|
||||
}
|
||||
|
||||
// Register views
|
||||
const VIEW_CONTAINER = Registry.as<IViewContainersRegistry>(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer({
|
||||
@@ -164,17 +159,6 @@ if (BrowserFeatures.clipboard.readText) {
|
||||
}
|
||||
}
|
||||
|
||||
if (platform.isWeb) {
|
||||
// Register standard external terminal keybinding as integrated terminal when in web as the
|
||||
// external terminal is not available
|
||||
KeybindingsRegistry.registerKeybindingRule({
|
||||
id: TERMINAL_COMMAND_ID.NEW,
|
||||
weight: KeybindingWeight.WorkbenchContrib,
|
||||
when: undefined,
|
||||
primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_C
|
||||
});
|
||||
}
|
||||
|
||||
// Delete word left: ctrl+w
|
||||
registerSendSequenceKeybinding(String.fromCharCode('W'.charCodeAt(0) - 64), {
|
||||
primary: KeyMod.CtrlCmd | KeyCode.Backspace,
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
|
||||
import { KeybindingWeight, KeybindingsRegistry } from 'vs/platform/keybinding/common/keybindingsRegistry';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { TERMINAL_COMMAND_ID } from 'vs/workbench/contrib/terminal/common/terminal';
|
||||
import { IConfigurationRegistry, Extensions } from 'vs/platform/configuration/common/configurationRegistry';
|
||||
import { getTerminalShellConfiguration } from 'vs/workbench/contrib/terminal/common/terminalConfiguration';
|
||||
|
||||
// Desktop shell configuration are registered in electron-browser as their default values rely
|
||||
// on process.env
|
||||
const configurationRegistry = Registry.as<IConfigurationRegistry>(Extensions.Configuration);
|
||||
configurationRegistry.registerConfiguration(getTerminalShellConfiguration());
|
||||
|
||||
// Register standard external terminal keybinding as integrated terminal when in web as the
|
||||
// external terminal is not available
|
||||
KeybindingsRegistry.registerKeybindingRule({
|
||||
id: TERMINAL_COMMAND_ID.NEW,
|
||||
weight: KeybindingWeight.WorkbenchContrib,
|
||||
when: undefined,
|
||||
primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_C
|
||||
});
|
||||
@@ -34,6 +34,10 @@ export class TerminalHover extends Disposable implements ITerminalWidget {
|
||||
super();
|
||||
}
|
||||
|
||||
dispose() {
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
attach(container: HTMLElement): void {
|
||||
const target = new CellHoverTarget(container, this._targetOptions);
|
||||
this._register(this._instantiationService.createInstance(HoverWidget, container, target, this._text, this._linkHandler, []));
|
||||
|
||||
@@ -315,7 +315,7 @@ export const terminalConfiguration: IConfigurationNode = {
|
||||
'terminal.integrated.experimentalLinkProvider': {
|
||||
description: localize('terminal.integrated.experimentalLinkProvider', "An experimental setting that aims to improve link detection in the terminal by improving when links are detected and by enabling shared link detection with the editor. Currently this only supports web links."),
|
||||
type: 'boolean',
|
||||
default: false
|
||||
default: true
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
import * as assert from 'assert';
|
||||
import { TerminalProtocolLinkProvider } from 'vs/workbench/contrib/terminal/browser/links/terminalProtocolLinkProvider';
|
||||
import { Terminal, ILink, IBufferRange, IBufferCellPosition } from 'xterm';
|
||||
import { Terminal, ILink } from 'xterm';
|
||||
import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock';
|
||||
import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
@@ -18,77 +18,69 @@ suite('Workbench - TerminalWebLinkProvider', () => {
|
||||
instantiationService.stub(IConfigurationService, TestConfigurationService);
|
||||
});
|
||||
|
||||
async function assertLink(text: string, expected: { text: string, range: [number, number][] }) {
|
||||
async function assertLink(text: string, expected: { text: string, range: [number, number][] }[]) {
|
||||
const xterm = new Terminal();
|
||||
const provider = instantiationService.createInstance(TerminalProtocolLinkProvider, xterm, () => { }, () => { });
|
||||
|
||||
// Write the text and wait for the parser to finish
|
||||
await new Promise<void>(r => xterm.write(text, r));
|
||||
|
||||
// Calculate positions just outside of link boundaries
|
||||
const noLinkPositions: IBufferCellPosition[] = [
|
||||
{ x: expected.range[0][0] - 1, y: expected.range[0][1] },
|
||||
{ x: expected.range[1][0] + 1, y: expected.range[1][1] }
|
||||
];
|
||||
|
||||
// Ensure outside positions do not detect the link
|
||||
for (let i = 0; i < noLinkPositions.length; i++) {
|
||||
const link = await new Promise<ILink | undefined>(r => provider.provideLink(noLinkPositions[i], r));
|
||||
assert.equal(link, undefined, `Just outside range boundary should not result in link, link found at (${link?.range.start.x}, ${link?.range.start.y}) to (${link?.range.end.x}, ${link?.range.end.y}) while checking (${noLinkPositions[i].x}, ${noLinkPositions[i].y})\nExpected link text=${expected.text}\nActual link text=${link?.text}`);
|
||||
}
|
||||
|
||||
// Convert range from [[startx, starty], [endx, endy]] to an IBufferRange
|
||||
const linkRange: IBufferRange = {
|
||||
start: { x: expected.range[0][0], y: expected.range[0][1] },
|
||||
end: { x: expected.range[1][0], y: expected.range[1][1] },
|
||||
};
|
||||
|
||||
// Calculate positions inside the link boundaries
|
||||
const linkPositions: IBufferCellPosition[] = [
|
||||
linkRange.start,
|
||||
linkRange.end
|
||||
];
|
||||
|
||||
// Ensure inside positions do detect the link
|
||||
for (let i = 0; i < linkPositions.length; i++) {
|
||||
const link = await new Promise<ILink | undefined>(r => provider.provideLink(linkPositions[i], r));
|
||||
assert.deepEqual(link?.text, expected.text);
|
||||
assert.deepEqual(link?.range, linkRange);
|
||||
}
|
||||
// Ensure all links are provided
|
||||
const links = (await new Promise<ILink[] | undefined>(r => provider.provideLinks(1, r)))!;
|
||||
assert.equal(links.length, expected.length);
|
||||
const actual = links.map(e => ({
|
||||
text: e.text,
|
||||
range: e.range
|
||||
}));
|
||||
const expectedVerbose = expected.map(e => ({
|
||||
text: e.text,
|
||||
range: {
|
||||
start: { x: e.range[0][0], y: e.range[0][1] },
|
||||
end: { x: e.range[1][0], y: e.range[1][1] },
|
||||
}
|
||||
}));
|
||||
assert.deepEqual(actual, expectedVerbose);
|
||||
}
|
||||
|
||||
// These tests are based on LinkComputer.test.ts
|
||||
test('LinkComputer cases', async () => {
|
||||
await assertLink('x = "http://foo.bar";', { range: [[6, 1], [19, 1]], text: 'http://foo.bar' });
|
||||
await assertLink('x = (http://foo.bar);', { range: [[6, 1], [19, 1]], text: 'http://foo.bar' });
|
||||
await assertLink('x = \'http://foo.bar\';', { range: [[6, 1], [19, 1]], text: 'http://foo.bar' });
|
||||
await assertLink('x = http://foo.bar ;', { range: [[6, 1], [19, 1]], text: 'http://foo.bar' });
|
||||
await assertLink('x = <http://foo.bar>;', { range: [[6, 1], [19, 1]], text: 'http://foo.bar' });
|
||||
await assertLink('x = {http://foo.bar};', { range: [[6, 1], [19, 1]], text: 'http://foo.bar' });
|
||||
await assertLink('(see http://foo.bar)', { range: [[6, 1], [19, 1]], text: 'http://foo.bar' });
|
||||
await assertLink('[see http://foo.bar]', { range: [[6, 1], [19, 1]], text: 'http://foo.bar' });
|
||||
await assertLink('{see http://foo.bar}', { range: [[6, 1], [19, 1]], text: 'http://foo.bar' });
|
||||
await assertLink('<see http://foo.bar>', { range: [[6, 1], [19, 1]], text: 'http://foo.bar' });
|
||||
await assertLink('<url>http://foo.bar</url>', { range: [[6, 1], [19, 1]], text: 'http://foo.bar' });
|
||||
await assertLink('// Click here to learn more. https://go.microsoft.com/fwlink/?LinkID=513275&clcid=0x409', { range: [[30, 1], [7, 2]], text: 'https://go.microsoft.com/fwlink/?LinkID=513275&clcid=0x409' });
|
||||
await assertLink('// Click here to learn more. https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx', { range: [[30, 1], [28, 2]], text: 'https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx' });
|
||||
await assertLink('// https://github.com/projectkudu/kudu/blob/master/Kudu.Core/Scripts/selectNodeVersion.js', { range: [[4, 1], [9, 2]], text: 'https://github.com/projectkudu/kudu/blob/master/Kudu.Core/Scripts/selectNodeVersion.js' });
|
||||
await assertLink('<!-- !!! Do not remove !!! WebContentRef(link:https://go.microsoft.com/fwlink/?LinkId=166007, area:Admin, updated:2015, nextUpdate:2016, tags:SqlServer) !!! Do not remove !!! -->', { range: [[49, 1], [14, 2]], text: 'https://go.microsoft.com/fwlink/?LinkId=166007' });
|
||||
await assertLink('For instructions, see https://go.microsoft.com/fwlink/?LinkId=166007.</value>', { range: [[23, 1], [68, 1]], text: 'https://go.microsoft.com/fwlink/?LinkId=166007' });
|
||||
await assertLink('For instructions, see https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx.</value>', { range: [[23, 1], [21, 2]], text: 'https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx' });
|
||||
await assertLink('x = "https://en.wikipedia.org/wiki/Zürich";', { range: [[6, 1], [41, 1]], text: 'https://en.wikipedia.org/wiki/Zürich' });
|
||||
await assertLink('請參閱 http://go.microsoft.com/fwlink/?LinkId=761051。', { range: [[8, 1], [53, 1]], text: 'http://go.microsoft.com/fwlink/?LinkId=761051' });
|
||||
await assertLink('(請參閱 http://go.microsoft.com/fwlink/?LinkId=761051)', { range: [[10, 1], [55, 1]], text: 'http://go.microsoft.com/fwlink/?LinkId=761051' });
|
||||
await assertLink('x = "file:///foo.bar";', { range: [[6, 1], [20, 1]], text: 'file:///foo.bar' });
|
||||
await assertLink('x = "file://c:/foo.bar";', { range: [[6, 1], [22, 1]], text: 'file://c:/foo.bar' });
|
||||
await assertLink('x = "file://shares/foo.bar";', { range: [[6, 1], [26, 1]], text: 'file://shares/foo.bar' });
|
||||
await assertLink('x = "file://shäres/foo.bar";', { range: [[6, 1], [26, 1]], text: 'file://shäres/foo.bar' });
|
||||
await assertLink('Some text, then http://www.bing.com.', { range: [[17, 1], [35, 1]], text: 'http://www.bing.com' });
|
||||
await assertLink('let url = `http://***/_api/web/lists/GetByTitle(\'Teambuildingaanvragen\')/items`;', { range: [[12, 1], [78, 1]], text: 'http://***/_api/web/lists/GetByTitle(\'Teambuildingaanvragen\')/items' });
|
||||
await assertLink('7. At this point, ServiceMain has been called. There is no functionality presently in ServiceMain, but you can consult the [MSDN documentation](https://msdn.microsoft.com/en-us/library/windows/desktop/ms687414(v=vs.85).aspx) to add functionality as desired!', { range: [[66, 2], [64, 3]], text: 'https://msdn.microsoft.com/en-us/library/windows/desktop/ms687414(v=vs.85).aspx' });
|
||||
await assertLink('let x = "http://[::1]:5000/connect/token"', { range: [[10, 1], [40, 1]], text: 'http://[::1]:5000/connect/token' });
|
||||
await assertLink('2. Navigate to **https://portal.azure.com**', { range: [[18, 1], [41, 1]], text: 'https://portal.azure.com' });
|
||||
await assertLink('POST|https://portal.azure.com|2019-12-05|', { range: [[6, 1], [29, 1]], text: 'https://portal.azure.com' });
|
||||
await assertLink('aa https://foo.bar/[this is foo site] aa', { range: [[5, 1], [38, 1]], text: 'https://foo.bar/[this is foo site]' });
|
||||
await assertLink('x = "http://foo.bar";', [{ range: [[6, 1], [19, 1]], text: 'http://foo.bar' }]);
|
||||
await assertLink('x = (http://foo.bar);', [{ range: [[6, 1], [19, 1]], text: 'http://foo.bar' }]);
|
||||
await assertLink('x = \'http://foo.bar\';', [{ range: [[6, 1], [19, 1]], text: 'http://foo.bar' }]);
|
||||
await assertLink('x = http://foo.bar ;', [{ range: [[6, 1], [19, 1]], text: 'http://foo.bar' }]);
|
||||
await assertLink('x = <http://foo.bar>;', [{ range: [[6, 1], [19, 1]], text: 'http://foo.bar' }]);
|
||||
await assertLink('x = {http://foo.bar};', [{ range: [[6, 1], [19, 1]], text: 'http://foo.bar' }]);
|
||||
await assertLink('(see http://foo.bar)', [{ range: [[6, 1], [19, 1]], text: 'http://foo.bar' }]);
|
||||
await assertLink('[see http://foo.bar]', [{ range: [[6, 1], [19, 1]], text: 'http://foo.bar' }]);
|
||||
await assertLink('{see http://foo.bar}', [{ range: [[6, 1], [19, 1]], text: 'http://foo.bar' }]);
|
||||
await assertLink('<see http://foo.bar>', [{ range: [[6, 1], [19, 1]], text: 'http://foo.bar' }]);
|
||||
await assertLink('<url>http://foo.bar</url>', [{ range: [[6, 1], [19, 1]], text: 'http://foo.bar' }]);
|
||||
await assertLink('// Click here to learn more. https://go.microsoft.com/fwlink/?LinkID=513275&clcid=0x409', [{ range: [[30, 1], [7, 2]], text: 'https://go.microsoft.com/fwlink/?LinkID=513275&clcid=0x409' }]);
|
||||
await assertLink('// Click here to learn more. https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx', [{ range: [[30, 1], [28, 2]], text: 'https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx' }]);
|
||||
await assertLink('// https://github.com/projectkudu/kudu/blob/master/Kudu.Core/Scripts/selectNodeVersion.js', [{ range: [[4, 1], [9, 2]], text: 'https://github.com/projectkudu/kudu/blob/master/Kudu.Core/Scripts/selectNodeVersion.js' }]);
|
||||
await assertLink('<!-- !!! Do not remove !!! WebContentRef(link:https://go.microsoft.com/fwlink/?LinkId=166007, area:Admin, updated:2015, nextUpdate:2016, tags:SqlServer) !!! Do not remove !!! -->', [{ range: [[49, 1], [14, 2]], text: 'https://go.microsoft.com/fwlink/?LinkId=166007' }]);
|
||||
await assertLink('For instructions, see https://go.microsoft.com/fwlink/?LinkId=166007.</value>', [{ range: [[23, 1], [68, 1]], text: 'https://go.microsoft.com/fwlink/?LinkId=166007' }]);
|
||||
await assertLink('For instructions, see https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx.</value>', [{ range: [[23, 1], [21, 2]], text: 'https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx' }]);
|
||||
await assertLink('x = "https://en.wikipedia.org/wiki/Zürich";', [{ range: [[6, 1], [41, 1]], text: 'https://en.wikipedia.org/wiki/Zürich' }]);
|
||||
await assertLink('請參閱 http://go.microsoft.com/fwlink/?LinkId=761051。', [{ range: [[8, 1], [53, 1]], text: 'http://go.microsoft.com/fwlink/?LinkId=761051' }]);
|
||||
await assertLink('(請參閱 http://go.microsoft.com/fwlink/?LinkId=761051)', [{ range: [[10, 1], [55, 1]], text: 'http://go.microsoft.com/fwlink/?LinkId=761051' }]);
|
||||
await assertLink('x = "file:///foo.bar";', [{ range: [[6, 1], [20, 1]], text: 'file:///foo.bar' }]);
|
||||
await assertLink('x = "file://c:/foo.bar";', [{ range: [[6, 1], [22, 1]], text: 'file://c:/foo.bar' }]);
|
||||
await assertLink('x = "file://shares/foo.bar";', [{ range: [[6, 1], [26, 1]], text: 'file://shares/foo.bar' }]);
|
||||
await assertLink('x = "file://shäres/foo.bar";', [{ range: [[6, 1], [26, 1]], text: 'file://shäres/foo.bar' }]);
|
||||
await assertLink('Some text, then http://www.bing.com.', [{ range: [[17, 1], [35, 1]], text: 'http://www.bing.com' }]);
|
||||
await assertLink('let url = `http://***/_api/web/lists/GetByTitle(\'Teambuildingaanvragen\')/items`;', [{ range: [[12, 1], [78, 1]], text: 'http://***/_api/web/lists/GetByTitle(\'Teambuildingaanvragen\')/items' }]);
|
||||
await assertLink('7. At this point, ServiceMain has been called. There is no functionality presently in ServiceMain, but you can consult the [MSDN documentation](https://msdn.microsoft.com/en-us/library/windows/desktop/ms687414(v=vs.85).aspx) to add functionality as desired!', [{ range: [[66, 2], [64, 3]], text: 'https://msdn.microsoft.com/en-us/library/windows/desktop/ms687414(v=vs.85).aspx' }]);
|
||||
await assertLink('let x = "http://[::1]:5000/connect/token"', [{ range: [[10, 1], [40, 1]], text: 'http://[::1]:5000/connect/token' }]);
|
||||
await assertLink('2. Navigate to **https://portal.azure.com**', [{ range: [[18, 1], [41, 1]], text: 'https://portal.azure.com' }]);
|
||||
await assertLink('POST|https://portal.azure.com|2019-12-05|', [{ range: [[6, 1], [29, 1]], text: 'https://portal.azure.com' }]);
|
||||
await assertLink('aa https://foo.bar/[this is foo site] aa', [{ range: [[5, 1], [38, 1]], text: 'https://foo.bar/[this is foo site]' }]);
|
||||
});
|
||||
|
||||
test('should support multiple link results', async () => {
|
||||
await assertLink('http://foo.bar http://bar.foo', [
|
||||
{ range: [[1, 1], [14, 1]], text: 'http://foo.bar' },
|
||||
{ range: [[16, 1], [29, 1]], text: 'http://bar.foo' }
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
import * as assert from 'assert';
|
||||
import { TerminalValidatedLocalLinkProvider } from 'vs/workbench/contrib/terminal/browser/links/terminalValidatedLocalLinkProvider';
|
||||
import { Terminal, ILink, IBufferRange, IBufferCellPosition } from 'xterm';
|
||||
import { Terminal, ILink } from 'xterm';
|
||||
import { OperatingSystem } from 'vs/base/common/platform';
|
||||
import { format } from 'vs/base/common/strings';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
@@ -76,43 +76,28 @@ suite('Workbench - TerminalValidatedLocalLinkProvider', () => {
|
||||
instantiationService.stub(IConfigurationService, TestConfigurationService);
|
||||
});
|
||||
|
||||
async function assertLink(text: string, os: OperatingSystem, expected: { text: string, range: [number, number][] }) {
|
||||
async function assertLink(text: string, os: OperatingSystem, expected: { text: string, range: [number, number][] }[]) {
|
||||
const xterm = new Terminal();
|
||||
const provider = instantiationService.createInstance(TerminalValidatedLocalLinkProvider, xterm, os, () => { }, () => { }, () => { }, (_: string, cb: (result: { uri: URI, isDirectory: boolean } | undefined) => void) => { cb({ uri: URI.file('/'), isDirectory: false }); });
|
||||
|
||||
// Write the text and wait for the parser to finish
|
||||
await new Promise<void>(r => xterm.write(text, r));
|
||||
|
||||
// Calculate positions just outside of link boundaries
|
||||
const noLinkPositions: IBufferCellPosition[] = [
|
||||
{ x: expected.range[0][0] - 1, y: expected.range[0][1] },
|
||||
{ x: expected.range[1][0] + 1, y: expected.range[1][1] }
|
||||
];
|
||||
|
||||
// Ensure outside positions do not detect the link
|
||||
for (let i = 0; i < noLinkPositions.length; i++) {
|
||||
const link = await new Promise<ILink | undefined>(r => provider.provideLink(noLinkPositions[i], r));
|
||||
assert.equal(link, undefined, `Just outside range boundary should not result in link, link found at (${link?.range.start.x}, ${link?.range.start.y}) to (${link?.range.end.x}, ${link?.range.end.y}) while checking (${noLinkPositions[i].x}, ${noLinkPositions[i].y})\nExpected link text=${expected.text}\nActual link text=${link?.text}`);
|
||||
}
|
||||
|
||||
// Convert range from [[startx, starty], [endx, endy]] to an IBufferRange
|
||||
const linkRange: IBufferRange = {
|
||||
start: { x: expected.range[0][0], y: expected.range[0][1] },
|
||||
end: { x: expected.range[1][0], y: expected.range[1][1] },
|
||||
};
|
||||
|
||||
// Calculate positions inside the link boundaries
|
||||
const linkPositions: IBufferCellPosition[] = [
|
||||
linkRange.start,
|
||||
linkRange.end
|
||||
];
|
||||
|
||||
// Ensure inside positions do detect the link
|
||||
for (let i = 0; i < linkPositions.length; i++) {
|
||||
const link = await new Promise<ILink | undefined>(r => provider.provideLink(linkPositions[i], r));
|
||||
assert.deepEqual(link?.text, expected.text);
|
||||
assert.deepEqual(link?.range, linkRange);
|
||||
}
|
||||
// Ensure all links are provided
|
||||
const links = (await new Promise<ILink[] | undefined>(r => provider.provideLinks(1, r)))!;
|
||||
assert.equal(links.length, expected.length);
|
||||
const actual = links.map(e => ({
|
||||
text: e.text,
|
||||
range: e.range
|
||||
}));
|
||||
const expectedVerbose = expected.map(e => ({
|
||||
text: e.text,
|
||||
range: {
|
||||
start: { x: e.range[0][0], y: e.range[0][1] },
|
||||
end: { x: e.range[1][0], y: e.range[1][1] },
|
||||
}
|
||||
}));
|
||||
assert.deepEqual(actual, expectedVerbose);
|
||||
}
|
||||
|
||||
suite('Linux/macOS', () => {
|
||||
@@ -122,19 +107,21 @@ suite('Workbench - TerminalValidatedLocalLinkProvider', () => {
|
||||
const linkFormat = supportedLinkFormats[i];
|
||||
test(`Format: ${linkFormat.urlFormat}`, async () => {
|
||||
const formattedLink = format(linkFormat.urlFormat, baseLink, linkFormat.line, linkFormat.column);
|
||||
await assertLink(formattedLink, OperatingSystem.Linux, { text: formattedLink, range: [[1, 1], [formattedLink.length, 1]] });
|
||||
await assertLink(` ${formattedLink} `, OperatingSystem.Linux, { text: formattedLink, range: [[2, 1], [formattedLink.length + 1, 1]] });
|
||||
await assertLink(`(${formattedLink})`, OperatingSystem.Linux, { text: formattedLink, range: [[2, 1], [formattedLink.length + 1, 1]] });
|
||||
await assertLink(`[${formattedLink}]`, OperatingSystem.Linux, { text: formattedLink, range: [[2, 1], [formattedLink.length + 1, 1]] });
|
||||
await assertLink(formattedLink, OperatingSystem.Linux, [{ text: formattedLink, range: [[1, 1], [formattedLink.length, 1]] }]);
|
||||
await assertLink(` ${formattedLink} `, OperatingSystem.Linux, [{ text: formattedLink, range: [[2, 1], [formattedLink.length + 1, 1]] }]);
|
||||
await assertLink(`(${formattedLink})`, OperatingSystem.Linux, [{ text: formattedLink, range: [[2, 1], [formattedLink.length + 1, 1]] }]);
|
||||
await assertLink(`[${formattedLink}]`, OperatingSystem.Linux, [{ text: formattedLink, range: [[2, 1], [formattedLink.length + 1, 1]] }]);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
test('Git diff links', async () => {
|
||||
await assertLink(`diff --git a/foo/bar b/foo/bar`, OperatingSystem.Linux, { text: 'foo/bar', range: [[14, 1], [20, 1]] });
|
||||
await assertLink(`diff --git a/foo/bar b/foo/bar`, OperatingSystem.Linux, { text: 'foo/bar', range: [[24, 1], [30, 1]] });
|
||||
await assertLink(`--- a/foo/bar`, OperatingSystem.Linux, { text: 'foo/bar', range: [[7, 1], [13, 1]] });
|
||||
await assertLink(`+++ b/foo/bar`, OperatingSystem.Linux, { text: 'foo/bar', range: [[7, 1], [13, 1]] });
|
||||
await assertLink(`diff --git a/foo/bar b/foo/bar`, OperatingSystem.Linux, [
|
||||
{ text: 'foo/bar', range: [[14, 1], [20, 1]] },
|
||||
{ text: 'foo/bar', range: [[24, 1], [30, 1]] }
|
||||
]);
|
||||
await assertLink(`--- a/foo/bar`, OperatingSystem.Linux, [{ text: 'foo/bar', range: [[7, 1], [13, 1]] }]);
|
||||
await assertLink(`+++ b/foo/bar`, OperatingSystem.Linux, [{ text: 'foo/bar', range: [[7, 1], [13, 1]] }]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -145,19 +132,28 @@ suite('Workbench - TerminalValidatedLocalLinkProvider', () => {
|
||||
const linkFormat = supportedLinkFormats[i];
|
||||
test(`Format: ${linkFormat.urlFormat}`, async () => {
|
||||
const formattedLink = format(linkFormat.urlFormat, baseLink, linkFormat.line, linkFormat.column);
|
||||
await assertLink(formattedLink, OperatingSystem.Windows, { text: formattedLink, range: [[1, 1], [formattedLink.length, 1]] });
|
||||
await assertLink(` ${formattedLink} `, OperatingSystem.Windows, { text: formattedLink, range: [[2, 1], [formattedLink.length + 1, 1]] });
|
||||
await assertLink(`(${formattedLink})`, OperatingSystem.Windows, { text: formattedLink, range: [[2, 1], [formattedLink.length + 1, 1]] });
|
||||
await assertLink(`[${formattedLink}]`, OperatingSystem.Windows, { text: formattedLink, range: [[2, 1], [formattedLink.length + 1, 1]] });
|
||||
await assertLink(formattedLink, OperatingSystem.Windows, [{ text: formattedLink, range: [[1, 1], [formattedLink.length, 1]] }]);
|
||||
await assertLink(` ${formattedLink} `, OperatingSystem.Windows, [{ text: formattedLink, range: [[2, 1], [formattedLink.length + 1, 1]] }]);
|
||||
await assertLink(`(${formattedLink})`, OperatingSystem.Windows, [{ text: formattedLink, range: [[2, 1], [formattedLink.length + 1, 1]] }]);
|
||||
await assertLink(`[${formattedLink}]`, OperatingSystem.Windows, [{ text: formattedLink, range: [[2, 1], [formattedLink.length + 1, 1]] }]);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
test('Git diff links', async () => {
|
||||
await assertLink(`diff --git a/foo/bar b/foo/bar`, OperatingSystem.Linux, { text: 'foo/bar', range: [[14, 1], [20, 1]] });
|
||||
await assertLink(`diff --git a/foo/bar b/foo/bar`, OperatingSystem.Linux, { text: 'foo/bar', range: [[24, 1], [30, 1]] });
|
||||
await assertLink(`--- a/foo/bar`, OperatingSystem.Linux, { text: 'foo/bar', range: [[7, 1], [13, 1]] });
|
||||
await assertLink(`+++ b/foo/bar`, OperatingSystem.Linux, { text: 'foo/bar', range: [[7, 1], [13, 1]] });
|
||||
await assertLink(`diff --git a/foo/bar b/foo/bar`, OperatingSystem.Linux, [
|
||||
{ text: 'foo/bar', range: [[14, 1], [20, 1]] },
|
||||
{ text: 'foo/bar', range: [[24, 1], [30, 1]] }
|
||||
]);
|
||||
await assertLink(`--- a/foo/bar`, OperatingSystem.Linux, [{ text: 'foo/bar', range: [[7, 1], [13, 1]] }]);
|
||||
await assertLink(`+++ b/foo/bar`, OperatingSystem.Linux, [{ text: 'foo/bar', range: [[7, 1], [13, 1]] }]);
|
||||
});
|
||||
});
|
||||
|
||||
test('should support multiple link results', async () => {
|
||||
await assertLink('./foo ./bar', OperatingSystem.Linux, [
|
||||
{ range: [[1, 1], [5, 1]], text: './foo' },
|
||||
{ range: [[7, 1], [11, 1]], text: './bar' }
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as assert from 'assert';
|
||||
import { Terminal, ILink, IBufferRange, IBufferCellPosition } from 'xterm';
|
||||
import { Terminal, ILink } from 'xterm';
|
||||
import { TerminalWordLinkProvider } from 'vs/workbench/contrib/terminal/browser/links/terminalWordLinkProvider';
|
||||
import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock';
|
||||
import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService';
|
||||
@@ -21,68 +21,62 @@ suite('Workbench - TerminalWordLinkProvider', () => {
|
||||
instantiationService.stub(IConfigurationService, configurationService);
|
||||
});
|
||||
|
||||
async function assertLink(text: string, expected: { text: string, range: [number, number][] }) {
|
||||
async function assertLink(text: string, expected: { text: string, range: [number, number][] }[]) {
|
||||
const xterm = new Terminal();
|
||||
const provider = instantiationService.createInstance(TerminalWordLinkProvider, xterm, () => { }, () => { });
|
||||
const provider: TerminalWordLinkProvider = instantiationService.createInstance(TerminalWordLinkProvider, xterm, () => { }, () => { });
|
||||
|
||||
// Write the text and wait for the parser to finish
|
||||
await new Promise<void>(r => xterm.write(text, r));
|
||||
|
||||
// Calculate positions just outside of link boundaries
|
||||
const noLinkPositions: IBufferCellPosition[] = [
|
||||
{ x: expected.range[0][0] - 1, y: expected.range[0][1] },
|
||||
{ x: expected.range[1][0] + 1, y: expected.range[1][1] }
|
||||
];
|
||||
|
||||
// Ensure outside positions do not detect the link
|
||||
for (let i = 0; i < noLinkPositions.length; i++) {
|
||||
const link = await new Promise<ILink | undefined>(r => provider.provideLink(noLinkPositions[i], r));
|
||||
assert.equal(link, undefined, `Just outside range boundary should not result in link, link found at (${link?.range.start.x}, ${link?.range.start.y}) to (${link?.range.end.x}, ${link?.range.end.y}) while checking (${noLinkPositions[i].x}, ${noLinkPositions[i].y})\nExpected link text=${expected.text}\nActual link text=${link?.text}`);
|
||||
}
|
||||
|
||||
// Convert range from [[startx, starty], [endx, endy]] to an IBufferRange
|
||||
const linkRange: IBufferRange = {
|
||||
start: { x: expected.range[0][0], y: expected.range[0][1] },
|
||||
end: { x: expected.range[1][0], y: expected.range[1][1] },
|
||||
};
|
||||
|
||||
// Calculate positions inside the link boundaries
|
||||
const linkPositions: IBufferCellPosition[] = [
|
||||
linkRange.start,
|
||||
linkRange.end
|
||||
];
|
||||
|
||||
// Ensure inside positions do detect the link
|
||||
for (let i = 0; i < linkPositions.length; i++) {
|
||||
const link = await new Promise<ILink | undefined>(r => provider.provideLink(linkPositions[i], r));
|
||||
assert.deepEqual(link?.text, expected.text);
|
||||
assert.deepEqual(link?.range, linkRange);
|
||||
}
|
||||
// Ensure all links are provided
|
||||
const links = (await new Promise<ILink[] | undefined>(r => provider.provideLinks(1, r)))!;
|
||||
assert.equal(links.length, expected.length);
|
||||
const actual = links.map(e => ({
|
||||
text: e.text,
|
||||
range: e.range
|
||||
}));
|
||||
const expectedVerbose = expected.map(e => ({
|
||||
text: e.text,
|
||||
range: {
|
||||
start: { x: e.range[0][0], y: e.range[0][1] },
|
||||
end: { x: e.range[1][0], y: e.range[1][1] },
|
||||
}
|
||||
}));
|
||||
assert.deepEqual(actual, expectedVerbose);
|
||||
}
|
||||
|
||||
test('should link words as defined by wordSeparators', async () => {
|
||||
await configurationService.setUserConfiguration('terminal', { integrated: { wordSeparators: ' ()[]' } });
|
||||
await assertLink('foo', { range: [[1, 1], [3, 1]], text: 'foo' });
|
||||
await assertLink(' foo ', { range: [[2, 1], [4, 1]], text: 'foo' });
|
||||
await assertLink('(foo)', { range: [[2, 1], [4, 1]], text: 'foo' });
|
||||
await assertLink('[foo]', { range: [[2, 1], [4, 1]], text: 'foo' });
|
||||
await assertLink('{foo}', { range: [[1, 1], [5, 1]], text: '{foo}' });
|
||||
await assertLink('foo', [{ range: [[1, 1], [3, 1]], text: 'foo' }]);
|
||||
await assertLink('foo', [{ range: [[1, 1], [3, 1]], text: 'foo' }]);
|
||||
await assertLink(' foo ', [{ range: [[2, 1], [4, 1]], text: 'foo' }]);
|
||||
await assertLink('(foo)', [{ range: [[2, 1], [4, 1]], text: 'foo' }]);
|
||||
await assertLink('[foo]', [{ range: [[2, 1], [4, 1]], text: 'foo' }]);
|
||||
await assertLink('{foo}', [{ range: [[1, 1], [5, 1]], text: '{foo}' }]);
|
||||
|
||||
await configurationService.setUserConfiguration('terminal', { integrated: { wordSeparators: ' ' } });
|
||||
await assertLink('foo', { range: [[1, 1], [3, 1]], text: 'foo' });
|
||||
await assertLink(' foo ', { range: [[2, 1], [4, 1]], text: 'foo' });
|
||||
await assertLink('(foo)', { range: [[1, 1], [5, 1]], text: '(foo)' });
|
||||
await assertLink('[foo]', { range: [[1, 1], [5, 1]], text: '[foo]' });
|
||||
await assertLink('{foo}', { range: [[1, 1], [5, 1]], text: '{foo}' });
|
||||
await assertLink('foo', [{ range: [[1, 1], [3, 1]], text: 'foo' }]);
|
||||
await assertLink(' foo ', [{ range: [[2, 1], [4, 1]], text: 'foo' }]);
|
||||
await assertLink('(foo)', [{ range: [[1, 1], [5, 1]], text: '(foo)' }]);
|
||||
await assertLink('[foo]', [{ range: [[1, 1], [5, 1]], text: '[foo]' }]);
|
||||
await assertLink('{foo}', [{ range: [[1, 1], [5, 1]], text: '{foo}' }]);
|
||||
});
|
||||
|
||||
test('should support wide characters', async () => {
|
||||
await configurationService.setUserConfiguration('terminal', { integrated: { wordSeparators: ' []' } });
|
||||
await assertLink('aabbccdd.txt ', { range: [[1, 1], [12, 1]], text: 'aabbccdd.txt' });
|
||||
await assertLink('我是学生.txt ', { range: [[1, 1], [12, 1]], text: '我是学生.txt' });
|
||||
await assertLink(' aabbccdd.txt ', { range: [[2, 1], [13, 1]], text: 'aabbccdd.txt' });
|
||||
await assertLink(' 我是学生.txt ', { range: [[2, 1], [13, 1]], text: '我是学生.txt' });
|
||||
await assertLink(' [aabbccdd.txt] ', { range: [[3, 1], [14, 1]], text: 'aabbccdd.txt' });
|
||||
await assertLink(' [我是学生.txt] ', { range: [[3, 1], [14, 1]], text: '我是学生.txt' });
|
||||
await assertLink('aabbccdd.txt ', [{ range: [[1, 1], [12, 1]], text: 'aabbccdd.txt' }]);
|
||||
await assertLink('我是学生.txt ', [{ range: [[1, 1], [12, 1]], text: '我是学生.txt' }]);
|
||||
await assertLink(' aabbccdd.txt ', [{ range: [[2, 1], [13, 1]], text: 'aabbccdd.txt' }]);
|
||||
await assertLink(' 我是学生.txt ', [{ range: [[2, 1], [13, 1]], text: '我是学生.txt' }]);
|
||||
await assertLink(' [aabbccdd.txt] ', [{ range: [[3, 1], [14, 1]], text: 'aabbccdd.txt' }]);
|
||||
await assertLink(' [我是学生.txt] ', [{ range: [[3, 1], [14, 1]], text: '我是学生.txt' }]);
|
||||
});
|
||||
|
||||
test('should support multiple link results', async () => {
|
||||
await configurationService.setUserConfiguration('terminal', { integrated: { wordSeparators: ' ' } });
|
||||
await assertLink('foo bar', [
|
||||
{ range: [[1, 1], [3, 1]], text: 'foo' },
|
||||
{ range: [[5, 1], [7, 1]], text: 'bar' }
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -100,7 +100,7 @@ class TimelineAggregate {
|
||||
return this.items[this.items.length - 1];
|
||||
}
|
||||
|
||||
add(timeline: Timeline) {
|
||||
add(timeline: Timeline, options: TimelineOptions) {
|
||||
let updated = false;
|
||||
|
||||
if (timeline.items.length !== 0 && this.items.length !== 0) {
|
||||
@@ -139,7 +139,10 @@ class TimelineAggregate {
|
||||
this.items.push(...timeline.items);
|
||||
}
|
||||
|
||||
this._cursor = timeline.paging?.cursor;
|
||||
// If we are not requesting more recent items than we have, then update the cursor
|
||||
if (options.cursor !== undefined || typeof options.limit !== 'object') {
|
||||
this._cursor = timeline.paging?.cursor;
|
||||
}
|
||||
|
||||
if (updated) {
|
||||
this.items.sort(
|
||||
@@ -626,7 +629,7 @@ export class TimelinePane extends ViewPane {
|
||||
updated = true;
|
||||
}
|
||||
else {
|
||||
updated = timeline.add(response);
|
||||
updated = timeline.add(response, request.options);
|
||||
}
|
||||
|
||||
if (updated) {
|
||||
@@ -923,6 +926,10 @@ export class TimelinePane extends ViewPane {
|
||||
}
|
||||
|
||||
private loadMore(item: LoadMoreCommand) {
|
||||
if (item.loading) {
|
||||
return;
|
||||
}
|
||||
|
||||
item.loading = true;
|
||||
this.tree.rerender(item);
|
||||
|
||||
|
||||
@@ -7,10 +7,10 @@ import { IUserDataSyncService, IUserDataSyncLogService, IUserDataSyncEnablementS
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { UserDataAutoSyncService as BaseUserDataAutoSyncService } from 'vs/platform/userDataSync/common/userDataAutoSyncService';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { UserDataSyncTrigger } from 'vs/workbench/contrib/userDataSync/browser/userDataSyncTrigger';
|
||||
import { IHostService } from 'vs/workbench/services/host/browser/host';
|
||||
import { IAuthenticationTokenService } from 'vs/platform/authentication/common/authentication';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { UserDataSyncTrigger } from 'vs/workbench/contrib/userDataSync/browser/userDataSyncTrigger';
|
||||
|
||||
export class UserDataAutoSyncService extends BaseUserDataAutoSyncService {
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@ import { toErrorMessage } from 'vs/base/common/errorMessage';
|
||||
import { canceled, isPromiseCanceledError } from 'vs/base/common/errors';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { Disposable, DisposableStore, dispose, MutableDisposable, toDisposable, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { isWeb } from 'vs/base/common/platform';
|
||||
import { isEqual, basename } from 'vs/base/common/resources';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import type { ICodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
@@ -39,7 +38,6 @@ import { IEditorInput, toResource, SideBySideEditor } from 'vs/workbench/common/
|
||||
import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput';
|
||||
import * as Constants from 'vs/workbench/contrib/logs/common/logConstants';
|
||||
import { IOutputService } from 'vs/workbench/contrib/output/common/output';
|
||||
import { UserDataSyncTrigger } from 'vs/workbench/contrib/userDataSync/browser/userDataSyncTrigger';
|
||||
import { IActivityService, IBadge, NumberBadge, ProgressBadge } from 'vs/workbench/services/activity/common/activity';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
|
||||
@@ -81,9 +79,10 @@ const resolveSettingsConflictsCommand = { id: 'workbench.userData.actions.resolv
|
||||
const resolveKeybindingsConflictsCommand = { id: 'workbench.userData.actions.resolveKeybindingsConflicts', title: localize('showKeybindingsConflicts', "Preferences Sync: Show Keybindings Conflicts") };
|
||||
const resolveSnippetsConflictsCommand = { id: 'workbench.userData.actions.resolveSnippetsConflicts', title: localize('showSnippetsConflicts', "Preferences Sync: Show User Snippets Conflicts") };
|
||||
const configureSyncCommand = { id: 'workbench.userData.actions.configureSync', title: localize('configure sync', "Preferences Sync: Configure...") };
|
||||
const showSyncActivityCommand = {
|
||||
id: 'workbench.userData.actions.showSyncActivity',
|
||||
title: localize('show sync log', "Preferences Sync: Show Log"),
|
||||
const showSyncActivityCommand = { id: 'workbench.userData.actions.showSyncActivity', title: localize('show sync log', "Preferences Sync: Show Log") };
|
||||
const syncNowCommand = {
|
||||
id: 'workbench.userData.actions.syncNow',
|
||||
title: localize('sync now', "Preferences Sync: Sync Now"),
|
||||
description(userDataSyncService: IUserDataSyncService): string | undefined {
|
||||
if (userDataSyncService.status === SyncStatus.Syncing) {
|
||||
return localize('sync is on with syncing', "syncing");
|
||||
@@ -97,7 +96,7 @@ const showSyncActivityCommand = {
|
||||
const showSyncSettingsCommand = { id: 'workbench.userData.actions.syncSettings', title: localize('sync settings', "Preferences Sync: Show Settings"), };
|
||||
|
||||
const CONTEXT_TURNING_ON_STATE = new RawContextKey<false>('userDataSyncTurningOn', false);
|
||||
const CONTEXT_ACCOUNT_STATE = new RawContextKey<string>('userDataSyncAccountStatus', AccountStatus.Uninitialized);
|
||||
export const CONTEXT_ACCOUNT_STATE = new RawContextKey<string>('userDataSyncAccountStatus', AccountStatus.Uninitialized);
|
||||
|
||||
export class UserDataSyncWorkbenchContribution extends Disposable implements IWorkbenchContribution {
|
||||
|
||||
@@ -161,10 +160,6 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
|
||||
|
||||
textModelResolverService.registerTextModelContentProvider(USER_DATA_SYNC_SCHEME, instantiationService.createInstance(UserDataRemoteContentProvider));
|
||||
registerEditorContribution(AcceptChangesContribution.ID, AcceptChangesContribution);
|
||||
|
||||
if (!isWeb) {
|
||||
this._register(instantiationService.createInstance(UserDataSyncTrigger).onDidTriggerSync(source => userDataAutoSyncService.triggerAutoSync([source])));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -894,6 +889,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
|
||||
private registerSyncStatusAction(): void {
|
||||
const that = this;
|
||||
const when = ContextKeyExpr.and(CONTEXT_SYNC_ENABLEMENT, CONTEXT_ACCOUNT_STATE.isEqualTo(AccountStatus.Available), CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized));
|
||||
this.registerSyncNowAction();
|
||||
this._register(registerAction2(class SyncStatusAction extends Action2 {
|
||||
constructor() {
|
||||
super({
|
||||
@@ -946,8 +942,9 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
|
||||
}
|
||||
items.push({ id: configureSyncCommand.id, label: configureSyncCommand.title });
|
||||
items.push({ id: showSyncSettingsCommand.id, label: showSyncSettingsCommand.title });
|
||||
items.push({ id: showSyncActivityCommand.id, label: showSyncActivityCommand.title, description: showSyncActivityCommand.description(that.userDataSyncService) });
|
||||
items.push({ id: showSyncActivityCommand.id, label: showSyncActivityCommand.title });
|
||||
items.push({ type: 'separator' });
|
||||
items.push({ id: syncNowCommand.id, label: syncNowCommand.title, description: syncNowCommand.description(that.userDataSyncService) });
|
||||
if (that.userDataSyncEnablementService.canToggleEnablement()) {
|
||||
const account = that.userDataSyncAccounts.current;
|
||||
items.push({ id: stopSyncCommand.id, label: stopSyncCommand.title, description: account ? `${account.accountName} (${that.authenticationService.getDisplayName(account.authenticationProviderId)})` : undefined });
|
||||
@@ -969,6 +966,21 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
|
||||
}));
|
||||
}
|
||||
|
||||
private registerSyncNowAction(): void {
|
||||
const that = this;
|
||||
this._register(registerAction2(class SyncNowAction extends Action2 {
|
||||
constructor() {
|
||||
super({
|
||||
id: syncNowCommand.id,
|
||||
title: syncNowCommand.title,
|
||||
});
|
||||
}
|
||||
run(): Promise<any> {
|
||||
return that.userDataSyncService.sync();
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
private registerTurnOffSyncAction(): void {
|
||||
const that = this;
|
||||
this._register(registerAction2(class StopSyncAction extends Action2 {
|
||||
|
||||
@@ -9,10 +9,10 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic
|
||||
import { SettingsEditor2Input, KeybindingsEditorInput, PreferencesEditorInput } from 'vs/workbench/services/preferences/common/preferencesEditorInput';
|
||||
import { isEqual } from 'vs/base/common/resources';
|
||||
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
|
||||
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
|
||||
import { VIEWLET_ID } from 'vs/workbench/contrib/extensions/common/extensions';
|
||||
import { IEditorInput } from 'vs/workbench/common/editor';
|
||||
import { IViewlet } from 'vs/workbench/common/viewlet';
|
||||
import { IViewsService } from 'vs/workbench/common/views';
|
||||
import { VIEW_CONTAINER_ID } from 'vs/workbench/contrib/userDataSync/browser/userDataSyncView';
|
||||
|
||||
export class UserDataSyncTrigger extends Disposable {
|
||||
|
||||
@@ -22,24 +22,15 @@ export class UserDataSyncTrigger extends Disposable {
|
||||
constructor(
|
||||
@IEditorService editorService: IEditorService,
|
||||
@IWorkbenchEnvironmentService private readonly workbenchEnvironmentService: IWorkbenchEnvironmentService,
|
||||
@IViewletService viewletService: IViewletService,
|
||||
@IViewsService viewsService: IViewsService,
|
||||
) {
|
||||
super();
|
||||
this._register(Event.any<string | undefined>(
|
||||
Event.map(editorService.onDidActiveEditorChange, () => this.getUserDataEditorInputSource(editorService.activeEditor)),
|
||||
Event.map(viewletService.onDidViewletOpen, viewlet => this.getUserDataViewletSource(viewlet))
|
||||
)(source => {
|
||||
if (source) {
|
||||
this._onDidTriggerSync.fire(source);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
private getUserDataViewletSource(viewlet: IViewlet): string | undefined {
|
||||
if (viewlet.getId() === VIEWLET_ID) {
|
||||
return 'extensionsViewlet';
|
||||
}
|
||||
return undefined;
|
||||
this._register(
|
||||
Event.filter(
|
||||
Event.any<string | undefined>(
|
||||
Event.map(editorService.onDidActiveEditorChange, () => this.getUserDataEditorInputSource(editorService.activeEditor)),
|
||||
Event.map(Event.filter(viewsService.onDidChangeViewContainerVisibility, e => [VIEWLET_ID, VIEW_CONTAINER_ID].includes(e.id) && e.visible), e => e.id)
|
||||
), source => source !== undefined)(source => this._onDidTriggerSync.fire(source!)));
|
||||
}
|
||||
|
||||
private getUserDataEditorInputSource(editorInput: IEditorInput | undefined): string | undefined {
|
||||
|
||||
@@ -10,9 +10,9 @@ import { localize } from 'vs/nls';
|
||||
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
|
||||
import { TreeViewPane, TreeView } from 'vs/workbench/browser/parts/views/treeView';
|
||||
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { ALL_SYNC_RESOURCES, CONTEXT_SYNC_ENABLEMENT, SyncResource, IUserDataSyncService, ISyncResourceHandle } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
import { ALL_SYNC_RESOURCES, SyncResource, IUserDataSyncService, ISyncResourceHandle, CONTEXT_SYNC_STATE, SyncStatus } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
import { registerAction2, Action2, MenuId } from 'vs/platform/actions/common/actions';
|
||||
import { IContextKeyService, RawContextKey, ContextKeyExpr, ContextKeyEqualsExpr } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IContextKeyService, RawContextKey, ContextKeyExpr, ContextKeyEqualsExpr, IContextKey } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { FolderThemeIcon } from 'vs/platform/theme/common/themeService';
|
||||
@@ -20,38 +20,45 @@ import { fromNow } from 'vs/base/common/date';
|
||||
import { pad, uppercaseFirstLetter } from 'vs/base/common/strings';
|
||||
import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer';
|
||||
import { Codicon } from 'vs/base/common/codicons';
|
||||
import { CONTEXT_ACCOUNT_STATE } from 'vs/workbench/contrib/userDataSync/browser/userDataSync';
|
||||
import { AccountStatus } from 'vs/workbench/contrib/userDataSync/browser/userDataSyncAccount';
|
||||
|
||||
export const VIEW_CONTAINER_ID = 'workbench.view.sync';
|
||||
const CONTEXT_ENABLE_VIEWS = new RawContextKey<boolean>(`showUserDataSyncViews`, false);
|
||||
|
||||
export class UserDataSyncViewContribution implements IWorkbenchContribution {
|
||||
|
||||
private readonly viewsEnablementContext: IContextKey<boolean>;
|
||||
|
||||
constructor(
|
||||
@IInstantiationService private readonly instantiationService: IInstantiationService,
|
||||
@IContextKeyService private readonly contextKeyService: IContextKeyService,
|
||||
@IUserDataSyncService private readonly userDataSyncService: IUserDataSyncService,
|
||||
) {
|
||||
const container = this.registerSyncViewContainer();
|
||||
this.registerBackupView(container, true);
|
||||
this.registerBackupView(container, false);
|
||||
this.viewsEnablementContext = CONTEXT_ENABLE_VIEWS.bindTo(this.contextKeyService);
|
||||
this.registerView(container, true);
|
||||
this.registerView(container, false);
|
||||
}
|
||||
|
||||
private registerSyncViewContainer(): ViewContainer {
|
||||
return Registry.as<IViewContainersRegistry>(Extensions.ViewContainersRegistry).registerViewContainer(
|
||||
{
|
||||
id: 'workbench.view.sync',
|
||||
id: VIEW_CONTAINER_ID,
|
||||
name: localize('sync preferences', "Preferences Sync"),
|
||||
ctorDescriptor: new SyncDescriptor(
|
||||
ViewPaneContainer,
|
||||
['workbench.view.sync', { mergeViewWithContainerWhenSingleView: true }]
|
||||
[VIEW_CONTAINER_ID, { mergeViewWithContainerWhenSingleView: true }]
|
||||
),
|
||||
icon: Codicon.sync.classNames,
|
||||
hideIfEmpty: true,
|
||||
}, ViewContainerLocation.Sidebar);
|
||||
}
|
||||
|
||||
private registerBackupView(container: ViewContainer, remote: boolean): void {
|
||||
const id = `workbench.views.sync.${remote ? 'remote' : 'local'}BackupView`;
|
||||
const name = remote ? localize('remote title', "Remote Backup") : localize('local title', "Local Backup");
|
||||
const contextKey = new RawContextKey<boolean>(`showUserDataSync${remote ? 'Remote' : 'Local'}BackupView`, false);
|
||||
const viewEnablementContext = contextKey.bindTo(this.contextKeyService);
|
||||
private registerView(container: ViewContainer, remote: boolean): void {
|
||||
const that = this;
|
||||
const id = `workbench.views.sync.${remote ? 'remote' : 'local'}DataView`;
|
||||
const name = remote ? localize('remote title', "Remote Data") : localize('local title', "Local Backup");
|
||||
const treeView = this.instantiationService.createInstance(TreeView, id, name);
|
||||
treeView.showCollapseAllAction = true;
|
||||
treeView.showRefreshAction = true;
|
||||
@@ -66,7 +73,7 @@ export class UserDataSyncViewContribution implements IWorkbenchContribution {
|
||||
id,
|
||||
name,
|
||||
ctorDescriptor: new SyncDescriptor(TreeViewPane),
|
||||
when: ContextKeyExpr.and(CONTEXT_SYNC_ENABLEMENT, contextKey),
|
||||
when: ContextKeyExpr.and(CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized), CONTEXT_ACCOUNT_STATE.isEqualTo(AccountStatus.Available), CONTEXT_ENABLE_VIEWS),
|
||||
canToggleVisibility: true,
|
||||
canMoveView: true,
|
||||
treeView,
|
||||
@@ -77,21 +84,21 @@ export class UserDataSyncViewContribution implements IWorkbenchContribution {
|
||||
registerAction2(class extends Action2 {
|
||||
constructor() {
|
||||
super({
|
||||
id: `workbench.actions.showSync${remote ? 'Remote' : 'Local'}BackupView`,
|
||||
id: `workbench.actions.showSync${remote ? 'Remote' : 'Local'}DataView`,
|
||||
title: remote ?
|
||||
{ value: localize('workbench.action.showSyncRemoteBackup', "Show Remote Backup"), original: `Show Remote Backup` }
|
||||
{ value: localize('workbench.action.showSyncRemoteBackup', "Show Remote Data"), original: `Show Remote Data` }
|
||||
: { value: localize('workbench.action.showSyncLocalBackup', "Show Local Backup"), original: `Show Local Backup` },
|
||||
category: { value: localize('sync preferences', "Preferences Sync"), original: `Preferences Sync` },
|
||||
menu: {
|
||||
id: MenuId.CommandPalette,
|
||||
when: CONTEXT_SYNC_ENABLEMENT
|
||||
when: ContextKeyExpr.and(CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized), CONTEXT_ACCOUNT_STATE.isEqualTo(AccountStatus.Available)),
|
||||
},
|
||||
});
|
||||
}
|
||||
async run(accessor: ServicesAccessor): Promise<void> {
|
||||
const viewDescriptorService = accessor.get(IViewDescriptorService);
|
||||
const viewsService = accessor.get(IViewsService);
|
||||
viewEnablementContext.set(true);
|
||||
that.viewsEnablementContext.set(true);
|
||||
const viewContainer = viewDescriptorService.getViewContainerByViewId(id);
|
||||
if (viewContainer) {
|
||||
const model = viewDescriptorService.getViewContainerModel(viewContainer);
|
||||
@@ -105,7 +112,6 @@ export class UserDataSyncViewContribution implements IWorkbenchContribution {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IUserDataAutoSyncService, UserDataSyncError } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { IChannel } from 'vs/base/parts/ipc/common/ipc';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { UserDataSyncTrigger } from 'vs/workbench/contrib/userDataSync/browser/userDataSyncTrigger';
|
||||
|
||||
export class UserDataAutoSyncService extends Disposable implements IUserDataAutoSyncService {
|
||||
|
||||
_serviceBrand: undefined;
|
||||
|
||||
private readonly channel: IChannel;
|
||||
get onError(): Event<UserDataSyncError> { return Event.map(this.channel.listen<Error>('onError'), e => UserDataSyncError.toUserDataSyncError(e)); }
|
||||
|
||||
constructor(
|
||||
@IInstantiationService instantiationService: IInstantiationService,
|
||||
@ISharedProcessService sharedProcessService: ISharedProcessService
|
||||
) {
|
||||
super();
|
||||
this.channel = sharedProcessService.getChannel('userDataAutoSync');
|
||||
this._register(instantiationService.createInstance(UserDataSyncTrigger).onDidTriggerSync(source => this.triggerAutoSync([source])));
|
||||
}
|
||||
|
||||
triggerAutoSync(sources: string[]): Promise<void> {
|
||||
|
||||
return this.channel.call('triggerAutoSync', [sources]);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -168,6 +168,9 @@ export abstract class BaseWebview<T extends HTMLElement> extends Disposable {
|
||||
private readonly _onDidFocus = this._register(new Emitter<void>());
|
||||
public readonly onDidFocus = this._onDidFocus.event;
|
||||
|
||||
private readonly _onDidBlur = this._register(new Emitter<void>());
|
||||
public readonly onDidBlur = this._onDidBlur.event;
|
||||
|
||||
public sendMessage(data: any): void {
|
||||
this._send('message', data);
|
||||
}
|
||||
@@ -267,6 +270,8 @@ export abstract class BaseWebview<T extends HTMLElement> extends Disposable {
|
||||
this._focused = isFocused;
|
||||
if (isFocused) {
|
||||
this._onDidFocus.fire();
|
||||
} else {
|
||||
this._onDidBlur.fire();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -125,6 +125,7 @@ export class DynamicWebviewEditorOverlay extends Disposable implements WebviewOv
|
||||
// Forward events from inner webview to outer listeners
|
||||
this._webviewEvents.clear();
|
||||
this._webviewEvents.add(webview.onDidFocus(() => { this._onDidFocus.fire(); }));
|
||||
this._webviewEvents.add(webview.onDidBlur(() => { this._onDidBlur.fire(); }));
|
||||
this._webviewEvents.add(webview.onDidClickLink(x => { this._onDidClickLink.fire(x); }));
|
||||
this._webviewEvents.add(webview.onMessage(x => { this._onMessage.fire(x); }));
|
||||
this._webviewEvents.add(webview.onMissingCsp(x => { this._onMissingCsp.fire(x); }));
|
||||
@@ -182,6 +183,9 @@ export class DynamicWebviewEditorOverlay extends Disposable implements WebviewOv
|
||||
private readonly _onDidFocus = this._register(new Emitter<void>());
|
||||
public readonly onDidFocus: Event<void> = this._onDidFocus.event;
|
||||
|
||||
private readonly _onDidBlur = this._register(new Emitter<void>());
|
||||
public readonly onDidBlur: Event<void> = this._onDidBlur.event;
|
||||
|
||||
private readonly _onDidClickLink = this._register(new Emitter<string>());
|
||||
public readonly onDidClickLink: Event<string> = this._onDidClickLink.event;
|
||||
|
||||
|
||||
@@ -79,6 +79,7 @@ export interface Webview extends IDisposable {
|
||||
state: string | undefined;
|
||||
|
||||
readonly onDidFocus: Event<void>;
|
||||
readonly onDidBlur: Event<void>;
|
||||
readonly onDidClickLink: Event<string>;
|
||||
readonly onDidScroll: Event<{ scrollYPercentage: number }>;
|
||||
readonly onDidWheel: Event<IMouseWheelEvent>;
|
||||
|
||||
Reference in New Issue
Block a user