mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-16 18:46:40 -05:00
Merge from vscode 5e80bf449c995aa32a59254c0ff845d37da11b70 (#9317)
This commit is contained in:
@@ -90,9 +90,9 @@ export class Gesture extends Disposable {
|
||||
this.targets = [];
|
||||
this.ignoreTargets = [];
|
||||
this._lastSetTapCountTime = 0;
|
||||
this._register(DomUtils.addDisposableListener(document, 'touchstart', (e: TouchEvent) => this.onTouchStart(e)));
|
||||
this._register(DomUtils.addDisposableListener(document, 'touchstart', (e: TouchEvent) => this.onTouchStart(e), { passive: false }));
|
||||
this._register(DomUtils.addDisposableListener(document, 'touchend', (e: TouchEvent) => this.onTouchEnd(e)));
|
||||
this._register(DomUtils.addDisposableListener(document, 'touchmove', (e: TouchEvent) => this.onTouchMove(e)));
|
||||
this._register(DomUtils.addDisposableListener(document, 'touchmove', (e: TouchEvent) => this.onTouchMove(e), { passive: false }));
|
||||
}
|
||||
|
||||
public static addTarget(element: HTMLElement): IDisposable {
|
||||
|
||||
@@ -93,7 +93,6 @@
|
||||
.monaco-dialog-box .dialog-message-row .dialog-message-container .dialog-message-detail {
|
||||
line-height: 22px;
|
||||
flex: 1; /* let the message always grow */
|
||||
opacity: .9;
|
||||
}
|
||||
|
||||
.monaco-dialog-box .dialog-message-row .dialog-message-container .dialog-message a:focus {
|
||||
|
||||
@@ -267,7 +267,13 @@ export class Dialog extends Disposable {
|
||||
if (this.checkbox) {
|
||||
this.checkbox.style(style);
|
||||
}
|
||||
|
||||
if (this.messageDetailElement) {
|
||||
const messageDetailColor = Color.fromHex(fgColor).transparent(.9);
|
||||
this.messageDetailElement.style.color = messageDetailColor.makeOpaque(Color.fromHex(bgColor)).toString();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -125,6 +125,19 @@ export module Iterator {
|
||||
};
|
||||
}
|
||||
|
||||
export function some<T>(iterator: Iterator<T> | NativeIterator<T>, fn: (t: T) => boolean): boolean {
|
||||
while (true) {
|
||||
const element = iterator.next();
|
||||
if (element.done) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (fn(element.value)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function forEach<T>(iterator: Iterator<T>, fn: (t: T) => void): void {
|
||||
for (let next = iterator.next(); !next.done; next = iterator.next()) {
|
||||
fn(next.value);
|
||||
|
||||
@@ -142,10 +142,6 @@ export function decode(buffer: Buffer, encoding: string): string {
|
||||
return iconv.decode(buffer, toNodeEncoding(encoding));
|
||||
}
|
||||
|
||||
export function encode(content: string | Buffer, encoding: string, options?: { addBOM?: boolean }): Buffer {
|
||||
return iconv.encode(content as string /* TODO report into upstream typings */, toNodeEncoding(encoding), options);
|
||||
}
|
||||
|
||||
export function encodingExists(encoding: string): boolean {
|
||||
return iconv.encodingExists(toNodeEncoding(encoding));
|
||||
}
|
||||
|
||||
@@ -14,7 +14,6 @@ import { promisify } from 'util';
|
||||
import { isRootOrDriveLetter } from 'vs/base/common/extpath';
|
||||
import { generateUuid } from 'vs/base/common/uuid';
|
||||
import { normalizeNFC } from 'vs/base/common/normalization';
|
||||
import { encode } from 'vs/base/node/encoding';
|
||||
|
||||
// See https://github.com/Microsoft/vscode/issues/30180
|
||||
const WIN32_MAX_FILE_SIZE = 300 * 1024 * 1024; // 300 MB
|
||||
@@ -320,10 +319,6 @@ function ensureWriteFileQueue(queueKey: string): Queue<void> {
|
||||
export interface IWriteFileOptions {
|
||||
mode?: number;
|
||||
flag?: string;
|
||||
encoding?: {
|
||||
charset: string;
|
||||
addBOM: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
interface IEnsuredWriteFileOptions extends IWriteFileOptions {
|
||||
@@ -339,10 +334,6 @@ let canFlush = true;
|
||||
//
|
||||
// See https://github.com/nodejs/node/blob/v5.10.0/lib/fs.js#L1194
|
||||
function doWriteFileAndFlush(path: string, data: string | Buffer | Uint8Array, options: IEnsuredWriteFileOptions, callback: (error: Error | null) => void): void {
|
||||
if (options.encoding) {
|
||||
data = encode(data instanceof Uint8Array ? Buffer.from(data) : data, options.encoding.charset, { addBOM: options.encoding.addBOM });
|
||||
}
|
||||
|
||||
if (!canFlush) {
|
||||
return fs.writeFile(path, data, { mode: options.mode, flag: options.flag }, callback);
|
||||
}
|
||||
@@ -378,10 +369,6 @@ function doWriteFileAndFlush(path: string, data: string | Buffer | Uint8Array, o
|
||||
export function writeFileSync(path: string, data: string | Buffer, options?: IWriteFileOptions): void {
|
||||
const ensuredOptions = ensureWriteOptions(options);
|
||||
|
||||
if (ensuredOptions.encoding) {
|
||||
data = encode(data, ensuredOptions.encoding.charset, { addBOM: ensuredOptions.encoding.addBOM });
|
||||
}
|
||||
|
||||
if (!canFlush) {
|
||||
return fs.writeFileSync(path, data, { mode: ensuredOptions.mode, flag: ensuredOptions.flag });
|
||||
}
|
||||
@@ -413,8 +400,7 @@ function ensureWriteOptions(options?: IWriteFileOptions): IEnsuredWriteFileOptio
|
||||
|
||||
return {
|
||||
mode: typeof options.mode === 'number' ? options.mode : 0o666,
|
||||
flag: typeof options.flag === 'string' ? options.flag : 'w',
|
||||
encoding: options.encoding
|
||||
flag: typeof options.flag === 'string' ? options.flag : 'w'
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -3,75 +3,11 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as fs from 'fs';
|
||||
import { VSBufferReadableStream, VSBufferReadable, VSBuffer } from 'vs/base/common/buffer';
|
||||
import { Readable } from 'stream';
|
||||
import { isUndefinedOrNull } from 'vs/base/common/types';
|
||||
import { UTF8, UTF8_with_bom, UTF8_BOM, UTF16be, UTF16le_BOM, UTF16be_BOM, UTF16le, UTF_ENCODING } from 'vs/base/node/encoding';
|
||||
|
||||
/**
|
||||
* Reads a file until a matching string is found.
|
||||
*
|
||||
* @param file The file to read.
|
||||
* @param matchingString The string to search for.
|
||||
* @param chunkBytes The number of bytes to read each iteration.
|
||||
* @param maximumBytesToRead The maximum number of bytes to read before giving up.
|
||||
* @param callback The finished callback.
|
||||
*/
|
||||
export function readToMatchingString(file: string, matchingString: string, chunkBytes: number, maximumBytesToRead: number): Promise<string | null> {
|
||||
return new Promise<string | null>((resolve, reject) =>
|
||||
fs.open(file, 'r', null, (err, fd) => {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
|
||||
function end(err: Error | null, result: string | null): void {
|
||||
fs.close(fd, closeError => {
|
||||
if (closeError) {
|
||||
return reject(closeError);
|
||||
}
|
||||
|
||||
if (err && (<any>err).code === 'EISDIR') {
|
||||
return reject(err); // we want to bubble this error up (file is actually a folder)
|
||||
}
|
||||
|
||||
return resolve(result);
|
||||
});
|
||||
}
|
||||
|
||||
const buffer = Buffer.allocUnsafe(maximumBytesToRead);
|
||||
let offset = 0;
|
||||
|
||||
function readChunk(): void {
|
||||
fs.read(fd, buffer, offset, chunkBytes, null, (err, bytesRead) => {
|
||||
if (err) {
|
||||
return end(err, null);
|
||||
}
|
||||
|
||||
if (bytesRead === 0) {
|
||||
return end(null, null);
|
||||
}
|
||||
|
||||
offset += bytesRead;
|
||||
|
||||
const newLineIndex = buffer.indexOf(matchingString);
|
||||
if (newLineIndex >= 0) {
|
||||
return end(null, buffer.toString('utf8').substr(0, newLineIndex));
|
||||
}
|
||||
|
||||
if (offset >= maximumBytesToRead) {
|
||||
return end(new Error(`Could not find ${matchingString} in first ${maximumBytesToRead} bytes of ${file}`), null);
|
||||
}
|
||||
|
||||
return readChunk();
|
||||
});
|
||||
}
|
||||
|
||||
readChunk();
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
export function streamToNodeReadable(stream: VSBufferReadableStream): Readable {
|
||||
return new class extends Readable {
|
||||
private listening = false;
|
||||
|
||||
@@ -24,7 +24,7 @@ import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { IStateService } from 'vs/platform/state/node/state';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { IURLService, IOpenURLOptions } from 'vs/platform/url/common/url';
|
||||
import { IURLService } from 'vs/platform/url/common/url';
|
||||
import { URLHandlerChannelClient, URLHandlerRouter } from 'vs/platform/url/common/urlIpc';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { NullTelemetryService, combinedAppender, LogAppender } from 'vs/platform/telemetry/common/telemetryUtils';
|
||||
@@ -73,10 +73,10 @@ import { IDiagnosticsService } from 'vs/platform/diagnostics/node/diagnosticsSer
|
||||
import { ExtensionHostDebugBroadcastChannel } from 'vs/platform/debug/common/extensionHostDebugIpc';
|
||||
import { IElectronMainService, ElectronMainService } from 'vs/platform/electron/electron-main/electronMainService';
|
||||
import { ISharedProcessMainService, SharedProcessMainService } from 'vs/platform/ipc/electron-main/sharedProcessMainService';
|
||||
import { assign } from 'vs/base/common/objects';
|
||||
import { IDialogMainService, DialogMainService } from 'vs/platform/dialogs/electron-main/dialogs';
|
||||
import { withNullAsUndefined } from 'vs/base/common/types';
|
||||
import { parseArgs, OPTIONS } from 'vs/platform/environment/node/argv';
|
||||
import { coalesce } from 'vs/base/common/arrays';
|
||||
|
||||
export class CodeApplication extends Disposable {
|
||||
|
||||
@@ -395,7 +395,7 @@ export class CodeApplication extends Disposable {
|
||||
const windows = appInstantiationService.invokeFunction(accessor => this.openFirstWindow(accessor, electronIpcServer, sharedProcessClient));
|
||||
|
||||
// Post Open Windows Tasks
|
||||
appInstantiationService.invokeFunction(this.afterWindowOpen.bind(this));
|
||||
appInstantiationService.invokeFunction(accessor => this.afterWindowOpen(accessor));
|
||||
|
||||
// Tracing: Stop tracing after windows are ready if enabled
|
||||
if (this.environmentService.args.trace) {
|
||||
@@ -575,9 +575,8 @@ export class CodeApplication extends Disposable {
|
||||
electronIpcServer.registerChannel('logger', loggerChannel);
|
||||
sharedProcessClient.then(client => client.registerChannel('logger', loggerChannel));
|
||||
|
||||
const windowsMainService = this.windowsMainService = accessor.get(IWindowsMainService);
|
||||
|
||||
// ExtensionHost Debug broadcast service
|
||||
const windowsMainService = this.windowsMainService = accessor.get(IWindowsMainService);
|
||||
electronIpcServer.registerChannel(ExtensionHostDebugBroadcastChannel.ChannelName, new ElectronExtensionHostDebugBroadcastChannel(windowsMainService));
|
||||
|
||||
// Signal phase: ready (services set)
|
||||
@@ -586,47 +585,67 @@ export class CodeApplication extends Disposable {
|
||||
// Propagate to clients
|
||||
this.dialogMainService = accessor.get(IDialogMainService);
|
||||
|
||||
// Create a URL handler to open file URIs in the active window
|
||||
// Check for initial URLs to handle from protocol link invocations
|
||||
const environmentService = accessor.get(IEnvironmentService);
|
||||
const pendingWindowOpenablesFromProtocolLinks: IWindowOpenable[] = [];
|
||||
const pendingProtocolLinksToHandle = coalesce([
|
||||
|
||||
// Windows/Linux: protocol handler invokes CLI with --open-url
|
||||
...environmentService.args['open-url'] ? environmentService.args._urls || [] : [],
|
||||
|
||||
// macOS: open-url events
|
||||
...((<any>global).getOpenUrls() || []) as string[]
|
||||
].map(pendingUrlToHandle => {
|
||||
try {
|
||||
return URI.parse(pendingUrlToHandle);
|
||||
} catch (error) {
|
||||
return undefined;
|
||||
}
|
||||
})).filter(pendingUriToHandle => {
|
||||
// filter out any protocol link that wants to open as window so that
|
||||
// we open the right set of windows on startup and not restore the
|
||||
// previous workspace too.
|
||||
const windowOpenable = this.getWindowOpenableFromProtocolLink(pendingUriToHandle);
|
||||
if (windowOpenable) {
|
||||
pendingWindowOpenablesFromProtocolLinks.push(windowOpenable);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
// Create a URL handler to open file URIs in the active window
|
||||
const app = this;
|
||||
urlService.registerHandler({
|
||||
async handleURL(uri: URI, options?: IOpenURLOptions): Promise<boolean> {
|
||||
async handleURL(uri: URI): Promise<boolean> {
|
||||
|
||||
// Catch file/remote URLs
|
||||
if ((uri.authority === Schemas.file || uri.authority === Schemas.vscodeRemote) && !!uri.path) {
|
||||
const cli = assign(Object.create(null), environmentService.args);
|
||||
const urisToOpen: IWindowOpenable[] = [];
|
||||
// Check for URIs to open in window
|
||||
const windowOpenableFromProtocolLink = app.getWindowOpenableFromProtocolLink(uri);
|
||||
if (windowOpenableFromProtocolLink) {
|
||||
windowsMainService.open({
|
||||
context: OpenContext.API,
|
||||
cli: { ...environmentService.args },
|
||||
urisToOpen: [windowOpenableFromProtocolLink],
|
||||
gotoLineMode: true
|
||||
});
|
||||
|
||||
// File path
|
||||
if (uri.authority === Schemas.file) {
|
||||
// we configure as fileUri, but later validation will
|
||||
// make sure to open as folder or workspace if possible
|
||||
urisToOpen.push({ fileUri: URI.file(uri.fsPath) });
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Remote path
|
||||
else {
|
||||
// Example conversion:
|
||||
// From: vscode://vscode-remote/wsl+ubuntu/mnt/c/GitDevelopment/monaco
|
||||
// To: vscode-remote://wsl+ubuntu/mnt/c/GitDevelopment/monaco
|
||||
const secondSlash = uri.path.indexOf(posix.sep, 1 /* skip over the leading slash */);
|
||||
if (secondSlash !== -1) {
|
||||
const authority = uri.path.substring(1, secondSlash);
|
||||
const path = uri.path.substring(secondSlash);
|
||||
const remoteUri = URI.from({ scheme: Schemas.vscodeRemote, authority, path, query: uri.query, fragment: uri.fragment });
|
||||
// If we have not yet handled the URI and we have no window opened (macOS only)
|
||||
// we first open a window and then try to open that URI within that window
|
||||
if (isMacintosh && windowsMainService.getWindowCount() === 0) {
|
||||
const [window] = windowsMainService.open({
|
||||
context: OpenContext.API,
|
||||
cli: { ...environmentService.args },
|
||||
forceEmpty: true,
|
||||
gotoLineMode: true
|
||||
});
|
||||
|
||||
if (hasWorkspaceFileExtension(path)) {
|
||||
urisToOpen.push({ workspaceUri: remoteUri });
|
||||
} else {
|
||||
urisToOpen.push({ folderUri: remoteUri });
|
||||
}
|
||||
}
|
||||
}
|
||||
await window.ready();
|
||||
|
||||
if (urisToOpen.length > 0) {
|
||||
windowsMainService.open({ context: OpenContext.API, cli, urisToOpen, gotoLineMode: true });
|
||||
|
||||
return true;
|
||||
}
|
||||
return urlService.open(uri);
|
||||
}
|
||||
|
||||
return false;
|
||||
@@ -638,37 +657,13 @@ export class CodeApplication extends Disposable {
|
||||
const activeWindowRouter = new StaticRouter(ctx => activeWindowManager.getActiveClientId().then(id => ctx === id));
|
||||
const urlHandlerRouter = new URLHandlerRouter(activeWindowRouter);
|
||||
const urlHandlerChannel = electronIpcServer.getChannel('urlHandler', urlHandlerRouter);
|
||||
const multiplexURLHandler = new URLHandlerChannelClient(urlHandlerChannel);
|
||||
|
||||
// On Mac, Code can be running without any open windows, so we must create a window to handle urls,
|
||||
// if there is none
|
||||
if (isMacintosh) {
|
||||
urlService.registerHandler({
|
||||
async handleURL(uri: URI, options?: IOpenURLOptions): Promise<boolean> {
|
||||
if (windowsMainService.getWindowCount() === 0) {
|
||||
const cli = { ...environmentService.args };
|
||||
const [window] = windowsMainService.open({ context: OpenContext.API, cli, forceEmpty: true, gotoLineMode: true });
|
||||
|
||||
await window.ready();
|
||||
|
||||
return urlService.open(uri);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Register the multiple URL handler
|
||||
urlService.registerHandler(multiplexURLHandler);
|
||||
urlService.registerHandler(new URLHandlerChannelClient(urlHandlerChannel));
|
||||
|
||||
// Watch Electron URLs and forward them to the UrlService
|
||||
const args = this.environmentService.args;
|
||||
const urls = args['open-url'] ? args._urls : [];
|
||||
const urlListener = new ElectronURLListener(urls || [], urlService, windowsMainService, this.environmentService);
|
||||
this._register(urlListener);
|
||||
this._register(new ElectronURLListener(pendingProtocolLinksToHandle, urlService, windowsMainService, this.environmentService));
|
||||
|
||||
// Open our first window
|
||||
const args = this.environmentService.args;
|
||||
const macOpenFiles: string[] = (<any>global).macOpenFiles;
|
||||
const context = !!process.env['VSCODE_CLI'] ? OpenContext.CLI : OpenContext.DESKTOP;
|
||||
const hasCliArgs = args._.length;
|
||||
@@ -677,6 +672,19 @@ export class CodeApplication extends Disposable {
|
||||
const noRecentEntry = args['skip-add-to-recently-opened'] === true;
|
||||
const waitMarkerFileURI = args.wait && args.waitMarkerFilePath ? URI.file(args.waitMarkerFilePath) : undefined;
|
||||
|
||||
// check for a pending window to open from URI
|
||||
// e.g. when running code with --open-uri from
|
||||
// a protocol handler
|
||||
if (pendingWindowOpenablesFromProtocolLinks.length > 0) {
|
||||
return windowsMainService.open({
|
||||
context,
|
||||
cli: args,
|
||||
urisToOpen: pendingWindowOpenablesFromProtocolLinks,
|
||||
gotoLineMode: true,
|
||||
initialStartup: true
|
||||
});
|
||||
}
|
||||
|
||||
// new window if "-n" or "--remote" was used without paths
|
||||
if ((args['new-window'] || args.remote) && !hasCliArgs && !hasFolderURIs && !hasFileURIs) {
|
||||
return windowsMainService.open({
|
||||
@@ -698,7 +706,6 @@ export class CodeApplication extends Disposable {
|
||||
urisToOpen: macOpenFiles.map(file => this.getWindowOpenableFromPathSync(file)),
|
||||
noRecentEntry,
|
||||
waitMarkerFileURI,
|
||||
gotoLineMode: false,
|
||||
initialStartup: true
|
||||
});
|
||||
}
|
||||
@@ -716,6 +723,40 @@ export class CodeApplication extends Disposable {
|
||||
});
|
||||
}
|
||||
|
||||
private getWindowOpenableFromProtocolLink(uri: URI): IWindowOpenable | undefined {
|
||||
if (!uri.path) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// File path
|
||||
if (uri.authority === Schemas.file) {
|
||||
// we configure as fileUri, but later validation will
|
||||
// make sure to open as folder or workspace if possible
|
||||
return { fileUri: URI.file(uri.fsPath) };
|
||||
}
|
||||
|
||||
// Remote path
|
||||
else if (uri.authority === Schemas.vscodeRemote) {
|
||||
// Example conversion:
|
||||
// From: vscode://vscode-remote/wsl+ubuntu/mnt/c/GitDevelopment/monaco
|
||||
// To: vscode-remote://wsl+ubuntu/mnt/c/GitDevelopment/monaco
|
||||
const secondSlash = uri.path.indexOf(posix.sep, 1 /* skip over the leading slash */);
|
||||
if (secondSlash !== -1) {
|
||||
const authority = uri.path.substring(1, secondSlash);
|
||||
const path = uri.path.substring(secondSlash);
|
||||
const remoteUri = URI.from({ scheme: Schemas.vscodeRemote, authority, path, query: uri.query, fragment: uri.fragment });
|
||||
|
||||
if (hasWorkspaceFileExtension(path)) {
|
||||
return { workspaceUri: remoteUri };
|
||||
} else {
|
||||
return { folderUri: remoteUri };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private getWindowOpenableFromPathSync(path: string): IWindowOpenable {
|
||||
try {
|
||||
const fileStat = statSync(path);
|
||||
@@ -734,6 +775,7 @@ export class CodeApplication extends Disposable {
|
||||
}
|
||||
|
||||
private afterWindowOpen(accessor: ServicesAccessor): void {
|
||||
|
||||
// Signal phase: after window open
|
||||
this.lifecycleMainService.phase = LifecycleMainPhase.AfterWindowOpen;
|
||||
|
||||
@@ -763,7 +805,7 @@ class ElectronExtensionHostDebugBroadcastChannel<TContext> extends ExtensionHost
|
||||
super();
|
||||
}
|
||||
|
||||
call(ctx: TContext, command: string, arg?: any): Promise<any> {
|
||||
async call(ctx: TContext, command: string, arg?: any): Promise<any> {
|
||||
if (command === 'openExtensionDevelopmentHostWindow') {
|
||||
const env = arg[1];
|
||||
const pargs = parseArgs(arg[0], OPTIONS);
|
||||
@@ -775,7 +817,6 @@ class ElectronExtensionHostDebugBroadcastChannel<TContext> extends ExtensionHost
|
||||
userEnv: Object.keys(env).length > 0 ? env : undefined
|
||||
});
|
||||
}
|
||||
return Promise.resolve();
|
||||
} else {
|
||||
return super.call(ctx, command, arg);
|
||||
}
|
||||
|
||||
@@ -19,8 +19,8 @@ export function validatePaths(args: ParsedArgs): ParsedArgs {
|
||||
args._ = [];
|
||||
}
|
||||
|
||||
// Normalize paths and watch out for goto line mode
|
||||
if (!args['remote']) {
|
||||
// Normalize paths and watch out for goto line mode
|
||||
const paths = doValidatePaths(args._, args.goto);
|
||||
args._ = paths;
|
||||
}
|
||||
|
||||
@@ -369,7 +369,7 @@ class Widget {
|
||||
|
||||
return {
|
||||
fitsAbove,
|
||||
aboveTop: Math.max(aboveTop, TOP_PADDING),
|
||||
aboveTop: aboveTop,
|
||||
aboveLeft,
|
||||
fitsBelow,
|
||||
belowTop,
|
||||
|
||||
@@ -121,6 +121,13 @@ export class RangeUtil {
|
||||
startChildIndex = Math.min(max, Math.max(min, startChildIndex));
|
||||
endChildIndex = Math.min(max, Math.max(min, endChildIndex));
|
||||
|
||||
if (startChildIndex === endChildIndex && startOffset === endOffset && startOffset === 0) {
|
||||
// We must find the position at the beginning of a <span>
|
||||
// To cover cases of empty <span>s, aboid using a range and use the <span>'s bounding box
|
||||
const clientRects = domNode.children[startChildIndex].getClientRects();
|
||||
return this._createHorizontalRangesFromClientRects(clientRects, clientRectDeltaLeft);
|
||||
}
|
||||
|
||||
// If crossing over to a span only to select offset 0, then use the previous span's maximum offset
|
||||
// Chrome is buggy and doesn't handle 0 offsets well sometimes.
|
||||
if (startChildIndex !== endChildIndex) {
|
||||
|
||||
@@ -195,7 +195,7 @@ export class ViewLine implements IVisibleLine {
|
||||
const endColumn = (selection.endLineNumber === lineNumber ? selection.endColumn : lineData.maxColumn);
|
||||
|
||||
if (startColumn < endColumn) {
|
||||
if (this._options.renderWhitespace !== 'selection') {
|
||||
if (options.themeType === HIGH_CONTRAST || this._options.renderWhitespace !== 'selection') {
|
||||
actualInlineDecorations.push(new LineDecoration(startColumn, endColumn, 'inline-selected-text', InlineDecorationType.Regular));
|
||||
} else {
|
||||
if (!selectionsOnLine) {
|
||||
|
||||
@@ -1149,15 +1149,15 @@ class InnerMinimap extends Disposable {
|
||||
this._gestureInProgress = true;
|
||||
this.scrollDueToTouchEvent(e);
|
||||
}
|
||||
});
|
||||
}, { passive: false });
|
||||
|
||||
this._sliderTouchMoveListener = dom.addStandardDisposableListener(this._domNode.domNode, EventType.Change, (e: GestureEvent) => {
|
||||
this._sliderTouchMoveListener = dom.addDisposableListener(this._domNode.domNode, EventType.Change, (e: GestureEvent) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
if (this._lastRenderData && this._gestureInProgress) {
|
||||
this.scrollDueToTouchEvent(e);
|
||||
}
|
||||
});
|
||||
}, { passive: false });
|
||||
|
||||
this._sliderTouchEndListener = dom.addStandardDisposableListener(this._domNode.domNode, EventType.End, (e: GestureEvent) => {
|
||||
e.preventDefault();
|
||||
|
||||
@@ -523,7 +523,7 @@ const editorConfiguration: IConfigurationNode = {
|
||||
'diffEditor.ignoreTrimWhitespace': {
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
description: nls.localize('ignoreTrimWhitespace', "Controls whether the diff editor shows changes in leading or trailing whitespace as diffs.")
|
||||
description: nls.localize('ignoreTrimWhitespace', "When enabled, the diff editor ignores changes in leading or trailing whitespace.")
|
||||
},
|
||||
'diffEditor.renderIndicators': {
|
||||
type: 'boolean',
|
||||
|
||||
@@ -506,6 +506,11 @@ export interface IEditorOptions {
|
||||
* Defaults to 'mouseover'.
|
||||
*/
|
||||
showFoldingControls?: 'always' | 'mouseover';
|
||||
/**
|
||||
* Controls whether clicking on the empty content after a folded line will unfold the line.
|
||||
* Defaults to false.
|
||||
*/
|
||||
unfoldOnClickInEmptyContent?: boolean;
|
||||
/**
|
||||
* Enable highlighting of matching brackets.
|
||||
* Defaults to 'always'.
|
||||
@@ -3328,6 +3333,7 @@ export const enum EditorOption {
|
||||
folding,
|
||||
foldingStrategy,
|
||||
foldingHighlight,
|
||||
unfoldOnClickInEmptyContent,
|
||||
fontFamily,
|
||||
fontInfo,
|
||||
fontLigatures,
|
||||
@@ -3627,6 +3633,10 @@ export const EditorOptions = {
|
||||
EditorOption.foldingHighlight, 'foldingHighlight', true,
|
||||
{ description: nls.localize('foldingHighlight', "Controls whether the editor should highlight folded ranges.") }
|
||||
)),
|
||||
unfoldOnClickInEmptyContent: register(new EditorBooleanOption(
|
||||
EditorOption.unfoldOnClickInEmptyContent, 'unfoldOnClickInEmptyContent', false,
|
||||
{ description: nls.localize('unfoldOnClickInEmptyContent', "Controls whether clicking on the empty content after a folded line will unfold the line.") }
|
||||
)),
|
||||
fontFamily: register(new EditorStringOption(
|
||||
EditorOption.fontFamily, 'fontFamily', EDITOR_FONT_DEFAULTS.fontFamily,
|
||||
{ description: nls.localize('fontFamily', "Controls the font family.") }
|
||||
|
||||
@@ -297,7 +297,7 @@ export class Cursor extends viewEvents.ViewEventEmitter implements ICursors {
|
||||
return this._cursors.getAll();
|
||||
}
|
||||
|
||||
public setStates(source: string, reason: CursorChangeReason, states: PartialCursorState[] | null): void {
|
||||
public setStates(source: string, reason: CursorChangeReason, states: PartialCursorState[] | null): boolean {
|
||||
if (states !== null && states.length > Cursor.MAX_CURSOR_COUNT) {
|
||||
states = states.slice(0, Cursor.MAX_CURSOR_COUNT);
|
||||
this._onDidReachMaxCursorCount.fire(undefined);
|
||||
@@ -311,7 +311,7 @@ export class Cursor extends viewEvents.ViewEventEmitter implements ICursors {
|
||||
|
||||
this._validateAutoClosedActions();
|
||||
|
||||
this._emitStateChangedIfNecessary(source, reason, oldState);
|
||||
return this._emitStateChangedIfNecessary(source, reason, oldState);
|
||||
}
|
||||
|
||||
public setColumnSelectData(columnSelectData: IColumnSelectData): void {
|
||||
@@ -411,7 +411,9 @@ export class Cursor extends viewEvents.ViewEventEmitter implements ICursors {
|
||||
} else {
|
||||
if (this._hasFocus && e.resultingSelection && e.resultingSelection.length > 0) {
|
||||
const cursorState = CursorState.fromModelSelections(e.resultingSelection);
|
||||
this.setStates('modelChange', e.isUndoing ? CursorChangeReason.Undo : e.isRedoing ? CursorChangeReason.Redo : CursorChangeReason.RecoverFromMarkers, cursorState);
|
||||
if (this.setStates('modelChange', e.isUndoing ? CursorChangeReason.Undo : e.isRedoing ? CursorChangeReason.Redo : CursorChangeReason.RecoverFromMarkers, cursorState)) {
|
||||
this._revealRange('modelChange', RevealTarget.Primary, viewEvents.VerticalRevealType.Simple, true, editorCommon.ScrollType.Smooth);
|
||||
}
|
||||
} else {
|
||||
const selectionsFromMarkers = this._cursors.readSelectionFromMarkers();
|
||||
this.setStates('modelChange', CursorChangeReason.RecoverFromMarkers, CursorState.fromModelSelections(selectionsFromMarkers));
|
||||
|
||||
@@ -199,85 +199,86 @@ export enum EditorOption {
|
||||
folding = 31,
|
||||
foldingStrategy = 32,
|
||||
foldingHighlight = 33,
|
||||
fontFamily = 34,
|
||||
fontInfo = 35,
|
||||
fontLigatures = 36,
|
||||
fontSize = 37,
|
||||
fontWeight = 38,
|
||||
formatOnPaste = 39,
|
||||
formatOnType = 40,
|
||||
glyphMargin = 41,
|
||||
gotoLocation = 42,
|
||||
hideCursorInOverviewRuler = 43,
|
||||
highlightActiveIndentGuide = 44,
|
||||
hover = 45,
|
||||
inDiffEditor = 46,
|
||||
letterSpacing = 47,
|
||||
lightbulb = 48,
|
||||
lineDecorationsWidth = 49,
|
||||
lineHeight = 50,
|
||||
lineNumbers = 51,
|
||||
lineNumbersMinChars = 52,
|
||||
links = 53,
|
||||
matchBrackets = 54,
|
||||
minimap = 55,
|
||||
mouseStyle = 56,
|
||||
mouseWheelScrollSensitivity = 57,
|
||||
mouseWheelZoom = 58,
|
||||
multiCursorMergeOverlapping = 59,
|
||||
multiCursorModifier = 60,
|
||||
multiCursorPaste = 61,
|
||||
occurrencesHighlight = 62,
|
||||
overviewRulerBorder = 63,
|
||||
overviewRulerLanes = 64,
|
||||
padding = 65,
|
||||
parameterHints = 66,
|
||||
peekWidgetDefaultFocus = 67,
|
||||
definitionLinkOpensInPeek = 68,
|
||||
quickSuggestions = 69,
|
||||
quickSuggestionsDelay = 70,
|
||||
readOnly = 71,
|
||||
renderControlCharacters = 72,
|
||||
renderIndentGuides = 73,
|
||||
renderFinalNewline = 74,
|
||||
renderLineHighlight = 75,
|
||||
renderValidationDecorations = 76,
|
||||
renderWhitespace = 77,
|
||||
revealHorizontalRightPadding = 78,
|
||||
roundedSelection = 79,
|
||||
rulers = 80,
|
||||
scrollbar = 81,
|
||||
scrollBeyondLastColumn = 82,
|
||||
scrollBeyondLastLine = 83,
|
||||
scrollPredominantAxis = 84,
|
||||
selectionClipboard = 85,
|
||||
selectionHighlight = 86,
|
||||
selectOnLineNumbers = 87,
|
||||
showFoldingControls = 88,
|
||||
showUnused = 89,
|
||||
snippetSuggestions = 90,
|
||||
smoothScrolling = 91,
|
||||
stopRenderingLineAfter = 92,
|
||||
suggest = 93,
|
||||
suggestFontSize = 94,
|
||||
suggestLineHeight = 95,
|
||||
suggestOnTriggerCharacters = 96,
|
||||
suggestSelection = 97,
|
||||
tabCompletion = 98,
|
||||
useTabStops = 99,
|
||||
wordSeparators = 100,
|
||||
wordWrap = 101,
|
||||
wordWrapBreakAfterCharacters = 102,
|
||||
wordWrapBreakBeforeCharacters = 103,
|
||||
wordWrapColumn = 104,
|
||||
wordWrapMinified = 105,
|
||||
wrappingIndent = 106,
|
||||
wrappingStrategy = 107,
|
||||
editorClassName = 108,
|
||||
pixelRatio = 109,
|
||||
tabFocusMode = 110,
|
||||
layoutInfo = 111,
|
||||
wrappingInfo = 112
|
||||
unfoldOnClickInEmptyContent = 34,
|
||||
fontFamily = 35,
|
||||
fontInfo = 36,
|
||||
fontLigatures = 37,
|
||||
fontSize = 38,
|
||||
fontWeight = 39,
|
||||
formatOnPaste = 40,
|
||||
formatOnType = 41,
|
||||
glyphMargin = 42,
|
||||
gotoLocation = 43,
|
||||
hideCursorInOverviewRuler = 44,
|
||||
highlightActiveIndentGuide = 45,
|
||||
hover = 46,
|
||||
inDiffEditor = 47,
|
||||
letterSpacing = 48,
|
||||
lightbulb = 49,
|
||||
lineDecorationsWidth = 50,
|
||||
lineHeight = 51,
|
||||
lineNumbers = 52,
|
||||
lineNumbersMinChars = 53,
|
||||
links = 54,
|
||||
matchBrackets = 55,
|
||||
minimap = 56,
|
||||
mouseStyle = 57,
|
||||
mouseWheelScrollSensitivity = 58,
|
||||
mouseWheelZoom = 59,
|
||||
multiCursorMergeOverlapping = 60,
|
||||
multiCursorModifier = 61,
|
||||
multiCursorPaste = 62,
|
||||
occurrencesHighlight = 63,
|
||||
overviewRulerBorder = 64,
|
||||
overviewRulerLanes = 65,
|
||||
padding = 66,
|
||||
parameterHints = 67,
|
||||
peekWidgetDefaultFocus = 68,
|
||||
definitionLinkOpensInPeek = 69,
|
||||
quickSuggestions = 70,
|
||||
quickSuggestionsDelay = 71,
|
||||
readOnly = 72,
|
||||
renderControlCharacters = 73,
|
||||
renderIndentGuides = 74,
|
||||
renderFinalNewline = 75,
|
||||
renderLineHighlight = 76,
|
||||
renderValidationDecorations = 77,
|
||||
renderWhitespace = 78,
|
||||
revealHorizontalRightPadding = 79,
|
||||
roundedSelection = 80,
|
||||
rulers = 81,
|
||||
scrollbar = 82,
|
||||
scrollBeyondLastColumn = 83,
|
||||
scrollBeyondLastLine = 84,
|
||||
scrollPredominantAxis = 85,
|
||||
selectionClipboard = 86,
|
||||
selectionHighlight = 87,
|
||||
selectOnLineNumbers = 88,
|
||||
showFoldingControls = 89,
|
||||
showUnused = 90,
|
||||
snippetSuggestions = 91,
|
||||
smoothScrolling = 92,
|
||||
stopRenderingLineAfter = 93,
|
||||
suggest = 94,
|
||||
suggestFontSize = 95,
|
||||
suggestLineHeight = 96,
|
||||
suggestOnTriggerCharacters = 97,
|
||||
suggestSelection = 98,
|
||||
tabCompletion = 99,
|
||||
useTabStops = 100,
|
||||
wordSeparators = 101,
|
||||
wordWrap = 102,
|
||||
wordWrapBreakAfterCharacters = 103,
|
||||
wordWrapBreakBeforeCharacters = 104,
|
||||
wordWrapColumn = 105,
|
||||
wordWrapMinified = 106,
|
||||
wrappingIndent = 107,
|
||||
wrappingStrategy = 108,
|
||||
editorClassName = 109,
|
||||
pixelRatio = 110,
|
||||
tabFocusMode = 111,
|
||||
layoutInfo = 112,
|
||||
wrappingInfo = 113
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
import * as strings from 'vs/base/common/strings';
|
||||
import { Constants } from 'vs/base/common/uint';
|
||||
import { InlineDecoration, InlineDecorationType } from 'vs/editor/common/viewModel/viewModel';
|
||||
import { LinePartMetadata } from 'vs/editor/common/viewLayout/viewLineRenderer';
|
||||
|
||||
export class LineDecoration {
|
||||
_lineDecorationBrand: void;
|
||||
@@ -28,8 +29,8 @@ export class LineDecoration {
|
||||
}
|
||||
|
||||
public static equalsArr(a: LineDecoration[], b: LineDecoration[]): boolean {
|
||||
let aLen = a.length;
|
||||
let bLen = b.length;
|
||||
const aLen = a.length;
|
||||
const bLen = b.length;
|
||||
if (aLen !== bLen) {
|
||||
return false;
|
||||
}
|
||||
@@ -49,8 +50,8 @@ export class LineDecoration {
|
||||
let result: LineDecoration[] = [], resultLen = 0;
|
||||
|
||||
for (let i = 0, len = lineDecorations.length; i < len; i++) {
|
||||
let d = lineDecorations[i];
|
||||
let range = d.range;
|
||||
const d = lineDecorations[i];
|
||||
const range = d.range;
|
||||
|
||||
if (range.endLineNumber < lineNumber || range.startLineNumber > lineNumber) {
|
||||
// Ignore decorations that sit outside this line
|
||||
@@ -62,8 +63,8 @@ export class LineDecoration {
|
||||
continue;
|
||||
}
|
||||
|
||||
let startColumn = (range.startLineNumber === lineNumber ? range.startColumn : minLineColumn);
|
||||
let endColumn = (range.endLineNumber === lineNumber ? range.endColumn : maxLineColumn);
|
||||
const startColumn = (range.startLineNumber === lineNumber ? range.startColumn : minLineColumn);
|
||||
const endColumn = (range.endLineNumber === lineNumber ? range.endColumn : maxLineColumn);
|
||||
|
||||
result[resultLen++] = new LineDecoration(startColumn, endColumn, d.inlineClassName, d.type);
|
||||
}
|
||||
@@ -71,16 +72,25 @@ export class LineDecoration {
|
||||
return result;
|
||||
}
|
||||
|
||||
private static _typeCompare(a: InlineDecorationType, b: InlineDecorationType): number {
|
||||
const ORDER = [2, 0, 1, 3];
|
||||
return ORDER[a] - ORDER[b];
|
||||
}
|
||||
|
||||
public static compare(a: LineDecoration, b: LineDecoration): number {
|
||||
if (a.startColumn === b.startColumn) {
|
||||
if (a.endColumn === b.endColumn) {
|
||||
if (a.className < b.className) {
|
||||
return -1;
|
||||
const typeCmp = LineDecoration._typeCompare(a.type, b.type);
|
||||
if (typeCmp === 0) {
|
||||
if (a.className < b.className) {
|
||||
return -1;
|
||||
}
|
||||
if (a.className > b.className) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
if (a.className > b.className) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
return typeCmp;
|
||||
}
|
||||
return a.endColumn - b.endColumn;
|
||||
}
|
||||
@@ -92,11 +102,13 @@ export class DecorationSegment {
|
||||
startOffset: number;
|
||||
endOffset: number;
|
||||
className: string;
|
||||
metadata: number;
|
||||
|
||||
constructor(startOffset: number, endOffset: number, className: string) {
|
||||
constructor(startOffset: number, endOffset: number, className: string, metadata: number) {
|
||||
this.startOffset = startOffset;
|
||||
this.endOffset = endOffset;
|
||||
this.className = className;
|
||||
this.metadata = metadata;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -104,13 +116,23 @@ class Stack {
|
||||
public count: number;
|
||||
private readonly stopOffsets: number[];
|
||||
private readonly classNames: string[];
|
||||
private readonly metadata: number[];
|
||||
|
||||
constructor() {
|
||||
this.stopOffsets = [];
|
||||
this.classNames = [];
|
||||
this.metadata = [];
|
||||
this.count = 0;
|
||||
}
|
||||
|
||||
private static _metadata(metadata: number[]): number {
|
||||
let result = 0;
|
||||
for (let i = 0, len = metadata.length; i < len; i++) {
|
||||
result |= metadata[i];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public consumeLowerThan(maxStopOffset: number, nextStartOffset: number, result: DecorationSegment[]): number {
|
||||
|
||||
while (this.count > 0 && this.stopOffsets[0] < maxStopOffset) {
|
||||
@@ -122,34 +144,37 @@ class Stack {
|
||||
}
|
||||
|
||||
// Basically we are consuming the first i + 1 elements of the stack
|
||||
result.push(new DecorationSegment(nextStartOffset, this.stopOffsets[i], this.classNames.join(' ')));
|
||||
result.push(new DecorationSegment(nextStartOffset, this.stopOffsets[i], this.classNames.join(' '), Stack._metadata(this.metadata)));
|
||||
nextStartOffset = this.stopOffsets[i] + 1;
|
||||
|
||||
// Consume them
|
||||
this.stopOffsets.splice(0, i + 1);
|
||||
this.classNames.splice(0, i + 1);
|
||||
this.metadata.splice(0, i + 1);
|
||||
this.count -= (i + 1);
|
||||
}
|
||||
|
||||
if (this.count > 0 && nextStartOffset < maxStopOffset) {
|
||||
result.push(new DecorationSegment(nextStartOffset, maxStopOffset - 1, this.classNames.join(' ')));
|
||||
result.push(new DecorationSegment(nextStartOffset, maxStopOffset - 1, this.classNames.join(' '), Stack._metadata(this.metadata)));
|
||||
nextStartOffset = maxStopOffset;
|
||||
}
|
||||
|
||||
return nextStartOffset;
|
||||
}
|
||||
|
||||
public insert(stopOffset: number, className: string): void {
|
||||
public insert(stopOffset: number, className: string, metadata: number): void {
|
||||
if (this.count === 0 || this.stopOffsets[this.count - 1] <= stopOffset) {
|
||||
// Insert at the end
|
||||
this.stopOffsets.push(stopOffset);
|
||||
this.classNames.push(className);
|
||||
this.metadata.push(metadata);
|
||||
} else {
|
||||
// Find the insertion position for `stopOffset`
|
||||
for (let i = 0; i < this.count; i++) {
|
||||
if (this.stopOffsets[i] >= stopOffset) {
|
||||
this.stopOffsets.splice(i, 0, stopOffset);
|
||||
this.classNames.splice(i, 0, className);
|
||||
this.metadata.splice(i, 0, metadata);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -170,14 +195,21 @@ export class LineDecorationsNormalizer {
|
||||
|
||||
let result: DecorationSegment[] = [];
|
||||
|
||||
let stack = new Stack();
|
||||
const stack = new Stack();
|
||||
let nextStartOffset = 0;
|
||||
|
||||
for (let i = 0, len = lineDecorations.length; i < len; i++) {
|
||||
let d = lineDecorations[i];
|
||||
const d = lineDecorations[i];
|
||||
let startColumn = d.startColumn;
|
||||
let endColumn = d.endColumn;
|
||||
let className = d.className;
|
||||
const className = d.className;
|
||||
const metadata = (
|
||||
d.type === InlineDecorationType.Before
|
||||
? LinePartMetadata.PSEUDO_BEFORE
|
||||
: d.type === InlineDecorationType.After
|
||||
? LinePartMetadata.PSEUDO_AFTER
|
||||
: 0
|
||||
);
|
||||
|
||||
// If the position would end up in the middle of a high-low surrogate pair, we move it to before the pair
|
||||
if (startColumn > 1) {
|
||||
@@ -194,15 +226,15 @@ export class LineDecorationsNormalizer {
|
||||
}
|
||||
}
|
||||
|
||||
let currentStartOffset = startColumn - 1;
|
||||
let currentEndOffset = endColumn - 2;
|
||||
const currentStartOffset = startColumn - 1;
|
||||
const currentEndOffset = endColumn - 2;
|
||||
|
||||
nextStartOffset = stack.consumeLowerThan(currentStartOffset, nextStartOffset, result);
|
||||
|
||||
if (stack.count === 0) {
|
||||
nextStartOffset = currentStartOffset;
|
||||
}
|
||||
stack.insert(currentEndOffset, className);
|
||||
stack.insert(currentEndOffset, className, metadata);
|
||||
}
|
||||
|
||||
stack.consumeLowerThan(Constants.MAX_SAFE_SMALL_INTEGER, nextStartOffset, result);
|
||||
|
||||
@@ -17,6 +17,16 @@ export const enum RenderWhitespace {
|
||||
All = 3
|
||||
}
|
||||
|
||||
export const enum LinePartMetadata {
|
||||
IS_WHITESPACE = 1,
|
||||
PSEUDO_BEFORE = 2,
|
||||
PSEUDO_AFTER = 4,
|
||||
|
||||
IS_WHITESPACE_MASK = 0b001,
|
||||
PSEUDO_BEFORE_MASK = 0b010,
|
||||
PSEUDO_AFTER_MASK = 0b100,
|
||||
}
|
||||
|
||||
class LinePart {
|
||||
_linePartBrand: void;
|
||||
|
||||
@@ -25,10 +35,16 @@ class LinePart {
|
||||
*/
|
||||
public readonly endIndex: number;
|
||||
public readonly type: string;
|
||||
public readonly metadata: number;
|
||||
|
||||
constructor(endIndex: number, type: string) {
|
||||
constructor(endIndex: number, type: string, metadata: number) {
|
||||
this.endIndex = endIndex;
|
||||
this.type = type;
|
||||
this.metadata = metadata;
|
||||
}
|
||||
|
||||
public isWhitespace(): boolean {
|
||||
return (this.metadata & LinePartMetadata.IS_WHITESPACE_MASK ? true : false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -470,7 +486,7 @@ function transformAndRemoveOverflowing(tokens: IViewLineTokens, fauxIndentLength
|
||||
|
||||
// The faux indent part of the line should have no token type
|
||||
if (fauxIndentLength > 0) {
|
||||
result[resultLen++] = new LinePart(fauxIndentLength, '');
|
||||
result[resultLen++] = new LinePart(fauxIndentLength, '', 0);
|
||||
}
|
||||
|
||||
for (let tokenIndex = 0, tokensLen = tokens.getCount(); tokenIndex < tokensLen; tokenIndex++) {
|
||||
@@ -481,10 +497,10 @@ function transformAndRemoveOverflowing(tokens: IViewLineTokens, fauxIndentLength
|
||||
}
|
||||
const type = tokens.getClassName(tokenIndex);
|
||||
if (endIndex >= len) {
|
||||
result[resultLen++] = new LinePart(len, type);
|
||||
result[resultLen++] = new LinePart(len, type, 0);
|
||||
break;
|
||||
}
|
||||
result[resultLen++] = new LinePart(endIndex, type);
|
||||
result[resultLen++] = new LinePart(endIndex, type, 0);
|
||||
}
|
||||
|
||||
return result;
|
||||
@@ -513,6 +529,7 @@ function splitLargeTokens(lineContent: string, tokens: LinePart[], onlyAtSpaces:
|
||||
const tokenEndIndex = token.endIndex;
|
||||
if (lastTokenEndIndex + Constants.LongToken < tokenEndIndex) {
|
||||
const tokenType = token.type;
|
||||
const tokenMetadata = token.metadata;
|
||||
|
||||
let lastSpaceOffset = -1;
|
||||
let currTokenStart = lastTokenEndIndex;
|
||||
@@ -522,13 +539,13 @@ function splitLargeTokens(lineContent: string, tokens: LinePart[], onlyAtSpaces:
|
||||
}
|
||||
if (lastSpaceOffset !== -1 && j - currTokenStart >= Constants.LongToken) {
|
||||
// Split at `lastSpaceOffset` + 1
|
||||
result[resultLen++] = new LinePart(lastSpaceOffset + 1, tokenType);
|
||||
result[resultLen++] = new LinePart(lastSpaceOffset + 1, tokenType, tokenMetadata);
|
||||
currTokenStart = lastSpaceOffset + 1;
|
||||
lastSpaceOffset = -1;
|
||||
}
|
||||
}
|
||||
if (currTokenStart !== tokenEndIndex) {
|
||||
result[resultLen++] = new LinePart(tokenEndIndex, tokenType);
|
||||
result[resultLen++] = new LinePart(tokenEndIndex, tokenType, tokenMetadata);
|
||||
}
|
||||
} else {
|
||||
result[resultLen++] = token;
|
||||
@@ -544,12 +561,13 @@ function splitLargeTokens(lineContent: string, tokens: LinePart[], onlyAtSpaces:
|
||||
let diff = (tokenEndIndex - lastTokenEndIndex);
|
||||
if (diff > Constants.LongToken) {
|
||||
const tokenType = token.type;
|
||||
const tokenMetadata = token.metadata;
|
||||
const piecesCount = Math.ceil(diff / Constants.LongToken);
|
||||
for (let j = 1; j < piecesCount; j++) {
|
||||
let pieceEndIndex = lastTokenEndIndex + (j * Constants.LongToken);
|
||||
result[resultLen++] = new LinePart(pieceEndIndex, tokenType);
|
||||
result[resultLen++] = new LinePart(pieceEndIndex, tokenType, tokenMetadata);
|
||||
}
|
||||
result[resultLen++] = new LinePart(tokenEndIndex, tokenType);
|
||||
result[resultLen++] = new LinePart(tokenEndIndex, tokenType, tokenMetadata);
|
||||
} else {
|
||||
result[resultLen++] = token;
|
||||
}
|
||||
@@ -640,17 +658,17 @@ function _applyRenderWhitespace(input: RenderLineInput, lineContent: string, len
|
||||
if (generateLinePartForEachWhitespace) {
|
||||
const lastEndIndex = (resultLen > 0 ? result[resultLen - 1].endIndex : fauxIndentLength);
|
||||
for (let i = lastEndIndex + 1; i <= charIndex; i++) {
|
||||
result[resultLen++] = new LinePart(i, 'mtkw');
|
||||
result[resultLen++] = new LinePart(i, 'mtkw', LinePartMetadata.IS_WHITESPACE);
|
||||
}
|
||||
} else {
|
||||
result[resultLen++] = new LinePart(charIndex, 'mtkw');
|
||||
result[resultLen++] = new LinePart(charIndex, 'mtkw', LinePartMetadata.IS_WHITESPACE);
|
||||
}
|
||||
tmpIndent = tmpIndent % tabSize;
|
||||
}
|
||||
} else {
|
||||
// was in regular token
|
||||
if (charIndex === tokenEndIndex || (isInWhitespace && charIndex > fauxIndentLength)) {
|
||||
result[resultLen++] = new LinePart(charIndex, tokenType);
|
||||
result[resultLen++] = new LinePart(charIndex, tokenType, 0);
|
||||
tmpIndent = tmpIndent % tabSize;
|
||||
}
|
||||
}
|
||||
@@ -693,13 +711,13 @@ function _applyRenderWhitespace(input: RenderLineInput, lineContent: string, len
|
||||
if (generateLinePartForEachWhitespace) {
|
||||
const lastEndIndex = (resultLen > 0 ? result[resultLen - 1].endIndex : fauxIndentLength);
|
||||
for (let i = lastEndIndex + 1; i <= len; i++) {
|
||||
result[resultLen++] = new LinePart(i, 'mtkw');
|
||||
result[resultLen++] = new LinePart(i, 'mtkw', LinePartMetadata.IS_WHITESPACE);
|
||||
}
|
||||
} else {
|
||||
result[resultLen++] = new LinePart(len, 'mtkw');
|
||||
result[resultLen++] = new LinePart(len, 'mtkw', LinePartMetadata.IS_WHITESPACE);
|
||||
}
|
||||
} else {
|
||||
result[resultLen++] = new LinePart(len, tokenType);
|
||||
result[resultLen++] = new LinePart(len, tokenType, 0);
|
||||
}
|
||||
|
||||
return result;
|
||||
@@ -720,42 +738,45 @@ function _applyInlineDecorations(lineContent: string, len: number, tokens: LineP
|
||||
const token = tokens[tokenIndex];
|
||||
const tokenEndIndex = token.endIndex;
|
||||
const tokenType = token.type;
|
||||
const tokenMetadata = token.metadata;
|
||||
|
||||
while (lineDecorationIndex < lineDecorationsLen && lineDecorations[lineDecorationIndex].startOffset < tokenEndIndex) {
|
||||
const lineDecoration = lineDecorations[lineDecorationIndex];
|
||||
|
||||
if (lineDecoration.startOffset > lastResultEndIndex) {
|
||||
lastResultEndIndex = lineDecoration.startOffset;
|
||||
result[resultLen++] = new LinePart(lastResultEndIndex, tokenType);
|
||||
result[resultLen++] = new LinePart(lastResultEndIndex, tokenType, tokenMetadata);
|
||||
}
|
||||
|
||||
if (lineDecoration.endOffset + 1 <= tokenEndIndex) {
|
||||
// This line decoration ends before this token ends
|
||||
lastResultEndIndex = lineDecoration.endOffset + 1;
|
||||
result[resultLen++] = new LinePart(lastResultEndIndex, tokenType + ' ' + lineDecoration.className);
|
||||
result[resultLen++] = new LinePart(lastResultEndIndex, tokenType + ' ' + lineDecoration.className, tokenMetadata | lineDecoration.metadata);
|
||||
lineDecorationIndex++;
|
||||
} else {
|
||||
// This line decoration continues on to the next token
|
||||
lastResultEndIndex = tokenEndIndex;
|
||||
result[resultLen++] = new LinePart(lastResultEndIndex, tokenType + ' ' + lineDecoration.className);
|
||||
result[resultLen++] = new LinePart(lastResultEndIndex, tokenType + ' ' + lineDecoration.className, tokenMetadata | lineDecoration.metadata);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (tokenEndIndex > lastResultEndIndex) {
|
||||
lastResultEndIndex = tokenEndIndex;
|
||||
result[resultLen++] = new LinePart(lastResultEndIndex, tokenType);
|
||||
result[resultLen++] = new LinePart(lastResultEndIndex, tokenType, tokenMetadata);
|
||||
}
|
||||
}
|
||||
|
||||
const lastTokenEndIndex = tokens[tokens.length - 1].endIndex;
|
||||
if (lineDecorationIndex < lineDecorationsLen && lineDecorations[lineDecorationIndex].startOffset === lastTokenEndIndex) {
|
||||
let classNames: string[] = [];
|
||||
let metadata = 0;
|
||||
while (lineDecorationIndex < lineDecorationsLen && lineDecorations[lineDecorationIndex].startOffset === lastTokenEndIndex) {
|
||||
classNames.push(lineDecorations[lineDecorationIndex].className);
|
||||
metadata |= lineDecorations[lineDecorationIndex].metadata;
|
||||
lineDecorationIndex++;
|
||||
}
|
||||
result[resultLen++] = new LinePart(lastResultEndIndex, classNames.join(' '));
|
||||
result[resultLen++] = new LinePart(lastResultEndIndex, classNames.join(' '), metadata);
|
||||
}
|
||||
|
||||
return result;
|
||||
@@ -788,6 +809,7 @@ function _renderLine(input: ResolvedRenderLineInput, sb: IStringBuilder): Render
|
||||
let visibleColumn = startVisibleColumn;
|
||||
let charOffsetInPart = 0;
|
||||
|
||||
let partDisplacement = 0;
|
||||
let prevPartContentCnt = 0;
|
||||
let partAbsoluteOffset = 0;
|
||||
|
||||
@@ -799,8 +821,9 @@ function _renderLine(input: ResolvedRenderLineInput, sb: IStringBuilder): Render
|
||||
const part = parts[partIndex];
|
||||
const partEndIndex = part.endIndex;
|
||||
const partType = part.type;
|
||||
const partRendersWhitespace = (renderWhitespace !== RenderWhitespace.None && (partType.indexOf('mtkw') >= 0));
|
||||
const partRendersWhitespace = (renderWhitespace !== RenderWhitespace.None && part.isWhitespace());
|
||||
const partRendersWhitespaceWithWidth = partRendersWhitespace && !fontIsMonospace && (partType === 'mtkw'/*only whitespace*/ || !containsForeignElements);
|
||||
const partIsEmptyAndHasPseudoAfter = (charIndex === partEndIndex && part.metadata === LinePartMetadata.PSEUDO_AFTER);
|
||||
charOffsetInPart = 0;
|
||||
|
||||
sb.appendASCIIString('<span class="');
|
||||
@@ -832,7 +855,8 @@ function _renderLine(input: ResolvedRenderLineInput, sb: IStringBuilder): Render
|
||||
sb.appendASCII(CharCode.GreaterThan);
|
||||
|
||||
for (; charIndex < partEndIndex; charIndex++) {
|
||||
characterMapping.setPartData(charIndex, partIndex, charOffsetInPart, partAbsoluteOffset);
|
||||
characterMapping.setPartData(charIndex, partIndex - partDisplacement, charOffsetInPart, partAbsoluteOffset);
|
||||
partDisplacement = 0;
|
||||
const charCode = lineContent.charCodeAt(charIndex);
|
||||
let charWidth: number;
|
||||
|
||||
@@ -872,7 +896,8 @@ function _renderLine(input: ResolvedRenderLineInput, sb: IStringBuilder): Render
|
||||
sb.appendASCII(CharCode.GreaterThan);
|
||||
|
||||
for (; charIndex < partEndIndex; charIndex++) {
|
||||
characterMapping.setPartData(charIndex, partIndex, charOffsetInPart, partAbsoluteOffset);
|
||||
characterMapping.setPartData(charIndex, partIndex - partDisplacement, charOffsetInPart, partAbsoluteOffset);
|
||||
partDisplacement = 0;
|
||||
const charCode = lineContent.charCodeAt(charIndex);
|
||||
|
||||
let producedCharacters = 1;
|
||||
@@ -933,6 +958,12 @@ function _renderLine(input: ResolvedRenderLineInput, sb: IStringBuilder): Render
|
||||
prevPartContentCnt = partContentCnt;
|
||||
}
|
||||
|
||||
if (partIsEmptyAndHasPseudoAfter) {
|
||||
partDisplacement++;
|
||||
} else {
|
||||
partDisplacement = 0;
|
||||
}
|
||||
|
||||
sb.appendASCIIString('</span>');
|
||||
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ import { FoldingDecorationProvider } from './foldingDecorations';
|
||||
import { FoldingRegions, FoldingRegion } from './foldingRanges';
|
||||
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
|
||||
import { ConfigurationChangedEvent, EditorOption } from 'vs/editor/common/config/editorOptions';
|
||||
import { IMarginData } from 'vs/editor/browser/controller/mouseTarget';
|
||||
import { IMarginData, IEmptyContentData } from 'vs/editor/browser/controller/mouseTarget';
|
||||
import { HiddenRangeModel } from 'vs/editor/contrib/folding/hiddenRangeModel';
|
||||
import { IRange } from 'vs/editor/common/core/range';
|
||||
import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry';
|
||||
@@ -62,6 +62,7 @@ export class FoldingController extends Disposable implements IEditorContribution
|
||||
private readonly editor: ICodeEditor;
|
||||
private _isEnabled: boolean;
|
||||
private _useFoldingProviders: boolean;
|
||||
private _unfoldOnClickInEmptyContent: boolean;
|
||||
|
||||
private readonly foldingDecorationProvider: FoldingDecorationProvider;
|
||||
|
||||
@@ -91,6 +92,7 @@ export class FoldingController extends Disposable implements IEditorContribution
|
||||
const options = this.editor.getOptions();
|
||||
this._isEnabled = options.get(EditorOption.folding);
|
||||
this._useFoldingProviders = options.get(EditorOption.foldingStrategy) !== 'indentation';
|
||||
this._unfoldOnClickInEmptyContent = options.get(EditorOption.unfoldOnClickInEmptyContent);
|
||||
|
||||
this.foldingModel = null;
|
||||
this.hiddenRangeModel = null;
|
||||
@@ -128,6 +130,9 @@ export class FoldingController extends Disposable implements IEditorContribution
|
||||
this._useFoldingProviders = options.get(EditorOption.foldingStrategy) !== 'indentation';
|
||||
this.onFoldingStrategyChanged();
|
||||
}
|
||||
if (e.hasChanged(EditorOption.unfoldOnClickInEmptyContent)) {
|
||||
this._unfoldOnClickInEmptyContent = options.get(EditorOption.unfoldOnClickInEmptyContent);
|
||||
}
|
||||
}));
|
||||
this.onModelChanged();
|
||||
}
|
||||
@@ -364,6 +369,15 @@ export class FoldingController extends Disposable implements IEditorContribution
|
||||
|
||||
iconClicked = true;
|
||||
break;
|
||||
case MouseTargetType.CONTENT_EMPTY: {
|
||||
if (this._unfoldOnClickInEmptyContent && this.hiddenRangeModel.hasRanges()) {
|
||||
const data = e.target.detail as IEmptyContentData;
|
||||
if (!data.isAfterLines) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
case MouseTargetType.CONTENT_TEXT: {
|
||||
if (this.hiddenRangeModel.hasRanges()) {
|
||||
let model = this.editor.getModel();
|
||||
|
||||
@@ -397,7 +397,7 @@ class MarkerNavigationAction extends EditorAction {
|
||||
|
||||
return editorService.openCodeEditor({
|
||||
resource: newMarker.resource,
|
||||
options: { pinned: false, revealIfOpened: true, selectionRevealType: TextEditorSelectionRevealType.CenterIfOutsideViewport, selection: newMarker }
|
||||
options: { pinned: false, revealIfOpened: true, selectionRevealType: TextEditorSelectionRevealType.NearTop, selection: newMarker }
|
||||
}, editor).then(editor => {
|
||||
if (!editor) {
|
||||
return undefined;
|
||||
|
||||
@@ -52,11 +52,11 @@ class MessageWidget {
|
||||
|
||||
const domNode = document.createElement('div');
|
||||
domNode.className = 'descriptioncontainer';
|
||||
domNode.setAttribute('aria-live', 'assertive');
|
||||
domNode.setAttribute('role', 'alert');
|
||||
|
||||
this._messageBlock = document.createElement('div');
|
||||
dom.addClass(this._messageBlock, 'message');
|
||||
this._messageBlock.setAttribute('aria-live', 'assertive');
|
||||
this._messageBlock.setAttribute('role', 'alert');
|
||||
domNode.appendChild(this._messageBlock);
|
||||
|
||||
this._relatedBlock = document.createElement('div');
|
||||
@@ -88,7 +88,8 @@ class MessageWidget {
|
||||
dispose(this._disposables);
|
||||
}
|
||||
|
||||
update({ source, message, relatedInformation, code }: IMarker): void {
|
||||
update(marker: IMarker): void {
|
||||
const { source, message, relatedInformation, code } = marker;
|
||||
let sourceAndCodeLength = (source?.length || 0) + '()'.length;
|
||||
if (code) {
|
||||
if (typeof code === 'string') {
|
||||
@@ -106,6 +107,7 @@ class MessageWidget {
|
||||
}
|
||||
|
||||
dom.clearNode(this._messageBlock);
|
||||
this._messageBlock.setAttribute('aria-label', this.getAriaLabel(marker));
|
||||
this._editor.applyFontInfo(this._messageBlock);
|
||||
let lastLineElement = this._messageBlock;
|
||||
for (const line of lines) {
|
||||
@@ -192,6 +194,32 @@ class MessageWidget {
|
||||
getHeightInLines(): number {
|
||||
return Math.min(17, this._lines);
|
||||
}
|
||||
|
||||
private getAriaLabel(marker: IMarker): string {
|
||||
let severityLabel = '';
|
||||
switch (marker.severity) {
|
||||
case MarkerSeverity.Error:
|
||||
severityLabel = nls.localize('Error', "Error");
|
||||
break;
|
||||
case MarkerSeverity.Warning:
|
||||
severityLabel = nls.localize('Warning', "Warning");
|
||||
break;
|
||||
case MarkerSeverity.Info:
|
||||
severityLabel = nls.localize('Info', "Info");
|
||||
break;
|
||||
case MarkerSeverity.Hint:
|
||||
severityLabel = nls.localize('Hint', "Hint");
|
||||
break;
|
||||
}
|
||||
|
||||
let ariaLabel = nls.localize('marker aria', "{0} at {1}. ", severityLabel, marker.startLineNumber + ':' + marker.startColumn);
|
||||
const model = this._editor.getModel();
|
||||
if (model && (marker.startLineNumber <= model.getLineCount()) && (marker.startLineNumber >= 1)) {
|
||||
const lineContent = model.getLineContent(marker.startLineNumber);
|
||||
ariaLabel = `${lineContent}, ${ariaLabel}`;
|
||||
}
|
||||
return ariaLabel;
|
||||
}
|
||||
}
|
||||
|
||||
export class MarkerNavigationWidget extends PeekViewWidget {
|
||||
@@ -316,7 +344,7 @@ export class MarkerNavigationWidget extends PeekViewWidget {
|
||||
}
|
||||
this._icon.className = `codicon ${SeverityIcon.className(MarkerSeverity.toSeverity(this._severity))}`;
|
||||
|
||||
this.editor.revealPositionInCenter(position, ScrollType.Smooth);
|
||||
this.editor.revealPositionNearTop(position, ScrollType.Smooth);
|
||||
this.editor.focus();
|
||||
}
|
||||
|
||||
|
||||
@@ -18,9 +18,9 @@ suite('Editor ViewLayout - ViewLineParts', () => {
|
||||
]);
|
||||
|
||||
assert.deepEqual(result, [
|
||||
new DecorationSegment(0, 1, 'c1'),
|
||||
new DecorationSegment(2, 2, 'c2 c1'),
|
||||
new DecorationSegment(3, 9, 'c1'),
|
||||
new DecorationSegment(0, 1, 'c1', 0),
|
||||
new DecorationSegment(2, 2, 'c2 c1', 0),
|
||||
new DecorationSegment(3, 9, 'c1', 0),
|
||||
]);
|
||||
});
|
||||
|
||||
@@ -32,8 +32,8 @@ suite('Editor ViewLayout - ViewLineParts', () => {
|
||||
]);
|
||||
|
||||
assert.deepEqual(result, [
|
||||
new DecorationSegment(14, 18, 'mtkw'),
|
||||
new DecorationSegment(19, 19, 'mtkw inline-folded')
|
||||
new DecorationSegment(14, 18, 'mtkw', 0),
|
||||
new DecorationSegment(19, 19, 'mtkw inline-folded', 0)
|
||||
]);
|
||||
});
|
||||
|
||||
@@ -66,24 +66,24 @@ suite('Editor ViewLayout - ViewLineParts', () => {
|
||||
new LineDecoration(1, 2, 'c1', InlineDecorationType.Regular),
|
||||
new LineDecoration(3, 4, 'c2', InlineDecorationType.Regular)
|
||||
]), [
|
||||
new DecorationSegment(0, 0, 'c1'),
|
||||
new DecorationSegment(2, 2, 'c2')
|
||||
new DecorationSegment(0, 0, 'c1', 0),
|
||||
new DecorationSegment(2, 2, 'c2', 0)
|
||||
]);
|
||||
|
||||
assert.deepEqual(LineDecorationsNormalizer.normalize('abcabcabcabcabcabcabcabcabcabc', [
|
||||
new LineDecoration(1, 3, 'c1', InlineDecorationType.Regular),
|
||||
new LineDecoration(3, 4, 'c2', InlineDecorationType.Regular)
|
||||
]), [
|
||||
new DecorationSegment(0, 1, 'c1'),
|
||||
new DecorationSegment(2, 2, 'c2')
|
||||
new DecorationSegment(0, 1, 'c1', 0),
|
||||
new DecorationSegment(2, 2, 'c2', 0)
|
||||
]);
|
||||
|
||||
assert.deepEqual(LineDecorationsNormalizer.normalize('abcabcabcabcabcabcabcabcabcabc', [
|
||||
new LineDecoration(1, 4, 'c1', InlineDecorationType.Regular),
|
||||
new LineDecoration(3, 4, 'c2', InlineDecorationType.Regular)
|
||||
]), [
|
||||
new DecorationSegment(0, 1, 'c1'),
|
||||
new DecorationSegment(2, 2, 'c1 c2')
|
||||
new DecorationSegment(0, 1, 'c1', 0),
|
||||
new DecorationSegment(2, 2, 'c1 c2', 0)
|
||||
]);
|
||||
|
||||
assert.deepEqual(LineDecorationsNormalizer.normalize('abcabcabcabcabcabcabcabcabcabc', [
|
||||
@@ -91,8 +91,8 @@ suite('Editor ViewLayout - ViewLineParts', () => {
|
||||
new LineDecoration(1, 4, 'c1*', InlineDecorationType.Regular),
|
||||
new LineDecoration(3, 4, 'c2', InlineDecorationType.Regular)
|
||||
]), [
|
||||
new DecorationSegment(0, 1, 'c1 c1*'),
|
||||
new DecorationSegment(2, 2, 'c1 c1* c2')
|
||||
new DecorationSegment(0, 1, 'c1 c1*', 0),
|
||||
new DecorationSegment(2, 2, 'c1 c1* c2', 0)
|
||||
]);
|
||||
|
||||
assert.deepEqual(LineDecorationsNormalizer.normalize('abcabcabcabcabcabcabcabcabcabc', [
|
||||
@@ -101,8 +101,8 @@ suite('Editor ViewLayout - ViewLineParts', () => {
|
||||
new LineDecoration(1, 4, 'c1**', InlineDecorationType.Regular),
|
||||
new LineDecoration(3, 4, 'c2', InlineDecorationType.Regular)
|
||||
]), [
|
||||
new DecorationSegment(0, 1, 'c1 c1* c1**'),
|
||||
new DecorationSegment(2, 2, 'c1 c1* c1** c2')
|
||||
new DecorationSegment(0, 1, 'c1 c1* c1**', 0),
|
||||
new DecorationSegment(2, 2, 'c1 c1* c1** c2', 0)
|
||||
]);
|
||||
|
||||
assert.deepEqual(LineDecorationsNormalizer.normalize('abcabcabcabcabcabcabcabcabcabc', [
|
||||
@@ -112,8 +112,8 @@ suite('Editor ViewLayout - ViewLineParts', () => {
|
||||
new LineDecoration(3, 4, 'c2', InlineDecorationType.Regular),
|
||||
new LineDecoration(3, 4, 'c2*', InlineDecorationType.Regular)
|
||||
]), [
|
||||
new DecorationSegment(0, 1, 'c1 c1* c1**'),
|
||||
new DecorationSegment(2, 2, 'c1 c1* c1** c2 c2*')
|
||||
new DecorationSegment(0, 1, 'c1 c1* c1**', 0),
|
||||
new DecorationSegment(2, 2, 'c1 c1* c1** c2 c2*', 0)
|
||||
]);
|
||||
|
||||
assert.deepEqual(LineDecorationsNormalizer.normalize('abcabcabcabcabcabcabcabcabcabc', [
|
||||
@@ -123,9 +123,9 @@ suite('Editor ViewLayout - ViewLineParts', () => {
|
||||
new LineDecoration(3, 4, 'c2', InlineDecorationType.Regular),
|
||||
new LineDecoration(3, 5, 'c2*', InlineDecorationType.Regular)
|
||||
]), [
|
||||
new DecorationSegment(0, 1, 'c1 c1* c1**'),
|
||||
new DecorationSegment(2, 2, 'c1 c1* c1** c2 c2*'),
|
||||
new DecorationSegment(3, 3, 'c2*')
|
||||
new DecorationSegment(0, 1, 'c1 c1* c1**', 0),
|
||||
new DecorationSegment(2, 2, 'c1 c1* c1** c2 c2*', 0),
|
||||
new DecorationSegment(3, 3, 'c2*', 0)
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -388,6 +388,66 @@ suite('viewLineRenderer.renderLine', () => {
|
||||
assertCharacterMapping(_actual.characterMapping, expectedOffsetsArr, [12, 12, 24, 1, 21, 2, 1, 20, 1, 1]);
|
||||
});
|
||||
|
||||
test('issue #91178: after decoration type shown before cursor', () => {
|
||||
const lineText = '//just a comment';
|
||||
const lineParts = createViewLineTokens([
|
||||
createPart(16, 1)
|
||||
]);
|
||||
const expectedOutput = [
|
||||
'<span class="mtk1">//just\u00a0a\u00a0com</span>',
|
||||
'<span class="mtk1 dec2"></span>',
|
||||
'<span class="mtk1 dec1"></span>',
|
||||
'<span class="mtk1">ment</span>',
|
||||
].join('');
|
||||
|
||||
const expectedCharacterMapping = new CharacterMapping(17, 4);
|
||||
expectedCharacterMapping.setPartData(0, 0, 0, 0);
|
||||
expectedCharacterMapping.setPartData(1, 0, 1, 0);
|
||||
expectedCharacterMapping.setPartData(2, 0, 2, 0);
|
||||
expectedCharacterMapping.setPartData(3, 0, 3, 0);
|
||||
expectedCharacterMapping.setPartData(4, 0, 4, 0);
|
||||
expectedCharacterMapping.setPartData(5, 0, 5, 0);
|
||||
expectedCharacterMapping.setPartData(6, 0, 6, 0);
|
||||
expectedCharacterMapping.setPartData(7, 0, 7, 0);
|
||||
expectedCharacterMapping.setPartData(8, 0, 8, 0);
|
||||
expectedCharacterMapping.setPartData(9, 0, 9, 0);
|
||||
expectedCharacterMapping.setPartData(10, 0, 10, 0);
|
||||
expectedCharacterMapping.setPartData(11, 0, 11, 0);
|
||||
expectedCharacterMapping.setPartData(12, 2, 0, 12);
|
||||
expectedCharacterMapping.setPartData(13, 3, 1, 12);
|
||||
expectedCharacterMapping.setPartData(14, 3, 2, 12);
|
||||
expectedCharacterMapping.setPartData(15, 3, 3, 12);
|
||||
expectedCharacterMapping.setPartData(16, 3, 4, 12);
|
||||
|
||||
const actual = renderViewLine(new RenderLineInput(
|
||||
true,
|
||||
false,
|
||||
lineText,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
0,
|
||||
lineParts,
|
||||
[
|
||||
new LineDecoration(13, 13, 'dec1', InlineDecorationType.After),
|
||||
new LineDecoration(13, 13, 'dec2', InlineDecorationType.Before),
|
||||
],
|
||||
4,
|
||||
0,
|
||||
10,
|
||||
10,
|
||||
10,
|
||||
-1,
|
||||
'none',
|
||||
false,
|
||||
false,
|
||||
null
|
||||
));
|
||||
|
||||
assert.equal(actual.html, '<span>' + expectedOutput + '</span>');
|
||||
assertCharacterMapping2(actual.characterMapping, expectedCharacterMapping);
|
||||
});
|
||||
|
||||
test('issue Microsoft/monaco-editor#280: Improved source code rendering for RTL languages', () => {
|
||||
let lineText = 'var קודמות = \"מיותר קודמות צ\'ט של, אם לשון העברית שינויים ויש, אם\";';
|
||||
|
||||
@@ -693,6 +753,33 @@ suite('viewLineRenderer.renderLine', () => {
|
||||
assert.equal(_actual.html, '<span>' + expectedOutput + '</span>');
|
||||
});
|
||||
|
||||
interface ICharMappingData {
|
||||
charOffset: number;
|
||||
partIndex: number;
|
||||
charIndex: number;
|
||||
}
|
||||
|
||||
function decodeCharacterMapping(source: CharacterMapping) {
|
||||
const mapping: ICharMappingData[] = [];
|
||||
for (let charOffset = 0; charOffset < source.length; charOffset++) {
|
||||
const partData = source.charOffsetToPartData(charOffset);
|
||||
const partIndex = CharacterMapping.getPartIndex(partData);
|
||||
const charIndex = CharacterMapping.getCharIndex(partData);
|
||||
mapping.push({ charOffset, partIndex, charIndex });
|
||||
}
|
||||
const absoluteOffsets: number[] = [];
|
||||
for (const absoluteOffset of source.getAbsoluteOffsets()) {
|
||||
absoluteOffsets.push(absoluteOffset);
|
||||
}
|
||||
return { mapping, absoluteOffsets };
|
||||
}
|
||||
|
||||
function assertCharacterMapping2(actual: CharacterMapping, expected: CharacterMapping): void {
|
||||
const _actual = decodeCharacterMapping(actual);
|
||||
const _expected = decodeCharacterMapping(expected);
|
||||
assert.deepEqual(_actual, _expected);
|
||||
}
|
||||
|
||||
function assertCharacterMapping(actual: CharacterMapping, expectedCharPartOffsets: number[][], expectedPartLengths: number[]): void {
|
||||
|
||||
assertCharPartOffsets(actual, expectedCharPartOffsets);
|
||||
|
||||
165
src/vs/monaco.d.ts
vendored
165
src/vs/monaco.d.ts
vendored
@@ -3031,6 +3031,11 @@ declare namespace monaco.editor {
|
||||
* Defaults to 'mouseover'.
|
||||
*/
|
||||
showFoldingControls?: 'always' | 'mouseover';
|
||||
/**
|
||||
* Controls whether clicking on the empty content after a folded line will unfold the line.
|
||||
* Defaults to false.
|
||||
*/
|
||||
unfoldOnClickInEmptyContent?: boolean;
|
||||
/**
|
||||
* Enable highlighting of matching brackets.
|
||||
* Defaults to 'always'.
|
||||
@@ -3824,85 +3829,86 @@ declare namespace monaco.editor {
|
||||
folding = 31,
|
||||
foldingStrategy = 32,
|
||||
foldingHighlight = 33,
|
||||
fontFamily = 34,
|
||||
fontInfo = 35,
|
||||
fontLigatures = 36,
|
||||
fontSize = 37,
|
||||
fontWeight = 38,
|
||||
formatOnPaste = 39,
|
||||
formatOnType = 40,
|
||||
glyphMargin = 41,
|
||||
gotoLocation = 42,
|
||||
hideCursorInOverviewRuler = 43,
|
||||
highlightActiveIndentGuide = 44,
|
||||
hover = 45,
|
||||
inDiffEditor = 46,
|
||||
letterSpacing = 47,
|
||||
lightbulb = 48,
|
||||
lineDecorationsWidth = 49,
|
||||
lineHeight = 50,
|
||||
lineNumbers = 51,
|
||||
lineNumbersMinChars = 52,
|
||||
links = 53,
|
||||
matchBrackets = 54,
|
||||
minimap = 55,
|
||||
mouseStyle = 56,
|
||||
mouseWheelScrollSensitivity = 57,
|
||||
mouseWheelZoom = 58,
|
||||
multiCursorMergeOverlapping = 59,
|
||||
multiCursorModifier = 60,
|
||||
multiCursorPaste = 61,
|
||||
occurrencesHighlight = 62,
|
||||
overviewRulerBorder = 63,
|
||||
overviewRulerLanes = 64,
|
||||
padding = 65,
|
||||
parameterHints = 66,
|
||||
peekWidgetDefaultFocus = 67,
|
||||
definitionLinkOpensInPeek = 68,
|
||||
quickSuggestions = 69,
|
||||
quickSuggestionsDelay = 70,
|
||||
readOnly = 71,
|
||||
renderControlCharacters = 72,
|
||||
renderIndentGuides = 73,
|
||||
renderFinalNewline = 74,
|
||||
renderLineHighlight = 75,
|
||||
renderValidationDecorations = 76,
|
||||
renderWhitespace = 77,
|
||||
revealHorizontalRightPadding = 78,
|
||||
roundedSelection = 79,
|
||||
rulers = 80,
|
||||
scrollbar = 81,
|
||||
scrollBeyondLastColumn = 82,
|
||||
scrollBeyondLastLine = 83,
|
||||
scrollPredominantAxis = 84,
|
||||
selectionClipboard = 85,
|
||||
selectionHighlight = 86,
|
||||
selectOnLineNumbers = 87,
|
||||
showFoldingControls = 88,
|
||||
showUnused = 89,
|
||||
snippetSuggestions = 90,
|
||||
smoothScrolling = 91,
|
||||
stopRenderingLineAfter = 92,
|
||||
suggest = 93,
|
||||
suggestFontSize = 94,
|
||||
suggestLineHeight = 95,
|
||||
suggestOnTriggerCharacters = 96,
|
||||
suggestSelection = 97,
|
||||
tabCompletion = 98,
|
||||
useTabStops = 99,
|
||||
wordSeparators = 100,
|
||||
wordWrap = 101,
|
||||
wordWrapBreakAfterCharacters = 102,
|
||||
wordWrapBreakBeforeCharacters = 103,
|
||||
wordWrapColumn = 104,
|
||||
wordWrapMinified = 105,
|
||||
wrappingIndent = 106,
|
||||
wrappingStrategy = 107,
|
||||
editorClassName = 108,
|
||||
pixelRatio = 109,
|
||||
tabFocusMode = 110,
|
||||
layoutInfo = 111,
|
||||
wrappingInfo = 112
|
||||
unfoldOnClickInEmptyContent = 34,
|
||||
fontFamily = 35,
|
||||
fontInfo = 36,
|
||||
fontLigatures = 37,
|
||||
fontSize = 38,
|
||||
fontWeight = 39,
|
||||
formatOnPaste = 40,
|
||||
formatOnType = 41,
|
||||
glyphMargin = 42,
|
||||
gotoLocation = 43,
|
||||
hideCursorInOverviewRuler = 44,
|
||||
highlightActiveIndentGuide = 45,
|
||||
hover = 46,
|
||||
inDiffEditor = 47,
|
||||
letterSpacing = 48,
|
||||
lightbulb = 49,
|
||||
lineDecorationsWidth = 50,
|
||||
lineHeight = 51,
|
||||
lineNumbers = 52,
|
||||
lineNumbersMinChars = 53,
|
||||
links = 54,
|
||||
matchBrackets = 55,
|
||||
minimap = 56,
|
||||
mouseStyle = 57,
|
||||
mouseWheelScrollSensitivity = 58,
|
||||
mouseWheelZoom = 59,
|
||||
multiCursorMergeOverlapping = 60,
|
||||
multiCursorModifier = 61,
|
||||
multiCursorPaste = 62,
|
||||
occurrencesHighlight = 63,
|
||||
overviewRulerBorder = 64,
|
||||
overviewRulerLanes = 65,
|
||||
padding = 66,
|
||||
parameterHints = 67,
|
||||
peekWidgetDefaultFocus = 68,
|
||||
definitionLinkOpensInPeek = 69,
|
||||
quickSuggestions = 70,
|
||||
quickSuggestionsDelay = 71,
|
||||
readOnly = 72,
|
||||
renderControlCharacters = 73,
|
||||
renderIndentGuides = 74,
|
||||
renderFinalNewline = 75,
|
||||
renderLineHighlight = 76,
|
||||
renderValidationDecorations = 77,
|
||||
renderWhitespace = 78,
|
||||
revealHorizontalRightPadding = 79,
|
||||
roundedSelection = 80,
|
||||
rulers = 81,
|
||||
scrollbar = 82,
|
||||
scrollBeyondLastColumn = 83,
|
||||
scrollBeyondLastLine = 84,
|
||||
scrollPredominantAxis = 85,
|
||||
selectionClipboard = 86,
|
||||
selectionHighlight = 87,
|
||||
selectOnLineNumbers = 88,
|
||||
showFoldingControls = 89,
|
||||
showUnused = 90,
|
||||
snippetSuggestions = 91,
|
||||
smoothScrolling = 92,
|
||||
stopRenderingLineAfter = 93,
|
||||
suggest = 94,
|
||||
suggestFontSize = 95,
|
||||
suggestLineHeight = 96,
|
||||
suggestOnTriggerCharacters = 97,
|
||||
suggestSelection = 98,
|
||||
tabCompletion = 99,
|
||||
useTabStops = 100,
|
||||
wordSeparators = 101,
|
||||
wordWrap = 102,
|
||||
wordWrapBreakAfterCharacters = 103,
|
||||
wordWrapBreakBeforeCharacters = 104,
|
||||
wordWrapColumn = 105,
|
||||
wordWrapMinified = 106,
|
||||
wrappingIndent = 107,
|
||||
wrappingStrategy = 108,
|
||||
editorClassName = 109,
|
||||
pixelRatio = 110,
|
||||
tabFocusMode = 111,
|
||||
layoutInfo = 112,
|
||||
wrappingInfo = 113
|
||||
}
|
||||
export const EditorOptions: {
|
||||
acceptSuggestionOnCommitCharacter: IEditorOption<EditorOption.acceptSuggestionOnCommitCharacter, boolean>;
|
||||
@@ -3939,6 +3945,7 @@ declare namespace monaco.editor {
|
||||
folding: IEditorOption<EditorOption.folding, boolean>;
|
||||
foldingStrategy: IEditorOption<EditorOption.foldingStrategy, 'auto' | 'indentation'>;
|
||||
foldingHighlight: IEditorOption<EditorOption.foldingHighlight, boolean>;
|
||||
unfoldOnClickInEmptyContent: IEditorOption<EditorOption.unfoldOnClickInEmptyContent, boolean>;
|
||||
fontFamily: IEditorOption<EditorOption.fontFamily, string>;
|
||||
fontInfo: IEditorOption<EditorOption.fontInfo, FontInfo>;
|
||||
fontLigatures2: IEditorOption<EditorOption.fontLigatures, string>;
|
||||
|
||||
@@ -109,6 +109,8 @@ export interface IProductConfiguration {
|
||||
|
||||
readonly msftInternalDomains?: string[];
|
||||
readonly linkProtectionTrustedDomains?: readonly string[];
|
||||
|
||||
readonly 'configurationSync.store'?: { url: string, authenticationProviderId: string };
|
||||
}
|
||||
|
||||
export interface IExeBasedExtensionTip {
|
||||
|
||||
@@ -12,7 +12,6 @@ import { URI } from 'vs/base/common/uri';
|
||||
import { IDisposable, DisposableStore, Disposable } from 'vs/base/common/lifecycle';
|
||||
import { IWindowsMainService } from 'vs/platform/windows/electron-main/windows';
|
||||
import { isWindows } from 'vs/base/common/platform';
|
||||
import { coalesce } from 'vs/base/common/arrays';
|
||||
import { disposableTimeout } from 'vs/base/common/async';
|
||||
|
||||
function uriFromRawUrl(url: string): URI | null {
|
||||
@@ -23,6 +22,16 @@ function uriFromRawUrl(url: string): URI | null {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A listener for URLs that are opened from the OS and handled by VSCode.
|
||||
* Depending on the platform, this works differently:
|
||||
* - Windows: we use `app.setAsDefaultProtocolClient()` to register VSCode with the OS
|
||||
* and additionally add the `open-url` command line argument to identify.
|
||||
* - macOS: we rely on `app.on('open-url')` to be called by the OS
|
||||
* - Linux: we have a special shortcut installed (`resources/linux/code-url-handler.desktop`)
|
||||
* that calls VSCode with the `open-url` command line argument
|
||||
* (https://github.com/microsoft/vscode/pull/56727)
|
||||
*/
|
||||
export class ElectronURLListener {
|
||||
|
||||
private uris: URI[] = [];
|
||||
@@ -31,36 +40,34 @@ export class ElectronURLListener {
|
||||
private disposables = new DisposableStore();
|
||||
|
||||
constructor(
|
||||
initial: string | string[],
|
||||
@IURLService private readonly urlService: IURLService,
|
||||
@IWindowsMainService windowsMainService: IWindowsMainService,
|
||||
@IEnvironmentService environmentService: IEnvironmentService
|
||||
initialUrisToHandle: URI[],
|
||||
private readonly urlService: IURLService,
|
||||
windowsMainService: IWindowsMainService,
|
||||
environmentService: IEnvironmentService
|
||||
) {
|
||||
const globalBuffer = ((<any>global).getOpenUrls() || []) as string[];
|
||||
const rawBuffer = [
|
||||
...(typeof initial === 'string' ? [initial] : initial),
|
||||
...globalBuffer
|
||||
];
|
||||
|
||||
this.uris = coalesce(rawBuffer.map(uriFromRawUrl));
|
||||
// the initial set of URIs we need to handle once the window is ready
|
||||
this.uris = initialUrisToHandle;
|
||||
|
||||
// Windows: install as protocol handler
|
||||
if (isWindows) {
|
||||
const windowsParameters = environmentService.isBuilt ? [] : [`"${environmentService.appRoot}"`];
|
||||
windowsParameters.push('--open-url', '--');
|
||||
app.setAsDefaultProtocolClient(product.urlProtocol, process.execPath, windowsParameters);
|
||||
}
|
||||
|
||||
// macOS: listen to `open-url` events from here on to handle
|
||||
const onOpenElectronUrl = Event.map(
|
||||
Event.fromNodeEventEmitter(app, 'open-url', (event: ElectronEvent, url: string) => ({ event, url })),
|
||||
({ event, url }) => {
|
||||
// always prevent default and return the url as string
|
||||
event.preventDefault();
|
||||
event.preventDefault(); // always prevent default and return the url as string
|
||||
return url;
|
||||
});
|
||||
|
||||
const onOpenUrl = Event.filter<URI | null, URI>(Event.map(onOpenElectronUrl, uriFromRawUrl), (uri): uri is URI => !!uri);
|
||||
onOpenUrl(this.urlService.open, this.urlService, this.disposables);
|
||||
|
||||
// Send initial links to the window once it has loaded
|
||||
const isWindowReady = windowsMainService.getWindows()
|
||||
.filter(w => w.isReady)
|
||||
.length > 0;
|
||||
|
||||
@@ -20,6 +20,7 @@ import { FormattingOptions } from 'vs/base/common/jsonFormatter';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { isEqual, joinPath } from 'vs/base/common/resources';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { IProductService } from 'vs/platform/product/common/productService';
|
||||
|
||||
export const CONFIGURATION_SYNC_STORE_KEY = 'configurationSync.store';
|
||||
|
||||
@@ -87,6 +88,7 @@ export function registerConfiguration(): IDisposable {
|
||||
description: localize('sync.keybindingsPerPlatform', "Synchronize keybindings per platform."),
|
||||
default: true,
|
||||
scope: ConfigurationScope.APPLICATION,
|
||||
tags: ['sync']
|
||||
},
|
||||
'sync.ignoredExtensions': {
|
||||
'type': 'array',
|
||||
@@ -95,7 +97,8 @@ export function registerConfiguration(): IDisposable {
|
||||
'default': [],
|
||||
'scope': ConfigurationScope.APPLICATION,
|
||||
uniqueItems: true,
|
||||
disallowSyncIgnore: true
|
||||
disallowSyncIgnore: true,
|
||||
tags: ['sync']
|
||||
},
|
||||
'sync.ignoredSettings': {
|
||||
'type': 'array',
|
||||
@@ -105,7 +108,8 @@ export function registerConfiguration(): IDisposable {
|
||||
$ref: ignoredSettingsSchemaId,
|
||||
additionalProperties: true,
|
||||
uniqueItems: true,
|
||||
disallowSyncIgnore: true
|
||||
disallowSyncIgnore: true,
|
||||
tags: ['sync']
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -140,8 +144,8 @@ export interface IUserDataSyncStore {
|
||||
authenticationProviderId: string;
|
||||
}
|
||||
|
||||
export function getUserDataSyncStore(configurationService: IConfigurationService): IUserDataSyncStore | undefined {
|
||||
const value = configurationService.getValue<{ url: string, authenticationProviderId: string }>(CONFIGURATION_SYNC_STORE_KEY);
|
||||
export function getUserDataSyncStore(productService: IProductService, configurationService: IConfigurationService): IUserDataSyncStore | undefined {
|
||||
const value = productService[CONFIGURATION_SYNC_STORE_KEY] || configurationService.getValue<{ url: string, authenticationProviderId: string }>(CONFIGURATION_SYNC_STORE_KEY);
|
||||
if (value && value.url && value.authenticationProviderId) {
|
||||
return {
|
||||
url: joinPath(URI.parse(value.url), 'v1'),
|
||||
|
||||
@@ -11,6 +11,7 @@ import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { IHeaders, IRequestOptions, IRequestContext } from 'vs/base/parts/request/common/request';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { IAuthenticationTokenService } from 'vs/platform/authentication/common/authentication';
|
||||
import { IProductService } from 'vs/platform/product/common/productService';
|
||||
|
||||
export class UserDataSyncStoreService extends Disposable implements IUserDataSyncStoreService {
|
||||
|
||||
@@ -19,13 +20,14 @@ export class UserDataSyncStoreService extends Disposable implements IUserDataSyn
|
||||
readonly userDataSyncStore: IUserDataSyncStore | undefined;
|
||||
|
||||
constructor(
|
||||
@IProductService productService: IProductService,
|
||||
@IConfigurationService configurationService: IConfigurationService,
|
||||
@IRequestService private readonly requestService: IRequestService,
|
||||
@IAuthenticationTokenService private readonly authTokenService: IAuthenticationTokenService,
|
||||
@IUserDataSyncLogService private readonly logService: IUserDataSyncLogService,
|
||||
) {
|
||||
super();
|
||||
this.userDataSyncStore = getUserDataSyncStore(configurationService);
|
||||
this.userDataSyncStore = getUserDataSyncStore(productService, configurationService);
|
||||
}
|
||||
|
||||
async read(key: string, oldValue: IUserData | null, source?: SyncSource): Promise<IUserData> {
|
||||
|
||||
@@ -34,6 +34,8 @@ import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { SettingsSynchroniser } from 'vs/platform/userDataSync/common/settingsSync';
|
||||
import { Emitter } from 'vs/base/common/event';
|
||||
import { IAuthenticationTokenService } from 'vs/platform/authentication/common/authentication';
|
||||
import product from 'vs/platform/product/common/product';
|
||||
import { IProductService } from 'vs/platform/product/common/productService';
|
||||
|
||||
export class UserDataSyncClient extends Disposable {
|
||||
|
||||
@@ -59,6 +61,8 @@ export class UserDataSyncClient extends Disposable {
|
||||
const logService = new NullLogService();
|
||||
this.instantiationService.stub(ILogService, logService);
|
||||
|
||||
this.instantiationService.stub(IProductService, { _serviceBrand: undefined, ...product });
|
||||
|
||||
const fileService = this._register(new FileService(logService));
|
||||
fileService.registerProvider(Schemas.inMemory, new InMemoryFileSystemProvider());
|
||||
this.instantiationService.stub(IFileService, fileService);
|
||||
|
||||
71
src/vs/vscode.proposed.d.ts
vendored
71
src/vs/vscode.proposed.d.ts
vendored
@@ -1558,12 +1558,9 @@ declare module 'vscode' {
|
||||
label: string;
|
||||
|
||||
/**
|
||||
* Optional id for the timeline item.
|
||||
*/
|
||||
/**
|
||||
* Optional id for the timeline item that has to be unique across your timeline source.
|
||||
* Optional id for the timeline item. It must be unique across all the timeline items provided by this source.
|
||||
*
|
||||
* If not provided, an id is generated using the timeline item's label.
|
||||
* If not provided, an id is generated using the timeline item's timestamp.
|
||||
*/
|
||||
id?: string;
|
||||
|
||||
@@ -1620,40 +1617,50 @@ declare module 'vscode' {
|
||||
* If the [uri](#Uri) is `undefined` that signals that the timeline source for all resources changed.
|
||||
*/
|
||||
uri?: Uri;
|
||||
}
|
||||
|
||||
export interface TimelineCursor {
|
||||
/**
|
||||
* A provider-defined cursor specifing the range of timeline items to be returned. Must be serializable.
|
||||
*/
|
||||
cursor?: any;
|
||||
|
||||
/**
|
||||
* A flag to specify whether the timeline items requested are before or after (default) the provided cursor.
|
||||
* A flag which indicates whether the entire timeline should be reset.
|
||||
*/
|
||||
before?: boolean;
|
||||
|
||||
/**
|
||||
* The maximum number of timeline items that should be returned.
|
||||
*/
|
||||
limit?: number;
|
||||
reset?: boolean;
|
||||
}
|
||||
|
||||
export interface Timeline {
|
||||
/**
|
||||
* A provider-defined cursor specifing the range of timeline items returned. Must be serializable.
|
||||
*/
|
||||
cursor?: any;
|
||||
readonly paging?: {
|
||||
/**
|
||||
* A set of provider-defined cursors specifing the range of timeline items returned.
|
||||
*/
|
||||
readonly cursors: {
|
||||
readonly before: string;
|
||||
readonly after?: string
|
||||
};
|
||||
|
||||
/**
|
||||
* A flag which indicates whether there are any more items that weren't returned.
|
||||
*/
|
||||
more?: boolean;
|
||||
/**
|
||||
* A flag which indicates whether there are more items that weren't returned.
|
||||
*/
|
||||
readonly more?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* An array of [timeline items](#TimelineItem).
|
||||
*/
|
||||
items: TimelineItem[];
|
||||
readonly items: readonly TimelineItem[];
|
||||
}
|
||||
|
||||
export interface TimelineOptions {
|
||||
/**
|
||||
* A provider-defined cursor specifing the range of timeline items that should be returned.
|
||||
*/
|
||||
cursor?: string;
|
||||
|
||||
/**
|
||||
* A flag to specify whether the timeline items being requested should be before or after (default) the provided cursor.
|
||||
*/
|
||||
before?: boolean;
|
||||
|
||||
/**
|
||||
* The maximum number or the ending cursor of timeline items that should be returned.
|
||||
*/
|
||||
limit?: number | string;
|
||||
}
|
||||
|
||||
export interface TimelineProvider {
|
||||
@@ -1666,23 +1673,23 @@ declare module 'vscode' {
|
||||
/**
|
||||
* An identifier of the source of the timeline items. This can be used to filter sources.
|
||||
*/
|
||||
id: string;
|
||||
readonly id: string;
|
||||
|
||||
/**
|
||||
* A human-readable string describing the source of the timeline items. This can be used as the display label when filtering sources.
|
||||
*/
|
||||
label: string;
|
||||
readonly label: string;
|
||||
|
||||
/**
|
||||
* Provide [timeline items](#TimelineItem) for a [Uri](#Uri).
|
||||
*
|
||||
* @param uri The [uri](#Uri) of the file to provide the timeline for.
|
||||
* @param options A set of options to determine how results should be returned.
|
||||
* @param token A cancellation token.
|
||||
* @param cursor TBD
|
||||
* @return The [timeline result](#TimelineResult) or a thenable that resolves to such. The lack of a result
|
||||
* can be signaled by returning `undefined`, `null`, or an empty array.
|
||||
*/
|
||||
provideTimeline(uri: Uri, cursor: TimelineCursor, token: CancellationToken): ProviderResult<Timeline>;
|
||||
provideTimeline(uri: Uri, options: TimelineOptions, token: CancellationToken): ProviderResult<Timeline>;
|
||||
}
|
||||
|
||||
export namespace workspace {
|
||||
|
||||
@@ -9,7 +9,7 @@ import { URI } from 'vs/base/common/uri';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { MainContext, MainThreadTimelineShape, IExtHostContext, ExtHostTimelineShape, ExtHostContext } from 'vs/workbench/api/common/extHost.protocol';
|
||||
import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
|
||||
import { TimelineChangeEvent, TimelineCursor, TimelineProviderDescriptor, ITimelineService } from 'vs/workbench/contrib/timeline/common/timeline';
|
||||
import { TimelineChangeEvent, TimelineOptions, TimelineProviderDescriptor, ITimelineService } from 'vs/workbench/contrib/timeline/common/timeline';
|
||||
|
||||
@extHostNamedCustomer(MainContext.MainThreadTimeline)
|
||||
export class MainThreadTimeline implements MainThreadTimelineShape {
|
||||
@@ -39,8 +39,8 @@ export class MainThreadTimeline implements MainThreadTimelineShape {
|
||||
this._timelineService.registerTimelineProvider({
|
||||
...provider,
|
||||
onDidChange: onDidChange.event,
|
||||
provideTimeline(uri: URI, cursor: TimelineCursor, token: CancellationToken, options?: { cacheResults?: boolean }) {
|
||||
return proxy.$getTimeline(provider.id, uri, cursor, token, options);
|
||||
provideTimeline(uri: URI, options: TimelineOptions, token: CancellationToken, internalOptions?: { cacheResults?: boolean }) {
|
||||
return proxy.$getTimeline(provider.id, uri, options, token, internalOptions);
|
||||
},
|
||||
dispose() {
|
||||
emitters.delete(provider.id);
|
||||
|
||||
@@ -49,7 +49,7 @@ import { SaveReason } from 'vs/workbench/common/editor';
|
||||
import { ExtensionActivationReason } from 'vs/workbench/api/common/extHostExtensionActivator';
|
||||
import { TunnelDto } from 'vs/workbench/api/common/extHostTunnelService';
|
||||
import { TunnelOptions } from 'vs/platform/remote/common/tunnel';
|
||||
import { Timeline, TimelineChangeEvent, TimelineCursor, TimelineProviderDescriptor } from 'vs/workbench/contrib/timeline/common/timeline';
|
||||
import { Timeline, TimelineChangeEvent, TimelineOptions, TimelineProviderDescriptor } from 'vs/workbench/contrib/timeline/common/timeline';
|
||||
import { revive } from 'vs/base/common/marshalling';
|
||||
import { CallHierarchyItem } from 'vs/workbench/contrib/callHierarchy/common/callHierarchy';
|
||||
import { Dto } from 'vs/base/common/types';
|
||||
@@ -1472,7 +1472,7 @@ export interface ExtHostTunnelServiceShape {
|
||||
}
|
||||
|
||||
export interface ExtHostTimelineShape {
|
||||
$getTimeline(source: string, uri: UriComponents, cursor: TimelineCursor, token: CancellationToken, options?: { cacheResults?: boolean }): Promise<Timeline | undefined>;
|
||||
$getTimeline(source: string, uri: UriComponents, options: TimelineOptions, token: CancellationToken, internalOptions?: { cacheResults?: boolean }): Promise<Timeline | undefined>;
|
||||
}
|
||||
|
||||
// --- proxy identifiers
|
||||
|
||||
@@ -7,7 +7,7 @@ import * as vscode from 'vscode';
|
||||
import { UriComponents, URI } from 'vs/base/common/uri';
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { ExtHostTimelineShape, MainThreadTimelineShape, IMainContext, MainContext } from 'vs/workbench/api/common/extHost.protocol';
|
||||
import { Timeline, TimelineCursor, TimelineItem, TimelineProvider } from 'vs/workbench/contrib/timeline/common/timeline';
|
||||
import { Timeline, TimelineItem, TimelineOptions, TimelineProvider } from 'vs/workbench/contrib/timeline/common/timeline';
|
||||
import { IDisposable, toDisposable, DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { CommandsConverter, ExtHostCommands } from 'vs/workbench/api/common/extHostCommands';
|
||||
@@ -16,7 +16,7 @@ import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
|
||||
|
||||
export interface IExtHostTimeline extends ExtHostTimelineShape {
|
||||
readonly _serviceBrand: undefined;
|
||||
$getTimeline(id: string, uri: UriComponents, cursor: vscode.TimelineCursor, token: vscode.CancellationToken, options?: { cacheResults?: boolean }): Promise<Timeline | undefined>;
|
||||
$getTimeline(id: string, uri: UriComponents, options: vscode.TimelineOptions, token: vscode.CancellationToken, internalOptions?: { cacheResults?: boolean }): Promise<Timeline | undefined>;
|
||||
}
|
||||
|
||||
export const IExtHostTimeline = createDecorator<IExtHostTimeline>('IExtHostTimeline');
|
||||
@@ -50,9 +50,9 @@ export class ExtHostTimeline implements IExtHostTimeline {
|
||||
});
|
||||
}
|
||||
|
||||
async $getTimeline(id: string, uri: UriComponents, cursor: vscode.TimelineCursor, token: vscode.CancellationToken, options?: { cacheResults?: boolean }): Promise<Timeline | undefined> {
|
||||
async $getTimeline(id: string, uri: UriComponents, options: vscode.TimelineOptions, token: vscode.CancellationToken, internalOptions?: { cacheResults?: boolean }): Promise<Timeline | undefined> {
|
||||
const provider = this._providers.get(id);
|
||||
return provider?.provideTimeline(URI.revive(uri), cursor, token, options);
|
||||
return provider?.provideTimeline(URI.revive(uri), options, token, internalOptions);
|
||||
}
|
||||
|
||||
registerTimelineProvider(scheme: string | string[], provider: vscode.TimelineProvider, _extensionId: ExtensionIdentifier, commandConverter: CommandsConverter): IDisposable {
|
||||
@@ -70,15 +70,15 @@ export class ExtHostTimeline implements IExtHostTimeline {
|
||||
...provider,
|
||||
scheme: scheme,
|
||||
onDidChange: undefined,
|
||||
async provideTimeline(uri: URI, cursor: TimelineCursor, token: CancellationToken, options?: { cacheResults?: boolean }) {
|
||||
async provideTimeline(uri: URI, options: TimelineOptions, token: CancellationToken, internalOptions?: { cacheResults?: boolean }) {
|
||||
timelineDisposables.clear();
|
||||
|
||||
// For now, only allow the caching of a single Uri
|
||||
if (options?.cacheResults && !itemsBySourceByUriMap.has(getUriKey(uri))) {
|
||||
if (internalOptions?.cacheResults && !itemsBySourceByUriMap.has(getUriKey(uri))) {
|
||||
itemsBySourceByUriMap.clear();
|
||||
}
|
||||
|
||||
const result = await provider.provideTimeline(uri, cursor, token);
|
||||
const result = await provider.provideTimeline(uri, options, token);
|
||||
// Intentional == we don't know how a provider will respond
|
||||
// eslint-disable-next-line eqeqeq
|
||||
if (result == null) {
|
||||
@@ -86,7 +86,7 @@ export class ExtHostTimeline implements IExtHostTimeline {
|
||||
}
|
||||
|
||||
// TODO: Determine if we should cache dependent on who calls us (internal vs external)
|
||||
const convertItem = convertTimelineItem(uri, options?.cacheResults ?? false);
|
||||
const convertItem = convertTimelineItem(uri, internalOptions?.cacheResults ?? false);
|
||||
return {
|
||||
...result,
|
||||
source: provider.id,
|
||||
@@ -143,6 +143,7 @@ export class ExtHostTimeline implements IExtHostTimeline {
|
||||
|
||||
return {
|
||||
...props,
|
||||
id: props.id ?? undefined,
|
||||
handle: handle,
|
||||
source: source,
|
||||
command: item.command ? commandConverter.toInternal(item.command, disposables) : undefined,
|
||||
|
||||
@@ -389,7 +389,7 @@ class WebviewDocumentStore {
|
||||
}
|
||||
|
||||
private key(viewType: string, resource: vscode.Uri): string {
|
||||
return `${viewType}@@@${resource.toString}`;
|
||||
return `${viewType}@@@${resource}`;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -21,10 +21,13 @@ import { isWindows, isLinux, isWeb } from 'vs/base/common/platform';
|
||||
import { IsMacNativeContext } from 'vs/workbench/browser/contextkeys';
|
||||
import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
|
||||
import { InEditorZenModeContext, IsCenteredLayoutContext, EditorAreaVisibleContext } from 'vs/workbench/common/editor';
|
||||
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { SideBarVisibleContext } from 'vs/workbench/common/viewlet';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { IViewDescriptorService, IViewContainersRegistry, Extensions as ViewContainerExtensions } from 'vs/workbench/common/views';
|
||||
import { IViewDescriptorService, IViewContainersRegistry, Extensions as ViewContainerExtensions, IViewsService, FocusedViewContext, ViewContainerLocation } from 'vs/workbench/common/views';
|
||||
import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput';
|
||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
|
||||
|
||||
const registry = Registry.as<IWorkbenchActionRegistry>(WorkbenchExtensions.WorkbenchActions);
|
||||
const viewCategory = nls.localize('view', "View");
|
||||
@@ -518,6 +521,92 @@ export class ResetViewLocationsAction extends Action {
|
||||
|
||||
registry.registerWorkbenchAction(SyncActionDescriptor.create(ResetViewLocationsAction, ResetViewLocationsAction.ID, ResetViewLocationsAction.LABEL), 'View: Reset View Locations', viewCategory);
|
||||
|
||||
// --- Move View with Command
|
||||
export class MoveFocusedViewAction extends Action {
|
||||
static readonly ID = 'workbench.action.moveFocusedView';
|
||||
static readonly LABEL = nls.localize('moveFocusedView', "Move Focused View");
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
@IViewDescriptorService private viewDescriptorService: IViewDescriptorService,
|
||||
@IViewsService private viewsService: IViewsService,
|
||||
@IQuickInputService private quickInputService: IQuickInputService,
|
||||
@IContextKeyService private contextKeyService: IContextKeyService,
|
||||
@INotificationService private notificationService: INotificationService,
|
||||
@IViewletService private viewletService: IViewletService
|
||||
) {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
run(): Promise<void> {
|
||||
const viewContainerRegistry = Registry.as<IViewContainersRegistry>(ViewContainerExtensions.ViewContainersRegistry);
|
||||
|
||||
const focusedView = FocusedViewContext.getValue(this.contextKeyService);
|
||||
|
||||
if (focusedView === undefined || focusedView.trim() === '') {
|
||||
this.notificationService.error(nls.localize('moveFocusedView.error.noFocusedView', "There is no view currently focused."));
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
const viewDescriptor = this.viewDescriptorService.getViewDescriptor(focusedView);
|
||||
if (!viewDescriptor || !viewDescriptor.canMoveView) {
|
||||
this.notificationService.error(nls.localize('moveFocusedView.error.nonMovableView', "The currently focused view is not movable {0}.", focusedView));
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
const quickPick = this.quickInputService.createQuickPick();
|
||||
quickPick.placeholder = nls.localize('moveFocusedView.selectDestination', "Select a destination area for the view...");
|
||||
quickPick.autoFocusOnList = true;
|
||||
|
||||
quickPick.items = [
|
||||
{
|
||||
id: 'sidebar',
|
||||
label: nls.localize('sidebar', "Sidebar")
|
||||
},
|
||||
{
|
||||
id: 'panel',
|
||||
label: nls.localize('panel', "Panel")
|
||||
}
|
||||
];
|
||||
|
||||
quickPick.onDidAccept(() => {
|
||||
const destination = quickPick.selectedItems[0];
|
||||
|
||||
if (destination.id === 'panel') {
|
||||
quickPick.hide();
|
||||
this.viewDescriptorService.moveViewToLocation(viewDescriptor!, ViewContainerLocation.Panel);
|
||||
this.viewsService.openView(focusedView, true);
|
||||
|
||||
return;
|
||||
} else if (destination.id === 'sidebar') {
|
||||
quickPick.placeholder = nls.localize('moveFocusedView.selectDestinationContainer', "Select a destination view group...");
|
||||
quickPick.items = this.viewletService.getViewlets().map(viewlet => {
|
||||
return {
|
||||
id: viewlet.id,
|
||||
label: viewlet.name
|
||||
};
|
||||
});
|
||||
|
||||
return;
|
||||
} else if (destination.id) {
|
||||
quickPick.hide();
|
||||
this.viewDescriptorService.moveViewsToContainer([viewDescriptor], viewContainerRegistry.get(destination.id)!);
|
||||
this.viewsService.openView(focusedView, true);
|
||||
return;
|
||||
}
|
||||
|
||||
quickPick.hide();
|
||||
});
|
||||
|
||||
quickPick.show();
|
||||
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
|
||||
registry.registerWorkbenchAction(SyncActionDescriptor.create(MoveFocusedViewAction, MoveFocusedViewAction.ID, MoveFocusedViewAction.LABEL), 'View: Move Focused View', viewCategory);
|
||||
|
||||
|
||||
// --- Resize View
|
||||
|
||||
|
||||
@@ -4,19 +4,17 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { posix } from 'vs/base/common/path';
|
||||
import { dirname, isEqual, basenameOrAuthority } from 'vs/base/common/resources';
|
||||
import { IconLabel, IIconLabelValueOptions, IIconLabelCreationOptions } from 'vs/base/browser/ui/iconLabel/iconLabel';
|
||||
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import { IModeService } from 'vs/editor/common/services/modeService';
|
||||
import { PLAINTEXT_MODE_ID } from 'vs/editor/common/modes/modesRegistry';
|
||||
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { IModelService } from 'vs/editor/common/services/modelService';
|
||||
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
|
||||
import { IDecorationsService, IResourceDecorationChangeEvent } from 'vs/workbench/services/decorations/browser/decorations';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { FileKind, FILES_ASSOCIATIONS_CONFIG, IFileService } from 'vs/platform/files/common/files';
|
||||
import { FileKind, FILES_ASSOCIATIONS_CONFIG } from 'vs/platform/files/common/files';
|
||||
import { ITextModel } from 'vs/editor/common/model';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
@@ -89,7 +87,6 @@ export class ResourceLabels extends Disposable {
|
||||
@IModelService private readonly modelService: IModelService,
|
||||
@IDecorationsService private readonly decorationsService: IDecorationsService,
|
||||
@IThemeService private readonly themeService: IThemeService,
|
||||
@IFileService private readonly fileService: IFileService,
|
||||
@ILabelService private readonly labelService: ILabelService,
|
||||
@ITextFileService private readonly textFileService: ITextFileService
|
||||
) {
|
||||
@@ -114,10 +111,6 @@ export class ResourceLabels extends Disposable {
|
||||
return; // we need the resource to compare
|
||||
}
|
||||
|
||||
if (this.fileService.canHandleResource(e.model.uri) && e.oldModeId === PLAINTEXT_MODE_ID) {
|
||||
return; // ignore transitions in files from no mode to specific mode because this happens each time a model is created
|
||||
}
|
||||
|
||||
this._widgets.forEach(widget => widget.notifyModelModeChanged(e.model));
|
||||
}));
|
||||
|
||||
@@ -218,11 +211,10 @@ export class ResourceLabel extends ResourceLabels {
|
||||
@IModelService modelService: IModelService,
|
||||
@IDecorationsService decorationsService: IDecorationsService,
|
||||
@IThemeService themeService: IThemeService,
|
||||
@IFileService fileService: IFileService,
|
||||
@ILabelService labelService: ILabelService,
|
||||
@ITextFileService textFileService: ITextFileService
|
||||
) {
|
||||
super(DEFAULT_LABELS_CONTAINER, instantiationService, extensionService, configurationService, modelService, decorationsService, themeService, fileService, labelService, textFileService);
|
||||
super(DEFAULT_LABELS_CONTAINER, instantiationService, extensionService, configurationService, modelService, decorationsService, themeService, labelService, textFileService);
|
||||
|
||||
this._label = this._register(this.create(container, options));
|
||||
}
|
||||
@@ -372,8 +364,8 @@ class ResourceLabelWidget extends IconLabel {
|
||||
let untitledDescription = untitledModel.resource.path;
|
||||
if (label.name !== untitledDescription) {
|
||||
label.description = untitledDescription;
|
||||
} else if (label.description === posix.sep) {
|
||||
label.description = undefined; // unset showing just "/" for untitled without associated resource
|
||||
} else {
|
||||
label.description = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -547,6 +547,13 @@ export class CompositeActionViewItem extends ActivityActionViewItem {
|
||||
this.updateFromDragging(container, true);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.compositeTransfer.hasData(DraggedViewIdentifier.prototype)) {
|
||||
const data = this.compositeTransfer.getData(DraggedViewIdentifier.prototype);
|
||||
if (Array.isArray(data) && data[0].id !== this.activity.id) {
|
||||
this.updateFromDragging(container, true);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
onDragOver: e => {
|
||||
@@ -575,7 +582,8 @@ export class CompositeActionViewItem extends ActivityActionViewItem {
|
||||
},
|
||||
|
||||
onDragLeave: e => {
|
||||
if (this.compositeTransfer.hasData(DraggedCompositeIdentifier.prototype)) {
|
||||
if (this.compositeTransfer.hasData(DraggedCompositeIdentifier.prototype) ||
|
||||
this.compositeTransfer.hasData(DraggedViewIdentifier.prototype)) {
|
||||
this.updateFromDragging(container, false);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -44,7 +44,10 @@ Registry.add(Extensions.WorkbenchActions, new class implements IWorkbenchActionR
|
||||
KeybindingsRegistry.registerKeybindingRule({
|
||||
id: descriptor.id,
|
||||
weight: weight,
|
||||
when: (descriptor.keybindingContext || when ? ContextKeyExpr.and(descriptor.keybindingContext, when) : null),
|
||||
when:
|
||||
descriptor.keybindingContext && when
|
||||
? ContextKeyExpr.and(descriptor.keybindingContext, when)
|
||||
: descriptor.keybindingContext || when || null,
|
||||
primary: keybindings ? keybindings.primary : 0,
|
||||
secondary: keybindings?.secondary,
|
||||
win: keybindings?.win,
|
||||
|
||||
@@ -154,6 +154,7 @@
|
||||
|
||||
.extensions-viewlet > .extensions .monaco-list-row > .extension > .details > .header-container > .header .extension-remote-badge > .codicon {
|
||||
font-size: 12px;
|
||||
color: currentColor;
|
||||
}
|
||||
|
||||
.extensions-viewlet.narrow > .extensions .extension > .icon-container,
|
||||
|
||||
@@ -80,7 +80,7 @@ export class SettingsEditor2 extends BaseEditor {
|
||||
private static CONFIG_SCHEMA_UPDATE_DELAYER = 500;
|
||||
|
||||
private static readonly SUGGESTIONS: string[] = [
|
||||
`@${MODIFIED_SETTING_TAG}`, '@tag:usesOnlineServices', `@${EXTENSION_SETTING_TAG}`
|
||||
`@${MODIFIED_SETTING_TAG}`, '@tag:usesOnlineServices', '@tag:sync', `@${EXTENSION_SETTING_TAG}`
|
||||
];
|
||||
|
||||
private static shouldSettingUpdateFast(type: SettingValueType | SettingValueType[]): boolean {
|
||||
|
||||
@@ -556,8 +556,8 @@ MenuRegistry.appendMenuItem(MenuId.MenubarEditMenu, {
|
||||
order: 1
|
||||
});
|
||||
|
||||
registry.registerWorkbenchAction(SyncActionDescriptor.create(FocusNextSearchResultAction, FocusNextSearchResultAction.ID, FocusNextSearchResultAction.LABEL, { primary: KeyCode.F4 }, ContextKeyExpr.and(Constants.HasSearchResults)), 'Focus Next Search Result', category);
|
||||
registry.registerWorkbenchAction(SyncActionDescriptor.create(FocusPreviousSearchResultAction, FocusPreviousSearchResultAction.ID, FocusPreviousSearchResultAction.LABEL, { primary: KeyMod.Shift | KeyCode.F4 }, ContextKeyExpr.and(Constants.HasSearchResults)), 'Focus Previous Search Result', category);
|
||||
registry.registerWorkbenchAction(SyncActionDescriptor.create(FocusNextSearchResultAction, FocusNextSearchResultAction.ID, FocusNextSearchResultAction.LABEL, { primary: KeyCode.F4 }, ContextKeyExpr.or(Constants.HasSearchResults, SearchEditorConstants.InSearchEditor)), 'Focus Next Search Result', category);
|
||||
registry.registerWorkbenchAction(SyncActionDescriptor.create(FocusPreviousSearchResultAction, FocusPreviousSearchResultAction.ID, FocusPreviousSearchResultAction.LABEL, { primary: KeyMod.Shift | KeyCode.F4 }, ContextKeyExpr.or(Constants.HasSearchResults, SearchEditorConstants.InSearchEditor)), 'Focus Previous Search Result', category);
|
||||
|
||||
registry.registerWorkbenchAction(SyncActionDescriptor.create(ReplaceInFilesAction, ReplaceInFilesAction.ID, ReplaceInFilesAction.LABEL, { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_H }), 'Replace in Files', category);
|
||||
MenuRegistry.appendMenuItem(MenuId.MenubarEditMenu, {
|
||||
|
||||
@@ -488,12 +488,19 @@ export class FocusNextSearchResultAction extends Action {
|
||||
static readonly LABEL = nls.localize('FocusNextSearchResult.label', "Focus Next Search Result");
|
||||
|
||||
constructor(id: string, label: string,
|
||||
@IViewsService private readonly viewsService: IViewsService
|
||||
@IViewsService private readonly viewsService: IViewsService,
|
||||
@IEditorService private readonly editorService: IEditorService,
|
||||
) {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
run(): Promise<any> {
|
||||
async run(): Promise<any> {
|
||||
const input = this.editorService.activeEditor;
|
||||
if (input instanceof SearchEditorInput) {
|
||||
// cast as we cannot import SearchEditor as a value b/c cyclic dependency.
|
||||
return (this.editorService.activeControl as SearchEditor).focusNextResult();
|
||||
}
|
||||
|
||||
return openSearchView(this.viewsService).then(searchView => {
|
||||
if (searchView) {
|
||||
searchView.selectNextMatch();
|
||||
@@ -507,12 +514,19 @@ export class FocusPreviousSearchResultAction extends Action {
|
||||
static readonly LABEL = nls.localize('FocusPreviousSearchResult.label', "Focus Previous Search Result");
|
||||
|
||||
constructor(id: string, label: string,
|
||||
@IViewsService private readonly viewsService: IViewsService
|
||||
@IViewsService private readonly viewsService: IViewsService,
|
||||
@IEditorService private readonly editorService: IEditorService,
|
||||
) {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
run(): Promise<any> {
|
||||
async run(): Promise<any> {
|
||||
const input = this.editorService.activeEditor;
|
||||
if (input instanceof SearchEditorInput) {
|
||||
// cast as we cannot import SearchEditor as a value b/c cyclic dependency.
|
||||
return (this.editorService.activeControl as SearchEditor).focusPreviousResult();
|
||||
}
|
||||
|
||||
return openSearchView(this.viewsService).then(searchView => {
|
||||
if (searchView) {
|
||||
searchView.selectPreviousMatch();
|
||||
|
||||
@@ -181,25 +181,26 @@ export class SearchView extends ViewPane {
|
||||
|
||||
this.container = dom.$('.search-view');
|
||||
|
||||
// globals
|
||||
this.viewletVisible = Constants.SearchViewVisibleKey.bindTo(this.contextKeyService);
|
||||
this.firstMatchFocused = Constants.FirstMatchFocusKey.bindTo(this.contextKeyService);
|
||||
this.fileMatchOrMatchFocused = Constants.FileMatchOrMatchFocusKey.bindTo(this.contextKeyService);
|
||||
this.fileMatchOrFolderMatchFocus = Constants.FileMatchOrFolderMatchFocusKey.bindTo(this.contextKeyService);
|
||||
this.fileMatchOrFolderMatchWithResourceFocus = Constants.FileMatchOrFolderMatchWithResourceFocusKey.bindTo(this.contextKeyService);
|
||||
this.fileMatchFocused = Constants.FileFocusKey.bindTo(this.contextKeyService);
|
||||
this.folderMatchFocused = Constants.FolderFocusKey.bindTo(this.contextKeyService);
|
||||
this.hasSearchResultsKey = Constants.HasSearchResults.bindTo(this.contextKeyService);
|
||||
this.matchFocused = Constants.MatchFocusKey.bindTo(this.contextKeyService);
|
||||
|
||||
// scoped
|
||||
this.contextKeyService = this._register(this.contextKeyService.createScoped(this.container));
|
||||
const viewletFocused = Constants.SearchViewFocusedKey.bindTo(this.contextKeyService);
|
||||
viewletFocused.set(true);
|
||||
|
||||
this.instantiationService = this.instantiationService.createChild(
|
||||
new ServiceCollection([IContextKeyService, this.contextKeyService]));
|
||||
|
||||
this.viewletVisible = Constants.SearchViewVisibleKey.bindTo(contextKeyService);
|
||||
Constants.SearchViewFocusedKey.bindTo(this.contextKeyService).set(true);
|
||||
this.inputBoxFocused = Constants.InputBoxFocusedKey.bindTo(this.contextKeyService);
|
||||
this.inputPatternIncludesFocused = Constants.PatternIncludesFocusedKey.bindTo(this.contextKeyService);
|
||||
this.inputPatternExclusionsFocused = Constants.PatternExcludesFocusedKey.bindTo(this.contextKeyService);
|
||||
this.firstMatchFocused = Constants.FirstMatchFocusKey.bindTo(contextKeyService);
|
||||
this.fileMatchOrMatchFocused = Constants.FileMatchOrMatchFocusKey.bindTo(contextKeyService);
|
||||
this.fileMatchOrFolderMatchFocus = Constants.FileMatchOrFolderMatchFocusKey.bindTo(contextKeyService);
|
||||
this.fileMatchOrFolderMatchWithResourceFocus = Constants.FileMatchOrFolderMatchWithResourceFocusKey.bindTo(contextKeyService);
|
||||
this.fileMatchFocused = Constants.FileFocusKey.bindTo(contextKeyService);
|
||||
this.folderMatchFocused = Constants.FolderFocusKey.bindTo(contextKeyService);
|
||||
this.matchFocused = Constants.MatchFocusKey.bindTo(this.contextKeyService);
|
||||
this.hasSearchResultsKey = Constants.HasSearchResults.bindTo(this.contextKeyService);
|
||||
|
||||
this.instantiationService = this.instantiationService.createChild(
|
||||
new ServiceCollection([IContextKeyService, this.contextKeyService]));
|
||||
|
||||
this.configurationService.onDidChangeConfiguration(e => {
|
||||
if (e.affectsConfiguration('search.sortOrder')) {
|
||||
|
||||
@@ -13,6 +13,7 @@ export const ToggleSearchEditorWholeWordCommandId = 'toggleSearchEditorWholeWord
|
||||
export const ToggleSearchEditorRegexCommandId = 'toggleSearchEditorRegex';
|
||||
export const ToggleSearchEditorContextLinesCommandId = 'toggleSearchEditorContextLines';
|
||||
export const RerunSearchEditorSearchCommandId = 'rerunSearchEditorSearch';
|
||||
export const SelectAllSearchEditorMatchesCommandId = 'selectAllSearchEditorMatches';
|
||||
|
||||
export const InSearchEditor = new RawContextKey<boolean>('inSearchEditor', false);
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileE
|
||||
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 { OpenResultsInEditorAction, OpenSearchEditorAction, toggleSearchEditorCaseSensitiveCommand, toggleSearchEditorContextLinesCommand, toggleSearchEditorRegexCommand, toggleSearchEditorWholeWordCommand } from 'vs/workbench/contrib/searchEditor/browser/searchEditorActions';
|
||||
import { OpenResultsInEditorAction, OpenSearchEditorAction, toggleSearchEditorCaseSensitiveCommand, toggleSearchEditorContextLinesCommand, toggleSearchEditorRegexCommand, toggleSearchEditorWholeWordCommand, selectAllSearchEditorMatchesCommand } from 'vs/workbench/contrib/searchEditor/browser/searchEditorActions';
|
||||
import { getOrMakeSearchEditorInput, SearchEditorInput } from 'vs/workbench/contrib/searchEditor/browser/searchEditorInput';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
|
||||
@@ -146,6 +146,14 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_L }
|
||||
});
|
||||
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: SearchEditorConstants.SelectAllSearchEditorMatchesCommandId,
|
||||
weight: KeybindingWeight.WorkbenchContrib,
|
||||
when: ContextKeyExpr.and(SearchEditorConstants.InSearchEditor),
|
||||
primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_L,
|
||||
handler: selectAllSearchEditorMatchesCommand
|
||||
});
|
||||
|
||||
CommandsRegistry.registerCommand(
|
||||
SearchEditorConstants.RerunSearchEditorSearchCommandId,
|
||||
(accessor: ServicesAccessor) => {
|
||||
|
||||
@@ -47,6 +47,8 @@ import { assertIsDefined } from 'vs/base/common/types';
|
||||
import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfigurationService';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { Position } from 'vs/editor/common/core/position';
|
||||
import { Selection } from 'vs/editor/common/core/selection';
|
||||
|
||||
const RESULT_LINE_REGEX = /^(\s+)(\d+)(:| )(\s+)(.*)$/;
|
||||
const FILE_LINE_REGEX = /^(\S.*):$/;
|
||||
@@ -299,6 +301,42 @@ export class SearchEditor extends BaseTextEditor {
|
||||
return this.configurationService.getValue<ISearchConfigurationProperties>('search');
|
||||
}
|
||||
|
||||
private iterateThroughMatches(reverse: boolean) {
|
||||
const model = this.searchResultEditor.getModel();
|
||||
if (!model) { return; }
|
||||
|
||||
const lastLine = model.getLineCount() ?? 1;
|
||||
const lastColumn = model.getLineLength(lastLine);
|
||||
|
||||
const fallbackStart = reverse ? new Position(lastLine, lastColumn) : new Position(1, 1);
|
||||
|
||||
const currentPosition = this.searchResultEditor.getSelection()?.getStartPosition() ?? fallbackStart;
|
||||
|
||||
const matchRanges = this.getInput()?.getMatchRanges();
|
||||
if (!matchRanges) { return; }
|
||||
|
||||
const matchRange = (reverse ? findPrevRange : findNextRange)(matchRanges, currentPosition);
|
||||
|
||||
this.searchResultEditor.setSelection(matchRange);
|
||||
this.searchResultEditor.revealLineInCenterIfOutsideViewport(matchRange.startLineNumber);
|
||||
this.searchResultEditor.focus();
|
||||
}
|
||||
|
||||
focusNextResult() {
|
||||
this.iterateThroughMatches(false);
|
||||
}
|
||||
|
||||
focusPreviousResult() {
|
||||
this.iterateThroughMatches(true);
|
||||
}
|
||||
|
||||
focusAllResults() {
|
||||
this.searchResultEditor
|
||||
.setSelections((this.getInput()?.getMatchRanges() ?? []).map(
|
||||
range => new Selection(range.startLineNumber, range.startColumn, range.endLineNumber, range.endColumn)));
|
||||
this.searchResultEditor.focus();
|
||||
}
|
||||
|
||||
async triggerSearch(_options?: { resetCursor?: boolean; delay?: number; }) {
|
||||
const options = { resetCursor: true, delay: 0, ..._options };
|
||||
|
||||
@@ -307,7 +345,7 @@ export class SearchEditor extends BaseTextEditor {
|
||||
await this.doRunSearch();
|
||||
this.toggleRunAgainMessage(false);
|
||||
if (options.resetCursor) {
|
||||
this.searchResultEditor.setSelection(new Range(1, 1, 1, 1));
|
||||
this.searchResultEditor.setPosition(new Position(1, 1));
|
||||
this.searchResultEditor.setScrollPosition({ scrollTop: 0, scrollLeft: 0 });
|
||||
}
|
||||
}, options.delay);
|
||||
@@ -523,3 +561,24 @@ registerThemingParticipant((theme, collector) => {
|
||||
});
|
||||
|
||||
export const searchEditorTextInputBorder = registerColor('searchEditor.textInputBorder', { dark: inputBorder, light: inputBorder, hc: inputBorder }, localize('textInputBoxBorder', "Search editor text input box border."));
|
||||
|
||||
function findNextRange(matchRanges: Range[], currentPosition: Position) {
|
||||
for (const matchRange of matchRanges) {
|
||||
if (Position.isBefore(currentPosition, matchRange.getStartPosition())) {
|
||||
return matchRange;
|
||||
}
|
||||
}
|
||||
return matchRanges[0];
|
||||
}
|
||||
|
||||
function findPrevRange(matchRanges: Range[], currentPosition: Position) {
|
||||
for (let i = matchRanges.length - 1; i >= 0; i--) {
|
||||
const matchRange = matchRanges[i];
|
||||
if (Position.isBefore(matchRange.getStartPosition(), currentPosition)) {
|
||||
{
|
||||
return matchRange;
|
||||
}
|
||||
}
|
||||
}
|
||||
return matchRanges[matchRanges.length - 1];
|
||||
}
|
||||
|
||||
@@ -54,6 +54,14 @@ export const toggleSearchEditorContextLinesCommand = (accessor: ServicesAccessor
|
||||
}
|
||||
};
|
||||
|
||||
export const selectAllSearchEditorMatchesCommand = (accessor: ServicesAccessor) => {
|
||||
const editorService = accessor.get(IEditorService);
|
||||
const input = editorService.activeEditor;
|
||||
if (input instanceof SearchEditorInput) {
|
||||
(editorService.activeControl as SearchEditor).focusAllResults();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
export class OpenSearchEditorAction extends Action {
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
opacity: 0.5;
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.timeline-tree-view .monaco-list .monaco-list-row .custom-view-tree-node-item .monaco-icon-label {
|
||||
|
||||
@@ -14,6 +14,8 @@ import { TimelineService } from 'vs/workbench/contrib/timeline/common/timelineSe
|
||||
import { TimelinePane } from './timelinePane';
|
||||
import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry';
|
||||
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { MenuId, MenuRegistry } from 'vs/platform/actions/common/actions';
|
||||
import { ICommandHandler, CommandsRegistry } from 'vs/platform/commands/common/commands';
|
||||
import product from 'vs/platform/product/common/product';
|
||||
|
||||
export class TimelinePaneDescriptor implements IViewDescriptor {
|
||||
@@ -42,13 +44,102 @@ configurationRegistry.registerConfiguration({
|
||||
'timeline.showView': {
|
||||
type: 'boolean',
|
||||
description: localize('timeline.showView', "Experimental: When enabled, shows a Timeline view in the Explorer sidebar."),
|
||||
default: false //product.quality !== 'stable'
|
||||
default: product.quality !== 'stable'
|
||||
},
|
||||
'timeline.excludeSources': {
|
||||
type: 'array',
|
||||
description: localize('timeline.excludeSources', "Experimental: An array of Timeline sources that should be excluded from the Timeline view"),
|
||||
default: null
|
||||
},
|
||||
}
|
||||
});
|
||||
|
||||
if (product.quality !== 'stable') {
|
||||
Registry.as<IViewsRegistry>(ViewExtensions.ViewsRegistry).registerViews([new TimelinePaneDescriptor()], VIEW_CONTAINER);
|
||||
Registry.as<IViewsRegistry>(ViewExtensions.ViewsRegistry).registerViews([new TimelinePaneDescriptor()], VIEW_CONTAINER);
|
||||
|
||||
namespace TimelineViewRefreshAction {
|
||||
|
||||
export const ID = 'timeline.refresh';
|
||||
export const LABEL = localize('timeline.refreshView', "Refresh");
|
||||
|
||||
export function handler(): ICommandHandler {
|
||||
return (accessor, arg) => {
|
||||
const service = accessor.get(ITimelineService);
|
||||
return service.reset();
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
CommandsRegistry.registerCommand(TimelineViewRefreshAction.ID, TimelineViewRefreshAction.handler());
|
||||
|
||||
// namespace TimelineViewRefreshHardAction {
|
||||
|
||||
// export const ID = 'timeline.refreshHard';
|
||||
// export const LABEL = localize('timeline.refreshHard', "Refresh (Hard)");
|
||||
|
||||
// export function handler(fetch?: 'all' | 'more'): ICommandHandler {
|
||||
// return (accessor, arg) => {
|
||||
// const service = accessor.get(ITimelineService);
|
||||
// return service.refresh(fetch);
|
||||
// };
|
||||
// }
|
||||
// }
|
||||
|
||||
// CommandsRegistry.registerCommand(TimelineViewRefreshAction.ID, TimelineViewRefreshAction.handler());
|
||||
|
||||
// namespace TimelineViewLoadMoreAction {
|
||||
|
||||
// export const ID = 'timeline.loadMore';
|
||||
// export const LABEL = localize('timeline.loadMoreInView', "Load More");
|
||||
|
||||
// export function handler(): ICommandHandler {
|
||||
// return (accessor, arg) => {
|
||||
// const service = accessor.get(ITimelineService);
|
||||
// return service.refresh('more');
|
||||
// };
|
||||
// }
|
||||
// }
|
||||
|
||||
// CommandsRegistry.registerCommand(TimelineViewLoadMoreAction.ID, TimelineViewLoadMoreAction.handler());
|
||||
|
||||
// namespace TimelineViewLoadAllAction {
|
||||
|
||||
// export const ID = 'timeline.loadAll';
|
||||
// export const LABEL = localize('timeline.loadAllInView', "Load All");
|
||||
|
||||
// export function handler(): ICommandHandler {
|
||||
// return (accessor, arg) => {
|
||||
// const service = accessor.get(ITimelineService);
|
||||
// return service.refresh('all');
|
||||
// };
|
||||
// }
|
||||
// }
|
||||
|
||||
// CommandsRegistry.registerCommand(TimelineViewLoadAllAction.ID, TimelineViewLoadAllAction.handler());
|
||||
|
||||
MenuRegistry.appendMenuItem(MenuId.TimelineTitle, ({
|
||||
group: 'navigation',
|
||||
order: 1,
|
||||
command: {
|
||||
id: TimelineViewRefreshAction.ID,
|
||||
title: TimelineViewRefreshAction.LABEL,
|
||||
icon: { id: 'codicon/refresh' }
|
||||
}
|
||||
}));
|
||||
|
||||
// MenuRegistry.appendMenuItem(MenuId.TimelineTitle, ({
|
||||
// group: 'navigation',
|
||||
// order: 2,
|
||||
// command: {
|
||||
// id: TimelineViewLoadMoreAction.ID,
|
||||
// title: TimelineViewLoadMoreAction.LABEL,
|
||||
// icon: { id: 'codicon/unfold' }
|
||||
// },
|
||||
// alt: {
|
||||
// id: TimelineViewLoadAllAction.ID,
|
||||
// title: TimelineViewLoadAllAction.LABEL,
|
||||
// icon: { id: 'codicon/unfold' }
|
||||
|
||||
// }
|
||||
// }));
|
||||
|
||||
registerSingleton(ITimelineService, TimelineService, true);
|
||||
|
||||
@@ -8,6 +8,7 @@ import { localize } from 'vs/nls';
|
||||
import * as DOM from 'vs/base/browser/dom';
|
||||
import { CancellationTokenSource } from 'vs/base/common/cancellation';
|
||||
import { FuzzyScore, createMatches } from 'vs/base/common/filters';
|
||||
import { Iterator } from 'vs/base/common/iterator';
|
||||
import { DisposableStore, IDisposable, Disposable } from 'vs/base/common/lifecycle';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { IconLabel } from 'vs/base/browser/ui/iconLabel/iconLabel';
|
||||
@@ -18,9 +19,9 @@ import { TreeResourceNavigator, WorkbenchObjectTree } from 'vs/platform/list/bro
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { ITimelineService, TimelineChangeEvent, TimelineProvidersChangeEvent, TimelineRequest, TimelineItem } from 'vs/workbench/contrib/timeline/common/timeline';
|
||||
import { ITimelineService, TimelineChangeEvent, TimelineItem, TimelineOptions, TimelineProvidersChangeEvent, TimelineRequest, Timeline } from 'vs/workbench/contrib/timeline/common/timeline';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { SideBySideEditor, toResource } from 'vs/workbench/common/editor';
|
||||
import { ICommandService } from 'vs/platform/commands/common/commands';
|
||||
@@ -28,7 +29,6 @@ import { IThemeService, LIGHT, ThemeIcon } from 'vs/platform/theme/common/themeS
|
||||
import { IViewDescriptorService } from 'vs/workbench/common/views';
|
||||
import { basename } from 'vs/base/common/path';
|
||||
import { IProgressService } from 'vs/platform/progress/common/progress';
|
||||
import { VIEWLET_ID } from 'vs/workbench/contrib/files/common/files';
|
||||
import { debounce } from 'vs/base/common/decorators';
|
||||
import { IOpenerService } from 'vs/platform/opener/common/opener';
|
||||
import { IActionViewItemProvider, ActionBar, ActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
@@ -40,13 +40,53 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
|
||||
// TODO[ECA]: Localize all the strings
|
||||
|
||||
type TreeElement = TimelineItem;
|
||||
const InitialPageSize = 20;
|
||||
const SubsequentPageSize = 40;
|
||||
|
||||
interface CommandItem {
|
||||
handle: 'vscode-command:loadMore';
|
||||
timestamp: number;
|
||||
label: string;
|
||||
themeIcon?: { id: string };
|
||||
description?: string;
|
||||
detail?: string;
|
||||
contextValue?: string;
|
||||
|
||||
// Make things easier for duck typing
|
||||
id: undefined;
|
||||
icon: undefined;
|
||||
iconDark: undefined;
|
||||
source: undefined;
|
||||
}
|
||||
|
||||
type TreeElement = TimelineItem | CommandItem;
|
||||
|
||||
// function isCommandItem(item: TreeElement | undefined): item is CommandItem {
|
||||
// return item?.handle.startsWith('vscode-command:') ?? false;
|
||||
// }
|
||||
|
||||
function isLoadMoreCommandItem(item: TreeElement | undefined): item is CommandItem & {
|
||||
handle: 'vscode-command:loadMore';
|
||||
} {
|
||||
return item?.handle === 'vscode-command:loadMore';
|
||||
}
|
||||
|
||||
function isTimelineItem(item: TreeElement | undefined): item is TimelineItem {
|
||||
return !item?.handle.startsWith('vscode-command:') ?? false;
|
||||
}
|
||||
|
||||
|
||||
interface TimelineActionContext {
|
||||
uri: URI | undefined;
|
||||
item: TreeElement;
|
||||
}
|
||||
|
||||
interface TimelineCursors {
|
||||
startCursors?: { before: any; after?: any };
|
||||
endCursors?: { before: any; after?: any };
|
||||
more: boolean;
|
||||
}
|
||||
|
||||
export class TimelinePane extends ViewPane {
|
||||
static readonly ID = 'timeline';
|
||||
static readonly TITLE = localize('timeline', 'Timeline');
|
||||
@@ -59,8 +99,9 @@ export class TimelinePane extends ViewPane {
|
||||
private _menus: TimelineMenus;
|
||||
private _visibilityDisposables: DisposableStore | undefined;
|
||||
|
||||
// private _excludedSources: Set<string> | undefined;
|
||||
private _items: TimelineItem[] = [];
|
||||
private _excludedSources: Set<string>;
|
||||
private _cursorsByProvider: Map<string, TimelineCursors> = new Map();
|
||||
private _items: { element: TreeElement }[] = [];
|
||||
private _loadingMessageTimer: any | undefined;
|
||||
private _pendingRequests = new Map<string, TimelineRequest>();
|
||||
private _uri: URI | undefined;
|
||||
@@ -87,6 +128,18 @@ export class TimelinePane extends ViewPane {
|
||||
|
||||
const scopedContextKeyService = this._register(this.contextKeyService.createScoped());
|
||||
scopedContextKeyService.createKey('view', TimelinePane.ID);
|
||||
|
||||
this._excludedSources = new Set(configurationService.getValue('timeline.excludeSources'));
|
||||
configurationService.onDidChangeConfiguration(this.onConfigurationChanged, this);
|
||||
}
|
||||
|
||||
private onConfigurationChanged(e: IConfigurationChangeEvent) {
|
||||
if (!e.affectsConfiguration('timeline.excludeSources')) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._excludedSources = new Set(this.configurationService.getValue('timeline.excludeSources'));
|
||||
this.loadTimeline(true);
|
||||
}
|
||||
|
||||
private onActiveEditorChanged() {
|
||||
@@ -105,7 +158,7 @@ export class TimelinePane extends ViewPane {
|
||||
|
||||
this._uri = uri;
|
||||
this._treeRenderer?.setUri(uri);
|
||||
this.loadTimeline();
|
||||
this.loadTimeline(true);
|
||||
}
|
||||
|
||||
private onProvidersChanged(e: TimelineProvidersChangeEvent) {
|
||||
@@ -116,16 +169,20 @@ export class TimelinePane extends ViewPane {
|
||||
}
|
||||
|
||||
if (e.added) {
|
||||
this.loadTimeline(e.added);
|
||||
this.loadTimeline(true, e.added);
|
||||
}
|
||||
}
|
||||
|
||||
private onTimelineChanged(e: TimelineChangeEvent) {
|
||||
if (e.uri === undefined || e.uri.toString(true) !== this._uri?.toString(true)) {
|
||||
this.loadTimeline([e.id]);
|
||||
if (e?.uri === undefined || e.uri.toString(true) !== this._uri?.toString(true)) {
|
||||
this.loadTimeline(e.reset ?? false, e?.id === undefined ? undefined : [e.id], { before: !e.reset });
|
||||
}
|
||||
}
|
||||
|
||||
private onReset() {
|
||||
this.loadTimeline(true);
|
||||
}
|
||||
|
||||
private _message: string | undefined;
|
||||
get message(): string | undefined {
|
||||
return this._message;
|
||||
@@ -160,22 +217,27 @@ export class TimelinePane extends ViewPane {
|
||||
DOM.clearNode(this._messageElement);
|
||||
}
|
||||
|
||||
private async loadTimeline(sources?: string[]) {
|
||||
private async loadTimeline(reset: boolean, sources?: string[], options: TimelineOptions = {}) {
|
||||
const defaultPageSize = reset ? InitialPageSize : SubsequentPageSize;
|
||||
|
||||
// If we have no source, we are reseting all sources, so cancel everything in flight and reset caches
|
||||
if (sources === undefined) {
|
||||
this._items.length = 0;
|
||||
if (reset) {
|
||||
this._items.length = 0;
|
||||
this._cursorsByProvider.clear();
|
||||
|
||||
if (this._loadingMessageTimer) {
|
||||
clearTimeout(this._loadingMessageTimer);
|
||||
this._loadingMessageTimer = undefined;
|
||||
if (this._loadingMessageTimer) {
|
||||
clearTimeout(this._loadingMessageTimer);
|
||||
this._loadingMessageTimer = undefined;
|
||||
}
|
||||
|
||||
for (const { tokenSource } of this._pendingRequests.values()) {
|
||||
tokenSource.dispose(true);
|
||||
}
|
||||
|
||||
this._pendingRequests.clear();
|
||||
}
|
||||
|
||||
for (const { tokenSource } of this._pendingRequests.values()) {
|
||||
tokenSource.dispose(true);
|
||||
}
|
||||
|
||||
this._pendingRequests.clear();
|
||||
|
||||
// TODO[ECA]: Are these the right the list of schemes to exclude? Is there a better way?
|
||||
if (this._uri && (this._uri.scheme === 'vscode-settings' || this._uri.scheme === 'webview-panel' || this._uri.scheme === 'walkThrough')) {
|
||||
this.message = 'The active editor cannot provide timeline information.';
|
||||
@@ -184,7 +246,7 @@ export class TimelinePane extends ViewPane {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._uri !== undefined) {
|
||||
if (reset && this._uri !== undefined) {
|
||||
this._loadingMessageTimer = setTimeout((uri: URI) => {
|
||||
if (uri !== this._uri) {
|
||||
return;
|
||||
@@ -200,50 +262,169 @@ export class TimelinePane extends ViewPane {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const source of sources ?? this.timelineService.getSources()) {
|
||||
const filteredSources = (sources ?? this.timelineService.getSources()).filter(s => !this._excludedSources.has(s));
|
||||
if (filteredSources.length === 0) {
|
||||
if (reset) {
|
||||
this.refresh();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
let lastIndex = this._items.length - 1;
|
||||
let lastItem = this._items[lastIndex]?.element;
|
||||
if (isLoadMoreCommandItem(lastItem)) {
|
||||
lastItem.themeIcon = { id: 'sync~spin' };
|
||||
// this._items.splice(lastIndex, 1);
|
||||
lastIndex--;
|
||||
|
||||
if (!reset && !options.before) {
|
||||
lastItem = this._items[lastIndex]?.element;
|
||||
const selection = [lastItem];
|
||||
this._tree.setSelection(selection);
|
||||
this._tree.setFocus(selection);
|
||||
}
|
||||
}
|
||||
|
||||
for (const source of filteredSources) {
|
||||
let request = this._pendingRequests.get(source);
|
||||
request?.tokenSource.dispose(true);
|
||||
|
||||
request = this.timelineService.getTimeline(source, this._uri, {}, new CancellationTokenSource(), { cacheResults: true })!;
|
||||
const cursors = this._cursorsByProvider.get(source);
|
||||
if (!reset) {
|
||||
// TODO: Handle pending request
|
||||
|
||||
this._pendingRequests.set(source, request);
|
||||
request.tokenSource.token.onCancellationRequested(() => this._pendingRequests.delete(source));
|
||||
if (cursors?.more === false) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const reusingToken = request?.tokenSource !== undefined;
|
||||
request = this.timelineService.getTimeline(
|
||||
source, this._uri,
|
||||
{
|
||||
cursor: options.before ? cursors?.startCursors?.before : (cursors?.endCursors ?? cursors?.startCursors)?.after,
|
||||
...options,
|
||||
limit: options.limit === 0 ? undefined : options.limit ?? defaultPageSize
|
||||
},
|
||||
request?.tokenSource ?? new CancellationTokenSource(), { cacheResults: true }
|
||||
)!;
|
||||
|
||||
this._pendingRequests.set(source, request);
|
||||
if (!reusingToken) {
|
||||
request.tokenSource.token.onCancellationRequested(() => this._pendingRequests.delete(source));
|
||||
}
|
||||
} else {
|
||||
request?.tokenSource.dispose(true);
|
||||
|
||||
request = this.timelineService.getTimeline(
|
||||
source, this._uri,
|
||||
{
|
||||
...options,
|
||||
limit: options.limit === 0 ? undefined : (reset ? cursors?.endCursors?.after : undefined) ?? options.limit ?? defaultPageSize
|
||||
},
|
||||
new CancellationTokenSource(), { cacheResults: true }
|
||||
)!;
|
||||
|
||||
this._pendingRequests.set(source, request);
|
||||
request.tokenSource.token.onCancellationRequested(() => this._pendingRequests.delete(source));
|
||||
}
|
||||
|
||||
this.handleRequest(request);
|
||||
}
|
||||
}
|
||||
|
||||
private async handleRequest(request: TimelineRequest) {
|
||||
let items;
|
||||
let timeline: Timeline | undefined;
|
||||
try {
|
||||
items = await this.progressService.withProgress({ location: VIEWLET_ID }, () => request.result.then(r => r?.items ?? []));
|
||||
timeline = await this.progressService.withProgress({ location: this.getProgressLocation() }, () => request.result);
|
||||
}
|
||||
finally {
|
||||
this._pendingRequests.delete(request.source);
|
||||
}
|
||||
catch { }
|
||||
|
||||
this._pendingRequests.delete(request.source);
|
||||
if (request.tokenSource.token.isCancellationRequested || request.uri !== this._uri) {
|
||||
if (
|
||||
timeline === undefined ||
|
||||
request.tokenSource.token.isCancellationRequested ||
|
||||
request.uri !== this._uri
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.replaceItems(request.source, items);
|
||||
}
|
||||
let items: TreeElement[];
|
||||
|
||||
private replaceItems(source: string, items?: TimelineItem[]) {
|
||||
const hasItems = this._items.length !== 0;
|
||||
const source = request.source;
|
||||
|
||||
if (items?.length) {
|
||||
this._items.splice(0, this._items.length, ...this._items.filter(i => i.source !== source), ...items);
|
||||
this._items.sort((a, b) => (b.timestamp - a.timestamp) || b.source.localeCompare(a.source, undefined, { numeric: true, sensitivity: 'base' }));
|
||||
if (timeline !== undefined) {
|
||||
if (timeline.paging !== undefined) {
|
||||
let cursors = this._cursorsByProvider.get(timeline.source ?? source);
|
||||
if (cursors === undefined) {
|
||||
cursors = { startCursors: timeline.paging.cursors, more: timeline.paging.more ?? false };
|
||||
this._cursorsByProvider.set(timeline.source, cursors);
|
||||
} else {
|
||||
if (request.options.before) {
|
||||
if (cursors.endCursors === undefined) {
|
||||
cursors.endCursors = cursors.startCursors;
|
||||
}
|
||||
cursors.startCursors = timeline.paging.cursors;
|
||||
}
|
||||
else {
|
||||
if (cursors.startCursors === undefined) {
|
||||
cursors.startCursors = timeline.paging.cursors;
|
||||
}
|
||||
cursors.endCursors = timeline.paging.cursors;
|
||||
}
|
||||
cursors.more = timeline.paging.more ?? true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this._cursorsByProvider.delete(source);
|
||||
}
|
||||
else if (this._items.length && this._items.some(i => i.source === source)) {
|
||||
this._items = this._items.filter(i => i.source !== source);
|
||||
items = (timeline.items as TreeElement[]) ?? [];
|
||||
|
||||
const alreadyHadItems = this._items.length !== 0;
|
||||
|
||||
let changed;
|
||||
if (request.options.cursor) {
|
||||
changed = this.mergeItems(request.source, items, request.options);
|
||||
} else {
|
||||
changed = this.replaceItems(request.source, items);
|
||||
}
|
||||
else {
|
||||
|
||||
if (!changed) {
|
||||
// If there are no items at all and no pending requests, make sure to refresh (to show the no timeline info message)
|
||||
if (this._items.length === 0 && this._pendingRequests.size === 0) {
|
||||
this.refresh();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._pendingRequests.size === 0 && this._items.length !== 0) {
|
||||
const lastIndex = this._items.length - 1;
|
||||
const lastItem = this._items[lastIndex]?.element;
|
||||
|
||||
if (timeline.paging?.more || Iterator.some(this._cursorsByProvider.values(), cursors => cursors.more)) {
|
||||
if (isLoadMoreCommandItem(lastItem)) {
|
||||
lastItem.themeIcon = undefined;
|
||||
}
|
||||
else {
|
||||
this._items.push({
|
||||
element: {
|
||||
handle: 'vscode-command:loadMore',
|
||||
label: 'Load more',
|
||||
timestamp: 0
|
||||
} as CommandItem
|
||||
});
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (isLoadMoreCommandItem(lastItem)) {
|
||||
this._items.splice(lastIndex, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If we have items already and there are other pending requests, debounce for a bit to wait for other requests
|
||||
if (hasItems && this._pendingRequests.size !== 0) {
|
||||
if (alreadyHadItems && this._pendingRequests.size !== 0) {
|
||||
this.refreshDebounced();
|
||||
}
|
||||
else {
|
||||
@@ -251,6 +432,79 @@ export class TimelinePane extends ViewPane {
|
||||
}
|
||||
}
|
||||
|
||||
private mergeItems(source: string, items: TreeElement[] | undefined, options: TimelineOptions): boolean {
|
||||
if (items?.length === undefined || items.length === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (options.before) {
|
||||
const ids = new Set();
|
||||
const timestamps = new Set();
|
||||
|
||||
for (const item of items) {
|
||||
if (item.id === undefined) {
|
||||
timestamps.add(item.timestamp);
|
||||
}
|
||||
else {
|
||||
ids.add(item.id);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove any duplicate items
|
||||
// I don't think we need to check all the items, just the most recent page
|
||||
let i = Math.min(SubsequentPageSize, this._items.length);
|
||||
let item;
|
||||
while (i--) {
|
||||
item = this._items[i].element;
|
||||
if (
|
||||
(item.id === undefined && ids.has(item.id)) ||
|
||||
(item.timestamp === undefined && timestamps.has(item.timestamp))
|
||||
) {
|
||||
this._items.splice(i, 1);
|
||||
}
|
||||
}
|
||||
|
||||
this._items.splice(0, 0, ...items.map(item => ({ element: item })));
|
||||
} else {
|
||||
this._items.push(...items.map(item => ({ element: item })));
|
||||
}
|
||||
|
||||
this.sortItems();
|
||||
return true;
|
||||
}
|
||||
|
||||
private replaceItems(source: string, items?: TreeElement[]): boolean {
|
||||
if (items?.length) {
|
||||
this._items.splice(
|
||||
0, this._items.length,
|
||||
...this._items.filter(item => item.element.source !== source),
|
||||
...items.map(item => ({ element: item }))
|
||||
);
|
||||
this.sortItems();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this._items.length && this._items.some(item => item.element.source === source)) {
|
||||
this._items = this._items.filter(item => item.element.source !== source);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private sortItems() {
|
||||
this._items.sort(
|
||||
(a, b) =>
|
||||
(b.element.timestamp - a.element.timestamp) ||
|
||||
(a.element.source === undefined
|
||||
? b.element.source === undefined ? 0 : 1
|
||||
: b.element.source === undefined ? -1 : b.element.source.localeCompare(a.element.source, undefined, { numeric: true, sensitivity: 'base' }))
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
private refresh() {
|
||||
if (this._loadingMessageTimer) {
|
||||
clearTimeout(this._loadingMessageTimer);
|
||||
@@ -263,7 +517,7 @@ export class TimelinePane extends ViewPane {
|
||||
this.message = undefined;
|
||||
}
|
||||
|
||||
this._tree.setChildren(null, this._items.map(item => ({ element: item })));
|
||||
this._tree.setChildren(null, this._items);
|
||||
}
|
||||
|
||||
@debounce(500)
|
||||
@@ -282,6 +536,7 @@ export class TimelinePane extends ViewPane {
|
||||
|
||||
this.timelineService.onDidChangeProviders(this.onProvidersChanged, this, this._visibilityDisposables);
|
||||
this.timelineService.onDidChangeTimeline(this.onTimelineChanged, this, this._visibilityDisposables);
|
||||
this.timelineService.onDidReset(this.onReset, this, this._visibilityDisposables);
|
||||
this.editorService.onDidActiveEditorChange(this.onActiveEditorChanged, this, this._visibilityDisposables);
|
||||
|
||||
this.onActiveEditorChanged();
|
||||
@@ -329,9 +584,24 @@ export class TimelinePane extends ViewPane {
|
||||
}
|
||||
|
||||
const selection = this._tree.getSelection();
|
||||
const command = selection.length === 1 ? selection[0]?.command : undefined;
|
||||
if (command) {
|
||||
this.commandService.executeCommand(command.id, ...(command.arguments || []));
|
||||
const item = selection.length === 1 ? selection[0] : undefined;
|
||||
// eslint-disable-next-line eqeqeq
|
||||
if (item == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isTimelineItem(item)) {
|
||||
if (item.command) {
|
||||
this.commandService.executeCommand(item.command.id, ...(item.command.arguments || []));
|
||||
}
|
||||
}
|
||||
else if (isLoadMoreCommandItem(item)) {
|
||||
// TODO: Change this, but right now this is the pending signal
|
||||
if (item.themeIcon !== undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.loadTimeline(false);
|
||||
}
|
||||
})
|
||||
);
|
||||
@@ -417,6 +687,11 @@ export class TimelineIdentityProvider implements IIdentityProvider<TreeElement>
|
||||
class TimelineActionRunner extends ActionRunner {
|
||||
|
||||
runAction(action: IAction, { uri, item }: TimelineActionContext): Promise<any> {
|
||||
if (!isTimelineItem(item)) {
|
||||
// TODO
|
||||
return action.run();
|
||||
}
|
||||
|
||||
return action.run(...[
|
||||
{
|
||||
$mid: 11,
|
||||
@@ -499,7 +774,7 @@ class TimelineTreeRenderer implements ITreeRenderer<TreeElement, FuzzyScore, Tim
|
||||
matches: createMatches(node.filterData)
|
||||
});
|
||||
|
||||
template.timestamp.textContent = fromNow(item.timestamp);
|
||||
template.timestamp.textContent = isTimelineItem(item) ? fromNow(item.timestamp) : '';
|
||||
|
||||
template.actionBar.context = { uri: this._uri, item: item } as TimelineActionContext;
|
||||
template.actionBar.actionRunner = new TimelineActionRunner();
|
||||
|
||||
@@ -19,6 +19,7 @@ export interface TimelineItem {
|
||||
handle: string;
|
||||
source: string;
|
||||
|
||||
id?: string;
|
||||
timestamp: number;
|
||||
label: string;
|
||||
icon?: URI,
|
||||
@@ -31,28 +32,34 @@ export interface TimelineItem {
|
||||
}
|
||||
|
||||
export interface TimelineChangeEvent {
|
||||
id: string;
|
||||
id?: string;
|
||||
uri?: URI;
|
||||
reset?: boolean
|
||||
}
|
||||
|
||||
export interface TimelineCursor {
|
||||
cursor?: any;
|
||||
export interface TimelineOptions {
|
||||
cursor?: string;
|
||||
before?: boolean;
|
||||
limit?: number;
|
||||
limit?: number | string;
|
||||
}
|
||||
|
||||
export interface Timeline {
|
||||
source: string;
|
||||
items: TimelineItem[];
|
||||
|
||||
cursor?: any;
|
||||
more?: boolean;
|
||||
paging?: {
|
||||
cursors: {
|
||||
before: string;
|
||||
after?: string
|
||||
};
|
||||
more?: boolean;
|
||||
}
|
||||
}
|
||||
|
||||
export interface TimelineProvider extends TimelineProviderDescriptor, IDisposable {
|
||||
onDidChange?: Event<TimelineChangeEvent>;
|
||||
|
||||
provideTimeline(uri: URI, cursor: TimelineCursor, token: CancellationToken, options?: { cacheResults?: boolean }): Promise<Timeline | undefined>;
|
||||
provideTimeline(uri: URI, options: TimelineOptions, token: CancellationToken, internalOptions?: { cacheResults?: boolean }): Promise<Timeline | undefined>;
|
||||
}
|
||||
|
||||
export interface TimelineProviderDescriptor {
|
||||
@@ -68,6 +75,7 @@ export interface TimelineProvidersChangeEvent {
|
||||
|
||||
export interface TimelineRequest {
|
||||
readonly result: Promise<Timeline | undefined>;
|
||||
readonly options: TimelineOptions;
|
||||
readonly source: string;
|
||||
readonly tokenSource: CancellationTokenSource;
|
||||
readonly uri: URI;
|
||||
@@ -78,13 +86,17 @@ export interface ITimelineService {
|
||||
|
||||
onDidChangeProviders: Event<TimelineProvidersChangeEvent>;
|
||||
onDidChangeTimeline: Event<TimelineChangeEvent>;
|
||||
onDidReset: Event<void>;
|
||||
|
||||
registerTimelineProvider(provider: TimelineProvider): IDisposable;
|
||||
unregisterTimelineProvider(id: string): void;
|
||||
|
||||
getSources(): string[];
|
||||
|
||||
getTimeline(id: string, uri: URI, cursor: TimelineCursor, tokenSource: CancellationTokenSource, options?: { cacheResults?: boolean }): TimelineRequest | undefined;
|
||||
getTimeline(id: string, uri: URI, options: TimelineOptions, tokenSource: CancellationTokenSource, internalOptions?: { cacheResults?: boolean }): TimelineRequest | undefined;
|
||||
|
||||
// refresh(fetch?: 'all' | 'more'): void;
|
||||
reset(): void;
|
||||
}
|
||||
|
||||
const TIMELINE_SERVICE_ID = 'timeline';
|
||||
|
||||
@@ -9,7 +9,7 @@ import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
// import { basename } from 'vs/base/common/path';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { ITimelineService, TimelineChangeEvent, TimelineCursor, TimelineProvidersChangeEvent, TimelineProvider } from './timeline';
|
||||
import { ITimelineService, TimelineChangeEvent, TimelineOptions, TimelineProvidersChangeEvent, TimelineProvider } from './timeline';
|
||||
|
||||
export class TimelineService implements ITimelineService {
|
||||
_serviceBrand: undefined;
|
||||
@@ -20,6 +20,9 @@ export class TimelineService implements ITimelineService {
|
||||
private readonly _onDidChangeTimeline = new Emitter<TimelineChangeEvent>();
|
||||
readonly onDidChangeTimeline: Event<TimelineChangeEvent> = this._onDidChangeTimeline.event;
|
||||
|
||||
private readonly _onDidReset = new Emitter<void>();
|
||||
readonly onDidReset: Event<void> = this._onDidReset.event;
|
||||
|
||||
private readonly _providers = new Map<string, TimelineProvider>();
|
||||
private readonly _providerSubscriptions = new Map<string, IDisposable>();
|
||||
|
||||
@@ -81,7 +84,7 @@ export class TimelineService implements ITimelineService {
|
||||
return [...this._providers.keys()];
|
||||
}
|
||||
|
||||
getTimeline(id: string, uri: URI, cursor: TimelineCursor, tokenSource: CancellationTokenSource, options?: { cacheResults?: boolean }) {
|
||||
getTimeline(id: string, uri: URI, options: TimelineOptions, tokenSource: CancellationTokenSource, internalOptions?: { cacheResults?: boolean }) {
|
||||
this.logService.trace(`TimelineService#getTimeline(${id}): uri=${uri.toString(true)}`);
|
||||
|
||||
const provider = this._providers.get(id);
|
||||
@@ -98,7 +101,7 @@ export class TimelineService implements ITimelineService {
|
||||
}
|
||||
|
||||
return {
|
||||
result: provider.provideTimeline(uri, cursor, tokenSource.token, options)
|
||||
result: provider.provideTimeline(uri, options, tokenSource.token, internalOptions)
|
||||
.then(result => {
|
||||
if (result === undefined) {
|
||||
return undefined;
|
||||
@@ -109,6 +112,7 @@ export class TimelineService implements ITimelineService {
|
||||
|
||||
return result;
|
||||
}),
|
||||
options: options,
|
||||
source: provider.id,
|
||||
tokenSource: tokenSource,
|
||||
uri: uri
|
||||
@@ -156,4 +160,12 @@ export class TimelineService implements ITimelineService {
|
||||
this._providerSubscriptions.delete(id);
|
||||
this._onDidChangeProviders.fire({ removed: [id] });
|
||||
}
|
||||
|
||||
// refresh(fetch?: 'all' | 'more') {
|
||||
// this._onDidChangeTimeline.fire({ fetch: fetch });
|
||||
// }
|
||||
|
||||
reset() {
|
||||
this._onDidReset.fire();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,14 +9,16 @@ import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle';
|
||||
import { UserDataSyncWorkbenchContribution } from 'vs/workbench/contrib/userDataSync/browser/userDataSync';
|
||||
import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration';
|
||||
import { IUserDataSyncEnablementService, getUserDataSyncStore } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
import { IProductService } from 'vs/platform/product/common/productService';
|
||||
|
||||
class UserDataSyncSettingsMigrationContribution implements IWorkbenchContribution {
|
||||
|
||||
constructor(
|
||||
@IProductService productService: IProductService,
|
||||
@IConfigurationService private readonly configurationService: IConfigurationService,
|
||||
@IUserDataSyncEnablementService userDataSyncEnablementService: IUserDataSyncEnablementService,
|
||||
) {
|
||||
if (getUserDataSyncStore(configurationService)) {
|
||||
if (getUserDataSyncStore(productService, configurationService)) {
|
||||
if (!configurationService.getValue('sync.enableSettings')) {
|
||||
userDataSyncEnablementService.setResourceEnablement('settings', false);
|
||||
}
|
||||
|
||||
@@ -47,6 +47,8 @@ import { IPreferencesService } from 'vs/workbench/services/preferences/common/pr
|
||||
import { IAuthenticationTokenService } from 'vs/platform/authentication/common/authentication';
|
||||
import { fromNow } from 'vs/base/common/date';
|
||||
import { IProductService } from 'vs/platform/product/common/productService';
|
||||
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
|
||||
import { IOpenerService } from 'vs/platform/opener/common/opener';
|
||||
|
||||
const enum AuthStatus {
|
||||
Initializing = 'Initializing',
|
||||
@@ -135,9 +137,11 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
|
||||
@ITelemetryService private readonly telemetryService: ITelemetryService,
|
||||
@IFileService private readonly fileService: IFileService,
|
||||
@IProductService private readonly productService: IProductService,
|
||||
@IStorageService private readonly storageService: IStorageService,
|
||||
@IOpenerService private readonly openerService: IOpenerService,
|
||||
) {
|
||||
super();
|
||||
this.userDataSyncStore = getUserDataSyncStore(configurationService);
|
||||
this.userDataSyncStore = getUserDataSyncStore(productService, configurationService);
|
||||
this.syncEnablementContext = CONTEXT_SYNC_ENABLEMENT.bindTo(contextKeyService);
|
||||
this.syncStatusContext = CONTEXT_SYNC_STATE.bindTo(contextKeyService);
|
||||
this.authenticationState = CONTEXT_AUTH_TOKEN_STATE.bindTo(contextKeyService);
|
||||
@@ -235,7 +239,6 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
|
||||
}
|
||||
|
||||
this.updateBadge();
|
||||
this.registerSyncStatusAction();
|
||||
}
|
||||
|
||||
private async onDidChangeSessions(providerId: string): Promise<void> {
|
||||
@@ -396,7 +399,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
|
||||
severity: Severity.Info,
|
||||
message: localize('turned off', "Sync was turned off from another device."),
|
||||
actions: {
|
||||
primary: [new Action('turn on sync', localize('Turn sync back on', "Turn Sync Back On"), undefined, true, () => this.turnOn())]
|
||||
primary: [new Action('turn on sync', localize('turn on sync', "Turn on Sync"), undefined, true, () => this.turnOn())]
|
||||
}
|
||||
});
|
||||
return;
|
||||
@@ -484,6 +487,24 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
|
||||
}
|
||||
|
||||
private async turnOn(): Promise<void> {
|
||||
if (!this.storageService.getBoolean('sync.donotAskPreviewConfirmation', StorageScope.GLOBAL, false)) {
|
||||
const result = await this.dialogService.show(
|
||||
Severity.Info,
|
||||
localize('sync preview message', "Synchronizing your preferences is a preview feature, please read the documentation before turning it on."),
|
||||
[
|
||||
localize('open doc', "Open Documentation"),
|
||||
localize('confirm', "Continue"),
|
||||
localize('cancel', "Cancel"),
|
||||
],
|
||||
{
|
||||
cancelId: 2
|
||||
}
|
||||
);
|
||||
switch (result.choice) {
|
||||
case 0: this.openerService.open(URI.parse('https://go.microsoft.com/fwlink/?LinkId=827846')); return;
|
||||
case 2: return;
|
||||
}
|
||||
}
|
||||
return new Promise((c, e) => {
|
||||
const disposables: DisposableStore = new DisposableStore();
|
||||
const quickPick = this.quickInputService.createQuickPick<ConfigureSyncQuickPickItem>();
|
||||
@@ -495,7 +516,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
|
||||
quickPick.customLabel = localize('turn on', "Turn on");
|
||||
} else {
|
||||
const displayName = this.authenticationService.getDisplayName(this.userDataSyncStore!.authenticationProviderId);
|
||||
quickPick.description = localize('sign in and turn on sync detail', "Please sign in with your {0} account to synchronize your following data across all your devices.", displayName);
|
||||
quickPick.description = localize('sign in and turn on sync detail', "Sign in with your {0} account to synchronize your data across devices.", displayName);
|
||||
quickPick.customLabel = localize('sign in and turn on sync', "Sign in & Turn on");
|
||||
}
|
||||
quickPick.placeholder = localize('configure sync placeholder', "Choose what to sync");
|
||||
@@ -523,6 +544,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
|
||||
await this.handleFirstTimeSync();
|
||||
this.userDataSyncEnablementService.setEnablement(true);
|
||||
this.notificationService.info(localize('sync turned on', "Sync will happen automatically from now on."));
|
||||
this.storageService.store('sync.donotAskPreviewConfirmation', true, StorageScope.GLOBAL);
|
||||
}
|
||||
|
||||
private getConfigureSyncQuickPickItems(): ConfigureSyncQuickPickItem[] {
|
||||
@@ -746,7 +768,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
|
||||
|
||||
private registerSignInAction(): void {
|
||||
const that = this;
|
||||
registerAction2(class StopSyncAction extends Action2 {
|
||||
this._register(registerAction2(class StopSyncAction extends Action2 {
|
||||
constructor() {
|
||||
super({
|
||||
id: signInCommand.id,
|
||||
@@ -766,7 +788,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
|
||||
that.notificationService.error(e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
private registerShowSettingsConflictsAction(): void {
|
||||
@@ -824,17 +846,14 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
|
||||
|
||||
}
|
||||
|
||||
private readonly _syncStatusActionDisposable = this._register(new MutableDisposable());
|
||||
private registerSyncStatusAction(): void {
|
||||
const that = this;
|
||||
const when = ContextKeyExpr.and(CONTEXT_SYNC_ENABLEMENT, CONTEXT_AUTH_TOKEN_STATE.isEqualTo(AuthStatus.SignedIn), CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized));
|
||||
this._syncStatusActionDisposable.value = registerAction2(class SyncStatusAction extends Action2 {
|
||||
this._register(registerAction2(class SyncStatusAction extends Action2 {
|
||||
constructor() {
|
||||
super({
|
||||
id: 'workbench.userData.actions.syncStatus',
|
||||
get title() {
|
||||
return getIdentityTitle(localize('sync is on', "Sync is on"), that.activeAccount);
|
||||
},
|
||||
title: localize('sync is on', "Sync is on"),
|
||||
menu: [
|
||||
{
|
||||
id: MenuId.GlobalActivity,
|
||||
@@ -890,12 +909,12 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
|
||||
quickPick.show();
|
||||
});
|
||||
}
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
private registerTurnOffSyncAction(): void {
|
||||
const that = this;
|
||||
registerAction2(class StopSyncAction extends Action2 {
|
||||
this._register(registerAction2(class StopSyncAction extends Action2 {
|
||||
constructor() {
|
||||
super({
|
||||
id: stopSyncCommand.id,
|
||||
@@ -915,12 +934,12 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
private registerConfigureSyncAction(): void {
|
||||
const that = this;
|
||||
registerAction2(class ShowSyncActivityAction extends Action2 {
|
||||
this._register(registerAction2(class ShowSyncActivityAction extends Action2 {
|
||||
constructor() {
|
||||
super({
|
||||
id: configureSyncCommand.id,
|
||||
@@ -932,12 +951,12 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
|
||||
});
|
||||
}
|
||||
run(): any { return that.configureSyncOptions(); }
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
private registerShowActivityAction(): void {
|
||||
const that = this;
|
||||
registerAction2(class ShowSyncActivityAction extends Action2 {
|
||||
this._register(registerAction2(class ShowSyncActivityAction extends Action2 {
|
||||
constructor() {
|
||||
super({
|
||||
id: showSyncActivityCommand.id,
|
||||
@@ -949,11 +968,11 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
|
||||
});
|
||||
}
|
||||
run(): any { return that.showSyncActivity(); }
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
private registerShowSettingsAction(): void {
|
||||
registerAction2(class ShowSyncSettingsAction extends Action2 {
|
||||
this._register(registerAction2(class ShowSyncSettingsAction extends Action2 {
|
||||
constructor() {
|
||||
super({
|
||||
id: showSyncSettingsCommand.id,
|
||||
@@ -965,9 +984,9 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
|
||||
});
|
||||
}
|
||||
run(accessor: ServicesAccessor): any {
|
||||
accessor.get(IPreferencesService).openGlobalSettings(false, { query: 'sync:' });
|
||||
accessor.get(IPreferencesService).openGlobalSettings(false, { query: '@tag:sync' });
|
||||
}
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -34,7 +34,6 @@ import { addDisposableListener, EventType, EventHelper } from 'vs/base/browser/d
|
||||
import { IWorkspacesService } from 'vs/platform/workspaces/common/workspaces';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { isEqual } from 'vs/base/common/resources';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
|
||||
/**
|
||||
* Stores the selection & view state of an editor and allows to compare it to other selection states.
|
||||
@@ -112,8 +111,7 @@ export class HistoryService extends Disposable implements IHistoryService {
|
||||
@IWorkspacesService private readonly workspacesService: IWorkspacesService,
|
||||
@IInstantiationService private readonly instantiationService: IInstantiationService,
|
||||
@IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService,
|
||||
@IContextKeyService private readonly contextKeyService: IContextKeyService,
|
||||
@ILogService private readonly logService: ILogService
|
||||
@IContextKeyService private readonly contextKeyService: IContextKeyService
|
||||
) {
|
||||
super();
|
||||
|
||||
@@ -277,7 +275,9 @@ export class HistoryService extends Disposable implements IHistoryService {
|
||||
}
|
||||
|
||||
clear(): void {
|
||||
this.ensureHistoryLoaded();
|
||||
|
||||
// History
|
||||
this.clearRecentlyOpened();
|
||||
|
||||
// Navigation (next, previous)
|
||||
this.navigationStackIndex = -1;
|
||||
@@ -289,9 +289,6 @@ export class HistoryService extends Disposable implements IHistoryService {
|
||||
// Closed files
|
||||
this.recentlyClosedFiles = [];
|
||||
|
||||
// History
|
||||
this.clearRecentlyOpened();
|
||||
|
||||
// Context Keys
|
||||
this.updateContextKeys();
|
||||
}
|
||||
@@ -719,8 +716,8 @@ export class HistoryService extends Disposable implements IHistoryService {
|
||||
private static readonly MAX_HISTORY_ITEMS = 200;
|
||||
private static readonly HISTORY_STORAGE_KEY = 'history.entries';
|
||||
|
||||
private history: Array<IEditorInput | IResourceInput> = [];
|
||||
private loaded = false;
|
||||
private history: Array<IEditorInput | IResourceInput> | undefined = undefined;
|
||||
|
||||
private readonly resourceFilter = this._register(this.instantiationService.createInstance(
|
||||
ResourceGlobMatcher,
|
||||
(root?: URI) => this.getExcludes(root),
|
||||
@@ -741,11 +738,10 @@ export class HistoryService extends Disposable implements IHistoryService {
|
||||
return;
|
||||
}
|
||||
|
||||
this.ensureHistoryLoaded();
|
||||
|
||||
const historyInput = this.preferResourceInput(input);
|
||||
|
||||
// Remove any existing entry and add to the beginning
|
||||
this.ensureHistoryLoaded(this.history);
|
||||
this.removeFromHistory(input);
|
||||
this.history.unshift(historyInput);
|
||||
|
||||
@@ -772,7 +768,7 @@ export class HistoryService extends Disposable implements IHistoryService {
|
||||
}
|
||||
|
||||
private removeExcludedFromHistory(): void {
|
||||
this.ensureHistoryLoaded();
|
||||
this.ensureHistoryLoaded(this.history);
|
||||
|
||||
this.history = this.history.filter(e => {
|
||||
const include = this.include(e);
|
||||
@@ -787,7 +783,7 @@ export class HistoryService extends Disposable implements IHistoryService {
|
||||
}
|
||||
|
||||
private removeFromHistory(arg1: IEditorInput | IResourceInput | FileChangesEvent): void {
|
||||
this.ensureHistoryLoaded();
|
||||
this.ensureHistoryLoaded(this.history);
|
||||
|
||||
this.history = this.history.filter(e => {
|
||||
const matches = this.matches(arg1, e);
|
||||
@@ -809,17 +805,59 @@ export class HistoryService extends Disposable implements IHistoryService {
|
||||
}
|
||||
|
||||
getHistory(): ReadonlyArray<IEditorInput | IResourceInput> {
|
||||
this.ensureHistoryLoaded();
|
||||
this.ensureHistoryLoaded(this.history);
|
||||
|
||||
return this.history.slice(0);
|
||||
}
|
||||
|
||||
private ensureHistoryLoaded(): void {
|
||||
if (!this.loaded) {
|
||||
this.loadHistory();
|
||||
private ensureHistoryLoaded(history: Array<IEditorInput | IResourceInput> | undefined): asserts history {
|
||||
if (!this.history) {
|
||||
this.history = this.loadHistory();
|
||||
}
|
||||
}
|
||||
|
||||
private loadHistory(): Array<IEditorInput | IResourceInput> {
|
||||
let entries: ISerializedEditorHistoryEntry[] = [];
|
||||
|
||||
const entriesRaw = this.storageService.get(HistoryService.HISTORY_STORAGE_KEY, StorageScope.WORKSPACE);
|
||||
if (entriesRaw) {
|
||||
entries = coalesce(JSON.parse(entriesRaw));
|
||||
}
|
||||
|
||||
this.loaded = true;
|
||||
const registry = Registry.as<IEditorInputFactoryRegistry>(EditorExtensions.EditorInputFactories);
|
||||
|
||||
return coalesce(entries.map(entry => {
|
||||
try {
|
||||
return this.safeLoadHistoryEntry(registry, entry);
|
||||
} catch (error) {
|
||||
return undefined; // https://github.com/Microsoft/vscode/issues/60960
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
private safeLoadHistoryEntry(registry: IEditorInputFactoryRegistry, entry: ISerializedEditorHistoryEntry): IEditorInput | IResourceInput | undefined {
|
||||
const serializedEditorHistoryEntry = entry;
|
||||
|
||||
// File resource: via URI.revive()
|
||||
if (serializedEditorHistoryEntry.resourceJSON) {
|
||||
return { resource: URI.revive(<UriComponents>serializedEditorHistoryEntry.resourceJSON) };
|
||||
}
|
||||
|
||||
// Editor input: via factory
|
||||
const { editorInputJSON } = serializedEditorHistoryEntry;
|
||||
if (editorInputJSON?.deserialized) {
|
||||
const factory = registry.getEditorInputFactory(editorInputJSON.typeId);
|
||||
if (factory) {
|
||||
const input = factory.deserialize(this.instantiationService, editorInputJSON.deserialized);
|
||||
if (input) {
|
||||
this.onEditorDispose(input, () => this.removeFromHistory(input), this.editorHistoryListeners);
|
||||
}
|
||||
|
||||
return withNullAsUndefined(input);
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private saveState(): void {
|
||||
@@ -850,58 +888,9 @@ export class HistoryService extends Disposable implements IHistoryService {
|
||||
return undefined;
|
||||
}));
|
||||
|
||||
this.logService.trace(`[editor history] saving ${entries.length} entries`);
|
||||
this.storageService.store(HistoryService.HISTORY_STORAGE_KEY, JSON.stringify(entries), StorageScope.WORKSPACE);
|
||||
}
|
||||
|
||||
private loadHistory(): void {
|
||||
let entries: ISerializedEditorHistoryEntry[] = [];
|
||||
|
||||
const entriesRaw = this.storageService.get(HistoryService.HISTORY_STORAGE_KEY, StorageScope.WORKSPACE);
|
||||
if (entriesRaw) {
|
||||
entries = coalesce(JSON.parse(entriesRaw));
|
||||
}
|
||||
|
||||
const registry = Registry.as<IEditorInputFactoryRegistry>(EditorExtensions.EditorInputFactories);
|
||||
|
||||
this.history = coalesce(entries.map(entry => {
|
||||
try {
|
||||
return this.safeLoadHistoryEntry(registry, entry);
|
||||
} catch (error) {
|
||||
this.logService.error(`[editor history] error loading one editor history entry: ${error.toString()}`);
|
||||
|
||||
return undefined; // https://github.com/Microsoft/vscode/issues/60960
|
||||
}
|
||||
}));
|
||||
|
||||
this.logService.trace(`[editor history] loading ${this.history.length} entries`);
|
||||
}
|
||||
|
||||
private safeLoadHistoryEntry(registry: IEditorInputFactoryRegistry, entry: ISerializedEditorHistoryEntry): IEditorInput | IResourceInput | undefined {
|
||||
const serializedEditorHistoryEntry = entry;
|
||||
|
||||
// File resource: via URI.revive()
|
||||
if (serializedEditorHistoryEntry.resourceJSON) {
|
||||
return { resource: URI.revive(<UriComponents>serializedEditorHistoryEntry.resourceJSON) };
|
||||
}
|
||||
|
||||
// Editor input: via factory
|
||||
const { editorInputJSON } = serializedEditorHistoryEntry;
|
||||
if (editorInputJSON?.deserialized) {
|
||||
const factory = registry.getEditorInputFactory(editorInputJSON.typeId);
|
||||
if (factory) {
|
||||
const input = factory.deserialize(this.instantiationService, editorInputJSON.deserialized);
|
||||
if (input) {
|
||||
this.onEditorDispose(input, () => this.removeFromHistory(input), this.editorHistoryListeners);
|
||||
}
|
||||
|
||||
return withNullAsUndefined(input);
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region Last Active Workspace/File
|
||||
@@ -925,8 +914,7 @@ export class HistoryService extends Disposable implements IHistoryService {
|
||||
}
|
||||
|
||||
// Multiple folders: find the last active one
|
||||
const history = this.getHistory();
|
||||
for (const input of history) {
|
||||
for (const input of this.getHistory()) {
|
||||
if (input instanceof EditorInput) {
|
||||
continue;
|
||||
}
|
||||
@@ -954,8 +942,7 @@ export class HistoryService extends Disposable implements IHistoryService {
|
||||
}
|
||||
|
||||
getLastActiveFile(filterByScheme: string): URI | undefined {
|
||||
const history = this.getHistory();
|
||||
for (const input of history) {
|
||||
for (const input of this.getHistory()) {
|
||||
let resource: URI | undefined;
|
||||
if (input instanceof EditorInput) {
|
||||
resource = toResource(input, { filterByScheme });
|
||||
|
||||
@@ -100,7 +100,7 @@ export class UntitledTextEditorModel extends BaseTextEditorModel implements IUnt
|
||||
return this.labelService.getUriBasenameLabel(this.resource);
|
||||
}
|
||||
|
||||
private dirty = false;
|
||||
private dirty = this.hasAssociatedFilePath || !!this.initialValue;
|
||||
private ignoreDirtyOnModelContentChange = false;
|
||||
|
||||
private versionId = 0;
|
||||
|
||||
@@ -11,7 +11,6 @@ import { IUntitledTextEditorService, UntitledTextEditorService } from 'vs/workbe
|
||||
import { workbenchInstantiationService, TestServiceAccessor } from 'vs/workbench/test/browser/workbenchTestServices';
|
||||
import { snapshotToString } from 'vs/workbench/services/textfile/common/textfiles';
|
||||
import { ModesRegistry, PLAINTEXT_MODE_ID } from 'vs/editor/common/modes/modesRegistry';
|
||||
import { IWorkingCopy } from 'vs/workbench/services/workingCopy/common/workingCopyService';
|
||||
import { IIdentifiedSingleEditOperation } from 'vs/editor/common/model';
|
||||
import { Range } from 'vs/editor/common/core/range';
|
||||
import { UntitledTextEditorInput } from 'vs/workbench/services/untitled/common/untitledTextEditorInput';
|
||||
@@ -120,9 +119,13 @@ suite('Untitled text editors', () => {
|
||||
test('associated resource is dirty', async () => {
|
||||
const service = accessor.untitledTextEditorService;
|
||||
const file = URI.file(join('C:\\', '/foo/file.txt'));
|
||||
const untitled = await service.resolve({ associatedResource: file });
|
||||
|
||||
assert.ok(untitled.hasAssociatedFilePath);
|
||||
const untitled = instantiationService.createInstance(UntitledTextEditorInput, service.create({ associatedResource: file }));
|
||||
assert.ok(untitled.isDirty());
|
||||
|
||||
const model = await untitled.resolve();
|
||||
|
||||
assert.ok(model.hasAssociatedFilePath);
|
||||
assert.equal(untitled.isDirty(), true);
|
||||
|
||||
untitled.dispose();
|
||||
@@ -197,20 +200,14 @@ suite('Untitled text editors', () => {
|
||||
const workingCopyService = accessor.workingCopyService;
|
||||
|
||||
const untitled = instantiationService.createInstance(UntitledTextEditorInput, service.create({ initialValue: 'Hello World' }));
|
||||
|
||||
let onDidChangeDirty: IWorkingCopy | undefined = undefined;
|
||||
const listener = workingCopyService.onDidChangeDirty(copy => {
|
||||
onDidChangeDirty = copy;
|
||||
});
|
||||
assert.ok(untitled.isDirty());
|
||||
|
||||
// dirty
|
||||
const model = await untitled.resolve();
|
||||
assert.ok(model.isDirty());
|
||||
assert.equal(workingCopyService.dirtyCount, 1);
|
||||
assert.equal(onDidChangeDirty, model);
|
||||
|
||||
untitled.dispose();
|
||||
listener.dispose();
|
||||
model.dispose();
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user