Merge branch 'master' into diff

This commit is contained in:
Joao Moreno
2017-02-17 16:36:23 +01:00
127 changed files with 5308 additions and 2439 deletions
+1 -1
View File
@@ -40,7 +40,7 @@ const nodeModules = ['electron', 'original-fs']
// Build
const builtInExtensions = [
{ name: 'ms-vscode.node-debug', version: '1.10.11' },
{ name: 'ms-vscode.node-debug', version: '1.10.12' },
{ name: 'ms-vscode.node-debug2', version: '1.10.0' }
];
+3 -3
View File
@@ -17,14 +17,14 @@ export class GitContentProvider {
private uris = new Set<Uri>();
constructor(private model: Model, onGitChange: Event<Uri>) {
constructor(private model: Model) {
this.disposables.push(
onGitChange(this.fireChangeEvents, this),
model.onDidChangeRepository(this.fireChangeEvents, this),
workspace.registerTextDocumentContentProvider('git', this)
);
}
private fireChangeEvents(): void {
private fireChangeEvents(arg): void {
for (let uri of this.uris) {
this.onDidChangeEmitter.fire(uri);
}
+2 -8
View File
@@ -11,7 +11,6 @@ import { Model } from './model';
import { GitSCMProvider } from './scmProvider';
import { CommandCenter } from './commands';
import { CheckoutStatusBar, SyncStatusBar } from './statusbar';
import { filterEvent, anyEvent } from './util';
import { GitContentProvider } from './contentProvider';
import { AutoFetcher } from './autofetch';
import { MergeDecorator } from './merge';
@@ -34,14 +33,10 @@ async function init(disposables: Disposable[]): Promise<void> {
return;
}
const fsWatcher = workspace.createFileSystemWatcher('**');
const onWorkspaceChange = anyEvent(fsWatcher.onDidChange, fsWatcher.onDidCreate, fsWatcher.onDidDelete);
const onGitChange = filterEvent(onWorkspaceChange, uri => /^\.git\//.test(workspace.asRelativePath(uri)));
const pathHint = workspace.getConfiguration('git').get<string>('path');
const info = await findGit(pathHint);
const git = new Git({ gitPath: info.path, version: info.version });
const model = new Model(git, rootPath, onWorkspaceChange);
const model = new Model(git, rootPath);
outputChannel.appendLine(localize('using git', "Using git {0} from {1}", info.version, info.path));
git.onOutput(str => outputChannel.append(str), null, disposables);
@@ -49,7 +44,7 @@ async function init(disposables: Disposable[]): Promise<void> {
const commitHandler = new CommitController();
const commandCenter = new CommandCenter(model, outputChannel);
const provider = new GitSCMProvider(model, commandCenter);
const contentProvider = new GitContentProvider(model, onGitChange);
const contentProvider = new GitContentProvider(model);
const checkoutStatusBar = new CheckoutStatusBar(model);
const syncStatusBar = new SyncStatusBar(model);
const autoFetcher = new AutoFetcher(model);
@@ -60,7 +55,6 @@ async function init(disposables: Disposable[]): Promise<void> {
commandCenter,
provider,
contentProvider,
fsWatcher,
checkoutStatusBar,
syncStatusBar,
autoFetcher,
+14 -5
View File
@@ -7,7 +7,7 @@
import { Uri, EventEmitter, Event, SCMResource, SCMResourceDecorations, SCMResourceGroup, Disposable, window, workspace } from 'vscode';
import { Git, Repository, Ref, Branch, Remote, PushOptions, Commit, GitErrorCodes, GitError } from './git';
import { anyEvent, eventToPromise, filterEvent, mapEvent, EmptyDisposable, combinedDisposable } from './util';
import { anyEvent, eventToPromise, filterEvent, mapEvent, EmptyDisposable, combinedDisposable, dispose } from './util';
import { memoize, throttle, debounce } from './decorators';
import { watch } from './watch';
import * as path from 'path';
@@ -222,6 +222,9 @@ export interface CommitOptions {
export class Model implements Disposable {
private _onDidChangeRepository = new EventEmitter<Uri>();
readonly onDidChangeRepository: Event<Uri> = this._onDidChangeRepository.event;
private _onDidChangeState = new EventEmitter<State>();
readonly onDidChangeState: Event<State> = this._onDidChangeState.event;
@@ -304,13 +307,18 @@ export class Model implements Disposable {
this._onDidChangeResources.fire(this.resources);
}
private onWorkspaceChange: Event<Uri>;
private repositoryDisposable: Disposable = EmptyDisposable;
private disposables: Disposable[] = [];
constructor(
private git: Git,
private rootPath: string,
private onWorkspaceChange: Event<Uri>
) {
const fsWatcher = workspace.createFileSystemWatcher('**');
this.onWorkspaceChange = anyEvent(fsWatcher.onDidChange, fsWatcher.onDidCreate, fsWatcher.onDidDelete);
this.disposables.push(fsWatcher);
this.status();
}
@@ -429,7 +437,6 @@ export class Model implements Disposable {
await this.run(Operation.Sync, () => this.repository.sync());
}
@throttle
async show(ref: string, uri: Uri): Promise<string> {
return await this.run(Operation.Show, async () => {
const relativePath = path.relative(this.repository.root, uri.fsPath).replace(/\\/g, '/');
@@ -487,12 +494,13 @@ export class Model implements Disposable {
this.repository = this.git.open(repositoryRoot);
const dotGitPath = path.join(repositoryRoot, '.git');
const { event, disposable: watcher } = watch(dotGitPath);
const { event: onRawGitChange, disposable: watcher } = watch(dotGitPath);
disposables.push(watcher);
const onGitChange = mapEvent(event, ({ filename }) => Uri.file(path.join(dotGitPath, filename)));
const onGitChange = mapEvent(onRawGitChange, ({ filename }) => Uri.file(path.join(dotGitPath, filename)));
const onRelevantGitChange = filterEvent(onGitChange, uri => !/\/\.git\/index\.lock$/.test(uri.fsPath));
onRelevantGitChange(this.onFSChange, this, disposables);
onRelevantGitChange(this._onDidChangeRepository.fire, this._onDidChangeRepository, disposables);
const onNonGitChange = filterEvent(this.onWorkspaceChange, uri => !/\/\.git\//.test(uri.fsPath));
onNonGitChange(this.onFSChange, this, disposables);
@@ -603,5 +611,6 @@ export class Model implements Disposable {
dispose(): void {
this.repositoryDisposable.dispose();
this.disposables = dispose(this.disposables);
}
}
+1 -4
View File
@@ -17,7 +17,6 @@ import URI from './utils/uri';
import * as URL from 'url';
import Strings = require('./utils/strings');
import { JSONDocument, JSONSchema, LanguageSettings, getLanguageService } from 'vscode-json-languageservice';
import { ProjectJSONContribution } from './jsoncontributions/projectJSONContribution';
import { getLanguageModelCache } from './languageModelCache';
import * as nls from 'vscode-nls';
@@ -119,9 +118,7 @@ let schemaRequestService = (uri: string): Thenable<string> => {
let languageService = getLanguageService({
schemaRequestService,
workspaceContext,
contributions: [
new ProjectJSONContribution()
]
contributions: []
});
// The settings interface describes the server relevant settings part
@@ -1,281 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { MarkedString, CompletionItemKind, CompletionItem, InsertTextFormat } from 'vscode-languageserver';
import Strings = require('../utils/strings');
import { XHRResponse, getErrorStatusDescription, xhr } from 'request-light';
import { JSONWorkerContribution, JSONPath, CompletionsCollector } from 'vscode-json-languageservice';
import * as nls from 'vscode-nls';
const localize = nls.loadMessageBundle();
const FEED_INDEX_URL = 'https://api.nuget.org/v3/index.json';
const LIMIT = 30;
const RESOLVE_ID = 'ProjectJSONContribution-';
const CACHE_EXPIRY = 1000 * 60 * 5; // 5 minutes
interface NugetServices {
'SearchQueryService'?: string;
'SearchAutocompleteService'?: string;
'PackageBaseAddress/3.0.0'?: string;
[key: string]: string;
}
export class ProjectJSONContribution implements JSONWorkerContribution {
private cachedProjects: { [id: string]: { version: string, description: string, time: number } } = {};
private cacheSize: number = 0;
private nugetIndexPromise: Thenable<NugetServices>;
public constructor() {
}
private isProjectJSONFile(resource: string): boolean {
return Strings.endsWith(resource, '/project.json');
}
private completeWithCache(id: string, item: CompletionItem): boolean {
let entry = this.cachedProjects[id];
if (entry) {
if (new Date().getTime() - entry.time > CACHE_EXPIRY) {
delete this.cachedProjects[id];
this.cacheSize--;
return false;
}
let insertTextValue = item.insertText;
item.detail = entry.version;
item.documentation = entry.description;
item.insertText = insertTextValue.replace(/\$1/, '${1:' + entry.version + '}');
return true;
}
return false;
}
private addCached(id: string, version: string, description: string) {
this.cachedProjects[id] = { version, description, time: new Date().getTime() };
this.cacheSize++;
if (this.cacheSize > 50) {
let currentTime = new Date().getTime();
for (let id in this.cachedProjects) {
let entry = this.cachedProjects[id];
if (currentTime - entry.time > CACHE_EXPIRY) {
delete this.cachedProjects[id];
this.cacheSize--;
}
}
}
}
private getNugetIndex(): Thenable<NugetServices> {
if (!this.nugetIndexPromise) {
this.nugetIndexPromise = this.makeJSONRequest<any>(FEED_INDEX_URL).then(indexContent => {
let services: NugetServices = {};
if (indexContent && Array.isArray(indexContent.resources)) {
let resources = <any[]>indexContent.resources;
for (let i = resources.length - 1; i >= 0; i--) {
let type = resources[i]['@type'];
let id = resources[i]['@id'];
if (type && id) {
services[type] = id;
}
}
}
return services;
});
}
return this.nugetIndexPromise;
}
private getNugetService(serviceType: string): Thenable<string> {
return this.getNugetIndex().then(services => {
let serviceURL = services[serviceType];
if (!serviceURL) {
return Promise.reject<string>(localize('json.nugget.error.missingservice', 'NuGet index document is missing service {0}', serviceType));
}
return serviceURL;
});
}
public collectDefaultCompletions(resource: string, result: CompletionsCollector): Thenable<any> {
if (this.isProjectJSONFile(resource)) {
let insertText = JSON.stringify({
'version': '${1:1.0.0-*}',
'dependencies': {},
'frameworks': {
'net461': {},
'netcoreapp1.0': {}
}
}, null, '\t');
result.add({ kind: CompletionItemKind.Class, label: localize('json.project.default', 'Default project.json'), insertText, insertTextFormat: InsertTextFormat.Snippet, documentation: '' });
}
return null;
}
private makeJSONRequest<T>(url: string): Thenable<T> {
return xhr({
url: url
}).then(success => {
if (success.status === 200) {
try {
return <T>JSON.parse(success.responseText);
} catch (e) {
return Promise.reject<T>(localize('json.nugget.error.invalidformat', '{0} is not a valid JSON document', url));
}
}
return Promise.reject<T>(localize('json.nugget.error.indexaccess', 'Request to {0} failed: {1}', url, success.responseText));
}, (error: XHRResponse) => {
return Promise.reject<T>(localize('json.nugget.error.access', 'Request to {0} failed: {1}', url, getErrorStatusDescription(error.status)));
});
}
public collectPropertyCompletions(resource: string, location: JSONPath, currentWord: string, addValue: boolean, isLast: boolean, result: CompletionsCollector): Thenable<any> {
if (this.isProjectJSONFile(resource) && (matches(location, ['dependencies']) || matches(location, ['frameworks', '*', 'dependencies']) || matches(location, ['frameworks', '*', 'frameworkAssemblies']))) {
return this.getNugetService('SearchAutocompleteService').then(service => {
let queryUrl: string;
if (currentWord.length > 0) {
queryUrl = service + '?q=' + encodeURIComponent(currentWord) + '&take=' + LIMIT;
} else {
queryUrl = service + '?take=' + LIMIT;
}
return this.makeJSONRequest<any>(queryUrl).then(resultObj => {
if (Array.isArray(resultObj.data)) {
let results = <any[]>resultObj.data;
for (let i = 0; i < results.length; i++) {
let name = results[i];
let insertText = JSON.stringify(name);
if (addValue) {
insertText += ': "$1"';
if (!isLast) {
insertText += ',';
}
}
let item: CompletionItem = { kind: CompletionItemKind.Property, label: name, insertText: insertText, insertTextFormat: InsertTextFormat.Snippet, filterText: JSON.stringify(name) };
if (!this.completeWithCache(name, item)) {
item.data = RESOLVE_ID + name;
}
result.add(item);
}
if (results.length === LIMIT) {
result.setAsIncomplete();
}
}
}, error => {
result.error(error);
});
}, error => {
result.error(error);
});
};
return null;
}
public collectValueCompletions(resource: string, location: JSONPath, currentKey: string, result: CompletionsCollector): Thenable<any> {
if (this.isProjectJSONFile(resource) && (matches(location, ['dependencies']) || matches(location, ['frameworks', '*', 'dependencies']) || matches(location, ['frameworks', '*', 'frameworkAssemblies']))) {
return this.getNugetService('PackageBaseAddress/3.0.0').then(service => {
let queryUrl = service + currentKey + '/index.json';
return this.makeJSONRequest<any>(queryUrl).then(obj => {
if (Array.isArray(obj.versions)) {
let results = <any[]>obj.versions;
for (let i = 0; i < results.length; i++) {
let curr = results[i];
let name = JSON.stringify(curr);
let label = name;
let documentation = '';
result.add({ kind: CompletionItemKind.Class, label: label, insertText: name, documentation: documentation });
}
if (results.length === LIMIT) {
result.setAsIncomplete();
}
}
}, error => {
result.error(error);
});
}, error => {
result.error(error);
});
}
return null;
}
public getInfoContribution(resource: string, location: JSONPath): Thenable<MarkedString[]> {
if (this.isProjectJSONFile(resource) && (matches(location, ['dependencies', '*']) || matches(location, ['frameworks', '*', 'dependencies', '*']) || matches(location, ['frameworks', '*', 'frameworkAssemblies', '*']))) {
let pack = <string>location[location.length - 1];
return this.getNugetService('SearchQueryService').then(service => {
let queryUrl = service + '?q=' + encodeURIComponent(pack) + '&take=' + 5;
return this.makeJSONRequest<any>(queryUrl).then(resultObj => {
let htmlContent: MarkedString[] = [];
htmlContent.push(localize('json.nugget.package.hover', '{0}', pack));
if (Array.isArray(resultObj.data)) {
let results = <any[]>resultObj.data;
for (let i = 0; i < results.length; i++) {
let res = results[i];
this.addCached(res.id, res.version, res.description);
if (res.id === pack) {
if (res.description) {
htmlContent.push(MarkedString.fromPlainText(res.description));
}
if (res.version) {
htmlContent.push(MarkedString.fromPlainText(localize('json.nugget.version.hover', 'Latest version: {0}', res.version)));
}
break;
}
}
}
return htmlContent;
}, (error) => {
return null;
});
}, (error) => {
return null;
});
}
return null;
}
public resolveSuggestion(item: CompletionItem): Thenable<CompletionItem> {
if (item.data && Strings.startsWith(item.data, RESOLVE_ID)) {
let pack = item.data.substring(RESOLVE_ID.length);
if (this.completeWithCache(pack, item)) {
return Promise.resolve(item);
}
return this.getNugetService('SearchQueryService').then(service => {
let queryUrl = service + '?q=' + encodeURIComponent(pack) + '&take=' + 10;
return this.makeJSONRequest<CompletionItem>(queryUrl).then(resultObj => {
let itemResolved = false;
if (Array.isArray(resultObj.data)) {
let results = <any[]>resultObj.data;
for (let i = 0; i < results.length; i++) {
let curr = results[i];
this.addCached(curr.id, curr.version, curr.description);
if (curr.id === pack) {
this.completeWithCache(pack, item);
itemResolved = true;
}
}
}
return itemResolved ? item : null;
});
});
};
return null;
}
}
function matches(segments: JSONPath, pattern: string[]) {
let k = 0;
for (let i = 0; k < pattern.length && i < segments.length; i++) {
if (pattern[k] === segments[i] || pattern[k] === '*') {
k++;
} else if (pattern[k] !== '**') {
return false;
}
}
return k === pattern.length;
}
+3 -3
View File
@@ -13,9 +13,9 @@
"resolved": "https://registry.npmjs.org/semver/-/semver-4.3.6.tgz"
},
"typescript": {
"version": "typescript@2.2.1-insiders.20170209",
"from": "typescript@typescript@2.2.1-insiders.20170209",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-2.2.1-insiders.20170209.tgz"
"version": "typescript@2.2.1-insiders.20170216",
"from": "typescript@typescript@2.2.1-insiders.20170216",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-2.2.1-insiders.20170216.tgz"
},
"vscode-extension-telemetry": {
"version": "0.0.5",
+1 -1
View File
@@ -14,7 +14,7 @@
"semver": "4.3.6",
"vscode-extension-telemetry": "^0.0.5",
"vscode-nls": "^2.0.1",
"typescript": "typescript@2.2.1-insiders.20170209"
"typescript": "typescript@2.2.1-insiders.20170216"
},
"devDependencies": {
"@types/node": "^7.0.4",
@@ -27,10 +27,10 @@ export default class TypeScriptCodeActionProvider implements CodeActionProvider
private supportedCodeActions: Promise<NumberSet>;
constructor(
private client: ITypescriptServiceClient,
modeId: string
private readonly client: ITypescriptServiceClient,
mode: string
) {
this.commandId = `typescript.codeActions.${modeId}`;
this.commandId = `_typescript.applyCodeAction.${mode}`;
this.supportedCodeActions = client.execute('getSupportedCodeFixes', null, undefined)
.then(response => response.body || [])
.then(codes => codes.map(code => +code).filter(code => !isNaN(code)))
@@ -70,40 +70,41 @@ export default class TypeScriptReferencesCodeLensProvider implements CodeLensPro
resolveCodeLens(inputCodeLens: CodeLens, token: CancellationToken): Promise<CodeLens> {
const codeLens = inputCodeLens as ReferencesCodeLens;
if (!codeLens.document) {
return Promise.reject<CodeLens>(codeLens);
}
const args: Proto.FileLocationRequestArgs = {
file: codeLens.file,
line: codeLens.range.start.line + 1,
offset: codeLens.range.start.character + 1
};
return this.client.execute('references', args, token).then(response => {
if (response && response.body) {
// Exclude original definition from references
const locations = response.body.refs
.filter(reference =>
!(reference.start.line === codeLens.range.start.line + 1
&& reference.start.offset === codeLens.range.start.character + 1))
.map(reference =>
new Location(this.client.asUrl(reference.file),
new Range(
new Position(reference.start.line - 1, reference.start.offset - 1),
new Position(reference.end.line - 1, reference.end.offset - 1))));
codeLens.command = {
title: locations.length + ' ' + (locations.length === 1 ? localize('oneReferenceLabel', 'reference') : localize('manyReferenceLabel', 'references')),
command: 'editor.action.showReferences',
arguments: [codeLens.document, codeLens.range.start, locations]
};
return Promise.resolve(codeLens);
if (!response || !response.body) {
throw codeLens;
}
return Promise.reject<CodeLens>(codeLens);
// Exclude original definition from references
const locations = response.body.refs
.filter(reference =>
!(reference.start.line === codeLens.range.start.line + 1
&& reference.start.offset === codeLens.range.start.character + 1))
.map(reference =>
new Location(this.client.asUrl(reference.file),
new Range(
reference.start.line - 1, reference.start.offset - 1,
reference.end.line - 1, reference.end.offset - 1)));
codeLens.command = {
title: locations.length === 1
? localize('oneReferenceLabel', '1 reference')
: localize('manyReferenceLabel', '{0} references', locations.length),
command: 'editor.action.showReferences',
arguments: [codeLens.document, codeLens.range.start, locations]
};
return codeLens;
}).catch(() => {
codeLens.command = {
title: localize('referenceErrorLabel', 'Could not determine references'),
command: ''
};
return Promise.resolve(codeLens);
return codeLens;
});
}
@@ -115,8 +116,8 @@ export default class TypeScriptReferencesCodeLensProvider implements CodeLensPro
const span = item.spans && item.spans[0];
if (span) {
const range = new Range(
new Position(span.start.line - 1, span.start.offset - 1),
new Position(span.end.line - 1, span.end.offset - 1));
span.start.line - 1, span.start.offset - 1,
span.end.line - 1, span.end.offset - 1);
// TODO: TS currently requires the position for 'references 'to be inside of the identifer
// Massage the range to make sure this is the case
+44 -52
View File
@@ -9,7 +9,7 @@
* ------------------------------------------------------------------------------------------ */
'use strict';
import { env, languages, commands, workspace, window, ExtensionContext, Memento, IndentAction, Diagnostic, DiagnosticCollection, Range, DocumentFilter, Disposable, Uri, MessageItem, TextEditor } from 'vscode';
import { env, languages, commands, workspace, window, ExtensionContext, Memento, IndentAction, Diagnostic, DiagnosticCollection, Range, Disposable, Uri, MessageItem, TextEditor } from 'vscode';
// This must be the first statement otherwise modules might got loaded with
// the wrong locale.
@@ -45,7 +45,6 @@ import * as BuildStatus from './utils/buildStatus';
import * as ProjectStatus from './utils/projectStatus';
import TypingsStatus, { AtaProgressReporter } from './utils/typingsStatus';
import * as VersionStatus from './utils/versionStatus';
import ProjectConfigStatus from './utils/projectConfigStatus';
interface LanguageDescription {
id: string;
@@ -126,9 +125,7 @@ export function activate(context: ExtensionContext): void {
context.subscriptions.push(commands.registerCommand('javascript.goToProjectConfig', goToProjectConfig.bind(null, false)));
window.onDidChangeActiveTextEditor(VersionStatus.showHideStatus, null, context.subscriptions);
client.onReady().then(() => {
context.subscriptions.push(new ProjectConfigStatus(client));
context.subscriptions.push(ProjectStatus.create(client,
path => new Promise(resolve => setTimeout(() => resolve(clientHost.handles(path)), 750)),
context.workspaceState));
@@ -153,15 +150,14 @@ class LanguageProvider {
private typingsStatus: TypingsStatus;
private referenceCodeLensProvider: ReferenceCodeLensProvider;
private _validate: boolean;
private _validate: boolean = true;
constructor(
private client: TypeScriptServiceClient,
private description: LanguageDescription
private readonly client: TypeScriptServiceClient,
private readonly description: LanguageDescription
) {
this.extensions = Object.create(null);
description.extensions.forEach(extension => this.extensions[extension] = true);
this._validate = true;
this.bufferSyncSupport = new BufferSyncSupport(client, description.modeIds, {
delete: (file: string) => {
@@ -186,54 +182,48 @@ class LanguageProvider {
}
private registerProviders(client: TypeScriptServiceClient): void {
const selector = this.description.modeIds;
const config = workspace.getConfiguration(this.id);
this.completionItemProvider = new CompletionItemProvider(client, this.typingsStatus);
this.completionItemProvider.updateConfiguration();
languages.registerCompletionItemProvider(selector, this.completionItemProvider, '.');
let hoverProvider = new HoverProvider(client);
let definitionProvider = new DefinitionProvider(client);
let implementationProvider = new ImplementationProvider(client);
const typeDefinitionProvider = new TypeDefintionProvider(client);
let documentHighlightProvider = new DocumentHighlightProvider(client);
let referenceProvider = new ReferenceProvider(client);
let documentSymbolProvider = new DocumentSymbolProvider(client);
let signatureHelpProvider = new SignatureHelpProvider(client);
let renameProvider = new RenameProvider(client);
this.formattingProvider = new FormattingProvider(client);
this.formattingProvider.updateConfiguration(config);
languages.registerOnTypeFormattingEditProvider(selector, this.formattingProvider, ';', '}', '\n');
if (this.formattingProvider.isEnabled()) {
this.formattingProviderRegistration = languages.registerDocumentRangeFormattingEditProvider(this.description.modeIds, this.formattingProvider);
this.formattingProviderRegistration = languages.registerDocumentRangeFormattingEditProvider(selector, this.formattingProvider);
}
this.referenceCodeLensProvider = new ReferenceCodeLensProvider(client);
this.referenceCodeLensProvider.updateConfiguration();
languages.registerHoverProvider(selector, new HoverProvider(client));
languages.registerDefinitionProvider(selector, new DefinitionProvider(client));
languages.registerDocumentHighlightProvider(selector, new DocumentHighlightProvider(client));
languages.registerReferenceProvider(selector, new ReferenceProvider(client));
languages.registerDocumentSymbolProvider(selector, new DocumentSymbolProvider(client));
languages.registerSignatureHelpProvider(selector, new SignatureHelpProvider(client), '(', ',');
languages.registerRenameProvider(selector, new RenameProvider(client));
if (client.apiVersion.has206Features()) {
languages.registerCodeLensProvider(this.description.modeIds, this.referenceCodeLensProvider);
this.referenceCodeLensProvider = new ReferenceCodeLensProvider(client);
this.referenceCodeLensProvider.updateConfiguration();
languages.registerCodeLensProvider(selector, this.referenceCodeLensProvider);
}
if (client.apiVersion.has213Features()) {
languages.registerCodeActionsProvider(selector, new CodeActionProvider(client, this.description.id));
}
if (client.apiVersion.has220Features()) {
languages.registerImplementationProvider(selector, new ImplementationProvider(client));
}
if (client.apiVersion.has213Features()) {
languages.registerTypeDefinitionProvider(selector, new TypeDefintionProvider(client));
}
this.description.modeIds.forEach(modeId => {
const selector: DocumentFilter = modeId;
languages.registerCompletionItemProvider(selector, this.completionItemProvider, '.');
languages.registerHoverProvider(selector, hoverProvider);
languages.registerDefinitionProvider(selector, definitionProvider);
if (client.apiVersion.has220Features()) {
// TODO: TS 2.1.5 returns incorrect results for implementation locations.
languages.registerImplementationProvider(selector, implementationProvider);
}
if (client.apiVersion.has213Features()) {
languages.registerTypeDefinitionProvider(selector, typeDefinitionProvider);
}
languages.registerDocumentHighlightProvider(selector, documentHighlightProvider);
languages.registerReferenceProvider(selector, referenceProvider);
languages.registerDocumentSymbolProvider(selector, documentSymbolProvider);
languages.registerSignatureHelpProvider(selector, signatureHelpProvider, '(', ',');
languages.registerRenameProvider(selector, renameProvider);
languages.registerOnTypeFormattingEditProvider(selector, this.formattingProvider, ';', '}', '\n');
languages.registerWorkspaceSymbolProvider(new WorkspaceSymbolProvider(client, modeId));
if (client.apiVersion.has213Features()) {
languages.registerCodeActionsProvider(selector, new CodeActionProvider(client, modeId));
}
languages.setLanguageConfiguration(modeId, {
indentationRules: {
@@ -249,18 +239,15 @@ class LanguageProvider {
beforeText: /^\s*\/\*\*(?!\/)([^\*]|\*(?!\/))*$/,
afterText: /^\s*\*\/$/,
action: { indentAction: IndentAction.IndentOutdent, appendText: ' * ' }
},
{
}, {
// e.g. /** ...|
beforeText: /^\s*\/\*\*(?!\/)([^\*]|\*(?!\/))*$/,
action: { indentAction: IndentAction.None, appendText: ' * ' }
},
{
}, {
// e.g. * ...|
beforeText: /^(\t|(\ \ ))*\ \*(\ ([^\*]|\*(?!\/))*)?$/,
action: { indentAction: IndentAction.None, appendText: '* ' }
},
{
}, {
// e.g. */|
beforeText: /^(\t|(\ \ ))*\ \*\/\s*$/,
action: { indentAction: IndentAction.None, removeText: 1 }
@@ -378,17 +365,22 @@ class TypeScriptServiceClientHost implements ITypescriptServiceClientHost {
private languages: LanguageProvider[];
private languagePerId: ObjectMap<LanguageProvider>;
constructor(descriptions: LanguageDescription[], storagePath: string | undefined, globalState: Memento, workspaceState: Memento) {
let handleProjectCreateOrDelete = () => {
constructor(
descriptions: LanguageDescription[],
storagePath: string | undefined,
globalState: Memento,
workspaceState: Memento
) {
const handleProjectCreateOrDelete = () => {
this.client.execute('reloadProjects', null, false);
this.triggerAllDiagnostics();
};
let handleProjectChange = () => {
const handleProjectChange = () => {
setTimeout(() => {
this.triggerAllDiagnostics();
}, 1500);
};
let watcher = workspace.createFileSystemWatcher('**/[tj]sconfig.json');
const watcher = workspace.createFileSystemWatcher('**/[tj]sconfig.json');
watcher.onDidCreate(handleProjectCreateOrDelete);
watcher.onDidDelete(handleProjectCreateOrDelete);
watcher.onDidChange(handleProjectChange);
@@ -397,7 +389,7 @@ class TypeScriptServiceClientHost implements ITypescriptServiceClientHost {
this.languages = [];
this.languagePerId = Object.create(null);
descriptions.forEach(description => {
let manager = new LanguageProvider(this.client, description);
const manager = new LanguageProvider(this.client, description);
this.languages.push(manager);
this.languagePerId[description.id] = manager;
});
@@ -124,6 +124,7 @@ export default class TypeScriptServiceClient implements ITypescriptServiceClient
private _apiVersion: API;
private telemetryReporter: TelemetryReporter;
constructor(host: ITypescriptServiceClientHost, storagePath: string | undefined, globalState: Memento, private workspaceState: Memento) {
this.host = host;
this.storagePath = storagePath;
@@ -9,7 +9,7 @@ import { TextEditor, Position, Range, Selection } from 'vscode';
import { ITypescriptServiceClient } from '../typescriptService';
import { FileLocationRequestArgs } from '../protocol';
import { FileLocationRequestArgs, DocCommandTemplateResponse } from '../protocol';
export default class JsDocCompletionHelper {
@@ -51,51 +51,47 @@ export default class JsDocCompletionHelper {
return false;
}
let cancelled = false;
const timer = setTimeout(() => {
cancelled = true;
}, 250);
const args: FileLocationRequestArgs = {
file: file,
line: start.line + 1,
offset: start.character + 1
};
return this.client.execute('docCommentTemplate', args)
.then(res => {
clearTimeout(timer);
if (cancelled || !res || !res.body) {
return false;
}
const commentText = res.body.newText;
return editor.edit(
edits => edits.insert(start, commentText),
{ undoStopBefore: false, undoStopAfter: true });
}, () => {
clearTimeout(timer);
return false;
return Promise.race([
this.client.execute('docCommentTemplate', args),
new Promise((_, reject) => {
setTimeout(reject, 250);
})
.then(didInsertComment => {
if (didInsertComment) {
const newCursorPosition = new Position(start.line + 1, editor.document.lineAt(start.line + 1).text.length);
editor.selection = new Selection(newCursorPosition, newCursorPosition);
return true;
}
]).then((res: DocCommandTemplateResponse) => {
if (!res || !res.body) {
return false;
}
const commentText = res.body.newText;
return editor.edit(
edits => edits.insert(start, commentText),
{ undoStopBefore: false, undoStopAfter: true });
}, () => {
return false;
}).then(didInsertComment => {
if (didInsertComment) {
const newCursorPosition = new Position(start.line + 1, editor.document.lineAt(start.line + 1).text.length);
editor.selection = new Selection(newCursorPosition, newCursorPosition);
return true;
}
// Revert to the original line content and restore position
return editor.edit(
edits => {
edits.insert(start, prefix[1] + suffix[0]);
}, {
undoStopBefore: false,
undoStopAfter: true
}
).then(() => {
editor.selection = new Selection(position, position);
return false;
});
// Revert to the original line content and restore position
return editor.edit(
edits => {
edits.insert(start, prefix[1] + suffix[0]);
}, {
undoStopBefore: false,
undoStopAfter: true
}
).then(() => {
editor.selection = new Selection(position, position);
return false;
});
});
});
}
}
@@ -1,84 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as vscode from 'vscode';
import { ITypescriptServiceClient } from '../typescriptService';
import { loadMessageBundle } from 'vscode-nls';
const localize = loadMessageBundle();
export default class ProjectConfigStatus implements vscode.Disposable {
private entry: vscode.StatusBarItem;
private subscription: vscode.Disposable;
constructor(private client: ITypescriptServiceClient) {
this.entry = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Right, Number.MIN_VALUE);
this.entry.color = 'white';
this.entry.command = 'typescript.goToProjectConfig';
this.subscription = vscode.window.onDidChangeActiveTextEditor(editor => this.showHideStatus(editor));
if (vscode.window.activeTextEditor) {
this.showHideStatus(vscode.window.activeTextEditor);
}
}
private showHideStatus(editor: vscode.TextEditor | undefined) {
editor = editor || vscode.window.activeTextEditor;
if (!editor || !vscode.workspace.rootPath) {
this.hide();
return;
}
const doc = editor.document;
const isTypeScript = !!(vscode.languages.match('typescript', doc) || vscode.languages.match('typescriptreact', doc));
if (isTypeScript || vscode.languages.match('javascript', doc) || vscode.languages.match('javascriptreact', doc)) {
this.showStatusForResource(doc.uri, isTypeScript);
} else {
this.hide();
}
}
private showStatusForResource(resource: vscode.Uri, isTypeScript: boolean) {
const file = this.client.normalizePath(resource);
if (!file) {
this.hide();
return;
}
return this.client.execute('projectInfo', { file, needFileNameList: false }).then(res => {
if (!res || !res.body || !res.body.configFileName) {
this.hide();
return;
}
const { configFileName } = res.body;
this.entry.tooltip = configFileName;
this.entry.command = isTypeScript ? 'typescript.goToProjectConfig' : 'javascript.goToProjectConfig';
if (configFileName.toLowerCase().endsWith('tsconfig.json')) {
this.entry.text = 'tsconfig';
} else if (configFileName.toLowerCase().endsWith('jsconfig.json')) {
this.entry.text = 'jsconfig';
} else {
this.entry.text = isTypeScript
? localize('typescript.projectConfigStatus.noTypeScriptProject', 'No TS Project')
: localize('typescript.projectConfigStatus.noJavaScriptProject', 'No JS Project');
}
this.entry.show();
});
}
private hide() {
this.entry.hide();
this.entry.text = '';
this.entry.tooltip = '';
}
dispose() {
this.entry.dispose();
this.subscription.dispose();
}
}
@@ -7,7 +7,7 @@
import vscode = require('vscode');
const versionBarEntry = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Right, Number.MIN_VALUE + 1);
const versionBarEntry = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Right, Number.MIN_VALUE);
export function showHideStatus() {
if (!versionBarEntry) {
+1 -1
View File
@@ -427,7 +427,7 @@
"xterm": {
"version": "2.3.0",
"from": "Tyriar/xterm.js#vscode-release/1.10",
"resolved": "git+https://github.com/Tyriar/xterm.js.git#5513303451202b0135601a2f026602ed391b3906"
"resolved": "git+https://github.com/Tyriar/xterm.js.git#a1543c16baee7ee35f6e3d6cd434c35afcf3b609"
},
"yauzl": {
"version": "2.3.1",
+1 -1
View File
@@ -2,7 +2,7 @@
"name": "code-oss-dev",
"version": "1.10.0",
"electronVersion": "1.4.6",
"distro": "4d6820ed4c9ffeac4febc40ac77bc391c8700a0d",
"distro": "fdc80c6b4d95e5f961bd3f85b927693915550942",
"author": {
"name": "Microsoft Corporation"
},
+34 -4
View File
@@ -31,8 +31,38 @@ if [ "@@NAME@@" != "code-oss" ]; then
APT_DIR=$(get_apt_config_value Dir)
APT_ETC=$APT_DIR$(get_apt_config_value Dir::Etc)
APT_SOURCE_PARTS=$APT_ETC/$(get_apt_config_value Dir::Etc::sourceparts)
CODE_SOURCE_LIST=$APT_SOURCE_PARTS/vscode.list
CODE_SOURCE_PART=$APT_SOURCE_PARTS/vscode.list
rm -f $CODE_SOURCE_LIST
# echo "deb [arch=amd64] https://packages.microsoft.com/repos/vscode stable main" > $CODE_SOURCE_LIST
fi
APT_TRUSTED_PARTS=$APT_ETC/$(get_apt_config_value Dir::Etc::trustedparts)
CODE_TRUSTED_PART=$APT_TRUSTED_PARTS/microsoft.gpg
# Sourced from https://packages.microsoft.com/keys/microsoft.asc
if [ ! -f $CODE_TRUSTED_PART ]; then
echo "-----BEGIN PGP PUBLIC KEY BLOCK-----
Version: GnuPG v1.4.7 (GNU/Linux)
mQENBFYxWIwBCADAKoZhZlJxGNGWzqV+1OG1xiQeoowKhssGAKvd+buXCGISZJwT
LXZqIcIiLP7pqdcZWtE9bSc7yBY2MalDp9Liu0KekywQ6VVX1T72NPf5Ev6x6DLV
7aVWsCzUAF+eb7DC9fPuFLEdxmOEYoPjzrQ7cCnSV4JQxAqhU4T6OjbvRazGl3ag
OeizPXmRljMtUUttHQZnRhtlzkmwIrUivbfFPD+fEoHJ1+uIdfOzZX8/oKHKLe2j
H632kvsNzJFlROVvGLYAk2WRcLu+RjjggixhwiB+Mu/A8Tf4V6b+YppS44q8EvVr
M+QvY7LNSOffSO6Slsy9oisGTdfE39nC7pVRABEBAAG0N01pY3Jvc29mdCAoUmVs
ZWFzZSBzaWduaW5nKSA8Z3Bnc2VjdXJpdHlAbWljcm9zb2Z0LmNvbT6JATUEEwEC
AB8FAlYxWIwCGwMGCwkIBwMCBBUCCAMDFgIBAh4BAheAAAoJEOs+lK2+EinPGpsH
/32vKy29Hg51H9dfFJMx0/a/F+5vKeCeVqimvyTM04C+XENNuSbYZ3eRPHGHFLqe
MNGxsfb7C7ZxEeW7J/vSzRgHxm7ZvESisUYRFq2sgkJ+HFERNrqfci45bdhmrUsy
7SWw9ybxdFOkuQoyKD3tBmiGfONQMlBaOMWdAsic965rvJsd5zYaZZFI1UwTkFXV
KJt3bp3Ngn1vEYXwijGTa+FXz6GLHueJwF0I7ug34DgUkAFvAs8Hacr2DRYxL5RJ
XdNgj4Jd2/g6T9InmWT0hASljur+dJnzNiNCkbn9KbX7J/qK1IbR8y560yRmFsU+
NdCFTW7wY0Fb1fWJ+/KTsC4=
=J6gs
-----END PGP PUBLIC KEY BLOCK-----
" | gpg --dearmor > microsoft.gpg
mv microsoft.gpg $CODE_TRUSTED_PART
fi
# Install repository source list if it does not already exist
if [ ! -f $CODE_SOURCE_PART ]; then
echo "deb [arch=amd64] https://packages.microsoft.com/repos/vscode stable main" > $CODE_SOURCE_PART
fi
fi
+4
View File
@@ -34,6 +34,10 @@ set ELECTRON_ENABLE_LOGGING=1
set ELECTRON_ENABLE_STACK_DUMPING=1
:: Launch Code
:: Use the following to get v8 tracing:
:: %CODE% --js-flags="--trace-hydrogen --trace-phase=Z --trace-deopt --code-comments --hydrogen-track-positions --redirect-code-traces" . %*
%CODE% . %*
popd
+7 -20
View File
@@ -10,7 +10,7 @@ import { onUnexpectedError } from 'vs/base/common/errors';
import { EventEmitter } from 'vs/base/common/eventEmitter';
import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
import { isObject } from 'vs/base/common/types';
import { isChrome, isWebKit } from 'vs/base/browser/browser';
import * as browser from 'vs/base/browser/browser';
import { IKeyboardEvent, StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { IMouseEvent, StandardMouseEvent } from 'vs/base/browser/mouseEvent';
import { CharCode } from 'vs/base/common/charCode';
@@ -211,6 +211,7 @@ export function addDisposableListener(node: Element | Window | Document, type: s
export interface IAddStandardDisposableListenerSignature {
(node: HTMLElement, type: 'click', handler: (event: IMouseEvent) => void, useCapture?: boolean): IDisposable;
(node: HTMLElement, type: 'mousedown', handler: (event: IMouseEvent) => void, useCapture?: boolean): IDisposable;
(node: HTMLElement, type: 'keydown', handler: (event: IKeyboardEvent) => void, useCapture?: boolean): IDisposable;
(node: HTMLElement, type: 'keypress', handler: (event: IKeyboardEvent) => void, useCapture?: boolean): IDisposable;
(node: HTMLElement, type: 'keyup', handler: (event: IKeyboardEvent) => void, useCapture?: boolean): IDisposable;
@@ -229,7 +230,7 @@ function _wrapAsStandardKeyboardEvent(handler: (e: IKeyboardEvent) => void): (e:
export let addStandardDisposableListener: IAddStandardDisposableListenerSignature = function addStandardDisposableListener(node: HTMLElement, type: string, handler: (event: any) => void, useCapture?: boolean): IDisposable {
let wrapHandler = handler;
if (type === 'click') {
if (type === 'click' || type === 'mousedown') {
wrapHandler = _wrapAsStandardMouseEvent(handler);
} else if (type === 'keydown' || type === 'keypress' || type === 'keyup') {
wrapHandler = _wrapAsStandardKeyboardEvent(handler);
@@ -377,21 +378,7 @@ class AnimationFrameQueueItem implements IDisposable {
if (!animFrameRequested) {
animFrameRequested = true;
// TODO@Alex: also check if it is electron
if (isChrome) {
let handle: number;
_animationFrame.request(function () {
clearTimeout(handle);
animationFrameRunner();
});
// This is a fallback in-case chrome dropped
// the request for an animation frame. This
// is sick but was spotted in the wild
handle = setTimeout(animationFrameRunner, 1000);
} else {
_animationFrame.request(animationFrameRunner);
}
_animationFrame.request(animationFrameRunner);
}
return item;
@@ -797,9 +784,9 @@ export const EventType = {
DROP: 'drop',
DRAG_END: 'dragend',
// Animation
ANIMATION_START: isWebKit ? 'webkitAnimationStart' : 'animationstart',
ANIMATION_END: isWebKit ? 'webkitAnimationEnd' : 'animationend',
ANIMATION_ITERATION: isWebKit ? 'webkitAnimationIteration' : 'animationiteration'
ANIMATION_START: browser.isWebKit ? 'webkitAnimationStart' : 'animationstart',
ANIMATION_END: browser.isWebKit ? 'webkitAnimationEnd' : 'animationend',
ANIMATION_ITERATION: browser.isWebKit ? 'webkitAnimationIteration' : 'animationiteration'
};
export interface EventLike {
+10 -10
View File
@@ -6,9 +6,9 @@
import * as dom from 'vs/base/browser/dom';
export abstract class FastDomNode {
export abstract class FastDomNode<T extends HTMLElement> {
private _domNode: HTMLElement;
private _domNode: T;
private _maxWidth: number;
private _width: number;
private _height: number;
@@ -26,11 +26,11 @@ export abstract class FastDomNode {
private _visibility: string;
private _transform: string;
public get domNode(): HTMLElement {
public get domNode(): T {
return this._domNode;
}
constructor(domNode: HTMLElement) {
constructor(domNode: T) {
this._domNode = domNode;
this._maxWidth = -1;
this._width = -1;
@@ -183,21 +183,21 @@ export abstract class FastDomNode {
this._setTransform(this._domNode, this._transform);
}
protected abstract _setTransform(domNode: HTMLElement, transform: string): void;
protected abstract _setTransform(domNode: T, transform: string): void;
public setAttribute(name: string, value: string): void {
this._domNode.setAttribute(name, value);
}
}
class WebKitFastDomNode extends FastDomNode {
protected _setTransform(domNode: HTMLElement, transform: string): void {
class WebKitFastDomNode<T extends HTMLElement> extends FastDomNode<T> {
protected _setTransform(domNode: T, transform: string): void {
(<any>domNode.style).webkitTransform = transform;
}
}
class StandardFastDomNode extends FastDomNode {
protected _setTransform(domNode: HTMLElement, transform: string): void {
class StandardFastDomNode<T extends HTMLElement> extends FastDomNode<T> {
protected _setTransform(domNode: T, transform: string): void {
domNode.style.transform = transform;
}
}
@@ -209,7 +209,7 @@ let useWebKitFastDomNode = false;
useWebKitFastDomNode = true;
}
})();
export function createFastDomNode(domNode: HTMLElement): FastDomNode {
export function createFastDomNode<T extends HTMLElement>(domNode: T): FastDomNode<T> {
if (useWebKitFastDomNode) {
return new WebKitFastDomNode(domNode);
} else {
@@ -48,12 +48,12 @@ export abstract class AbstractScrollbar extends Widget {
protected _host: ScrollbarHost;
protected _scrollable: Scrollable;
private _lazyRender: boolean;
private _scrollbarState: ScrollbarState;
protected _scrollbarState: ScrollbarState;
private _visibilityController: ScrollbarVisibilityController;
private _mouseMoveMonitor: GlobalMouseMoveMonitor<IStandardMouseMoveEventData>;
public domNode: FastDomNode;
public slider: FastDomNode;
public domNode: FastDomNode<HTMLElement>;
public slider: FastDomNode<HTMLElement>;
protected _shouldRender: boolean;
@@ -37,9 +37,9 @@ export class ScrollableElement extends Widget {
private _horizontalScrollbar: HorizontalScrollbar;
private _domNode: HTMLElement;
private _leftShadowDomNode: FastDomNode;
private _topShadowDomNode: FastDomNode;
private _topLeftShadowDomNode: FastDomNode;
private _leftShadowDomNode: FastDomNode<HTMLElement>;
private _topShadowDomNode: FastDomNode<HTMLElement>;
private _topLeftShadowDomNode: FastDomNode<HTMLElement>;
private _listenOnDomNode: HTMLElement;
@@ -145,6 +145,10 @@ export class ScrollableElement extends Widget {
this._verticalScrollbar.delegateMouseDown(browserEvent);
}
public getVerticalSliderVerticalCenter(): number {
return this._verticalScrollbar.getVerticalSliderVerticalCenter();
}
public updateState(newState: INewScrollState): void {
this._scrollable.updateState(newState);
}
@@ -365,7 +369,9 @@ export class ScrollableElement extends Widget {
}
private _scheduleHide(): void {
this._hideTimeout.cancelAndSet(() => this._hide(), HIDE_TIMEOUT);
if (!this._mouseIsOver && !this._isDragging) {
this._hideTimeout.cancelAndSet(() => this._hide(), HIDE_TIMEOUT);
}
}
}
@@ -171,6 +171,10 @@ export class ScrollbarState {
return this._computedSliderPosition;
}
public getSliderCenter(): number {
return (this._computedSliderPosition + this._computedSliderSize / 2);
}
public convertSliderPositionToScrollPosition(desiredSliderPosition: number): number {
return desiredSliderPosition / this._computedRatio;
}
@@ -13,7 +13,7 @@ export class ScrollbarVisibilityController extends Disposable {
private _visibility: ScrollbarVisibility;
private _visibleClassName: string;
private _invisibleClassName: string;
private _domNode: FastDomNode;
private _domNode: FastDomNode<HTMLElement>;
private _shouldBeVisible: boolean;
private _isNeeded: boolean;
private _isVisible: boolean;
@@ -59,7 +59,7 @@ export class ScrollbarVisibilityController extends Disposable {
}
}
public setDomNode(domNode: FastDomNode): void {
public setDomNode(domNode: FastDomNode<HTMLElement>): void {
this._domNode = domNode;
this._domNode.setClassName(this._invisibleClassName);
@@ -60,6 +60,10 @@ export class VerticalScrollbar extends AbstractScrollbar {
this._createSlider(0, Math.floor((options.verticalScrollbarSize - options.verticalSliderSize) / 2), options.verticalSliderSize, null);
}
public getVerticalSliderVerticalCenter(): number {
return this._scrollbarState.getSliderCenter();
}
protected _updateSlider(sliderSize: number, sliderPosition: number): void {
this.slider.setHeight(sliderSize);
if (this._canUseTranslate3d) {
+333 -168
View File
@@ -3,27 +3,161 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as Object from 'vs/base/common/objects';
import { CharCode } from 'vs/base/common/charCode';
export interface RGBA { r: number; g: number; b: number; a: number; }
export interface HSLA { h: number; s: number; l: number; a: number; }
export class RGBA {
_rgbaBrand: void;
/**
* Red: integer in [0-255]
*/
public readonly r: number;
/**
* Green: integer in [0-255]
*/
public readonly g: number;
/**
* Blue: integer in [0-255]
*/
public readonly b: number;
/**
* Alpha: integer in [0-255]
*/
public readonly a: number;
constructor(r: number, g: number, b: number, a: number) {
this.r = RGBA._clampInt_0_255(r);
this.g = RGBA._clampInt_0_255(g);
this.b = RGBA._clampInt_0_255(b);
this.a = RGBA._clampInt_0_255(a);
}
public static equals(a: RGBA, b: RGBA): boolean {
return (
a.r === b.r
&& a.g === b.g
&& a.b === b.b
&& a.a === b.a
);
}
private static _clampInt_0_255(c: number): number {
if (c < 0) {
return 0;
}
if (c > 255) {
return 255;
}
return c | 0;
}
}
/**
* http://en.wikipedia.org/wiki/HSL_color_space
*/
export class HSLA {
_hslaBrand: void;
/**
* Hue: float in [0, 360]
*/
public readonly h: number;
/**
* Saturation: float in [0, 1]
*/
public readonly s: number;
/**
* Luminosity: float in [0, 1]
*/
public readonly l: number;
/**
* Alpha: float in [0, 1]
*/
public readonly a: number;
constructor(h: number, s: number, l: number, a: number) {
this.h = HSLA._clampFloat_0_360(h);
this.s = HSLA._clampFloat_0_1(s);
this.l = HSLA._clampFloat_0_1(l);
this.a = HSLA._clampFloat_0_1(a);
}
private static _clampFloat_0_360(hue: number): number {
if (hue < 0) {
return 0.0;
}
if (hue > 360) {
return 360.0;
}
return hue;
}
private static _clampFloat_0_1(n: number): number {
if (n < 0) {
return 0.0;
}
if (n > 1) {
return 1.0;
}
return n;
}
}
/**
* Converts an Hex color value to RGB.
* returns r, g, and b are contained in the set [0, 255]
* @param hex string (#RRGGBB or #RRGGBBAA).
*/
function hex2rgba(hex: string): RGBA {
function parseHex(str: string) {
return parseInt('0x' + str);
if (!hex) {
// Invalid color
return new RGBA(255, 0, 0, 255);
}
if (hex.charAt(0) === '#' && hex.length >= 7) {
let r = parseHex(hex.substr(1, 2));
let g = parseHex(hex.substr(3, 2));
let b = parseHex(hex.substr(5, 2));
let a = hex.length === 9 ? parseHex(hex.substr(7, 2)) / 0xff : 1;
return { r, g, b, a };
if (hex.length === 7 && hex.charCodeAt(0) === CharCode.Hash) {
// #RRGGBB format
const r = 16 * _parseHexDigit(hex.charCodeAt(1)) + _parseHexDigit(hex.charCodeAt(2));
const g = 16 * _parseHexDigit(hex.charCodeAt(3)) + _parseHexDigit(hex.charCodeAt(4));
const b = 16 * _parseHexDigit(hex.charCodeAt(5)) + _parseHexDigit(hex.charCodeAt(6));
return new RGBA(r, g, b, 255);
}
return { r: 255, g: 0, b: 0, a: 1 };
if (hex.length === 9 && hex.charCodeAt(0) === CharCode.Hash) {
// #RRGGBBAA format
const r = 16 * _parseHexDigit(hex.charCodeAt(1)) + _parseHexDigit(hex.charCodeAt(2));
const g = 16 * _parseHexDigit(hex.charCodeAt(3)) + _parseHexDigit(hex.charCodeAt(4));
const b = 16 * _parseHexDigit(hex.charCodeAt(5)) + _parseHexDigit(hex.charCodeAt(6));
const a = 16 * _parseHexDigit(hex.charCodeAt(7)) + _parseHexDigit(hex.charCodeAt(8));
return new RGBA(r, g, b, a);
}
// Invalid color
return new RGBA(255, 0, 0, 255);
}
function _parseHexDigit(charCode: CharCode): number {
switch (charCode) {
case CharCode.Digit0: return 0;
case CharCode.Digit1: return 1;
case CharCode.Digit2: return 2;
case CharCode.Digit3: return 3;
case CharCode.Digit4: return 4;
case CharCode.Digit5: return 5;
case CharCode.Digit6: return 6;
case CharCode.Digit7: return 7;
case CharCode.Digit8: return 8;
case CharCode.Digit9: return 9;
case CharCode.a: return 10;
case CharCode.A: return 10;
case CharCode.b: return 11;
case CharCode.B: return 11;
case CharCode.c: return 12;
case CharCode.C: return 12;
case CharCode.d: return 13;
case CharCode.D: return 13;
case CharCode.e: return 14;
case CharCode.E: return 14;
case CharCode.f: return 15;
case CharCode.F: return 15;
}
return 0;
}
/**
@@ -33,13 +167,17 @@ function hex2rgba(hex: string): RGBA {
* returns h in the set [0, 360], s, and l in the set [0, 1].
*/
function rgba2hsla(rgba: RGBA): HSLA {
let r = rgba.r / 255;
let g = rgba.g / 255;
let b = rgba.b / 255;
let a = rgba.a === void 0 ? rgba.a : 1;
const r = rgba.r / 255;
const g = rgba.g / 255;
const b = rgba.b / 255;
const a = rgba.a / 255;
let max = Math.max(r, g, b), min = Math.min(r, g, b);
let h = 0, s = 0, l = Math.round(((min + max) / 2) * 1000) / 1000, chroma = max - min;
const max = Math.max(r, g, b);
const min = Math.min(r, g, b);
let h = 0;
let s = 0;
const l = Math.round(((min + max) / 2) * 1000) / 1000;
const chroma = max - min;
if (chroma > 0) {
s = Math.min(Math.round((l <= 0.5 ? chroma / (2 * l) : chroma / (2 - (2 * l))) * 1000) / 1000, 1);
@@ -51,7 +189,7 @@ function rgba2hsla(rgba: RGBA): HSLA {
h *= 60;
h = Math.round(h);
}
return { h, s, l, a };
return new HSLA(h, s, l, a);
}
/**
@@ -61,170 +199,53 @@ function rgba2hsla(rgba: RGBA): HSLA {
* returns r, g, and b in the set [0, 255].
*/
function hsla2rgba(hsla: HSLA): RGBA {
let h = hsla.h / 360;
let s = Math.min(hsla.s, 1);
let l = Math.min(hsla.l, 1);
let a = hsla.a === void 0 ? hsla.a : 1;
const h = hsla.h / 360;
const s = Math.min(hsla.s, 1);
const l = Math.min(hsla.l, 1);
const a = hsla.a;
let r: number, g: number, b: number;
if (s === 0) {
r = g = b = l; // achromatic
} else {
let hue2rgb = function hue2rgb(p: number, q: number, t: number) {
if (t < 0) {
t += 1;
}
if (t > 1) {
t -= 1;
}
if (t < 1 / 6) {
return p + (q - p) * 6 * t;
}
if (t < 1 / 2) {
return q;
}
if (t < 2 / 3) {
return p + (q - p) * (2 / 3 - t) * 6;
}
return p;
};
let q = l < 0.5 ? l * (1 + s) : l + s - l * s;
let p = 2 * l - q;
r = hue2rgb(p, q, h + 1 / 3);
g = hue2rgb(p, q, h);
b = hue2rgb(p, q, h - 1 / 3);
const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
const p = 2 * l - q;
r = _hue2rgb(p, q, h + 1 / 3);
g = _hue2rgb(p, q, h);
b = _hue2rgb(p, q, h - 1 / 3);
}
return { r: Math.round(r * 255), g: Math.round(g * 255), b: Math.round(b * 255), a };
return new RGBA(Math.round(r * 255), Math.round(g * 255), Math.round(b * 255), Math.round(a * 255));
}
export function hexToCSS(hex: string) {
if (hex.length === 9) {
return toCSSColor(hex2rgba(hex));
function _hue2rgb(p: number, q: number, t: number) {
if (t < 0) {
t += 1;
}
return hex;
}
function toCSSColor(rgba: RGBA): string {
return `rgba(${rgba.r}, ${rgba.g}, ${rgba.b}, ${+rgba.a.toFixed(2)})`;
if (t > 1) {
t -= 1;
}
if (t < 1 / 6) {
return p + (q - p) * 6 * t;
}
if (t < 1 / 2) {
return q;
}
if (t < 2 / 3) {
return p + (q - p) * (2 / 3 - t) * 6;
}
return p;
}
export class Color {
private rgba: RGBA;
private hsla: HSLA;
private str: string;
constructor(arg: string | RGBA) {
this.rgba = typeof arg === 'string' ? hex2rgba(arg) : <RGBA>arg;
this.str = null;
}
/**
* http://www.w3.org/TR/WCAG20/#relativeluminancedef
* Returns the number in the set [0, 1]. O => Darkest Black. 1 => Lightest white.
*/
public getLuminosity(): number {
let luminosityFor = function (color: number): number {
let c = color / 255;
return (c <= 0.03928) ? c / 12.92 : Math.pow(((c + 0.055) / 1.055), 2.4);
};
let R = luminosityFor(this.rgba.r);
let G = luminosityFor(this.rgba.g);
let B = luminosityFor(this.rgba.b);
let luminosity = 0.2126 * R + 0.7152 * G + 0.0722 * B;
return Math.round(luminosity * 10000) / 10000;
}
/**
* http://www.w3.org/TR/WCAG20/#contrast-ratiodef
* Returns the contrast ration number in the set [1, 21].
*/
public getContrast(another: Color): number {
let lum1 = this.getLuminosity();
let lum2 = another.getLuminosity();
return lum1 > lum2 ? (lum1 + 0.05) / (lum2 + 0.05) : (lum2 + 0.05) / (lum1 + 0.05);
}
/**
* http://24ways.org/2010/calculating-color-contrast
* Return 'true' if darker color otherwise 'false'
*/
public isDarker(): boolean {
var yiq = (this.rgba.r * 299 + this.rgba.g * 587 + this.rgba.b * 114) / 1000;
return yiq < 128;
}
/**
* http://24ways.org/2010/calculating-color-contrast
* Return 'true' if lighter color otherwise 'false'
*/
public isLighter(): boolean {
var yiq = (this.rgba.r * 299 + this.rgba.g * 587 + this.rgba.b * 114) / 1000;
return yiq >= 128;
}
public isLighterThan(another: Color): boolean {
let lum1 = this.getLuminosity();
let lum2 = another.getLuminosity();
return lum1 > lum2;
}
public isDarkerThan(another: Color): boolean {
let lum1 = this.getLuminosity();
let lum2 = another.getLuminosity();
return lum1 < lum2;
}
public lighten(factor: number): Color {
let hsl = this.toHSLA();
hsl.l += hsl.l * factor;
return new Color(hsla2rgba(hsl));
}
public darken(factor: number): Color {
let hsl = this.toHSLA();
hsl.l -= hsl.l * factor;
return new Color(hsla2rgba(hsl));
}
public transparent(factor: number): Color {
let p = this.rgba;
return new Color({ r: p.r, g: p.g, b: p.b, a: p.a * factor });
}
public opposite(): Color {
return new Color({
r: 255 - this.rgba.r,
g: 255 - this.rgba.g,
b: 255 - this.rgba.b,
a: this.rgba.a
});
}
public toString(): string {
if (!this.str) {
this.str = toCSSColor(this.rgba);
}
return this.str;
}
public toHSLA(): HSLA {
if (!this.hsla) {
this.hsla = rgba2hsla(this.rgba);
}
return Object.clone(this.hsla);
}
public toRGBA(): RGBA {
return Object.clone(this.rgba);
}
public static fromRGBA(rgba: RGBA): Color {
return new Color(rgba);
}
/**
* Creates a color from a hex string (#RRGGBB or #RRGGBBAA).
*/
public static fromHex(hex: string): Color {
return new Color(hex);
}
@@ -233,12 +254,155 @@ export class Color {
return new Color(hsla2rgba(hsla));
}
private readonly rgba: RGBA;
private hsla: HSLA;
private constructor(arg: string | RGBA) {
if (arg instanceof RGBA) {
this.rgba = arg;
} else {
this.rgba = hex2rgba(arg);
}
this.hsla = null;
}
public equals(other: Color): boolean {
if (!other) {
return false;
}
return RGBA.equals(this.rgba, other.rgba);
}
/**
* http://www.w3.org/TR/WCAG20/#relativeluminancedef
* Returns the number in the set [0, 1]. O => Darkest Black. 1 => Lightest white.
*/
public getLuminosity(): number {
const R = Color._luminosityFor(this.rgba.r);
const G = Color._luminosityFor(this.rgba.g);
const B = Color._luminosityFor(this.rgba.b);
const luminosity = 0.2126 * R + 0.7152 * G + 0.0722 * B;
return Math.round(luminosity * 10000) / 10000;
}
private static _luminosityFor(color: number): number {
const c = color / 255;
return (c <= 0.03928) ? c / 12.92 : Math.pow(((c + 0.055) / 1.055), 2.4);
}
/**
* http://www.w3.org/TR/WCAG20/#contrast-ratiodef
* Returns the contrast ration number in the set [1, 21].
*/
public getContrast(another: Color): number {
const lum1 = this.getLuminosity();
const lum2 = another.getLuminosity();
return lum1 > lum2 ? (lum1 + 0.05) / (lum2 + 0.05) : (lum2 + 0.05) / (lum1 + 0.05);
}
/**
* http://24ways.org/2010/calculating-color-contrast
* Return 'true' if darker color otherwise 'false'
*/
public isDarker(): boolean {
const yiq = (this.rgba.r * 299 + this.rgba.g * 587 + this.rgba.b * 114) / 1000;
return yiq < 128;
}
/**
* http://24ways.org/2010/calculating-color-contrast
* Return 'true' if lighter color otherwise 'false'
*/
public isLighter(): boolean {
const yiq = (this.rgba.r * 299 + this.rgba.g * 587 + this.rgba.b * 114) / 1000;
return yiq >= 128;
}
public isLighterThan(another: Color): boolean {
const lum1 = this.getLuminosity();
const lum2 = another.getLuminosity();
return lum1 > lum2;
}
public isDarkerThan(another: Color): boolean {
const lum1 = this.getLuminosity();
const lum2 = another.getLuminosity();
return lum1 < lum2;
}
public lighten(factor: number): Color {
const hsl = this.toHSLA();
const result = new HSLA(hsl.h, hsl.s, hsl.l + hsl.l * factor, hsl.a);
return new Color(hsla2rgba(result));
}
public darken(factor: number): Color {
const hsl = this.toHSLA();
const result = new HSLA(hsl.h, hsl.s, hsl.l - hsl.l * factor, hsl.a);
return new Color(hsla2rgba(result));
}
public transparent(factor: number): Color {
const p = this.rgba;
return new Color(new RGBA(p.r, p.g, p.b, Math.round(p.a * factor)));
}
public opposite(): Color {
return new Color(new RGBA(
255 - this.rgba.r,
255 - this.rgba.g,
255 - this.rgba.b,
this.rgba.a
));
}
public toString(): string {
const rgba = this.rgba;
return `rgba(${rgba.r}, ${rgba.g}, ${rgba.b}, ${+(rgba.a / 255).toFixed(2)})`;
}
/**
* Prins the color as #RRGGBB
*/
public toRGBHex(): string {
const rgba = this.rgba;
return `#${Color._toTwoDigitHex(rgba.r)}${Color._toTwoDigitHex(rgba.g)}${Color._toTwoDigitHex(rgba.b)}`;
}
/**
* Prins the color as #RRGGBBAA
*/
public toRGBAHex(): string {
const rgba = this.rgba;
return `#${Color._toTwoDigitHex(rgba.r)}${Color._toTwoDigitHex(rgba.g)}${Color._toTwoDigitHex(rgba.b)}${Color._toTwoDigitHex(rgba.a)}`;
}
private static _toTwoDigitHex(n: number): string {
let r = n.toString(16);
if (r.length !== 2) {
return '0' + r;
}
return r;
}
public toHSLA(): HSLA {
if (this.hsla === null) {
this.hsla = rgba2hsla(this.rgba);
}
return this.hsla;
}
public toRGBA(): RGBA {
return this.rgba;
}
public static getLighterColor(of: Color, relative: Color, factor?: number): Color {
if (of.isLighterThan(relative)) {
return of;
}
factor = factor ? factor : 0.5;
let lum1 = of.getLuminosity(), lum2 = relative.getLuminosity();
const lum1 = of.getLuminosity();
const lum2 = relative.getLuminosity();
factor = factor * (lum2 - lum1) / lum2;
return of.lighten(factor);
}
@@ -248,8 +412,9 @@ export class Color {
return of;
}
factor = factor ? factor : 0.5;
let lum1 = of.getLuminosity(), lum2 = relative.getLuminosity();
const lum1 = of.getLuminosity();
const lum2 = relative.getLuminosity();
factor = factor * (lum1 - lum2) / lum1;
return of.darken(factor);
}
}
}
+124 -94
View File
@@ -5,82 +5,112 @@
'use strict';
import { Color } from 'vs/base/common/color';
import * as assert from 'assert';
import { Color, RGBA, HSLA } from 'vs/base/common/color';
suite('Color', () => {
test('rgba2hsla', function () {
assert.deepEqual({ h: 0, s: 0, l: 0, a: 1 }, Color.fromRGBA({ r: 0, g: 0, b: 0, a: 1 }).toHSLA());
assert.deepEqual({ h: 0, s: 0, l: 1, a: 1 }, Color.fromRGBA({ r: 255, g: 255, b: 255, a: 1 }).toHSLA());
assert.deepEqual(new HSLA(0, 0, 0, 1), Color.fromRGBA(new RGBA(0, 0, 0, 255)).toHSLA());
assert.deepEqual(new HSLA(0, 0, 1, 1), Color.fromRGBA(new RGBA(255, 255, 255, 255)).toHSLA());
assert.deepEqual({ h: 0, s: 1, l: 0.5, a: 1 }, Color.fromRGBA({ r: 255, g: 0, b: 0, a: 1 }).toHSLA());
assert.deepEqual({ h: 120, s: 1, l: 0.5, a: 1 }, Color.fromRGBA({ r: 0, g: 255, b: 0, a: 1 }).toHSLA());
assert.deepEqual({ h: 240, s: 1, l: 0.5, a: 1 }, Color.fromRGBA({ r: 0, g: 0, b: 255, a: 1 }).toHSLA());
assert.deepEqual(new HSLA(0, 1, 0.5, 1), Color.fromRGBA(new RGBA(255, 0, 0, 255)).toHSLA());
assert.deepEqual(new HSLA(120, 1, 0.5, 1), Color.fromRGBA(new RGBA(0, 255, 0, 255)).toHSLA());
assert.deepEqual(new HSLA(240, 1, 0.5, 1), Color.fromRGBA(new RGBA(0, 0, 255, 255)).toHSLA());
assert.deepEqual({ h: 60, s: 1, l: 0.5, a: 1 }, Color.fromRGBA({ r: 255, g: 255, b: 0, a: 1 }).toHSLA());
assert.deepEqual({ h: 180, s: 1, l: 0.5, a: 1 }, Color.fromRGBA({ r: 0, g: 255, b: 255, a: 1 }).toHSLA());
assert.deepEqual({ h: 300, s: 1, l: 0.5, a: 1 }, Color.fromRGBA({ r: 255, g: 0, b: 255, a: 1 }).toHSLA());
assert.deepEqual(new HSLA(60, 1, 0.5, 1), Color.fromRGBA(new RGBA(255, 255, 0, 255)).toHSLA());
assert.deepEqual(new HSLA(180, 1, 0.5, 1), Color.fromRGBA(new RGBA(0, 255, 255, 255)).toHSLA());
assert.deepEqual(new HSLA(300, 1, 0.5, 1), Color.fromRGBA(new RGBA(255, 0, 255, 255)).toHSLA());
assert.deepEqual({ h: 0, s: 0, l: 0.753, a: 1 }, Color.fromRGBA({ r: 192, g: 192, b: 192, a: 1 }).toHSLA());
assert.deepEqual(new HSLA(0, 0, 0.753, 1), Color.fromRGBA(new RGBA(192, 192, 192, 255)).toHSLA());
assert.deepEqual({ h: 0, s: 0, l: 0.502, a: 1 }, Color.fromRGBA({ r: 128, g: 128, b: 128, a: 1 }).toHSLA());
assert.deepEqual({ h: 0, s: 1, l: 0.251, a: 1 }, Color.fromRGBA({ r: 128, g: 0, b: 0, a: 1 }).toHSLA());
assert.deepEqual({ h: 60, s: 1, l: 0.251, a: 1 }, Color.fromRGBA({ r: 128, g: 128, b: 0, a: 1 }).toHSLA());
assert.deepEqual({ h: 120, s: 1, l: 0.251, a: 1 }, Color.fromRGBA({ r: 0, g: 128, b: 0, a: 1 }).toHSLA());
assert.deepEqual({ h: 300, s: 1, l: 0.251, a: 1 }, Color.fromRGBA({ r: 128, g: 0, b: 128, a: 1 }).toHSLA());
assert.deepEqual({ h: 180, s: 1, l: 0.251, a: 1 }, Color.fromRGBA({ r: 0, g: 128, b: 128, a: 1 }).toHSLA());
assert.deepEqual({ h: 240, s: 1, l: 0.251, a: 1 }, Color.fromRGBA({ r: 0, g: 0, b: 128, a: 1 }).toHSLA());
assert.deepEqual(new HSLA(0, 0, 0.502, 1), Color.fromRGBA(new RGBA(128, 128, 128, 255)).toHSLA());
assert.deepEqual(new HSLA(0, 1, 0.251, 1), Color.fromRGBA(new RGBA(128, 0, 0, 255)).toHSLA());
assert.deepEqual(new HSLA(60, 1, 0.251, 1), Color.fromRGBA(new RGBA(128, 128, 0, 255)).toHSLA());
assert.deepEqual(new HSLA(120, 1, 0.251, 1), Color.fromRGBA(new RGBA(0, 128, 0, 255)).toHSLA());
assert.deepEqual(new HSLA(300, 1, 0.251, 1), Color.fromRGBA(new RGBA(128, 0, 128, 255)).toHSLA());
assert.deepEqual(new HSLA(180, 1, 0.251, 1), Color.fromRGBA(new RGBA(0, 128, 128, 255)).toHSLA());
assert.deepEqual(new HSLA(240, 1, 0.251, 1), Color.fromRGBA(new RGBA(0, 0, 128, 255)).toHSLA());
});
test('hsla2rgba', function () {
assert.deepEqual({ r: 0, g: 0, b: 0, a: 1 }, Color.fromHSLA({ h: 0, s: 0, l: 0, a: 1 }).toRGBA());
assert.deepEqual({ r: 255, g: 255, b: 255, a: 1 }, Color.fromHSLA({ h: 0, s: 0, l: 1, a: 1 }).toRGBA());
assert.deepEqual(new RGBA(0, 0, 0, 255), Color.fromHSLA(new HSLA(0, 0, 0, 1)).toRGBA());
assert.deepEqual(new RGBA(255, 255, 255, 255), Color.fromHSLA(new HSLA(0, 0, 1, 1)).toRGBA());
assert.deepEqual({ r: 255, g: 0, b: 0, a: 1 }, Color.fromHSLA({ h: 0, s: 1, l: 0.5, a: 1 }).toRGBA());
assert.deepEqual({ r: 0, g: 255, b: 0, a: 1 }, Color.fromHSLA({ h: 120, s: 1, l: 0.5, a: 1 }).toRGBA());
assert.deepEqual({ r: 0, g: 0, b: 255, a: 1 }, Color.fromHSLA({ h: 240, s: 1, l: 0.5, a: 1 }).toRGBA());
assert.deepEqual(new RGBA(255, 0, 0, 255), Color.fromHSLA(new HSLA(0, 1, 0.5, 1)).toRGBA());
assert.deepEqual(new RGBA(0, 255, 0, 255), Color.fromHSLA(new HSLA(120, 1, 0.5, 1)).toRGBA());
assert.deepEqual(new RGBA(0, 0, 255, 255), Color.fromHSLA(new HSLA(240, 1, 0.5, 1)).toRGBA());
assert.deepEqual({ r: 255, g: 255, b: 0, a: 1 }, Color.fromHSLA({ h: 60, s: 1, l: 0.5, a: 1 }).toRGBA());
assert.deepEqual({ r: 0, g: 255, b: 255, a: 1 }, Color.fromHSLA({ h: 180, s: 1, l: 0.5, a: 1 }).toRGBA());
assert.deepEqual({ r: 255, g: 0, b: 255, a: 1 }, Color.fromHSLA({ h: 300, s: 1, l: 0.5, a: 1 }).toRGBA());
assert.deepEqual(new RGBA(255, 255, 0, 255), Color.fromHSLA(new HSLA(60, 1, 0.5, 1)).toRGBA());
assert.deepEqual(new RGBA(0, 255, 255, 255), Color.fromHSLA(new HSLA(180, 1, 0.5, 1)).toRGBA());
assert.deepEqual(new RGBA(255, 0, 255, 255), Color.fromHSLA(new HSLA(300, 1, 0.5, 1)).toRGBA());
assert.deepEqual({ r: 192, g: 192, b: 192, a: 1 }, Color.fromHSLA({ h: 0, s: 0, l: 0.753, a: 1 }).toRGBA());
assert.deepEqual(new RGBA(192, 192, 192, 255), Color.fromHSLA(new HSLA(0, 0, 0.753, 1)).toRGBA());
assert.deepEqual({ r: 128, g: 128, b: 128, a: 1 }, Color.fromHSLA({ h: 0, s: 0, l: 0.502, a: 1 }).toRGBA());
assert.deepEqual({ r: 128, g: 0, b: 0, a: 1 }, Color.fromHSLA({ h: 0, s: 1, l: 0.251, a: 1 }).toRGBA());
assert.deepEqual({ r: 128, g: 128, b: 0, a: 1 }, Color.fromHSLA({ h: 60, s: 1, l: 0.251, a: 1 }).toRGBA());
assert.deepEqual({ r: 0, g: 128, b: 0, a: 1 }, Color.fromHSLA({ h: 120, s: 1, l: 0.251, a: 1 }).toRGBA());
assert.deepEqual({ r: 128, g: 0, b: 128, a: 1 }, Color.fromHSLA({ h: 300, s: 1, l: 0.251, a: 1 }).toRGBA());
assert.deepEqual({ r: 0, g: 128, b: 128, a: 1 }, Color.fromHSLA({ h: 180, s: 1, l: 0.251, a: 1 }).toRGBA());
assert.deepEqual({ r: 0, g: 0, b: 128, a: 1 }, Color.fromHSLA({ h: 240, s: 1, l: 0.251, a: 1 }).toRGBA());
assert.deepEqual(new RGBA(128, 128, 128, 255), Color.fromHSLA(new HSLA(0, 0, 0.502, 1)).toRGBA());
assert.deepEqual(new RGBA(128, 0, 0, 255), Color.fromHSLA(new HSLA(0, 1, 0.251, 1)).toRGBA());
assert.deepEqual(new RGBA(128, 128, 0, 255), Color.fromHSLA(new HSLA(60, 1, 0.251, 1)).toRGBA());
assert.deepEqual(new RGBA(0, 128, 0, 255), Color.fromHSLA(new HSLA(120, 1, 0.251, 1)).toRGBA());
assert.deepEqual(new RGBA(128, 0, 128, 255), Color.fromHSLA(new HSLA(300, 1, 0.251, 1)).toRGBA());
assert.deepEqual(new RGBA(0, 128, 128, 255), Color.fromHSLA(new HSLA(180, 1, 0.251, 1)).toRGBA());
assert.deepEqual(new RGBA(0, 0, 128, 255), Color.fromHSLA(new HSLA(240, 1, 0.251, 1)).toRGBA());
});
test('hex2rgba', function () {
assert.deepEqual({ r: 0, g: 0, b: 0, a: 1 }, Color.fromHex('#000000').toRGBA());
assert.deepEqual({ r: 255, g: 255, b: 255, a: 1 }, Color.fromHex('#FFFFFF').toRGBA());
assert.deepEqual(new RGBA(0, 0, 0, 255), Color.fromHex('#000000').toRGBA());
assert.deepEqual(new RGBA(255, 255, 255, 255), Color.fromHex('#FFFFFF').toRGBA());
assert.deepEqual({ r: 255, g: 0, b: 0, a: 1 }, Color.fromHex('#FF0000').toRGBA());
assert.deepEqual({ r: 0, g: 255, b: 0, a: 1 }, Color.fromHex('#00FF00').toRGBA());
assert.deepEqual({ r: 0, g: 0, b: 255, a: 1 }, Color.fromHex('#0000FF').toRGBA());
assert.deepEqual(new RGBA(255, 0, 0, 255), Color.fromHex('#FF0000').toRGBA());
assert.deepEqual(new RGBA(0, 255, 0, 255), Color.fromHex('#00FF00').toRGBA());
assert.deepEqual(new RGBA(0, 0, 255, 255), Color.fromHex('#0000FF').toRGBA());
assert.deepEqual({ r: 255, g: 255, b: 0, a: 1 }, Color.fromHex('#FFFF00').toRGBA());
assert.deepEqual({ r: 0, g: 255, b: 255, a: 1 }, Color.fromHex('#00FFFF').toRGBA());
assert.deepEqual({ r: 255, g: 0, b: 255, a: 1 }, Color.fromHex('#FF00FF').toRGBA());
assert.deepEqual(new RGBA(255, 255, 0, 255), Color.fromHex('#FFFF00').toRGBA());
assert.deepEqual(new RGBA(0, 255, 255, 255), Color.fromHex('#00FFFF').toRGBA());
assert.deepEqual(new RGBA(255, 0, 255, 255), Color.fromHex('#FF00FF').toRGBA());
assert.deepEqual({ r: 192, g: 192, b: 192, a: 1 }, Color.fromHex('#C0C0C0').toRGBA());
assert.deepEqual(new RGBA(192, 192, 192, 255), Color.fromHex('#C0C0C0').toRGBA());
assert.deepEqual({ r: 128, g: 128, b: 128, a: 1 }, Color.fromHex('#808080').toRGBA());
assert.deepEqual({ r: 128, g: 0, b: 0, a: 1 }, Color.fromHex('#800000').toRGBA());
assert.deepEqual({ r: 128, g: 128, b: 0, a: 1 }, Color.fromHex('#808000').toRGBA());
assert.deepEqual({ r: 0, g: 128, b: 0, a: 1 }, Color.fromHex('#008000').toRGBA());
assert.deepEqual({ r: 128, g: 0, b: 128, a: 1 }, Color.fromHex('#800080').toRGBA());
assert.deepEqual({ r: 0, g: 128, b: 128, a: 1 }, Color.fromHex('#008080').toRGBA());
assert.deepEqual({ r: 0, g: 0, b: 128, a: 1 }, Color.fromHex('#000080').toRGBA());
assert.deepEqual(new RGBA(128, 128, 128, 255), Color.fromHex('#808080').toRGBA());
assert.deepEqual(new RGBA(128, 0, 0, 255), Color.fromHex('#800000').toRGBA());
assert.deepEqual(new RGBA(128, 128, 0, 255), Color.fromHex('#808000').toRGBA());
assert.deepEqual(new RGBA(0, 128, 0, 255), Color.fromHex('#008000').toRGBA());
assert.deepEqual(new RGBA(128, 0, 128, 255), Color.fromHex('#800080').toRGBA());
assert.deepEqual(new RGBA(0, 128, 128, 255), Color.fromHex('#008080').toRGBA());
assert.deepEqual(new RGBA(0, 0, 128, 255), Color.fromHex('#000080').toRGBA());
function assertParseColor(input: string, expected: RGBA): void {
let actual = Color.fromHex(input).toRGBA();
assert.deepEqual(actual, expected, input);
}
// invalid
assertParseColor(null, new RGBA(255, 0, 0, 255));
assertParseColor('', new RGBA(255, 0, 0, 255));
assertParseColor('#', new RGBA(255, 0, 0, 255));
assertParseColor('#0102030', new RGBA(255, 0, 0, 255));
// somewhat valid
assertParseColor('#FFFFG0', new RGBA(255, 255, 0, 255));
assertParseColor('#FFFFg0', new RGBA(255, 255, 0, 255));
assertParseColor('#-FFF00', new RGBA(15, 255, 0, 255));
// valid
assertParseColor('#000000', new RGBA(0, 0, 0, 255));
assertParseColor('#010203', new RGBA(1, 2, 3, 255));
assertParseColor('#040506', new RGBA(4, 5, 6, 255));
assertParseColor('#070809', new RGBA(7, 8, 9, 255));
assertParseColor('#0a0A0a', new RGBA(10, 10, 10, 255));
assertParseColor('#0b0B0b', new RGBA(11, 11, 11, 255));
assertParseColor('#0c0C0c', new RGBA(12, 12, 12, 255));
assertParseColor('#0d0D0d', new RGBA(13, 13, 13, 255));
assertParseColor('#0e0E0e', new RGBA(14, 14, 14, 255));
assertParseColor('#0f0F0f', new RGBA(15, 15, 15, 255));
assertParseColor('#a0A0a0', new RGBA(160, 160, 160, 255));
assertParseColor('#FFFFFF', new RGBA(255, 255, 255, 255));
});
test('isLighterColor', function () {
let color1 = Color.fromHSLA({ h: 60, s: 1, l: 0.5, a: 1 }), color2 = Color.fromHSLA({ h: 0, s: 0, l: 0.753, a: 1 });
let color1 = Color.fromHSLA(new HSLA(60, 1, 0.5, 1)), color2 = Color.fromHSLA(new HSLA(0, 0, 0.753, 1));
assert.ok(color1.isLighterThan(color2));
@@ -89,80 +119,80 @@ suite('Color', () => {
});
test('getLighterColor', function () {
let color1 = Color.fromHSLA({ h: 60, s: 1, l: 0.5, a: 1 }), color2 = Color.fromHSLA({ h: 0, s: 0, l: 0.753, a: 1 });
let color1 = Color.fromHSLA(new HSLA(60, 1, 0.5, 1)), color2 = Color.fromHSLA(new HSLA(0, 0, 0.753, 1));
assert.deepEqual(color1.toHSLA(), Color.getLighterColor(color1, color2).toHSLA());
assert.deepEqual({ h: 0, s: 0, l: 0.914, a: 1 }, Color.getLighterColor(color2, color1).toHSLA());
assert.deepEqual({ h: 0, s: 0, l: 0.851, a: 1 }, Color.getLighterColor(color2, color1, 0.3).toHSLA());
assert.deepEqual({ h: 0, s: 0, l: 0.98, a: 1 }, Color.getLighterColor(color2, color1, 0.7).toHSLA());
assert.deepEqual({ h: 0, s: 0, l: 1, a: 1 }, Color.getLighterColor(color2, color1, 1).toHSLA());
assert.deepEqual(new HSLA(0, 0, 0.914, 1), Color.getLighterColor(color2, color1).toHSLA());
assert.deepEqual(new HSLA(0, 0, 0.851, 1), Color.getLighterColor(color2, color1, 0.3).toHSLA());
assert.deepEqual(new HSLA(0, 0, 0.98, 1), Color.getLighterColor(color2, color1, 0.7).toHSLA());
assert.deepEqual(new HSLA(0, 0, 1, 1), Color.getLighterColor(color2, color1, 1).toHSLA());
});
test('isDarkerColor', function () {
let color1 = Color.fromHSLA({ h: 60, s: 1, l: 0.5, a: 1 }), color2 = Color.fromHSLA({ h: 0, s: 0, l: 0.753, a: 1 });
let color1 = Color.fromHSLA(new HSLA(60, 1, 0.5, 1)), color2 = Color.fromHSLA(new HSLA(0, 0, 0.753, 1));
assert.ok(color2.isDarkerThan(color1));
});
test('getDarkerColor', function () {
let color1 = Color.fromHSLA({ h: 60, s: 1, l: 0.5, a: 1 }), color2 = Color.fromHSLA({ h: 0, s: 0, l: 0.753, a: 1 });
let color1 = Color.fromHSLA(new HSLA(60, 1, 0.5, 1)), color2 = Color.fromHSLA(new HSLA(0, 0, 0.753, 1));
assert.deepEqual(color2.toHSLA(), Color.getDarkerColor(color2, color1).toHSLA());
assert.deepEqual({ h: 60, s: 1, l: 0.392, a: 1 }, Color.getDarkerColor(color1, color2).toHSLA());
assert.deepEqual({ h: 60, s: 1, l: 0.435, a: 1 }, Color.getDarkerColor(color1, color2, 0.3).toHSLA());
assert.deepEqual({ h: 60, s: 1, l: 0.349, a: 1 }, Color.getDarkerColor(color1, color2, 0.7).toHSLA());
assert.deepEqual({ h: 60, s: 1, l: 0.284, a: 1 }, Color.getDarkerColor(color1, color2, 1).toHSLA());
assert.deepEqual(new HSLA(60, 1, 0.392, 1), Color.getDarkerColor(color1, color2).toHSLA());
assert.deepEqual(new HSLA(60, 1, 0.435, 1), Color.getDarkerColor(color1, color2, 0.3).toHSLA());
assert.deepEqual(new HSLA(60, 1, 0.349, 1), Color.getDarkerColor(color1, color2, 0.7).toHSLA());
assert.deepEqual(new HSLA(60, 1, 0.284, 1), Color.getDarkerColor(color1, color2, 1).toHSLA());
// Abyss theme
assert.deepEqual({ h: 355, s: 0.874, l: 0.157, a: 1 }, Color.getDarkerColor(Color.fromHex('#770811'), Color.fromHex('#000c18'), 0.4).toHSLA());
assert.deepEqual(new HSLA(355, 0.874, 0.157, 1), Color.getDarkerColor(Color.fromHex('#770811'), Color.fromHex('#000c18'), 0.4).toHSLA());
});
test('luminosity', function () {
assert.deepEqual(0, Color.fromRGBA({ r: 0, g: 0, b: 0, a: 1 }).getLuminosity());
assert.deepEqual(1, Color.fromRGBA({ r: 255, g: 255, b: 255, a: 1 }).getLuminosity());
assert.deepEqual(0, Color.fromRGBA(new RGBA(0, 0, 0, 255)).getLuminosity());
assert.deepEqual(1, Color.fromRGBA(new RGBA(255, 255, 255, 255)).getLuminosity());
assert.deepEqual(0.2126, Color.fromRGBA({ r: 255, g: 0, b: 0, a: 1 }).getLuminosity());
assert.deepEqual(0.7152, Color.fromRGBA({ r: 0, g: 255, b: 0, a: 1 }).getLuminosity());
assert.deepEqual(0.0722, Color.fromRGBA({ r: 0, g: 0, b: 255, a: 1 }).getLuminosity());
assert.deepEqual(0.2126, Color.fromRGBA(new RGBA(255, 0, 0, 255)).getLuminosity());
assert.deepEqual(0.7152, Color.fromRGBA(new RGBA(0, 255, 0, 255)).getLuminosity());
assert.deepEqual(0.0722, Color.fromRGBA(new RGBA(0, 0, 255, 255)).getLuminosity());
assert.deepEqual(0.9278, Color.fromRGBA({ r: 255, g: 255, b: 0, a: 1 }).getLuminosity());
assert.deepEqual(0.7874, Color.fromRGBA({ r: 0, g: 255, b: 255, a: 1 }).getLuminosity());
assert.deepEqual(0.2848, Color.fromRGBA({ r: 255, g: 0, b: 255, a: 1 }).getLuminosity());
assert.deepEqual(0.9278, Color.fromRGBA(new RGBA(255, 255, 0, 255)).getLuminosity());
assert.deepEqual(0.7874, Color.fromRGBA(new RGBA(0, 255, 255, 255)).getLuminosity());
assert.deepEqual(0.2848, Color.fromRGBA(new RGBA(255, 0, 255, 255)).getLuminosity());
assert.deepEqual(0.5271, Color.fromRGBA({ r: 192, g: 192, b: 192, a: 1 }).getLuminosity());
assert.deepEqual(0.5271, Color.fromRGBA(new RGBA(192, 192, 192, 255)).getLuminosity());
assert.deepEqual(0.2159, Color.fromRGBA({ r: 128, g: 128, b: 128, a: 1 }).getLuminosity());
assert.deepEqual(0.0459, Color.fromRGBA({ r: 128, g: 0, b: 0, a: 1 }).getLuminosity());
assert.deepEqual(0.2003, Color.fromRGBA({ r: 128, g: 128, b: 0, a: 1 }).getLuminosity());
assert.deepEqual(0.1544, Color.fromRGBA({ r: 0, g: 128, b: 0, a: 1 }).getLuminosity());
assert.deepEqual(0.0615, Color.fromRGBA({ r: 128, g: 0, b: 128, a: 1 }).getLuminosity());
assert.deepEqual(0.17, Color.fromRGBA({ r: 0, g: 128, b: 128, a: 1 }).getLuminosity());
assert.deepEqual(0.0156, Color.fromRGBA({ r: 0, g: 0, b: 128, a: 1 }).getLuminosity());
assert.deepEqual(0.2159, Color.fromRGBA(new RGBA(128, 128, 128, 255)).getLuminosity());
assert.deepEqual(0.0459, Color.fromRGBA(new RGBA(128, 0, 0, 255)).getLuminosity());
assert.deepEqual(0.2003, Color.fromRGBA(new RGBA(128, 128, 0, 255)).getLuminosity());
assert.deepEqual(0.1544, Color.fromRGBA(new RGBA(0, 128, 0, 255)).getLuminosity());
assert.deepEqual(0.0615, Color.fromRGBA(new RGBA(128, 0, 128, 255)).getLuminosity());
assert.deepEqual(0.17, Color.fromRGBA(new RGBA(0, 128, 128, 255)).getLuminosity());
assert.deepEqual(0.0156, Color.fromRGBA(new RGBA(0, 0, 128, 255)).getLuminosity());
});
test('contrast', function () {
assert.deepEqual(0, Color.fromRGBA({ r: 0, g: 0, b: 0, a: 1 }).getLuminosity());
assert.deepEqual(1, Color.fromRGBA({ r: 255, g: 255, b: 255, a: 1 }).getLuminosity());
assert.deepEqual(0, Color.fromRGBA(new RGBA(0, 0, 0, 255)).getLuminosity());
assert.deepEqual(1, Color.fromRGBA(new RGBA(255, 255, 255, 255)).getLuminosity());
assert.deepEqual(0.2126, Color.fromRGBA({ r: 255, g: 0, b: 0, a: 1 }).getLuminosity());
assert.deepEqual(0.7152, Color.fromRGBA({ r: 0, g: 255, b: 0, a: 1 }).getLuminosity());
assert.deepEqual(0.0722, Color.fromRGBA({ r: 0, g: 0, b: 255, a: 1 }).getLuminosity());
assert.deepEqual(0.2126, Color.fromRGBA(new RGBA(255, 0, 0, 255)).getLuminosity());
assert.deepEqual(0.7152, Color.fromRGBA(new RGBA(0, 255, 0, 255)).getLuminosity());
assert.deepEqual(0.0722, Color.fromRGBA(new RGBA(0, 0, 255, 255)).getLuminosity());
assert.deepEqual(0.9278, Color.fromRGBA({ r: 255, g: 255, b: 0, a: 1 }).getLuminosity());
assert.deepEqual(0.7874, Color.fromRGBA({ r: 0, g: 255, b: 255, a: 1 }).getLuminosity());
assert.deepEqual(0.2848, Color.fromRGBA({ r: 255, g: 0, b: 255, a: 1 }).getLuminosity());
assert.deepEqual(0.9278, Color.fromRGBA(new RGBA(255, 255, 0, 255)).getLuminosity());
assert.deepEqual(0.7874, Color.fromRGBA(new RGBA(0, 255, 255, 255)).getLuminosity());
assert.deepEqual(0.2848, Color.fromRGBA(new RGBA(255, 0, 255, 255)).getLuminosity());
assert.deepEqual(0.5271, Color.fromRGBA({ r: 192, g: 192, b: 192, a: 1 }).getLuminosity());
assert.deepEqual(0.5271, Color.fromRGBA(new RGBA(192, 192, 192, 255)).getLuminosity());
assert.deepEqual(0.2159, Color.fromRGBA({ r: 128, g: 128, b: 128, a: 1 }).getLuminosity());
assert.deepEqual(0.0459, Color.fromRGBA({ r: 128, g: 0, b: 0, a: 1 }).getLuminosity());
assert.deepEqual(0.2003, Color.fromRGBA({ r: 128, g: 128, b: 0, a: 1 }).getLuminosity());
assert.deepEqual(0.1544, Color.fromRGBA({ r: 0, g: 128, b: 0, a: 1 }).getLuminosity());
assert.deepEqual(0.0615, Color.fromRGBA({ r: 128, g: 0, b: 128, a: 1 }).getLuminosity());
assert.deepEqual(0.17, Color.fromRGBA({ r: 0, g: 128, b: 128, a: 1 }).getLuminosity());
assert.deepEqual(0.0156, Color.fromRGBA({ r: 0, g: 0, b: 128, a: 1 }).getLuminosity());
assert.deepEqual(0.2159, Color.fromRGBA(new RGBA(128, 128, 128, 255)).getLuminosity());
assert.deepEqual(0.0459, Color.fromRGBA(new RGBA(128, 0, 0, 255)).getLuminosity());
assert.deepEqual(0.2003, Color.fromRGBA(new RGBA(128, 128, 0, 255)).getLuminosity());
assert.deepEqual(0.1544, Color.fromRGBA(new RGBA(0, 128, 0, 255)).getLuminosity());
assert.deepEqual(0.0615, Color.fromRGBA(new RGBA(128, 0, 128, 255)).getLuminosity());
assert.deepEqual(0.17, Color.fromRGBA(new RGBA(0, 128, 128, 255)).getLuminosity());
assert.deepEqual(0.0156, Color.fromRGBA(new RGBA(0, 0, 128, 255)).getLuminosity());
});
});
@@ -226,7 +226,7 @@ export class Configuration extends CommonEditorConfiguration {
domNode.style.lineHeight = fontInfo.lineHeight + 'px';
}
public static applyFontInfo(domNode: FastDomNode, fontInfo: BareFontInfo): void {
public static applyFontInfo(domNode: FastDomNode<HTMLElement>, fontInfo: BareFontInfo): void {
domNode.setFontFamily(fontInfo.fontFamily);
domNode.setFontWeight(fontInfo.fontWeight);
domNode.setFontSize(fontInfo.fontSize);
@@ -292,6 +292,10 @@ export class Configuration extends CommonEditorConfiguration {
return browser.canUseTranslate3d && browser.getZoomLevel() === 0;
}
protected _getPixelRatio(): number {
return browser.getPixelRatio();
}
protected readConfiguration(bareFontInfo: BareFontInfo): FontInfo {
return CSSBasedConfiguration.INSTANCE.readConfiguration(bareFontInfo);
}
@@ -29,9 +29,12 @@ class ClipboardEventWrapper implements IClipboardEvent {
return false;
}
public setTextData(text: string): void {
public setTextData(text: string, richText: string): void {
if (this._event.clipboardData) {
this._event.clipboardData.setData('text/plain', text);
if (richText !== null) {
this._event.clipboardData.setData('text/html', richText);
}
this._event.preventDefault();
return;
}
@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
'use strict';
import { Theme, IThemeRule } from 'vs/editor/common/modes/supports/tokenization';
import { Theme, IThemeRule, generateTokensCSSForColorMap } from 'vs/editor/common/modes/supports/tokenization';
import { IStandaloneColorService, BuiltinTheme, ITheme } from 'vs/editor/common/services/standaloneColorService';
import { vs, vs_dark, hc_black } from 'vs/editor/common/standalone/themes';
import * as dom from 'vs/base/browser/dom';
@@ -61,18 +61,6 @@ export class StandaloneColorServiceImpl implements IStandaloneColorService {
this.setTheme(VS_THEME_NAME);
}
private static _generateCSS(colorMap: string[]): string {
let rules: string[] = [];
for (let i = 1, len = colorMap.length; i < len; i++) {
let color = colorMap[i];
rules[i] = `.mtk${i} { color: #${color}; }`;
}
rules.push('.mtki { font-style: italic; }');
rules.push('.mtkb { font-weight: bold; }');
rules.push('.mtku { text-decoration: underline; }');
return rules.join('\n');
}
public defineTheme(themeName: string, themeData: ITheme): void {
if (!/^[a-z0-9\-]+$/i.test(themeName) || isBuiltinTheme(themeName)) {
throw new Error('Illegal theme name!');
@@ -107,7 +95,7 @@ export class StandaloneColorServiceImpl implements IStandaloneColorService {
this._theme = Theme.createFromRawTheme(themeData.rules);
let colorMap = this._theme.getColorMap();
let cssRules = StandaloneColorServiceImpl._generateCSS(colorMap);
let cssRules = generateTokensCSSForColorMap(colorMap);
this._styleElement.innerHTML = cssRules;
TokenizationRegistry.setColorMap(colorMap);
+10 -5
View File
@@ -7,7 +7,7 @@
import { IDisposable } from 'vs/base/common/lifecycle';
import { TPromise } from 'vs/base/common/winjs.base';
import { IModel } from 'vs/editor/common/editorCommon';
import { TokenizationRegistry, ITokenizationSupport } from 'vs/editor/common/modes';
import { ColorId, MetadataConsts, FontStyle, TokenizationRegistry, ITokenizationSupport } from 'vs/editor/common/modes';
import { IModeService } from 'vs/editor/common/services/modeService';
import { renderViewLine, RenderLineInput } from 'vs/editor/common/viewLayout/viewLineRenderer';
import { ViewLineToken } from 'vs/editor/common/core/viewLineToken';
@@ -56,7 +56,7 @@ export class Colorizer {
return new TPromise<void>((c, e, p) => {
listener = TokenizationRegistry.onDidChange((e) => {
if (e.languages.indexOf(language) >= 0) {
if (e.changedLanguages.indexOf(language) >= 0) {
stopListening();
c(void 0);
}
@@ -126,6 +126,12 @@ function _colorize(lines: string[], tabSize: number, tokenizationSupport: IToken
function _fakeColorize(lines: string[], tabSize: number): string {
let html: string[] = [];
const defaultMetadata = (
(FontStyle.None << MetadataConsts.FONT_STYLE_OFFSET)
| (ColorId.DefaultForeground << MetadataConsts.FOREGROUND_OFFSET)
| (ColorId.DefaultBackground << MetadataConsts.BACKGROUND_OFFSET)
) >>> 0;
for (let i = 0, length = lines.length; i < length; i++) {
let line = lines[i];
@@ -134,7 +140,7 @@ function _fakeColorize(lines: string[], tabSize: number): string {
line,
false,
0,
[new ViewLineToken(line.length, '')],
[new ViewLineToken(line.length, defaultMetadata)],
[],
tabSize,
0,
@@ -153,12 +159,11 @@ function _fakeColorize(lines: string[], tabSize: number): string {
function _actualColorize(lines: string[], tabSize: number, tokenizationSupport: ITokenizationSupport): string {
let html: string[] = [];
let state = tokenizationSupport.getInitialState();
let colorMap = TokenizationRegistry.getColorMap();
for (let i = 0, length = lines.length; i < length; i++) {
let line = lines[i];
let tokenizeResult = tokenizationSupport.tokenize2(line, state, 0);
let lineTokens = new LineTokens(colorMap, tokenizeResult.tokens, line);
let lineTokens = new LineTokens(tokenizeResult.tokens, line);
let renderResult = renderViewLine(new RenderLineInput(
false,
line,
@@ -344,9 +344,11 @@ export function createMonacoEditorAPI(): typeof monaco.editor {
TextEditorCursorBlinkingStyle: editorCommon.TextEditorCursorBlinkingStyle,
ContentWidgetPositionPreference: ContentWidgetPositionPreference,
OverlayWidgetPositionPreference: OverlayWidgetPositionPreference,
RenderMinimap: editorCommon.RenderMinimap,
// classes
InternalEditorScrollbarOptions: <any>editorCommon.InternalEditorScrollbarOptions,
InternalEditorMinimapOptions: <any>editorCommon.InternalEditorMinimapOptions,
EditorWrappingInfo: <any>editorCommon.EditorWrappingInfo,
InternalEditorViewOptions: <any>editorCommon.InternalEditorViewOptions,
EditorContribOptions: <any>editorCommon.EditorContribOptions,
+6 -1
View File
@@ -49,6 +49,7 @@ import { IPointerHandlerHelper } from 'vs/editor/browser/controller/mouseHandler
import { ViewOutgoingEvents } from 'vs/editor/browser/view/viewOutgoingEvents';
import { ViewportData } from 'vs/editor/common/viewLayout/viewLinesViewportData';
import { EditorScrollbar } from 'vs/editor/browser/viewParts/editorScrollbar/editorScrollbar';
import { Minimap } from 'vs/editor/browser/viewParts/minimap/minimap';
export class View extends ViewEventHandler implements editorBrowser.IView, IDisposable {
@@ -270,6 +271,9 @@ export class View extends ViewEventHandler implements editorBrowser.IView, IDisp
let rulers = new Rulers(this._context);
this.viewParts.push(rulers);
let minimap = new Minimap(this._context, this.layoutProvider, this._scrollbar);
this.viewParts.push(minimap);
// -------------- Wire dom nodes up
this.linesContentContainer = this._scrollbar.getScrollbarContainerDomNode();
@@ -292,6 +296,7 @@ export class View extends ViewEventHandler implements editorBrowser.IView, IDisp
this.overflowGuardContainer.appendChild(this.overlayWidgets.domNode);
this.overflowGuardContainer.appendChild(this.textArea);
this.overflowGuardContainer.appendChild(this.textAreaCover);
this.overflowGuardContainer.appendChild(minimap.getDomNode());
this.domNode.appendChild(this.overflowGuardContainer);
this.domNode.appendChild(this.contentWidgets.overflowingContentWidgetsDomNode);
}
@@ -477,7 +482,7 @@ export class View extends ViewEventHandler implements editorBrowser.IView, IDisp
StyleMutator.setHeight(this.linesContent, 1000000);
StyleMutator.setLeft(this.linesContentContainer, layoutInfo.contentLeft);
StyleMutator.setWidth(this.linesContentContainer, layoutInfo.contentWidth);
StyleMutator.setWidth(this.linesContentContainer, layoutInfo.contentWidth + layoutInfo.minimapWidth);
StyleMutator.setHeight(this.linesContentContainer, layoutInfo.contentHeight);
this.outgoingEvents.emitViewLayoutChanged(layoutInfo);
+21 -13
View File
@@ -108,7 +108,19 @@ export class RenderedLinesCollection<T extends ILine> {
let startLineNumber = this.getStartLineNumber();
let endLineNumber = this.getEndLineNumber();
// Record what needs to be deleted, notify lines that survive after deletion
if (deleteToLineNumber < startLineNumber) {
// deleting above the viewport
let deleteCnt = deleteToLineNumber - deleteFromLineNumber + 1;
this._rendLineNumberStart -= deleteCnt;
return null;
}
if (deleteFromLineNumber > endLineNumber) {
// deleted below the viewport
return null;
}
// Record what needs to be deleted
let deleteStartIndex = 0;
let deleteCount = 0;
for (let lineNumber = startLineNumber; lineNumber <= endLineNumber; lineNumber++) {
@@ -154,18 +166,14 @@ export class RenderedLinesCollection<T extends ILine> {
let startLineNumber = this.getStartLineNumber();
let endLineNumber = this.getEndLineNumber();
// Notify lines after the change
let notifiedSomeone = false;
for (let lineNumber = startLineNumber; lineNumber <= endLineNumber; lineNumber++) {
let lineIndex = lineNumber - this._rendLineNumberStart;
if (lineNumber === changedLineNumber) {
this._lines[lineIndex].onContentChanged();
notifiedSomeone = true;
}
if (changedLineNumber < startLineNumber || changedLineNumber > endLineNumber) {
// a line has been changed above or below the viewport
return false;
}
return notifiedSomeone;
// Notify the line
this._lines[changedLineNumber - this._rendLineNumberStart].onContentChanged();
return true;
}
public onModelLinesInserted(insertFromLineNumber: number, insertToLineNumber: number): T[] {
@@ -244,7 +252,7 @@ export class RenderedLinesCollection<T extends ILine> {
export abstract class ViewLayer<T extends IVisibleLine> extends ViewPart {
protected domNode: FastDomNode;
protected domNode: FastDomNode<HTMLElement>;
protected _linesCollection: RenderedLinesCollection<T>;
private _renderer: ViewLayerRenderer<T>;
private _scrollDomNode: HTMLElement;
@@ -353,7 +361,7 @@ export abstract class ViewLayer<T extends IVisibleLine> extends ViewPart {
this._scrollDomNodeIsAbove = resCtx.scrollDomNodeIsAbove;
}
private _createDomNode(): FastDomNode {
private _createDomNode(): FastDomNode<HTMLElement> {
let domNode = createFastDomNode(document.createElement('div'));
domNode.setClassName('view-layer');
domNode.setPosition('absolute');
+1 -1
View File
@@ -114,7 +114,7 @@ export class ViewOverlayLine implements IVisibleLine {
private _configuration: IConfiguration;
private _dynamicOverlays: DynamicViewOverlay[];
private _domNode: FastDomNode;
private _domNode: FastDomNode<HTMLElement>;
private _renderedContent: string;
private _lineHeight: number;
@@ -390,10 +390,6 @@ export class ViewContentWidgets extends ViewPart {
}
public prepareRender(ctx: IRenderingContext): void {
if (!this.shouldRender()) {
throw new Error('I did not ask to render!');
}
let data: IMyRenderData = {};
let keys = Object.keys(this._widgets);
@@ -104,9 +104,6 @@ export class CurrentLineHighlightOverlay extends DynamicViewOverlay {
// --- end event handlers
public prepareRender(ctx: IRenderingContext): void {
if (!this.shouldRender()) {
throw new Error('I did not ask to render!');
}
this._scrollWidth = ctx.scrollWidth;
}
@@ -78,10 +78,6 @@ export class DecorationsOverlay extends DynamicViewOverlay {
// --- end event handlers
public prepareRender(ctx: IRenderingContext): void {
if (!this.shouldRender()) {
throw new Error('I did not ask to render!');
}
let _decorations = ctx.getDecorationsInViewport();
// Keep only decorations with `className`
@@ -125,4 +125,8 @@ export class EditorScrollbar implements IDisposable {
public delegateVerticalScrollbarMouseDown(browserEvent: MouseEvent): void {
this.scrollbar.delegateVerticalScrollbarMouseDown(browserEvent);
}
public getVerticalSliderVerticalCenter(): number {
return this.scrollbar.getVerticalSliderVerticalCenter();
}
}
@@ -163,10 +163,6 @@ export class GlyphMarginOverlay extends DedupOverlay {
}
public prepareRender(ctx: IRenderingContext): void {
if (!this.shouldRender()) {
throw new Error('I did not ask to render!');
}
if (!this._glyphMargin) {
this._renderResult = null;
return;
@@ -73,10 +73,6 @@ export class IndentGuidesOverlay extends DynamicViewOverlay {
// --- end event handlers
public prepareRender(ctx: IRenderingContext): void {
if (!this.shouldRender()) {
throw new Error('I did not ask to render!');
}
if (!this._enabled) {
this._renderResult = null;
return;
@@ -231,7 +231,7 @@ export class ViewLine implements IVisibleLine {
}
interface IRenderedViewLine {
domNode: FastDomNode;
domNode: FastDomNode<HTMLElement>;
readonly input: RenderLineInput;
getWidth(): number;
getVisibleRangesForRange(startColumn: number, endColumn: number, context: DomReadingContext): HorizontalRange[];
@@ -243,14 +243,14 @@ interface IRenderedViewLine {
*/
class FastRenderedViewLine implements IRenderedViewLine {
public domNode: FastDomNode;
public domNode: FastDomNode<HTMLElement>;
public readonly input: RenderLineInput;
private readonly _characterMapping: CharacterMapping;
private readonly _charWidth: number;
private _charOffset: Uint32Array;
constructor(domNode: FastDomNode, renderLineInput: RenderLineInput, characterMapping: CharacterMapping) {
constructor(domNode: FastDomNode<HTMLElement>, renderLineInput: RenderLineInput, characterMapping: CharacterMapping) {
this.domNode = domNode;
this.input = renderLineInput;
@@ -345,7 +345,7 @@ class FastRenderedViewLine implements IRenderedViewLine {
*/
class RenderedViewLine {
public domNode: FastDomNode;
public domNode: FastDomNode<HTMLElement>;
public readonly input: RenderLineInput;
protected readonly _characterMapping: CharacterMapping;
@@ -357,7 +357,7 @@ class RenderedViewLine {
*/
private _pixelOffsetCache: Int32Array;
constructor(domNode: FastDomNode, renderLineInput: RenderLineInput, characterMapping: CharacterMapping, containsRTL: boolean) {
constructor(domNode: FastDomNode<HTMLElement>, renderLineInput: RenderLineInput, characterMapping: CharacterMapping, containsRTL: boolean) {
this.domNode = domNode;
this.input = renderLineInput;
this._characterMapping = characterMapping;
@@ -548,17 +548,17 @@ class WebKitRenderedViewLine extends RenderedViewLine {
}
}
const createRenderedLine: (domNode: FastDomNode, renderLineInput: RenderLineInput, characterMapping: CharacterMapping, containsRTL: boolean) => RenderedViewLine = (function () {
const createRenderedLine: (domNode: FastDomNode<HTMLElement>, renderLineInput: RenderLineInput, characterMapping: CharacterMapping, containsRTL: boolean) => RenderedViewLine = (function () {
if (browser.isWebKit) {
return createWebKitRenderedLine;
}
return createNormalRenderedLine;
})();
function createWebKitRenderedLine(domNode: FastDomNode, renderLineInput: RenderLineInput, characterMapping: CharacterMapping, containsRTL: boolean): RenderedViewLine {
function createWebKitRenderedLine(domNode: FastDomNode<HTMLElement>, renderLineInput: RenderLineInput, characterMapping: CharacterMapping, containsRTL: boolean): RenderedViewLine {
return new WebKitRenderedViewLine(domNode, renderLineInput, characterMapping, containsRTL);
}
function createNormalRenderedLine(domNode: FastDomNode, renderLineInput: RenderLineInput, characterMapping: CharacterMapping, containsRTL: boolean): RenderedViewLine {
function createNormalRenderedLine(domNode: FastDomNode<HTMLElement>, renderLineInput: RenderLineInput, characterMapping: CharacterMapping, containsRTL: boolean): RenderedViewLine {
return new RenderedViewLine(domNode, renderLineInput, characterMapping, containsRTL);
}
@@ -412,10 +412,6 @@ export class ViewLines extends ViewLayer<ViewLine> implements IViewLines {
}
public renderText(viewportData: ViewportData, onAfterLinesRendered: () => void): void {
if (!this.shouldRender()) {
throw new Error('I did not ask to render!');
}
// (1) render lines - ensures lines are in the DOM
super._renderLines(viewportData);
this._lastRenderedData.setBigNumbersDelta(viewportData.bigNumbersDelta);
@@ -18,7 +18,7 @@ export class Margin extends ViewPart {
private _contentLeft: number;
private _glyphMarginLeft: number;
private _glyphMarginWidth: number;
private _glyphMarginBackgroundDomNode: FastDomNode;
private _glyphMarginBackgroundDomNode: FastDomNode<HTMLElement>;
constructor(context: ViewContext) {
super(context);
@@ -0,0 +1,32 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.monaco-editor.vs .minimap-slider {
background: rgba(100, 100, 100, .4);
}
.monaco-editor.vs-dark .minimap-slider {
background: rgba(121, 121, 121, .4);
}
.monaco-editor.hc-black .minimap-slider {
background: rgba(111, 195, 223, .6);
}
.monaco-editor.vs .minimap-slider:hover,
.monaco-editor.vs-dark .minimap-slider:hover {
background: rgba(100, 100, 100, .7);
}
.monaco-editor.hc-black .minimap-slider:hover {
background: rgba(111, 195, 223, .8);
}
.monaco-editor.vs .minimap-slider.active {
background: rgba(0, 0, 0, .6);
}
.monaco-editor.vs-dark .minimap-slider.active {
background: rgba(191, 191, 191, .4);
}
.monaco-editor.hc-black .minimap-slider.active {
background: rgba(111, 195, 223, 1);
}
@@ -0,0 +1,736 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import 'vs/css!./minimap';
import { ViewPart } from 'vs/editor/browser/view/viewPart';
import { ViewContext } from 'vs/editor/common/view/viewContext';
import { IRenderingContext, IRestrictedRenderingContext } from 'vs/editor/common/view/renderingContext';
import { getOrCreateMinimapCharRenderer } from 'vs/editor/common/view/runtimeMinimapCharRenderer';
import * as browser from 'vs/base/browser/browser';
import * as dom from 'vs/base/browser/dom';
import { MinimapCharRenderer, ParsedColor, MinimapTokensColorTracker, Constants } from 'vs/editor/common/view/minimapCharRenderer';
import * as editorCommon from 'vs/editor/common/editorCommon';
import { CharCode } from 'vs/base/common/charCode';
import { IViewLayout, ViewLineData } from 'vs/editor/common/viewModel/viewModel';
import { ColorId } from 'vs/editor/common/modes';
import { FastDomNode, createFastDomNode } from 'vs/base/browser/styleMutator';
import { IDisposable } from 'vs/base/common/lifecycle';
import { EditorScrollbar } from 'vs/editor/browser/viewParts/editorScrollbar/editorScrollbar';
import { RenderedLinesCollection, ILine } from 'vs/editor/browser/view/viewLayer';
import { Range } from 'vs/editor/common/core/range';
const enum RenderMinimap {
None = 0,
Small = 1,
Large = 2
}
class MinimapOptions {
public readonly renderMinimap: RenderMinimap;
public readonly pixelRatio: number;
public readonly lineHeight: number;
/**
* container dom node width (in CSS px)
*/
public readonly minimapWidth: number;
/**
* container dom node height (in CSS px)
*/
public readonly minimapHeight: number;
/**
* canvas backing store width (in device px)
*/
public readonly canvasInnerWidth: number;
/**
* canvas backing store height (in device px)
*/
public readonly canvasInnerHeight: number;
/**
* canvas width (in CSS px)
*/
public readonly canvasOuterWidth: number;
/**
* canvas height (in CSS px)
*/
public readonly canvasOuterHeight: number;
constructor(configuration: editorCommon.IConfiguration) {
const pixelRatio = browser.getPixelRatio();
const layoutInfo = configuration.editor.layoutInfo;
this.renderMinimap = layoutInfo.renderMinimap | 0;
this.pixelRatio = pixelRatio;
this.lineHeight = configuration.editor.lineHeight;
this.minimapWidth = layoutInfo.minimapWidth;
this.minimapHeight = layoutInfo.height;
this.canvasInnerWidth = Math.floor(pixelRatio * this.minimapWidth);
this.canvasInnerHeight = Math.floor(pixelRatio * this.minimapHeight);
this.canvasOuterWidth = this.canvasInnerWidth / pixelRatio;
this.canvasOuterHeight = this.canvasInnerHeight / pixelRatio;
}
public equals(other: MinimapOptions): boolean {
return (this.renderMinimap === other.renderMinimap
&& this.pixelRatio === other.pixelRatio
&& this.lineHeight === other.lineHeight
&& this.minimapWidth === other.minimapWidth
&& this.minimapHeight === other.minimapHeight
&& this.canvasInnerWidth === other.canvasInnerWidth
&& this.canvasInnerHeight === other.canvasInnerHeight
&& this.canvasOuterWidth === other.canvasOuterWidth
&& this.canvasOuterHeight === other.canvasOuterHeight
);
}
}
class MinimapLayout {
/**
* slider dom node top (in CSS px)
*/
public readonly sliderTop: number;
/**
* slider dom node height (in CSS px)
*/
public readonly sliderHeight: number;
/**
* minimap render start line number.
*/
public readonly startLineNumber: number;
/**
* minimap render end line number.
*/
public readonly endLineNumber: number;
constructor(
lastRenderData: RenderData,
options: MinimapOptions,
viewportStartLineNumber: number,
viewportEndLineNumber: number,
viewportHeight: number,
lineCount: number,
scrollbarSliderCenter: number
) {
const pixelRatio = options.pixelRatio;
const minimapLineHeight = (options.renderMinimap === RenderMinimap.Large ? Constants.x2_CHAR_HEIGHT : Constants.x1_CHAR_HEIGHT);
const minimapLinesFitting = Math.floor(options.canvasInnerHeight / minimapLineHeight);
const lineHeight = options.lineHeight;
// Sometimes, the number of rendered lines varies for a constant viewport height.
// The reason is that only parts of the viewportStartLineNumber or viewportEndLineNumber are visible.
// This leads to an apparent tremor in the minimap's slider height.
// We try here to compensate, making the slider slightly incorrect in these cases, but more pleasing to the eye.
let viewportLineCount = viewportEndLineNumber - viewportStartLineNumber + 1;
const expectedViewportLineCount = Math.round(viewportHeight / lineHeight);
if (viewportLineCount > expectedViewportLineCount) {
viewportLineCount = expectedViewportLineCount;
}
if (minimapLinesFitting >= lineCount) {
// All lines fit in the minimap => no minimap scrolling
this.startLineNumber = 1;
this.endLineNumber = lineCount;
} else {
// The desire is to align (centers) the minimap's slider with the scrollbar's slider
// For a resolved this.startLineNumber, we can compute the minimap's slider's center with the following formula:
// scrollbarSliderCenter = (viewportStartLineNumber - this.startLineNumber + viewportLineCount/2) * minimapLineHeight / pixelRatio;
// =>
// scrollbarSliderCenter = (viewportStartLineNumber - this.startLineNumber + viewportLineCount/2) * minimapLineHeight / pixelRatio;
// scrollbarSliderCenter * pixelRatio / minimapLineHeight = viewportStartLineNumber - this.startLineNumber + viewportLineCount/2
// this.startLineNumber = viewportStartLineNumber + viewportLineCount/2 - scrollbarSliderCenter * pixelRatio / minimapLineHeight
let desiredStartLineNumber = Math.floor(viewportStartLineNumber + viewportLineCount / 2 - scrollbarSliderCenter * pixelRatio / minimapLineHeight);
let desiredEndLineNumber = desiredStartLineNumber + minimapLinesFitting - 1;
// Aligning the slider's centers can result (correctly) in tremor.
// i.e. scrolling down might result in the startLineNumber going up.
// Avoid this tremor by being consistent w.r.t. the previous computed result
if (lastRenderData) {
const lastLayoutDecision = lastRenderData.renderedLayout;
if (lastLayoutDecision.viewportStartLineNumber <= viewportStartLineNumber) {
// going down => make sure we don't go above our previous decision
if (desiredStartLineNumber < lastLayoutDecision.startLineNumber) {
desiredStartLineNumber = lastLayoutDecision.startLineNumber;
desiredEndLineNumber = desiredStartLineNumber + minimapLinesFitting - 1;
}
}
if (lastLayoutDecision.viewportStartLineNumber >= viewportStartLineNumber) {
// going up => make sure we don't go below our previous decision
if (desiredEndLineNumber > lastLayoutDecision.endLineNumber) {
desiredEndLineNumber = lastLayoutDecision.endLineNumber;
desiredStartLineNumber = desiredEndLineNumber - minimapLinesFitting + 1;
}
}
}
// Aligning the slider's centers is a very good thing, but this would make
// the minimap never scroll all the way to the top or to the bottom of the file.
// We therefore check that the viewport lines are in the minimap viewport.
// (a) validate on start line number
if (desiredStartLineNumber < 1) {
// must start after 1
desiredStartLineNumber = 1;
desiredEndLineNumber = desiredStartLineNumber + minimapLinesFitting - 1;
}
if (desiredStartLineNumber > viewportStartLineNumber) {
// must contain the viewport's start line number
desiredStartLineNumber = viewportStartLineNumber;
desiredEndLineNumber = desiredStartLineNumber + minimapLinesFitting - 1;
}
// (b) validate on end line number
if (desiredEndLineNumber > lineCount) {
// must end before line count
desiredEndLineNumber = lineCount;
desiredStartLineNumber = desiredEndLineNumber - minimapLinesFitting + 1;
}
if (desiredEndLineNumber < viewportEndLineNumber) {
// must contain the viewport's end line number
desiredEndLineNumber = viewportEndLineNumber;
desiredStartLineNumber = desiredEndLineNumber - minimapLinesFitting + 1;
}
this.startLineNumber = desiredStartLineNumber;
this.endLineNumber = desiredEndLineNumber;
}
this.sliderTop = Math.floor((viewportStartLineNumber - this.startLineNumber) * minimapLineHeight / pixelRatio);
this.sliderHeight = Math.floor(viewportLineCount * minimapLineHeight / pixelRatio);
}
}
class RenderedLayout {
/**
* editor viewport start line number.
*/
public readonly viewportStartLineNumber: number;
/**
* editor viewport end line number.
*/
public readonly viewportEndLineNumber: number;
/**
* minimap rendered start line number.
*/
public readonly startLineNumber: number;
/**
* minimap rendered end line number.
*/
public readonly endLineNumber: number;
constructor(
viewportStartLineNumber: number,
viewportEndLineNumber: number,
startLineNumber: number,
endLineNumber: number
) {
this.viewportStartLineNumber = viewportStartLineNumber;
this.viewportEndLineNumber = viewportEndLineNumber;
this.startLineNumber = startLineNumber;
this.endLineNumber = endLineNumber;
}
}
class MinimapLine implements ILine {
public static INVALID = new MinimapLine(-1);
dy: number;
constructor(dy: number) {
this.dy = dy;
}
public onContentChanged(): void {
this.dy = -1;
}
public onTokensChanged(): void {
this.dy = -1;
}
}
class RenderData {
/**
* last rendered layout.
*/
public readonly renderedLayout: RenderedLayout;
private readonly _imageData: ImageData;
private readonly _renderedLines: RenderedLinesCollection<MinimapLine>;
constructor(
renderedLayout: RenderedLayout,
imageData: ImageData,
lines: MinimapLine[]
) {
this.renderedLayout = renderedLayout;
this._imageData = imageData;
this._renderedLines = new RenderedLinesCollection(
() => MinimapLine.INVALID
);
this._renderedLines._set(renderedLayout.startLineNumber, lines);
}
_get(): { imageData: ImageData; rendLineNumberStart: number; lines: MinimapLine[]; } {
let tmp = this._renderedLines._get();
return {
imageData: this._imageData,
rendLineNumberStart: tmp.rendLineNumberStart,
lines: tmp.lines
};
}
public onModelLinesDeleted(e: editorCommon.IViewLinesDeletedEvent): void {
this._renderedLines.onModelLinesDeleted(e.fromLineNumber, e.toLineNumber);
}
public onModelLineChanged(e: editorCommon.IViewLineChangedEvent): boolean {
return this._renderedLines.onModelLineChanged(e.lineNumber);
}
public onModelLinesInserted(e: editorCommon.IViewLinesInsertedEvent): void {
this._renderedLines.onModelLinesInserted(e.fromLineNumber, e.toLineNumber);
}
public onModelTokensChanged(e: editorCommon.IViewTokensChangedEvent): boolean {
return this._renderedLines.onModelTokensChanged(e.ranges);
}
}
/**
* Some sort of double buffering.
*
* Keeps two buffers around that will be rotated for painting.
* Always gives a buffer that is filled with the background color.
*/
class MinimapBuffers {
private readonly _backgroundFillData: Uint8ClampedArray;
private readonly _buffers: [ImageData, ImageData];
private _lastUsedBuffer: number;
constructor(ctx: CanvasRenderingContext2D, WIDTH: number, HEIGHT: number, background: ParsedColor) {
this._backgroundFillData = MinimapBuffers._createBackgroundFillData(WIDTH, HEIGHT, background);
this._buffers = [
ctx.createImageData(WIDTH, HEIGHT),
ctx.createImageData(WIDTH, HEIGHT)
];
this._lastUsedBuffer = 0;
}
public getBuffer(): ImageData {
// rotate buffers
this._lastUsedBuffer = 1 - this._lastUsedBuffer;
let result = this._buffers[this._lastUsedBuffer];
// fill with background color
result.data.set(this._backgroundFillData);
return result;
}
private static _createBackgroundFillData(WIDTH: number, HEIGHT: number, background: ParsedColor): Uint8ClampedArray {
const backgroundR = background.r;
const backgroundG = background.g;
const backgroundB = background.b;
let result = new Uint8ClampedArray(WIDTH * HEIGHT * 4);
let offset = 0;
for (let i = 0; i < HEIGHT; i++) {
for (let j = 0; j < WIDTH; j++) {
result[offset] = backgroundR;
result[offset + 1] = backgroundG;
result[offset + 2] = backgroundB;
result[offset + 3] = 255;
offset += 4;
}
}
return result;
}
}
export class Minimap extends ViewPart {
private readonly _viewLayout: IViewLayout;
private readonly _editorScrollbar: EditorScrollbar;
private readonly _domNode: FastDomNode<HTMLElement>;
private readonly _canvas: FastDomNode<HTMLCanvasElement>;
private readonly _slider: FastDomNode<HTMLElement>;
private readonly _tokensColorTracker: MinimapTokensColorTracker;
private readonly _tokensColorTrackerListener: IDisposable;
private readonly _mouseDownListener: IDisposable;
private readonly _minimapCharRenderer: MinimapCharRenderer;
private _options: MinimapOptions;
private _lastRenderData: RenderData;
private _buffers: MinimapBuffers;
constructor(context: ViewContext, viewLayout: IViewLayout, editorScrollbar: EditorScrollbar) {
super(context);
this._viewLayout = viewLayout;
this._editorScrollbar = editorScrollbar;
this._options = new MinimapOptions(this._context.configuration);
this._lastRenderData = null;
this._buffers = null;
this._domNode = createFastDomNode(document.createElement('div'));
this._domNode.setPosition('absolute');
this._domNode.setRight(this._context.configuration.editor.layoutInfo.verticalScrollbarWidth);
this._canvas = createFastDomNode(document.createElement('canvas'));
this._canvas.setPosition('absolute');
this._canvas.setLeft(0);
this._domNode.domNode.appendChild(this._canvas.domNode);
this._slider = createFastDomNode(document.createElement('div'));
this._slider.setPosition('absolute');
this._slider.setClassName('minimap-slider');
this._domNode.domNode.appendChild(this._slider.domNode);
this._tokensColorTracker = MinimapTokensColorTracker.getInstance();
this._tokensColorTrackerListener = this._tokensColorTracker.onDidChange(() => this._buffers = null);
this._minimapCharRenderer = getOrCreateMinimapCharRenderer();
this._applyLayout();
this._mouseDownListener = dom.addStandardDisposableListener(this._canvas.domNode, 'mousedown', (e) => {
e.preventDefault();
const renderMinimap = this._options.renderMinimap;
if (renderMinimap === RenderMinimap.None) {
return;
}
if (!this._lastRenderData) {
return;
}
const minimapLineHeight = (renderMinimap === RenderMinimap.Large ? Constants.x2_CHAR_HEIGHT : Constants.x1_CHAR_HEIGHT);
const internalOffsetY = this._options.pixelRatio * e.browserEvent.offsetY;
const lineIndex = Math.floor(internalOffsetY / minimapLineHeight);
let lineNumber = lineIndex + this._lastRenderData.renderedLayout.startLineNumber;
lineNumber = Math.min(lineNumber, this._context.model.getLineCount());
let revealPositionEvent: editorCommon.IViewRevealRangeEvent = {
range: new Range(lineNumber, 1, lineNumber, 1),
verticalType: editorCommon.VerticalRevealType.Center,
revealHorizontal: false,
revealCursor: false
};
this._context.privateViewEventBus.emit(editorCommon.ViewEventNames.RevealRangeEvent, revealPositionEvent);
});
}
public dispose(): void {
this._tokensColorTrackerListener.dispose();
this._mouseDownListener.dispose();
super.dispose();
}
public getDomNode(): HTMLElement {
return this._domNode.domNode;
}
private _applyLayout(): void {
this._domNode.setWidth(this._options.minimapWidth);
this._domNode.setHeight(this._options.minimapHeight);
this._canvas.setWidth(this._options.canvasOuterWidth);
this._canvas.setHeight(this._options.canvasOuterHeight);
this._canvas.domNode.width = this._options.canvasInnerWidth;
this._canvas.domNode.height = this._options.canvasInnerHeight;
this._slider.setWidth(this._options.minimapWidth);
this._buffers = null;
}
private _getBuffer(): ImageData {
if (!this._buffers) {
this._buffers = new MinimapBuffers(
this._canvas.domNode.getContext('2d'),
this._options.canvasInnerWidth,
this._options.canvasInnerHeight,
this._tokensColorTracker.getColor(ColorId.DefaultBackground)
);
}
return this._buffers.getBuffer();
}
// ---- begin view event handlers
private _onOptionsMaybeChanged(): boolean {
let opts = new MinimapOptions(this._context.configuration);
if (this._options.equals(opts)) {
return false;
}
this._options = opts;
this._lastRenderData = null;
this._applyLayout();
return true;
}
public onLineMappingChanged(): boolean {
this._lastRenderData = null;
return true;
}
public onModelFlushed(): boolean {
this._lastRenderData = null;
return true;
}
public onModelLinesDeleted(e: editorCommon.IViewLinesDeletedEvent): boolean {
if (this._lastRenderData) {
this._lastRenderData.onModelLinesDeleted(e);
}
return true;
}
public onModelLineChanged(e: editorCommon.IViewLineChangedEvent): boolean {
if (this._lastRenderData) {
return this._lastRenderData.onModelLineChanged(e);
}
return false;
}
public onModelLinesInserted(e: editorCommon.IViewLinesInsertedEvent): boolean {
if (this._lastRenderData) {
this._lastRenderData.onModelLinesInserted(e);
}
return true;
}
public onModelTokensChanged(e: editorCommon.IViewTokensChangedEvent): boolean {
if (this._lastRenderData) {
return this._lastRenderData.onModelTokensChanged(e);
}
return false;
}
public onConfigurationChanged(e: editorCommon.IConfigurationChangedEvent): boolean {
return this._onOptionsMaybeChanged();
}
public onLayoutChanged(layoutInfo: editorCommon.EditorLayoutInfo): boolean {
return this._onOptionsMaybeChanged();
}
public onScrollChanged(e: editorCommon.IScrollEvent): boolean {
return e.scrollTopChanged || e.scrollHeightChanged;
}
public onZonesChanged(): boolean {
this._lastRenderData = null;
return true;
}
// --- end event handlers
public prepareRender(ctx: IRenderingContext): void {
// Nothing to read
}
public render(renderingCtx: IRestrictedRenderingContext): void {
const renderMinimap = this._options.renderMinimap;
if (renderMinimap === RenderMinimap.None) {
return;
}
const layout = new MinimapLayout(
this._lastRenderData,
this._options,
renderingCtx.visibleRange.startLineNumber,
renderingCtx.visibleRange.endLineNumber,
renderingCtx.viewportHeight,
this._context.model.getLineCount(),
this._editorScrollbar.getVerticalSliderVerticalCenter()
);
this._slider.setTop(layout.sliderTop);
this._slider.setHeight(layout.sliderHeight);
const startLineNumber = layout.startLineNumber;
const endLineNumber = layout.endLineNumber;
const minimapLineHeight = (renderMinimap === RenderMinimap.Large ? Constants.x2_CHAR_HEIGHT : Constants.x1_CHAR_HEIGHT);
const imageData = this._getBuffer();
// Render untouched lines by using last rendered data.
let needed = Minimap._renderUntouchedLines(
imageData,
startLineNumber,
endLineNumber,
minimapLineHeight,
this._lastRenderData
);
// Fetch rendering info from view model for rest of lines that need rendering.
const lineInfo = this._context.model.getMinimapLinesRenderingData(startLineNumber, endLineNumber, needed);
const tabSize = lineInfo.tabSize;
const background = this._tokensColorTracker.getColor(ColorId.DefaultBackground);
// Render the rest of lines
let dy = 0;
let renderedLines: MinimapLine[] = [];
for (let lineIndex = 0, lineCount = endLineNumber - startLineNumber + 1; lineIndex < lineCount; lineIndex++) {
if (needed[lineIndex]) {
Minimap._renderLine(
imageData,
background,
renderMinimap,
this._tokensColorTracker,
this._minimapCharRenderer,
dy,
tabSize,
lineInfo.data[lineIndex]
);
}
renderedLines[lineIndex] = new MinimapLine(dy);
dy += minimapLineHeight;
}
// Save rendered data for reuse on next frame if possible
this._lastRenderData = new RenderData(
new RenderedLayout(
renderingCtx.visibleRange.startLineNumber,
renderingCtx.visibleRange.endLineNumber,
startLineNumber,
endLineNumber
),
imageData,
renderedLines
);
// Finally, paint to the canvas
const ctx = this._canvas.domNode.getContext('2d');
ctx.putImageData(imageData, 0, 0);
}
private static _renderUntouchedLines(
target: ImageData,
startLineNumber: number,
endLineNumber: number,
minimapLineHeight: number,
lastRenderData: RenderData,
): boolean[] {
let needed: boolean[] = [];
if (!lastRenderData) {
for (let i = 0, len = endLineNumber - startLineNumber + 1; i < len; i++) {
needed[i] = true;
}
return needed;
}
const _lastData = lastRenderData._get();
const lastTargetData = _lastData.imageData.data;
const lastStartLineNumber = _lastData.rendLineNumberStart;
const lastLines = _lastData.lines;
const lastLinesLength = lastLines.length;
const WIDTH = target.width;
const targetData = target.data;
let copySourceStart = -1;
let copySourceEnd = -1;
let copyDestStart = -1;
let copyDestEnd = -1;
let dest_dy = 0;
for (let lineNumber = startLineNumber; lineNumber <= endLineNumber; lineNumber++) {
const lineIndex = lineNumber - startLineNumber;
const lastLineIndex = lineNumber - lastStartLineNumber;
const source_dy = (lastLineIndex >= 0 && lastLineIndex < lastLinesLength ? lastLines[lastLineIndex].dy : -1);
if (source_dy === -1) {
needed[lineIndex] = true;
dest_dy += minimapLineHeight;
continue;
}
let sourceStart = source_dy * WIDTH * 4;
let sourceEnd = (source_dy + minimapLineHeight) * WIDTH * 4;
let destStart = dest_dy * WIDTH * 4;
let destEnd = (dest_dy + minimapLineHeight) * WIDTH * 4;
if (copySourceEnd === sourceStart && copyDestEnd === destStart) {
// contiguous zone => extend copy request
copySourceEnd = sourceEnd;
copyDestEnd = destEnd;
} else {
if (copySourceStart !== -1) {
// flush existing copy request
targetData.set(lastTargetData.subarray(copySourceStart, copySourceEnd), copyDestStart);
}
copySourceStart = sourceStart;
copySourceEnd = sourceEnd;
copyDestStart = destStart;
copyDestEnd = destEnd;
}
needed[lineIndex] = false;
dest_dy += minimapLineHeight;
}
if (copySourceStart !== -1) {
// flush existing copy request
targetData.set(lastTargetData.subarray(copySourceStart, copySourceEnd), copyDestStart);
}
return needed;
}
private static _renderLine(
target: ImageData,
backgroundColor: ParsedColor,
renderMinimap: RenderMinimap,
colorTracker: MinimapTokensColorTracker,
minimapCharRenderer: MinimapCharRenderer,
dy: number,
tabSize: number,
lineData: ViewLineData
): void {
const content = lineData.content;
const tokens = lineData.tokens;
const charWidth = (renderMinimap === RenderMinimap.Large ? Constants.x2_CHAR_WIDTH : Constants.x1_CHAR_WIDTH);
const maxDx = target.width - charWidth;
let dx = 0;
let charIndex = 0;
let tabsCharDelta = 0;
for (let tokenIndex = 0, tokensLen = tokens.length; tokenIndex < tokensLen; tokenIndex++) {
const token = tokens[tokenIndex];
const tokenEndIndex = token.endIndex;
const tokenColorId = token.getForeground();
const tokenColor = colorTracker.getColor(tokenColorId);
for (; charIndex < tokenEndIndex; charIndex++) {
if (dx > maxDx) {
// hit edge of minimap
return;
}
const charCode = content.charCodeAt(charIndex);
if (charCode === CharCode.Tab) {
let insertSpacesCount = tabSize - (charIndex + tabsCharDelta) % tabSize;
tabsCharDelta += insertSpacesCount - 1;
// No need to render anything since tab is invisible
dx += insertSpacesCount * charWidth;
} else if (charCode === CharCode.Space) {
// No need to render anything since space is invisible
dx += charWidth;
} else {
if (renderMinimap === RenderMinimap.Large) {
minimapCharRenderer.x2RenderChar(target, dx, dy, charCode, tokenColor, backgroundColor);
} else {
minimapCharRenderer.x1RenderChar(target, dx, dy, charCode, tokenColor, backgroundColor);
}
dx += charWidth;
}
}
}
}
}
@@ -139,9 +139,6 @@ export class ViewOverlayWidgets extends ViewPart {
public prepareRender(ctx: IRenderingContext): void {
// Nothing to read
if (!this.shouldRender()) {
throw new Error('I did not ask to render!');
}
}
public render(ctx: IRestrictedRenderingContext): void {
@@ -11,6 +11,9 @@ import { OverviewRulerImpl } from 'vs/editor/browser/viewParts/overviewRuler/ove
import { ViewContext } from 'vs/editor/common/view/viewContext';
import { IRenderingContext, IRestrictedRenderingContext } from 'vs/editor/common/view/renderingContext';
import { Position } from 'vs/editor/common/core/position';
import { MinimapTokensColorTracker } from 'vs/editor/common/view/minimapCharRenderer';
import { ColorId } from 'vs/editor/common/modes';
import { IDisposable } from 'vs/base/common/lifecycle';
export class DecorationsOverviewRuler extends ViewPart {
@@ -20,6 +23,9 @@ export class DecorationsOverviewRuler extends ViewPart {
private static _CURSOR_COLOR = 'rgba(0, 0, 102, 0.8)';
private static _CURSOR_COLOR_DARK = 'rgba(152, 152, 152, 0.8)';
private readonly _tokensColorTracker: MinimapTokensColorTracker;
private readonly _tokensColorTrackerListener: IDisposable;
private _overviewRuler: OverviewRulerImpl;
private _shouldUpdateDecorations: boolean;
@@ -33,6 +39,7 @@ export class DecorationsOverviewRuler extends ViewPart {
constructor(context: ViewContext, scrollHeight: number, getVerticalOffsetForLine: (lineNumber: number) => number) {
super(context);
this._tokensColorTracker = MinimapTokensColorTracker.getInstance();
this._overviewRuler = new OverviewRulerImpl(
1,
'decorationsOverviewRuler',
@@ -47,6 +54,9 @@ export class DecorationsOverviewRuler extends ViewPart {
let theme = this._context.configuration.editor.viewInfo.theme;
this._overviewRuler.setUseDarkColor(!themes.isLightTheme(theme), false);
this._updateBackground(false);
this._tokensColorTrackerListener = this._tokensColorTracker.onDidChange(() => this._updateBackground(true));
this._shouldUpdateDecorations = true;
this._zonesFromDecorations = [];
@@ -60,6 +70,16 @@ export class DecorationsOverviewRuler extends ViewPart {
public dispose(): void {
super.dispose();
this._overviewRuler.dispose();
this._tokensColorTrackerListener.dispose();
}
private _updateBackground(render: boolean): void {
this._overviewRuler.setUseBackground(
(this._context.configuration.editor.viewInfo.minimap.enabled
? this._tokensColorTracker.getColor(ColorId.DefaultBackground)
: null),
true
);
}
// ---- begin view event handlers
@@ -104,6 +124,11 @@ export class DecorationsOverviewRuler extends ViewPart {
shouldRender = true;
}
if (e.viewInfo.minimap) {
this._updateBackground(false);
shouldRender = true;
}
return shouldRender;
}
@@ -179,9 +204,6 @@ export class DecorationsOverviewRuler extends ViewPart {
public prepareRender(ctx: IRenderingContext): void {
// Nothing to read
if (!this.shouldRender()) {
throw new Error('I did not ask to render!');
}
}
public render(ctx: IRestrictedRenderingContext): void {
@@ -9,6 +9,7 @@ import { OverviewRulerPosition, OverviewRulerLane, OverviewRulerZone, ColorZone
import { IDisposable } from 'vs/base/common/lifecycle';
import * as browser from 'vs/base/browser/browser';
import { OverviewZoneManager } from 'vs/editor/common/view/overviewZoneManager';
import { ParsedColor } from 'vs/editor/common/view/minimapCharRenderer';
export class OverviewRulerImpl {
@@ -17,6 +18,7 @@ export class OverviewRulerImpl {
private _lanesCount: number;
private _zoneManager: OverviewZoneManager;
private _canUseTranslate3d: boolean;
private _background: ParsedColor;
private _zoomListener: IDisposable;
@@ -31,6 +33,7 @@ export class OverviewRulerImpl {
this._lanesCount = 3;
this._canUseTranslate3d = canUseTranslate3d;
this._background = null;
this._zoneManager = new OverviewZoneManager(getVerticalOffsetForLine);
this._zoneManager.setMinimumHeight(minimumHeight);
@@ -97,6 +100,14 @@ export class OverviewRulerImpl {
}
}
public setUseBackground(background: ParsedColor, render: boolean): void {
this._background = background;
if (render) {
this.render(true);
}
}
public getDomNode(): HTMLCanvasElement {
return this._domNode;
}
@@ -154,7 +165,12 @@ export class OverviewRulerImpl {
let id2Color = this._zoneManager.getId2Color();
let ctx = this._domNode.getContext('2d');
ctx.clearRect(0, 0, width, height);
if (this._background === null) {
ctx.clearRect(0, 0, width, height);
} else {
ctx.fillStyle = this._background.toCSSHex();
ctx.fillRect(0, 0, width, height);
}
if (colorZones.length > 0) {
let remainingWidth = width - this._canvasLeftOffset;
@@ -51,9 +51,6 @@ export class Rulers extends ViewPart {
public prepareRender(ctx: IRenderingContext): void {
// Nothing to read
if (!this.shouldRender()) {
throw new Error('I did not ask to render!');
}
}
public render(ctx: IRestrictedRenderingContext): void {
@@ -69,9 +69,6 @@ export class ScrollDecorationViewPart extends ViewPart {
public prepareRender(ctx: IRenderingContext): void {
// Nothing to read
if (!this.shouldRender()) {
throw new Error('I did not ask to render!');
}
}
public render(ctx: IRestrictedRenderingContext): void {
@@ -362,9 +362,6 @@ export class SelectionsOverlay extends DynamicViewOverlay {
private _previousFrameVisibleRangesWithStyle: LineVisibleRangesWithStyle[][] = [];
public prepareRender(ctx: IRenderingContext): void {
if (!this.shouldRender()) {
throw new Error('I did not ask to render!');
}
let output: string[] = [];
let visibleStartLineNumber = ctx.visibleRange.startLineNumber;
@@ -38,7 +38,7 @@ export class ViewCursorRenderData {
export class ViewCursor {
private readonly _context: ViewContext;
private readonly _isSecondary: boolean;
private readonly _domNode: FastDomNode;
private readonly _domNode: FastDomNode<HTMLElement>;
private _cursorStyle: TextEditorCursorStyle;
private _lineHeight: number;
@@ -27,7 +27,7 @@ export class ViewCursors extends ViewPart {
private _isVisible: boolean;
private _domNode: FastDomNode;
private _domNode: FastDomNode<HTMLElement>;
private _startCursorBlinkAnimation: TimeoutTimer;
private _blinkingEnabled: boolean;
@@ -297,10 +297,6 @@ export class ViewCursors extends ViewPart {
// ---- IViewPart implementation
public prepareRender(ctx: IRenderingContext): void {
if (!this.shouldRender()) {
throw new Error('I did not ask to render!');
}
this._primaryCursor.prepareRender(ctx);
for (let i = 0, len = this._secondaryCursors.length; i < len; i++) {
this._secondaryCursors[i].prepareRender(ctx);
@@ -300,9 +300,6 @@ export class ViewZones extends ViewPart {
public prepareRender(ctx: IRenderingContext): void {
// Nothing to read
if (!this.shouldRender()) {
throw new Error('I did not ask to render!');
}
}
public render(ctx: IRestrictedRenderingContext): void {
@@ -30,6 +30,7 @@ import { Selection } from 'vs/editor/common/core/selection';
import { InlineDecoration } from 'vs/editor/common/viewModel/viewModel';
import { IAddedAction } from 'vs/editor/common/commonCodeEditor';
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
import { ColorId, MetadataConsts, FontStyle } from 'vs/editor/common/modes';
interface IEditorDiffDecorations {
decorations: editorCommon.IModelDeltaDecoration[];
@@ -1900,12 +1901,18 @@ class InlineViewZonesComputer extends ViewZonesComputer {
let actualDecorations = Decoration.filter(decorations, lineNumber, 1, lineContent.length + 1);
const defaultMetadata = (
(FontStyle.None << MetadataConsts.FONT_STYLE_OFFSET)
| (ColorId.DefaultForeground << MetadataConsts.FOREGROUND_OFFSET)
| (ColorId.DefaultBackground << MetadataConsts.BACKGROUND_OFFSET)
) >>> 0;
let r = renderViewLine(new RenderLineInput(
(config.fontInfo.isMonospace && !config.viewInfo.disableMonospaceOptimizations),
lineContent,
originalModel.mightContainRTL(),
0,
[new ViewLineToken(lineContent.length, '')],
[new ViewLineToken(lineContent.length, defaultMetadata)],
actualDecorations,
tabSize,
config.fontInfo.spaceWidth,
+4
View File
@@ -1006,6 +1006,10 @@ export abstract class CommonCodeEditor extends EventEmitter implements editorCom
protected abstract _registerDecorationType(key: string, options: editorCommon.IDecorationRenderOptions, parentTypeKey?: string): void;
protected abstract _removeDecorationType(key: string): void;
protected abstract _resolveDecorationOptions(typeKey: string, writable: boolean): editorCommon.IModelDecorationOptions;
public getTelemetryData(): { [key: string]: any; } {
return null;
}
}
class EditorContextKeysManager extends Disposable {
@@ -103,13 +103,15 @@ class InternalEditorOptionsHelper {
}
public static createInternalEditorOptions(
outerWidth: number, outerHeight: number,
outerWidth: number,
outerHeight: number,
opts: editorCommon.IEditorOptions,
fontInfo: FontInfo,
editorClassName: string,
isDominatedByLongLines: boolean,
maxLineNumber: number,
canUseTranslate3d: boolean
canUseTranslate3d: boolean,
pixelRatio: number
): editorCommon.InternalEditorOptions {
let wrappingColumn = toInteger(opts.wrappingColumn, -1);
@@ -126,6 +128,7 @@ class InternalEditorOptionsHelper {
let mouseWheelScrollSensitivity = toFloat(opts.mouseWheelScrollSensitivity, 1);
let scrollbar = this._sanitizeScrollbarOpts(opts.scrollbar, mouseWheelScrollSensitivity);
let minimap = this._sanitizeMinimapOpts(opts.minimap);
let glyphMargin = toBoolean(opts.glyphMargin);
let lineNumbers = opts.lineNumbers;
@@ -177,13 +180,16 @@ class InternalEditorOptionsHelper {
lineHeight: fontInfo.lineHeight,
showLineNumbers: renderLineNumbers,
lineNumbersMinChars: lineNumbersMinChars,
lineDecorationsWidth: lineDecorationsWidth,
maxDigitWidth: fontInfo.maxDigitWidth,
maxLineNumber: maxLineNumber,
lineDecorationsWidth: lineDecorationsWidth,
typicalHalfwidthCharacterWidth: fontInfo.typicalHalfwidthCharacterWidth,
maxDigitWidth: fontInfo.maxDigitWidth,
verticalScrollbarWidth: scrollbar.verticalScrollbarSize,
horizontalScrollbarHeight: scrollbar.horizontalScrollbarSize,
scrollbarArrowSize: scrollbar.arrowSize,
verticalScrollbarHasArrows: scrollbar.verticalHasArrows
verticalScrollbarHasArrows: scrollbar.verticalHasArrows,
minimap: minimap.enabled,
pixelRatio: pixelRatio
});
if (isDominatedByLongLines && wrappingColumn > 0) {
@@ -196,13 +202,13 @@ class InternalEditorOptionsHelper {
// If viewport width wrapping is enabled
bareWrappingInfo = {
isViewportWrapping: true,
wrappingColumn: Math.max(1, Math.floor((layoutInfo.contentWidth - layoutInfo.verticalScrollbarWidth) / fontInfo.typicalHalfwidthCharacterWidth))
wrappingColumn: Math.max(1, layoutInfo.viewportColumn)
};
} else if (wrappingColumn > 0 && wordWrap === true) {
// Enable smart viewport wrapping
bareWrappingInfo = {
isViewportWrapping: true,
wrappingColumn: Math.min(wrappingColumn, Math.floor((layoutInfo.contentWidth - layoutInfo.verticalScrollbarWidth) / fontInfo.typicalHalfwidthCharacterWidth))
wrappingColumn: Math.min(wrappingColumn, layoutInfo.viewportColumn)
};
} else if (wrappingColumn > 0) {
// Wrapping is enabled
@@ -276,6 +282,7 @@ class InternalEditorOptionsHelper {
renderIndentGuides: toBoolean(opts.renderIndentGuides),
renderLineHighlight: renderLineHighlight,
scrollbar: scrollbar,
minimap: minimap,
fixedOverflowWidgets: toBoolean(opts.fixedOverflowWidgets)
});
@@ -353,6 +360,12 @@ class InternalEditorOptionsHelper {
mouseWheelScrollSensitivity: mouseWheelScrollSensitivity
});
}
private static _sanitizeMinimapOpts(raw: editorCommon.IEditorMinimapOptions): editorCommon.InternalEditorMinimapOptions {
return new editorCommon.InternalEditorMinimapOptions({
enabled: toBooleanWithDefault(raw.enabled, false)
});
}
}
function toBoolean(value: any): boolean {
@@ -516,7 +529,8 @@ export abstract class CommonEditorConfiguration extends Disposable implements ed
editorClassName,
this._isDominatedByLongLines,
this._maxLineNumber,
canUseTranslate3d
canUseTranslate3d,
this._getPixelRatio()
);
}
@@ -546,6 +560,8 @@ export abstract class CommonEditorConfiguration extends Disposable implements ed
protected abstract _getCanUseTranslate3d(): boolean;
protected abstract _getPixelRatio(): number;
protected abstract readConfiguration(styling: BareFontInfo): FontInfo;
}
@@ -625,6 +641,11 @@ const editorConfiguration: IConfigurationNode = {
'default': DefaultConfig.editor.scrollBeyondLastLine,
'description': nls.localize('scrollBeyondLastLine', "Controls if the editor will scroll beyond the last line")
},
'editor.minimap.enabled': {
'type': 'boolean',
'default': DefaultConfig.editor.minimap.enabled,
'description': nls.localize('minimap.enabled', "Controls if the minimap is shown")
},
'editor.wrappingColumn': {
'type': 'integer',
'default': DefaultConfig.editor.wrappingColumn,
@@ -56,6 +56,9 @@ class ConfigClass implements IConfiguration {
verticalHasArrows: false,
horizontalHasArrows: false
},
minimap: {
enabled: false
},
fixedOverflowWidgets: false,
overviewRulerLanes: 2,
cursorBlinking: 'blink',
+1 -1
View File
@@ -1063,7 +1063,7 @@ export class Cursor extends EventEmitter {
validatedViewPosition = primary.convertModelPositionToViewPosition(validatedPosition.lineNumber, validatedPosition.column);
}
let result = ColumnSelection.columnSelect(primary.config, primary.viewModel, primary.viewState.selection.getStartPosition(), validatedViewPosition.lineNumber, ctx.eventData.mouseColumn - 1);
let result = ColumnSelection.columnSelect(primary.config, primary.viewModel, primary.viewState.selection, validatedViewPosition.lineNumber, ctx.eventData.mouseColumn - 1);
let selections = result.viewSelections.map(viewSel => primary.convertViewSelectionToModelSelection(viewSel));
ctx.shouldRevealTarget = (result.reversed ? RevealTarget.TopMost : RevealTarget.BottomMost);
@@ -66,8 +66,9 @@ export class ColumnSelection {
};
}
public static columnSelect(config: CursorConfiguration, model: ICursorSimpleModel, fromViewPosition: Position, toViewLineNumber: number, toViewVisualColumn: number): IColumnSelectResult {
let fromViewVisibleColumn = CursorColumns.visibleColumnFromColumn2(config, model, fromViewPosition);
public static columnSelect(config: CursorConfiguration, model: ICursorSimpleModel, fromViewSelection: Selection, toViewLineNumber: number, toViewVisualColumn: number): IColumnSelectResult {
const fromViewPosition = new Position(fromViewSelection.selectionStartLineNumber, fromViewSelection.selectionStartColumn);
const fromViewVisibleColumn = CursorColumns.visibleColumnFromColumn2(config, model, fromViewPosition);
return ColumnSelection._columnSelect(config, model, fromViewPosition.lineNumber, fromViewVisibleColumn, toViewLineNumber, toViewVisualColumn);
}
@@ -76,7 +77,7 @@ export class ColumnSelection {
toViewVisualColumn--;
}
return this.columnSelect(config, model, cursor.selection.getStartPosition(), toViewLineNumber, toViewVisualColumn);
return this.columnSelect(config, model, cursor.selection, toViewLineNumber, toViewVisualColumn);
}
public static columnSelectRight(config: CursorConfiguration, model: ICursorSimpleModel, cursor: SingleCursorState, toViewLineNumber: number, toViewVisualColumn: number): IColumnSelectResult {
@@ -93,7 +94,7 @@ export class ColumnSelection {
toViewVisualColumn++;
}
return this.columnSelect(config, model, cursor.selection.getStartPosition(), toViewLineNumber, toViewVisualColumn);
return this.columnSelect(config, model, cursor.selection, toViewLineNumber, toViewVisualColumn);
}
public static columnSelectUp(config: CursorConfiguration, model: ICursorSimpleModel, cursor: SingleCursorState, isPaged: boolean, toViewLineNumber: number, toViewVisualColumn: number): IColumnSelectResult {
@@ -104,7 +105,7 @@ export class ColumnSelection {
toViewLineNumber = 1;
}
return this.columnSelect(config, model, cursor.selection.getStartPosition(), toViewLineNumber, toViewVisualColumn);
return this.columnSelect(config, model, cursor.selection, toViewLineNumber, toViewVisualColumn);
}
public static columnSelectDown(config: CursorConfiguration, model: ICursorSimpleModel, cursor: SingleCursorState, isPaged: boolean, toViewLineNumber: number, toViewVisualColumn: number): IColumnSelectResult {
@@ -115,6 +116,6 @@ export class ColumnSelection {
toViewLineNumber = model.getLineCount();
}
return this.columnSelect(config, model, cursor.selection.getStartPosition(), toViewLineNumber, toViewVisualColumn);
return this.columnSelect(config, model, cursor.selection, toViewLineNumber, toViewVisualColumn);
}
}
@@ -11,8 +11,10 @@ import { KeyCode } from 'vs/base/common/keyCodes';
import { Disposable } from 'vs/base/common/lifecycle';
import { IClipboardEvent, ICompositionEvent, IKeyboardEventWrapper, ISimpleModel, ITextAreaWrapper, ITypeData, TextAreaState, TextAreaStrategy, createTextAreaState } from 'vs/editor/common/controller/textAreaState';
import { Range } from 'vs/editor/common/core/range';
import { Position } from 'vs/editor/common/core/position';
import { EndOfLinePreference } from 'vs/editor/common/editorCommon';
export const CopyOptions = {
forceCopyWithSyntaxHighlighting: false
};
const enum ReadFromTextArea {
Type,
@@ -324,9 +326,13 @@ export class TextAreaHandler extends Disposable {
// ------------- Clipboard operations
private _ensureClipboardGetsEditorSelection(e: IClipboardEvent): void {
let whatToCopy = this._getPlainTextToCopy();
let whatToCopy = this.model.getPlainTextToCopy(this.selections, this.Browser.enableEmptySelectionClipboard);
if (e.canUseTextData()) {
e.setTextData(whatToCopy);
let whatHTMLToCopy = null;
if (!this.Browser.isEdgeOrIE && (whatToCopy.length < 65536 || CopyOptions.forceCopyWithSyntaxHighlighting)) {
whatHTMLToCopy = this.model.getHTMLToCopy(this.selections, this.Browser.enableEmptySelectionClipboard);
}
e.setTextData(whatToCopy, whatHTMLToCopy);
} else {
this.setTextAreaState('copy or cut', this.textAreaState.fromText(whatToCopy), false);
}
@@ -344,31 +350,4 @@ export class TextAreaHandler extends Disposable {
this.lastCopiedValueIsFromEmptySelection = (selections.length === 1 && selections[0].isEmpty());
}
}
private _getPlainTextToCopy(): string {
let newLineCharacter = this.model.getEOL();
let selections = this.selections;
if (selections.length === 1) {
let range: Range = selections[0];
if (range.isEmpty()) {
if (this.Browser.enableEmptySelectionClipboard) {
let modelLineNumber = this.model.coordinatesConverter.convertViewPositionToModelPosition(new Position(range.startLineNumber, 1)).lineNumber;
return this.model.getModelLineContent(modelLineNumber) + newLineCharacter;
} else {
return '';
}
}
return this.model.getValueInRange(range, EndOfLinePreference.TextDefined);
} else {
selections = selections.slice(0).sort(Range.compareRangesUsingStarts);
let result: string[] = [];
for (let i = 0; i < selections.length; i++) {
result.push(this.model.getValueInRange(selections[i], EndOfLinePreference.TextDefined));
}
return result.join(newLineCharacter);
}
}
}
}
@@ -13,7 +13,7 @@ import { Constants } from 'vs/editor/common/core/uint';
export interface IClipboardEvent {
canUseTextData(): boolean;
setTextData(text: string): void;
setTextData(text: string, richText: string): void;
getTextData(): string;
}
@@ -56,6 +56,8 @@ export interface ISimpleModel {
getValueInRange(range: Range, eol: EndOfLinePreference): string;
getModelLineContent(lineNumber: number): string;
getLineCount(): number;
getPlainTextToCopy(ranges: Range[], enableEmptySelectionClipboard: boolean): string;
getHTMLToCopy(ranges: Range[], enableEmptySelectionClipboard: boolean): string;
coordinatesConverter: {
convertViewPositionToModelPosition(viewPosition: Position): Position;
+7 -36
View File
@@ -5,30 +5,12 @@
'use strict';
import { TokenMetadata } from 'vs/editor/common/model/tokensBinaryEncoding';
import { ViewLineToken } from 'vs/editor/common/core/viewLineToken';
import { ViewLineTokenFactory, ViewLineToken } from 'vs/editor/common/core/viewLineToken';
import { ColorId, FontStyle, StandardTokenType, LanguageId } from 'vs/editor/common/modes';
const STANDARD_TOKEN_TYPE_REGEXP = /\b(comment|string|regex)\b/;
export function toStandardTokenType(tokenType: string): StandardTokenType {
let m = tokenType.match(STANDARD_TOKEN_TYPE_REGEXP);
if (!m) {
return StandardTokenType.Other;
}
switch (m[1]) {
case 'comment':
return StandardTokenType.Comment;
case 'string':
return StandardTokenType.String;
case 'regex':
return StandardTokenType.RegEx;
}
throw new Error('Unexpected match for standard token type!');
}
export class LineToken {
_lineTokenBrand: void;
private readonly _colorMap: string[];
private readonly _source: LineTokens;
private readonly _tokenIndex: number;
private readonly _metadata: number;
@@ -55,20 +37,11 @@ export class LineToken {
return TokenMetadata.getForeground(this._metadata);
}
public get foreground(): string {
return this._colorMap[this.foregroundId];
}
public get backgroundId(): ColorId {
return TokenMetadata.getBackground(this._metadata);
}
public get background(): string {
return this._colorMap[this.backgroundId];
}
constructor(colorMap: string[], source: LineTokens, tokenIndex: number, tokenCount: number, startOffset: number, endOffset: number, metadata: number) {
this._colorMap = colorMap;
constructor(source: LineTokens, tokenIndex: number, tokenCount: number, startOffset: number, endOffset: number, metadata: number) {
this._source = source;
this._tokenIndex = tokenIndex;
this._metadata = metadata;
@@ -100,14 +73,12 @@ export class LineToken {
export class LineTokens {
_lineTokensBrand: void;
private readonly _colorMap: string[];
private readonly _tokens: Uint32Array;
private readonly _tokensCount: number;
private readonly _text: string;
private readonly _textLength: number;
constructor(colorMap: string[], tokens: Uint32Array, text: string) {
this._colorMap = colorMap;
constructor(tokens: Uint32Array, text: string) {
this._tokens = tokens;
this._tokensCount = (this._tokens.length >>> 1);
this._text = text;
@@ -159,7 +130,7 @@ export class LineTokens {
* @return The index of the token containing the offset.
*/
public findTokenIndexAtOffset(offset: number): number {
return TokenMetadata.findIndexInSegmentsArray(this._tokens, offset);
return ViewLineTokenFactory.findIndexInSegmentsArray(this._tokens, offset);
}
public findTokenAtOffset(offset: number): LineToken {
@@ -176,7 +147,7 @@ export class LineTokens {
endOffset = this._textLength;
}
let metadata = this._tokens[(tokenIndex << 1) + 1];
return new LineToken(this._colorMap, this, tokenIndex, this._tokensCount, startOffset, endOffset, metadata);
return new LineToken(this, tokenIndex, this._tokensCount, startOffset, endOffset, metadata);
}
public firstToken(): LineToken {
@@ -194,10 +165,10 @@ export class LineTokens {
}
public inflate(): ViewLineToken[] {
return TokenMetadata.inflateArr(this._tokens, this._textLength);
return ViewLineTokenFactory.inflateArr(this._tokens, this._textLength);
}
public sliceAndInflate(startOffset: number, endOffset: number, deltaOffset: number): ViewLineToken[] {
return TokenMetadata.sliceAndInflate(this._tokens, startOffset, endOffset, deltaOffset, this._textLength);
return ViewLineTokenFactory.sliceAndInflate(this._tokens, startOffset, endOffset, deltaOffset, this._textLength);
}
}
+74 -4
View File
@@ -4,6 +4,9 @@
*--------------------------------------------------------------------------------------------*/
'use strict';
import { ColorId } from 'vs/editor/common/modes';
import { TokenMetadata } from 'vs/editor/common/model/tokensBinaryEncoding';
/**
* A token on a line.
*/
@@ -14,17 +17,25 @@ export class ViewLineToken {
* last char index of this token (not inclusive).
*/
public readonly endIndex: number;
public readonly type: string;
private readonly _metadata: number;
constructor(endIndex: number, type: string) {
constructor(endIndex: number, metadata: number) {
this.endIndex = endIndex;
this.type = type;
this._metadata = metadata;
}
public getForeground(): ColorId {
return TokenMetadata.getForeground(this._metadata);
}
public getType(): string {
return TokenMetadata.getClassNameFromMetadata(this._metadata);
}
private static _equals(a: ViewLineToken, b: ViewLineToken): boolean {
return (
a.endIndex === b.endIndex
&& a.type === b.type
&& a._metadata === b._metadata
);
}
@@ -42,3 +53,62 @@ export class ViewLineToken {
return true;
}
}
export class ViewLineTokenFactory {
public static inflateArr(tokens: Uint32Array, lineLength: number): ViewLineToken[] {
let result: ViewLineToken[] = [];
for (let i = 0, len = (tokens.length >>> 1); i < len; i++) {
let endOffset = (i + 1 < len ? tokens[((i + 1) << 1)] : lineLength);
let metadata = tokens[(i << 1) + 1];
result[i] = new ViewLineToken(endOffset, metadata);
}
return result;
}
public static sliceAndInflate(tokens: Uint32Array, startOffset: number, endOffset: number, deltaOffset: number, lineLength: number): ViewLineToken[] {
const tokenIndex = this.findIndexInSegmentsArray(tokens, startOffset);
const maxEndOffset = (endOffset - startOffset + deltaOffset);
let result: ViewLineToken[] = [], resultLen = 0;
for (let i = tokenIndex, len = (tokens.length >>> 1); i < len; i++) {
let tokenStartOffset = tokens[(i << 1)];
if (tokenStartOffset >= endOffset) {
break;
}
let tokenEndOffset = (i + 1 < len ? tokens[((i + 1) << 1)] : lineLength);
let newEndOffset = Math.min(maxEndOffset, tokenEndOffset - startOffset + deltaOffset);
let metadata = tokens[(i << 1) + 1];
result[resultLen++] = new ViewLineToken(newEndOffset, metadata);
}
return result;
}
public static findIndexInSegmentsArray(tokens: Uint32Array, desiredIndex: number): number {
let low = 0;
let high = (tokens.length >>> 1) - 1;
while (low < high) {
let mid = low + Math.ceil((high - low) / 2);
let value = tokens[(mid << 1)];
if (value > desiredIndex) {
high = mid - 1;
} else {
low = mid;
}
}
return low;
}
}
+88
View File
@@ -153,6 +153,18 @@ export interface IEditorScrollbarOptions {
horizontalSliderSize?: number;
}
/**
* Configuration options for editor minimap
*/
export interface IEditorMinimapOptions {
/**
* Enable the rendering of the minimap.
* Defaults to false.
*/
enabled?: boolean;
}
/**
* Describes how to indent wrapped lines.
*/
@@ -257,6 +269,10 @@ export interface IEditorOptions {
* Control the behavior and rendering of the scrollbars.
*/
scrollbar?: IEditorScrollbarOptions;
/**
* Control the behavior and rendering of the minimap.
*/
minimap?: IEditorMinimapOptions;
/**
* Display overflow widgets as `fixed`.
* Defaults to `false`.
@@ -612,6 +628,37 @@ export class InternalEditorScrollbarOptions {
}
}
export class InternalEditorMinimapOptions {
readonly _internalEditorMinimapOptionsBrand: void;
readonly enabled: boolean;
/**
* @internal
*/
constructor(source: {
enabled: boolean;
}) {
this.enabled = Boolean(source.enabled);
}
/**
* @internal
*/
public equals(other: InternalEditorMinimapOptions): boolean {
return (
this.enabled === other.enabled
);
}
/**
* @internal
*/
public clone(): InternalEditorMinimapOptions {
return new InternalEditorMinimapOptions(this);
}
}
export class EditorWrappingInfo {
readonly _editorWrappingInfoBrand: void;
@@ -692,6 +739,7 @@ export class InternalEditorViewOptions {
readonly renderIndentGuides: boolean;
readonly renderLineHighlight: 'none' | 'gutter' | 'line' | 'all';
readonly scrollbar: InternalEditorScrollbarOptions;
readonly minimap: InternalEditorMinimapOptions;
readonly fixedOverflowWidgets: boolean;
/**
@@ -724,6 +772,7 @@ export class InternalEditorViewOptions {
renderIndentGuides: boolean;
renderLineHighlight: 'none' | 'gutter' | 'line' | 'all';
scrollbar: InternalEditorScrollbarOptions;
minimap: InternalEditorMinimapOptions;
fixedOverflowWidgets: boolean;
}) {
this.theme = String(source.theme);
@@ -752,6 +801,7 @@ export class InternalEditorViewOptions {
this.renderIndentGuides = Boolean(source.renderIndentGuides);
this.renderLineHighlight = source.renderLineHighlight;
this.scrollbar = source.scrollbar.clone();
this.minimap = source.minimap.clone();
this.fixedOverflowWidgets = Boolean(source.fixedOverflowWidgets);
}
@@ -814,6 +864,7 @@ export class InternalEditorViewOptions {
&& this.renderIndentGuides === other.renderIndentGuides
&& this.renderLineHighlight === other.renderLineHighlight
&& this.scrollbar.equals(other.scrollbar)
&& this.minimap.equals(other.minimap)
&& this.fixedOverflowWidgets === other.fixedOverflowWidgets
);
}
@@ -849,6 +900,7 @@ export class InternalEditorViewOptions {
renderIndentGuides: this.renderIndentGuides !== newOpts.renderIndentGuides,
renderLineHighlight: this.renderLineHighlight !== newOpts.renderLineHighlight,
scrollbar: (!this.scrollbar.equals(newOpts.scrollbar)),
minimap: (!this.minimap.equals(newOpts.minimap)),
fixedOverflowWidgets: this.fixedOverflowWidgets !== newOpts.fixedOverflowWidgets
};
}
@@ -888,6 +940,7 @@ export interface IViewConfigurationChangedEvent {
readonly renderIndentGuides: boolean;
readonly renderLineHighlight: boolean;
readonly scrollbar: boolean;
readonly minimap: boolean;
readonly fixedOverflowWidgets: boolean;
}
@@ -2743,6 +2796,12 @@ export class OverviewRulerPosition {
}
}
export enum RenderMinimap {
None = 0,
Small = 1,
Large = 2
}
/**
* The internal layout details of the editor.
*/
@@ -2810,6 +2869,21 @@ export class EditorLayoutInfo {
*/
readonly contentHeight: number;
/**
* The width of the minimap
*/
readonly minimapWidth: number;
/**
* Minimap render type
*/
readonly renderMinimap: RenderMinimap;
/**
* The number of columns (of typical characters) fitting on a viewport line.
*/
readonly viewportColumn: number;
/**
* The width of the vertical scrollbar.
*/
@@ -2842,6 +2916,9 @@ export class EditorLayoutInfo {
contentLeft: number;
contentWidth: number;
contentHeight: number;
renderMinimap: RenderMinimap;
minimapWidth: number;
viewportColumn: number;
verticalScrollbarWidth: number;
horizontalScrollbarHeight: number;
overviewRuler: OverviewRulerPosition;
@@ -2860,6 +2937,9 @@ export class EditorLayoutInfo {
this.contentLeft = source.contentLeft | 0;
this.contentWidth = source.contentWidth | 0;
this.contentHeight = source.contentHeight | 0;
this.renderMinimap = source.renderMinimap | 0;
this.minimapWidth = source.minimapWidth | 0;
this.viewportColumn = source.viewportColumn | 0;
this.verticalScrollbarWidth = source.verticalScrollbarWidth | 0;
this.horizontalScrollbarHeight = source.horizontalScrollbarHeight | 0;
this.overviewRuler = source.overviewRuler.clone();
@@ -2884,6 +2964,9 @@ export class EditorLayoutInfo {
&& this.contentLeft === other.contentLeft
&& this.contentWidth === other.contentWidth
&& this.contentHeight === other.contentHeight
&& this.renderMinimap === other.renderMinimap
&& this.minimapWidth === other.minimapWidth
&& this.viewportColumn === other.viewportColumn
&& this.verticalScrollbarWidth === other.verticalScrollbarWidth
&& this.horizontalScrollbarHeight === other.horizontalScrollbarHeight
&& this.overviewRuler.equals(other.overviewRuler)
@@ -4039,6 +4122,11 @@ export interface ICommonCodeEditor extends IEditor {
* Get the layout info for the editor.
*/
getLayoutInfo(): EditorLayoutInfo;
/**
* @internal
*/
getTelemetryData(): { [key: string]: any; };
}
export interface ICommonDiffEditor extends IEditor {
@@ -62,12 +62,12 @@ export abstract class EditorAction extends ConfigEditorCommand {
}
public runEditorCommand(accessor: ServicesAccessor, editor: editorCommon.ICommonCodeEditor, args: any): void | TPromise<void> {
this.reportTelemetry(accessor);
this.reportTelemetry(accessor, editor);
return this.run(accessor, editor, args);
}
protected reportTelemetry(accessor: ServicesAccessor) {
accessor.get(ITelemetryService).publicLog('editorActionInvoked', { name: this.label, id: this.id });
protected reportTelemetry(accessor: ServicesAccessor, editor: editorCommon.ICommonCodeEditor) {
accessor.get(ITelemetryService).publicLog('editorActionInvoked', { name: this.label, id: this.id, ...editor.getTelemetryData() });
}
public abstract run(accessor: ServicesAccessor, editor: editorCommon.ICommonCodeEditor, args: any): void | TPromise<void>;
+3 -3
View File
@@ -258,16 +258,16 @@ export class ModelLine {
this._lineTokens = tokens.buffer;
}
public getTokens(topLevelLanguageId: LanguageId, colorMap: string[]): LineTokens {
public getTokens(topLevelLanguageId: LanguageId): LineTokens {
let rawLineTokens = this._lineTokens;
if (rawLineTokens) {
return new LineTokens(colorMap, new Uint32Array(rawLineTokens), this._text);
return new LineTokens(new Uint32Array(rawLineTokens), this._text);
}
let lineTokens = new Uint32Array(2);
lineTokens[0] = 0;
lineTokens[1] = ModelLine._getDefaultMetadata(topLevelLanguageId);
return new LineTokens(colorMap, lineTokens, this._text);
return new LineTokens(lineTokens, this._text);
}
// --- END TOKENS
@@ -63,7 +63,6 @@ export class TextModelWithTokens extends TextModel implements editorCommon.IToke
private _languageIdentifier: LanguageIdentifier;
private _tokenizationListener: IDisposable;
private _colorMap: string[];
private _tokenizationSupport: ITokenizationSupport;
private _invalidLineStartIndex: number;
@@ -78,7 +77,7 @@ export class TextModelWithTokens extends TextModel implements editorCommon.IToke
this._languageIdentifier = languageIdentifier || NULL_LANGUAGE_IDENTIFIER;
this._tokenizationListener = TokenizationRegistry.onDidChange((e) => {
if (e.languages.indexOf(this._languageIdentifier.language) === -1) {
if (e.changedLanguages.indexOf(this._languageIdentifier.language) === -1) {
return;
}
@@ -140,7 +139,6 @@ export class TextModelWithTokens extends TextModel implements editorCommon.IToke
}
}
this._colorMap = TokenizationRegistry.getColorMap();
this._lastState = null;
this._invalidLineStartIndex = 0;
this._beginBackgroundTokenization();
@@ -183,7 +181,7 @@ export class TextModelWithTokens extends TextModel implements editorCommon.IToke
}
private _getLineTokens(lineNumber: number): LineTokens {
return this._lines[lineNumber - 1].getTokens(this._languageIdentifier.id, this._colorMap);
return this._lines[lineNumber - 1].getTokens(this._languageIdentifier.id);
}
public getLanguageIdentifier(): LanguageIdentifier {
@@ -4,35 +4,10 @@
*--------------------------------------------------------------------------------------------*/
'use strict';
import { ViewLineToken } from 'vs/editor/common/core/viewLineToken';
import { ColorId, FontStyle, StandardTokenType, MetadataConsts, LanguageId } from 'vs/editor/common/modes';
export class TokenMetadata {
public static toBinaryStr(metadata: number): string {
let r = metadata.toString(2);
while (r.length < 32) {
r = '0' + r;
}
return r;
}
public static printMetadata(metadata: number): void {
let languageId = TokenMetadata.getLanguageId(metadata);
let tokenType = TokenMetadata.getTokenType(metadata);
let fontStyle = TokenMetadata.getFontStyle(metadata);
let foreground = TokenMetadata.getForeground(metadata);
let background = TokenMetadata.getBackground(metadata);
console.log({
languageId: languageId,
tokenType: tokenType,
fontStyle: fontStyle,
foreground: foreground,
background: background,
});
}
public static getLanguageId(metadata: number): LanguageId {
return (metadata & MetadataConsts.LANGUAGEID_MASK) >>> MetadataConsts.LANGUAGEID_OFFSET;
}
@@ -53,7 +28,7 @@ export class TokenMetadata {
return (metadata & MetadataConsts.BACKGROUND_MASK) >>> MetadataConsts.BACKGROUND_OFFSET;
}
private static _getClassNameFromMetadata(metadata: number): string {
public static getClassNameFromMetadata(metadata: number): string {
let foreground = this.getForeground(metadata);
let className = 'mtk' + foreground;
@@ -70,59 +45,4 @@ export class TokenMetadata {
return className;
}
public static inflateArr(tokens: Uint32Array, lineLength: number): ViewLineToken[] {
let result: ViewLineToken[] = [];
for (let i = 0, len = (tokens.length >>> 1); i < len; i++) {
let endOffset = (i + 1 < len ? tokens[((i + 1) << 1)] : lineLength);
let metadata = tokens[(i << 1) + 1];
result[i] = new ViewLineToken(endOffset, this._getClassNameFromMetadata(metadata));
}
return result;
}
public static sliceAndInflate(tokens: Uint32Array, startOffset: number, endOffset: number, deltaOffset: number, lineLength: number): ViewLineToken[] {
let tokenIndex = this.findIndexInSegmentsArray(tokens, startOffset);
let result: ViewLineToken[] = [], resultLen = 0;
for (let i = tokenIndex, len = (tokens.length >>> 1); i < len; i++) {
let tokenStartOffset = tokens[(i << 1)];
if (tokenStartOffset >= endOffset) {
break;
}
let tokenEndOffset = (i + 1 < len ? tokens[((i + 1) << 1)] : lineLength);
let newEndOffset = tokenEndOffset - startOffset + deltaOffset;
let metadata = tokens[(i << 1) + 1];
result[resultLen++] = new ViewLineToken(newEndOffset, this._getClassNameFromMetadata(metadata));
}
return result;
}
public static findIndexInSegmentsArray(tokens: Uint32Array, desiredIndex: number): number {
let low = 0;
let high = (tokens.length >>> 1) - 1;
while (low < high) {
let mid = low + Math.ceil((high - low) / 2);
let value = tokens[(mid << 1)];
if (value > desiredIndex) {
high = mid - 1;
} else {
low = mid;
}
}
return low;
}
}
+27 -40
View File
@@ -13,7 +13,9 @@ import LanguageFeatureRegistry from 'vs/editor/common/modes/languageFeatureRegis
import { CancellationToken } from 'vs/base/common/cancellation';
import { Position } from 'vs/editor/common/core/position';
import { Range } from 'vs/editor/common/core/range';
import Event, { Emitter } from 'vs/base/common/event';
import Event from 'vs/base/common/event';
import { TokenizationRegistryImpl } from 'vs/editor/common/modes/tokenizationRegistry';
import { Color } from 'vs/base/common/color';
/**
* Open ended enum at runtime
@@ -801,60 +803,45 @@ export const LinkProviderRegistry = new LanguageFeatureRegistry<LinkProvider>();
* @internal
*/
export interface ITokenizationSupportChangedEvent {
languages: string[];
changedLanguages: string[];
changedColorMap: boolean;
}
/**
* @internal
*/
export class TokenizationRegistryImpl {
export interface ITokenizationRegistry {
private _map: { [language: string]: ITokenizationSupport };
private _onDidChange: Emitter<ITokenizationSupportChangedEvent> = new Emitter<ITokenizationSupportChangedEvent>();
public onDidChange: Event<ITokenizationSupportChangedEvent> = this._onDidChange.event;
private _colorMap: string[];
constructor() {
this._map = Object.create(null);
this._colorMap = null;
}
/**
* An event triggered when:
* - a tokenization support is registered, unregistered or changed.
* - the color map is changed.
*/
onDidChange: Event<ITokenizationSupportChangedEvent>;
/**
* Fire a change event for a language.
* This is useful for languages that embed other languages.
*/
public fire(languages: string[]): void {
this._onDidChange.fire({ languages: languages });
}
fire(languages: string[]): void;
public register(language: string, support: ITokenizationSupport): IDisposable {
this._map[language] = support;
this.fire([language]);
return {
dispose: () => {
if (this._map[language] !== support) {
return;
}
delete this._map[language];
this.fire([language]);
}
};
}
/**
* Register a tokenization support.
*/
register(language: string, support: ITokenizationSupport): IDisposable;
public get(language: string): ITokenizationSupport {
return (this._map[language] || null);
}
/**
* Get the tokenization support for a language.
* Returns null if not found.
*/
get(language: string): ITokenizationSupport;
public setColorMap(colorMap: string[]): void {
this._colorMap = colorMap;
this.fire(Object.keys(this._map));
}
/**
* Set the new color map that all tokens will use in their ColorId binary encoded bits for foreground and background.
*/
setColorMap(colorMap: Color[]): void;
public getColorMap(): string[] {
return this._colorMap;
}
getColorMap(): Color[];
}
/**
@@ -398,8 +398,8 @@ export class MonarchTokenizer implements modes.ITokenizationSupport {
return;
}
let isOneOfMyEmbeddedModes = false;
for (let i = 0, len = e.languages.length; i < len; i++) {
let language = e.languages[i];
for (let i = 0, len = e.changedLanguages.length; i < len; i++) {
let language = e.changedLanguages[i];
if (this._embeddedModes[language]) {
isOneOfMyEmbeddedModes = true;
break;
@@ -4,8 +4,8 @@
*--------------------------------------------------------------------------------------------*/
'use strict';
import { ColorId, FontStyle, MetadataConsts, LanguageId } from 'vs/editor/common/modes';
import { toStandardTokenType } from 'vs/editor/common/core/lineTokens';
import { ColorId, FontStyle, MetadataConsts, LanguageId, StandardTokenType } from 'vs/editor/common/modes';
import { Color } from 'vs/base/common/color';
export interface IThemeRule {
token: string;
@@ -142,7 +142,7 @@ function resolveParsedThemeRules(parsedThemeRules: ParsedThemeRule[]): Theme {
export class ColorMap {
private _lastColorId: number;
private _id2color: string[];
private _id2color: Color[];
private _color2id: Map<string, ColorId>;
constructor() {
@@ -165,11 +165,11 @@ export class ColorMap {
}
value = ++this._lastColorId;
this._color2id.set(color, value);
this._id2color[value] = color;
this._id2color[value] = Color.fromHex('#' + color);
return value;
}
public getColorMap(): string[] {
public getColorMap(): Color[] {
return this._id2color.slice(0);
}
@@ -187,15 +187,15 @@ export class Theme {
private readonly _colorMap: ColorMap;
private readonly _root: ThemeTrieElement;
private readonly _cache: Map<string, ThemeTrieElementRule>;
private readonly _cache: Map<string, number>;
constructor(colorMap: ColorMap, root: ThemeTrieElement) {
this._colorMap = colorMap;
this._root = root;
this._cache = new Map<string, ThemeTrieElementRule>();
this._cache = new Map<string, number>();
}
public getColorMap(): string[] {
public getColorMap(): Color[] {
return this._colorMap.getColorMap();
}
@@ -207,26 +207,46 @@ export class Theme {
}
public _match(token: string): ThemeTrieElementRule {
let result = this._cache.get(token);
if (typeof result === 'undefined') {
result = this._root.match(token);
this._cache.set(token, result);
}
return result;
return this._root.match(token);
}
public match(languageId: LanguageId, token: string): number {
let rule = this._match(token);
let standardToken = toStandardTokenType(token);
// The cache contains the metadata without the language bits set.
let result = this._cache.get(token);
if (typeof result === 'undefined') {
let rule = this._match(token);
let standardToken = toStandardTokenType(token);
result = (
rule.metadata
| (standardToken << MetadataConsts.TOKEN_TYPE_OFFSET)
) >>> 0;
this._cache.set(token, result);
}
return (
rule.metadata
| (standardToken << MetadataConsts.TOKEN_TYPE_OFFSET)
result
| (languageId << MetadataConsts.LANGUAGEID_OFFSET)
) >>> 0;
}
}
const STANDARD_TOKEN_TYPE_REGEXP = /\b(comment|string|regex)\b/;
export function toStandardTokenType(tokenType: string): StandardTokenType {
let m = tokenType.match(STANDARD_TOKEN_TYPE_REGEXP);
if (!m) {
return StandardTokenType.Other;
}
switch (m[1]) {
case 'comment':
return StandardTokenType.Comment;
case 'string':
return StandardTokenType.String;
case 'regex':
return StandardTokenType.RegEx;
}
throw new Error('Unexpected match for standard token type!');
}
export function strcmp(a: string, b: string): number {
if (a < b) {
return -1;
@@ -370,3 +390,15 @@ export class ThemeTrieElement {
child.insert(tail, fontStyle, foreground, background);
}
}
export function generateTokensCSSForColorMap(colorMap: Color[]): string {
let rules: string[] = [];
for (let i = 1, len = colorMap.length; i < len; i++) {
let color = colorMap[i];
rules[i] = `.mtk${i} { color: ${color.toString()}; }`;
}
rules.push('.mtki { font-style: italic; }');
rules.push('.mtkb { font-weight: bold; }');
rules.push('.mtku { text-decoration: underline; }');
return rules.join('\n');
}
@@ -8,11 +8,101 @@ import * as strings from 'vs/base/common/strings';
import { IState, ITokenizationSupport, TokenizationRegistry, LanguageId } from 'vs/editor/common/modes';
import { NULL_STATE, nullTokenize2 } from 'vs/editor/common/modes/nullMode';
import { LineTokens } from 'vs/editor/common/core/lineTokens';
import { CharCode } from 'vs/base/common/charCode';
import { ViewLineToken } from 'vs/editor/common/core/viewLineToken';
export function tokenizeToString(text: string, languageId: string): string {
return _tokenizeToString(text, _getSafeTokenizationSupport(languageId));
}
export function tokenizeLineToHTML(text: string, viewLineTokens: ViewLineToken[], rules: { [key: string]: string }, options: { startOffset: number, endOffset: number, tabSize: number }): string {
let tabSize = options.tabSize;
let result = `<div>`;
let charIndex = options.startOffset;
let tabsCharDelta = 0;
for (let tokenIndex = 0, lenJ = viewLineTokens.length; tokenIndex < lenJ; tokenIndex++) {
const token = viewLineTokens[tokenIndex];
const tokenEndIndex = token.endIndex;
if (token.endIndex < options.startOffset) {
continue;
}
const tokenType = token.getType();
let partContentCnt = 0;
let partContent = '';
for (; charIndex < tokenEndIndex && charIndex < options.endOffset; charIndex++) {
const charCode = text.charCodeAt(charIndex);
switch (charCode) {
case CharCode.Tab:
let insertSpacesCount = tabSize - (charIndex + tabsCharDelta) % tabSize;
tabsCharDelta += insertSpacesCount - 1;
while (insertSpacesCount > 0) {
partContent += '&nbsp;';
partContentCnt++;
insertSpacesCount--;
}
break;
case CharCode.Space:
partContent += '&nbsp;';
partContentCnt++;
break;
case CharCode.LessThan:
partContent += '&lt;';
partContentCnt++;
break;
case CharCode.GreaterThan:
partContent += '&gt;';
partContentCnt++;
break;
case CharCode.Ampersand:
partContent += '&amp;';
partContentCnt++;
break;
case CharCode.Null:
partContent += '&#00;';
partContentCnt++;
break;
case CharCode.UTF8_BOM:
case CharCode.LINE_SEPARATOR_2028:
partContent += '\ufffd';
partContentCnt++;
break;
case CharCode.CarriageReturn:
// zero width space, because carriage return would introduce a line break
partContent += '&#8203';
partContentCnt++;
break;
default:
partContent += String.fromCharCode(charCode);
partContentCnt++;
}
}
// TODO: adopt new view line tokens.
let style = tokenType.split(' ').map(type => rules[type]).join('');
result += `<span style="${style}">${partContent}</span>`;
if (token.endIndex > options.endOffset) {
break;
}
}
result += `</div>`;
return result;
}
function _getSafeTokenizationSupport(languageId: string): ITokenizationSupport {
let tokenizationSupport = TokenizationRegistry.get(languageId);
if (tokenizationSupport) {
@@ -37,13 +127,13 @@ function _tokenizeToString(text: string, tokenizationSupport: ITokenizationSuppo
}
let tokenizationResult = tokenizationSupport.tokenize2(line, currentState, 0);
let lineTokens = new LineTokens(null, tokenizationResult.tokens, line);
let lineTokens = new LineTokens(tokenizationResult.tokens, line);
let viewLineTokens = lineTokens.inflate();
let startOffset = 0;
for (let j = 0, lenJ = viewLineTokens.length; j < lenJ; j++) {
const viewLineToken = viewLineTokens[j];
result += `<span class="${viewLineToken.type}">${strings.escape(line.substring(startOffset, viewLineToken.endIndex))}</span>`;
result += `<span class="${viewLineToken.getType()}">${strings.escape(line.substring(startOffset, viewLineToken.endIndex))}</span>`;
startOffset = viewLineToken.endIndex;
}
@@ -0,0 +1,62 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { IDisposable } from 'vs/base/common/lifecycle';
import Event, { Emitter } from 'vs/base/common/event';
import { ITokenizationRegistry, ITokenizationSupport, ITokenizationSupportChangedEvent } from 'vs/editor/common/modes';
import { Color } from 'vs/base/common/color';
export class TokenizationRegistryImpl implements ITokenizationRegistry {
private _map: { [language: string]: ITokenizationSupport };
private _onDidChange: Emitter<ITokenizationSupportChangedEvent> = new Emitter<ITokenizationSupportChangedEvent>();
public onDidChange: Event<ITokenizationSupportChangedEvent> = this._onDidChange.event;
private _colorMap: Color[];
constructor() {
this._map = Object.create(null);
this._colorMap = null;
}
public fire(languages: string[]): void {
this._onDidChange.fire({
changedLanguages: languages,
changedColorMap: false
});
}
public register(language: string, support: ITokenizationSupport): IDisposable {
this._map[language] = support;
this.fire([language]);
return {
dispose: () => {
if (this._map[language] !== support) {
return;
}
delete this._map[language];
this.fire([language]);
}
};
}
public get(language: string): ITokenizationSupport {
return (this._map[language] || null);
}
public setColorMap(colorMap: Color[]): void {
this._colorMap = colorMap;
this._onDidChange.fire({
changedLanguages: Object.keys(this._map),
changedColorMap: true
});
}
public getColorMap(): Color[] {
return this._colorMap;
}
}
+3 -3
View File
@@ -9,7 +9,7 @@ import { IThemeRule } from 'vs/editor/common/modes/supports/tokenization';
/* -------------------------------- Begin vs tokens -------------------------------- */
export const vs: IThemeRule[] = [
{ token: '', foreground: '000000' },
{ token: '', foreground: '000000', background: 'fffffe' },
{ token: 'invalid', foreground: 'cd3131' },
{ token: 'emphasis', fontStyle: 'italic' },
{ token: 'strong', fontStyle: 'bold' },
@@ -70,7 +70,7 @@ export const vs: IThemeRule[] = [
/* -------------------------------- Begin vs-dark tokens -------------------------------- */
export const vs_dark: IThemeRule[] = [
{ token: '', foreground: 'D4D4D4' },
{ token: '', foreground: 'D4D4D4', background: '1E1E1E' },
{ token: 'invalid', foreground: 'f44747' },
{ token: 'emphasis', fontStyle: 'italic' },
{ token: 'strong', fontStyle: 'bold' },
@@ -130,7 +130,7 @@ export const vs_dark: IThemeRule[] = [
/* -------------------------------- Begin hc-black tokens -------------------------------- */
export const hc_black: IThemeRule[] = [
{ token: '', foreground: 'FFFFFF' },
{ token: '', foreground: 'FFFFFF', background: '000000' },
{ token: 'invalid', foreground: 'f44747' },
{ token: 'emphasis', fontStyle: 'italic' },
{ token: 'strong', fontStyle: 'bold' },
+248 -98
View File
@@ -5,6 +5,124 @@
'use strict';
import { CharCode } from 'vs/base/common/charCode';
import { ColorId, TokenizationRegistry } from 'vs/editor/common/modes';
import Event, { Emitter } from 'vs/base/common/event';
import { Color } from 'vs/base/common/color';
export class ParsedColor {
public readonly r: number;
public readonly g: number;
public readonly b: number;
public readonly isLight: boolean;
constructor(r, g, b) {
this.r = r;
this.g = g;
this.b = b;
this.isLight = ((r + g + b) / (3 * 255) > 0.5);
}
public toCSSHex(): string {
return `#${ParsedColor._toTwoDigitHex(this.r)}${ParsedColor._toTwoDigitHex(this.g)}${ParsedColor._toTwoDigitHex(this.b)}`;
}
private static _toTwoDigitHex(n: number): string {
let r = n.toString(16);
if (r.length !== 2) {
return '0' + r;
}
return r;
}
}
export class MinimapTokensColorTracker {
private static _INSTANCE: MinimapTokensColorTracker = null;
public static getInstance(): MinimapTokensColorTracker {
if (!this._INSTANCE) {
this._INSTANCE = new MinimapTokensColorTracker();
}
return this._INSTANCE;
}
private _colors: ParsedColor[];
private _onDidChange = new Emitter<void>();
public onDidChange: Event<void> = this._onDidChange.event;
private constructor() {
this._setColorMap(TokenizationRegistry.getColorMap());
TokenizationRegistry.onDidChange((e) => {
if (e.changedColorMap) {
this._setColorMap(TokenizationRegistry.getColorMap());
}
});
}
private _setColorMap(colorMap: Color[]): void {
this._colors = [null];
for (let colorId = 1; colorId < colorMap.length; colorId++) {
const color = colorMap[colorId].toRGBA();
this._colors[colorId] = new ParsedColor(color.r, color.g, color.b);
}
this._onDidChange.fire(void 0);
}
public getColor(colorId: ColorId): ParsedColor {
if (colorId < 1 || colorId >= this._colors.length) {
// background color (basically invisible)
colorId = 2;
}
return this._colors[colorId];
}
public static _parseColor(color: string): ParsedColor {
if (!color) {
return new ParsedColor(0, 0, 0);
}
if (color.charCodeAt(0) === CharCode.Hash) {
color = color.substr(1, 6);
} else {
color = color.substr(0, 6);
}
if (color.length !== 6) {
return new ParsedColor(0, 0, 0);
}
let r = 16 * this._parseHexDigit(color.charCodeAt(0)) + this._parseHexDigit(color.charCodeAt(1));
let g = 16 * this._parseHexDigit(color.charCodeAt(2)) + this._parseHexDigit(color.charCodeAt(3));
let b = 16 * this._parseHexDigit(color.charCodeAt(4)) + this._parseHexDigit(color.charCodeAt(5));
return new ParsedColor(r, g, b);
}
private static _parseHexDigit(charCode: CharCode): number {
switch (charCode) {
case CharCode.Digit0: return 0;
case CharCode.Digit1: return 1;
case CharCode.Digit2: return 2;
case CharCode.Digit3: return 3;
case CharCode.Digit4: return 4;
case CharCode.Digit5: return 5;
case CharCode.Digit6: return 6;
case CharCode.Digit7: return 7;
case CharCode.Digit8: return 8;
case CharCode.Digit9: return 9;
case CharCode.a: return 10;
case CharCode.A: return 10;
case CharCode.b: return 11;
case CharCode.B: return 11;
case CharCode.c: return 12;
case CharCode.C: return 12;
case CharCode.d: return 13;
case CharCode.D: return 13;
case CharCode.e: return 14;
case CharCode.E: return 14;
case CharCode.f: return 15;
case CharCode.F: return 15;
}
return 0;
}
}
export const enum Constants {
START_CH_CODE = 32, // Space
@@ -22,7 +140,6 @@ export const enum Constants {
x1_CHAR_WIDTH = 1,
RGBA_CHANNELS_CNT = 4,
CA_CHANNELS_CNT = 2,
}
export class MinimapCharRenderer {
@@ -32,119 +149,152 @@ export class MinimapCharRenderer {
public readonly x2charData: Uint8ClampedArray;
public readonly x1charData: Uint8ClampedArray;
public readonly x2charDataLight: Uint8ClampedArray;
public readonly x1charDataLight: Uint8ClampedArray;
constructor(x2CharData: Uint8ClampedArray, x1CharData: Uint8ClampedArray) {
const x2ExpectedLen = Constants.x2_CHAR_HEIGHT * Constants.x2_CHAR_WIDTH * Constants.CA_CHANNELS_CNT * Constants.CHAR_COUNT;
const x2ExpectedLen = Constants.x2_CHAR_HEIGHT * Constants.x2_CHAR_WIDTH * Constants.CHAR_COUNT;
if (x2CharData.length !== x2ExpectedLen) {
throw new Error('Invalid x2CharData');
}
const x1ExpectedLen = Constants.x1_CHAR_HEIGHT * Constants.x1_CHAR_WIDTH * Constants.CA_CHANNELS_CNT * Constants.CHAR_COUNT;
const x1ExpectedLen = Constants.x1_CHAR_HEIGHT * Constants.x1_CHAR_WIDTH * Constants.CHAR_COUNT;
if (x1CharData.length !== x1ExpectedLen) {
throw new Error('Invalid x1CharData');
}
this.x2charData = x2CharData;
this.x1charData = x1CharData;
this.x2charDataLight = MinimapCharRenderer.soften(x2CharData, 12 / 15);
this.x1charDataLight = MinimapCharRenderer.soften(x1CharData, 50 / 60);
}
/**
* Assumes a line height of 4px and a char width of 2px
*/
public x2RenderChar(target: Uint8ClampedArray, lineLen: number, lineIndex: number, charIndex: number, chCode: number): void {
const x2CharData = this.x2charData;
const outWidth = Constants.x2_CHAR_WIDTH * Constants.RGBA_CHANNELS_CNT * lineLen;
if (chCode < Constants.START_CH_CODE || chCode > Constants.END_CH_CODE) {
chCode = CharCode.N;
private static soften(input: Uint8ClampedArray, ratio: number): Uint8ClampedArray {
let result = new Uint8ClampedArray(input.length);
for (let i = 0, len = input.length; i < len; i++) {
result[i] = input[i] * ratio;
}
const chIndex = chCode - Constants.START_CH_CODE;
let sourceOffset = chIndex * Constants.x2_CHAR_HEIGHT * Constants.x2_CHAR_WIDTH * Constants.CA_CHANNELS_CNT;
const c1 = x2CharData[sourceOffset];
const a1 = x2CharData[sourceOffset + 1];
const c2 = x2CharData[sourceOffset + 2];
const a2 = x2CharData[sourceOffset + 3];
const c3 = x2CharData[sourceOffset + 4];
const a3 = x2CharData[sourceOffset + 5];
const c4 = x2CharData[sourceOffset + 6];
const a4 = x2CharData[sourceOffset + 7];
const c5 = x2CharData[sourceOffset + 8];
const a5 = x2CharData[sourceOffset + 9];
const c6 = x2CharData[sourceOffset + 10];
const a6 = x2CharData[sourceOffset + 11];
const c7 = x2CharData[sourceOffset + 12];
const a7 = x2CharData[sourceOffset + 13];
const c8 = x2CharData[sourceOffset + 14];
const a8 = x2CharData[sourceOffset + 15];
let resultOffset = Constants.x2_CHAR_WIDTH * Constants.RGBA_CHANNELS_CNT * charIndex;
target[resultOffset + 0] = c1;
target[resultOffset + 1] = c1;
target[resultOffset + 2] = c1;
target[resultOffset + 3] = a1;
target[resultOffset + 4] = c2;
target[resultOffset + 5] = c2;
target[resultOffset + 6] = c2;
target[resultOffset + 7] = a2;
resultOffset += outWidth;
target[resultOffset + 0] = c3;
target[resultOffset + 1] = c3;
target[resultOffset + 2] = c3;
target[resultOffset + 3] = a3;
target[resultOffset + 4] = c4;
target[resultOffset + 5] = c4;
target[resultOffset + 6] = c4;
target[resultOffset + 7] = a4;
resultOffset += outWidth;
target[resultOffset + 0] = c5;
target[resultOffset + 1] = c5;
target[resultOffset + 2] = c5;
target[resultOffset + 3] = a5;
target[resultOffset + 4] = c6;
target[resultOffset + 5] = c6;
target[resultOffset + 6] = c6;
target[resultOffset + 7] = a6;
resultOffset += outWidth;
target[resultOffset + 0] = c7;
target[resultOffset + 1] = c7;
target[resultOffset + 2] = c7;
target[resultOffset + 3] = a7;
target[resultOffset + 4] = c8;
target[resultOffset + 5] = c8;
target[resultOffset + 6] = c8;
target[resultOffset + 7] = a8;
return result;
}
/**
* Assumes a line height of 2px and a char width of 1px
*/
public x1RenderChar(target: Uint8ClampedArray, lineLen: number, lineIndex: number, charIndex: number, chCode: number): void {
const x1CharData = this.x1charData;
const outWidth = Constants.x1_CHAR_WIDTH * Constants.RGBA_CHANNELS_CNT * lineLen;
if (chCode < Constants.START_CH_CODE || chCode > Constants.END_CH_CODE) {
chCode = CharCode.N;
private static _getChIndex(chCode: number): number {
chCode -= Constants.START_CH_CODE;
if (chCode < 0) {
chCode += Constants.CHAR_COUNT;
}
const chIndex = chCode - Constants.START_CH_CODE;
return (chCode % Constants.CHAR_COUNT);
}
let sourceOffset = chIndex * Constants.x1_CHAR_HEIGHT * Constants.x1_CHAR_WIDTH * Constants.CA_CHANNELS_CNT;
const c1 = x1CharData[sourceOffset];
const a1 = x1CharData[sourceOffset + 1];
const c2 = x1CharData[sourceOffset + 2];
const a2 = x1CharData[sourceOffset + 3];
public x2RenderChar(target: ImageData, dx: number, dy: number, chCode: number, color: ParsedColor, backgroundColor: ParsedColor): void {
if (dx + Constants.x2_CHAR_WIDTH > target.width || dy + Constants.x2_CHAR_HEIGHT > target.height) {
console.warn('bad render request outside image data');
return;
}
const x2CharData = backgroundColor.isLight ? this.x2charDataLight : this.x2charData;
const chIndex = MinimapCharRenderer._getChIndex(chCode);
let resultOffset = Constants.x1_CHAR_WIDTH * Constants.RGBA_CHANNELS_CNT * charIndex;
target[resultOffset + 0] = c1;
target[resultOffset + 1] = c1;
target[resultOffset + 2] = c1;
target[resultOffset + 3] = a1;
resultOffset += outWidth;
target[resultOffset + 0] = c2;
target[resultOffset + 1] = c2;
target[resultOffset + 2] = c2;
target[resultOffset + 3] = a2;
const outWidth = target.width * Constants.RGBA_CHANNELS_CNT;
const backgroundR = backgroundColor.r;
const backgroundG = backgroundColor.g;
const backgroundB = backgroundColor.b;
const deltaR = color.r - backgroundR;
const deltaG = color.g - backgroundG;
const deltaB = color.b - backgroundB;
const dest = target.data;
const sourceOffset = chIndex * Constants.x2_CHAR_HEIGHT * Constants.x2_CHAR_WIDTH;
let destOffset = dy * outWidth + dx * Constants.RGBA_CHANNELS_CNT;
{
const c = x2CharData[sourceOffset] / 255;
dest[destOffset + 0] = backgroundR + deltaR * c;
dest[destOffset + 1] = backgroundG + deltaG * c;
dest[destOffset + 2] = backgroundB + deltaB * c;
}
{
const c = x2CharData[sourceOffset + 1] / 255;
dest[destOffset + 4] = backgroundR + deltaR * c;
dest[destOffset + 5] = backgroundG + deltaG * c;
dest[destOffset + 6] = backgroundB + deltaB * c;
}
destOffset += outWidth;
{
const c = x2CharData[sourceOffset + 2] / 255;
dest[destOffset + 0] = backgroundR + deltaR * c;
dest[destOffset + 1] = backgroundG + deltaG * c;
dest[destOffset + 2] = backgroundB + deltaB * c;
}
{
const c = x2CharData[sourceOffset + 3] / 255;
dest[destOffset + 4] = backgroundR + deltaR * c;
dest[destOffset + 5] = backgroundG + deltaG * c;
dest[destOffset + 6] = backgroundB + deltaB * c;
}
destOffset += outWidth;
{
const c = x2CharData[sourceOffset + 4] / 255;
dest[destOffset + 0] = backgroundR + deltaR * c;
dest[destOffset + 1] = backgroundG + deltaG * c;
dest[destOffset + 2] = backgroundB + deltaB * c;
}
{
const c = x2CharData[sourceOffset + 5] / 255;
dest[destOffset + 4] = backgroundR + deltaR * c;
dest[destOffset + 5] = backgroundG + deltaG * c;
dest[destOffset + 6] = backgroundB + deltaB * c;
}
destOffset += outWidth;
{
const c = x2CharData[sourceOffset + 6] / 255;
dest[destOffset + 0] = backgroundR + deltaR * c;
dest[destOffset + 1] = backgroundG + deltaG * c;
dest[destOffset + 2] = backgroundB + deltaB * c;
}
{
const c = x2CharData[sourceOffset + 7] / 255;
dest[destOffset + 4] = backgroundR + deltaR * c;
dest[destOffset + 5] = backgroundG + deltaG * c;
dest[destOffset + 6] = backgroundB + deltaB * c;
}
}
public x1RenderChar(target: ImageData, dx: number, dy: number, chCode: number, color: ParsedColor, backgroundColor: ParsedColor): void {
if (dx + Constants.x1_CHAR_WIDTH > target.width || dy + Constants.x1_CHAR_HEIGHT > target.height) {
console.warn('bad render request outside image data');
return;
}
const x1CharData = backgroundColor.isLight ? this.x1charDataLight : this.x1charData;
const chIndex = MinimapCharRenderer._getChIndex(chCode);
const outWidth = target.width * Constants.RGBA_CHANNELS_CNT;
const backgroundR = backgroundColor.r;
const backgroundG = backgroundColor.g;
const backgroundB = backgroundColor.b;
const deltaR = color.r - backgroundR;
const deltaG = color.g - backgroundG;
const deltaB = color.b - backgroundB;
const dest = target.data;
const sourceOffset = chIndex * Constants.x1_CHAR_HEIGHT * Constants.x1_CHAR_WIDTH;
let destOffset = dy * outWidth + dx * Constants.RGBA_CHANNELS_CNT;
{
const c = x1CharData[sourceOffset] / 255;
dest[destOffset + 0] = backgroundR + deltaR * c;
dest[destOffset + 1] = backgroundG + deltaG * c;
dest[destOffset + 2] = backgroundB + deltaB * c;
}
destOffset += outWidth;
{
const c = x1CharData[sourceOffset + 1] / 255;
dest[destOffset + 0] = backgroundR + deltaR * c;
dest[destOffset + 1] = backgroundG + deltaG * c;
dest[destOffset + 2] = backgroundB + deltaB * c;
}
}
}
File diff suppressed because it is too large Load Diff
@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
'use strict';
import { EditorLayoutInfo, OverviewRulerPosition } from 'vs/editor/common/editorCommon';
import { RenderMinimap, EditorLayoutInfo, OverviewRulerPosition } from 'vs/editor/common/editorCommon';
export interface IEditorLayoutProviderOpts {
outerWidth: number;
@@ -15,15 +15,20 @@ export interface IEditorLayoutProviderOpts {
showLineNumbers: boolean;
lineNumbersMinChars: number;
lineDecorationsWidth: number;
maxDigitWidth: number;
maxLineNumber: number;
lineDecorationsWidth: number;
typicalHalfwidthCharacterWidth: number;
maxDigitWidth: number;
verticalScrollbarWidth: number;
verticalScrollbarHasArrows: boolean;
scrollbarArrowSize: number;
horizontalScrollbarHeight: number;
minimap: boolean;
pixelRatio: number;
}
export class EditorLayoutProvider {
@@ -34,13 +39,16 @@ export class EditorLayoutProvider {
const lineHeight = _opts.lineHeight | 0;
const showLineNumbers = Boolean(_opts.showLineNumbers);
const lineNumbersMinChars = _opts.lineNumbersMinChars | 0;
const lineDecorationsWidth = _opts.lineDecorationsWidth | 0;
const maxDigitWidth = Number(_opts.maxDigitWidth);
const maxLineNumber = _opts.maxLineNumber | 0;
const lineDecorationsWidth = _opts.lineDecorationsWidth | 0;
const typicalHalfwidthCharacterWidth = Number(_opts.typicalHalfwidthCharacterWidth);
const maxDigitWidth = Number(_opts.maxDigitWidth);
const verticalScrollbarWidth = _opts.verticalScrollbarWidth | 0;
const verticalScrollbarHasArrows = Boolean(_opts.verticalScrollbarHasArrows);
const scrollbarArrowSize = _opts.scrollbarArrowSize | 0;
const horizontalScrollbarHeight = _opts.horizontalScrollbarHeight | 0;
const minimap = Boolean(_opts.minimap);
const pixelRatio = Number(_opts.pixelRatio);
let lineNumbersWidth = 0;
if (showLineNumbers) {
@@ -53,13 +61,48 @@ export class EditorLayoutProvider {
glyphMarginWidth = lineHeight;
}
let contentWidth = outerWidth - glyphMarginWidth - lineNumbersWidth - lineDecorationsWidth;
let glyphMarginLeft = 0;
let lineNumbersLeft = glyphMarginLeft + glyphMarginWidth;
let decorationsLeft = lineNumbersLeft + lineNumbersWidth;
let contentLeft = decorationsLeft + lineDecorationsWidth;
let remainingWidth = outerWidth - glyphMarginWidth - lineNumbersWidth - lineDecorationsWidth;
let renderMinimap: RenderMinimap;
let minimapWidth: number;
let contentWidth: number;
if (!minimap) {
minimapWidth = 0;
renderMinimap = RenderMinimap.None;
contentWidth = remainingWidth;
} else {
let minimapCharWidth: number;
if (pixelRatio >= 2) {
renderMinimap = RenderMinimap.Large;
minimapCharWidth = 2 / pixelRatio;
} else {
renderMinimap = RenderMinimap.Small;
minimapCharWidth = 1 / pixelRatio;
}
// Given:
// viewportColumn = (contentWidth - verticalScrollbarWidth) / typicalHalfwidthCharacterWidth
// minimapWidth = viewportColumn * minimapCharWidth
// contentWidth = remainingWidth - minimapWidth
// What are good values for contentWidth and minimapWidth ?
// minimapWidth = ((contentWidth - verticalScrollbarWidth) / typicalHalfwidthCharacterWidth) * minimapCharWidth
// typicalHalfwidthCharacterWidth * minimapWidth = (contentWidth - verticalScrollbarWidth) * minimapCharWidth
// typicalHalfwidthCharacterWidth * minimapWidth = (remainingWidth - minimapWidth - verticalScrollbarWidth) * minimapCharWidth
// (typicalHalfwidthCharacterWidth + minimapCharWidth) * minimapWidth = (remainingWidth - verticalScrollbarWidth) * minimapCharWidth
// minimapWidth = ((remainingWidth - verticalScrollbarWidth) * minimapCharWidth) / (typicalHalfwidthCharacterWidth + minimapCharWidth)
minimapWidth = Math.max(0, Math.floor(((remainingWidth - verticalScrollbarWidth) * minimapCharWidth) / (typicalHalfwidthCharacterWidth + minimapCharWidth)));
contentWidth = remainingWidth - minimapWidth;
}
let viewportColumn = Math.max(1, Math.floor((contentWidth - verticalScrollbarWidth) / typicalHalfwidthCharacterWidth));
let verticalArrowSize = (verticalScrollbarHasArrows ? scrollbarArrowSize : 0);
return new EditorLayoutInfo({
@@ -82,6 +125,11 @@ export class EditorLayoutProvider {
contentWidth: contentWidth,
contentHeight: outerHeight,
renderMinimap: renderMinimap,
minimapWidth: minimapWidth,
viewportColumn: viewportColumn,
verticalScrollbarWidth: verticalScrollbarWidth,
horizontalScrollbarHeight: horizontalScrollbarHeight,
@@ -15,6 +15,21 @@ export const enum RenderWhitespace {
All = 2
}
class LinePart {
_linePartBrand: void;
/**
* last char index of this token (not inclusive).
*/
public readonly endIndex: number;
public readonly type: string;
constructor(endIndex: number, type: string) {
this.endIndex = endIndex;
this.type = type;
}
}
export class RenderLineInput {
public readonly useMonospaceOptimizations: boolean;
@@ -236,7 +251,7 @@ class ResolvedRenderLineInput {
public readonly lineContent: string,
public readonly len: number,
public readonly isOverflowing: boolean,
public readonly tokens: ViewLineToken[],
public readonly parts: LinePart[],
public readonly containsForeignElements: boolean,
public readonly tabSize: number,
public readonly containsRTL: boolean,
@@ -305,25 +320,17 @@ function resolveRenderLineInput(input: RenderLineInput): ResolvedRenderLineInput
* In the rendering phase, characters are always looped until token.endIndex.
* Ensure that all tokens end before `len` and the last one ends precisely at `len`.
*/
function removeOverflowing(tokens: ViewLineToken[], len: number): ViewLineToken[] {
if (tokens.length === 0) {
return tokens;
}
if (tokens[tokens.length - 1].endIndex === len) {
return tokens;
}
let result: ViewLineToken[] = [];
function removeOverflowing(tokens: ViewLineToken[], len: number): LinePart[] {
let result: LinePart[] = [];
for (let tokenIndex = 0, tokensLen = tokens.length; tokenIndex < tokensLen; tokenIndex++) {
const endIndex = tokens[tokenIndex].endIndex;
if (endIndex === len) {
result[tokenIndex] = tokens[tokenIndex];
const token = tokens[tokenIndex];
const endIndex = token.endIndex;
const type = token.getType();
if (endIndex >= len) {
result[tokenIndex] = new LinePart(len, type);
break;
}
if (endIndex > len) {
result[tokenIndex] = new ViewLineToken(len, tokens[tokenIndex].type);
break;
}
result[tokenIndex] = tokens[tokenIndex];
result[tokenIndex] = new LinePart(endIndex, type);
}
return result;
}
@@ -340,9 +347,9 @@ const enum Constants {
* It appears that having very large spans causes very slow reading of character positions.
* So here we try to avoid that.
*/
function splitLargeTokens(tokens: ViewLineToken[]): ViewLineToken[] {
function splitLargeTokens(tokens: LinePart[]): LinePart[] {
let lastTokenEndIndex = 0;
let result: ViewLineToken[] = [], resultLen = 0;
let result: LinePart[] = [], resultLen = 0;
for (let i = 0, len = tokens.length; i < len; i++) {
const token = tokens[i];
const tokenEndIndex = token.endIndex;
@@ -352,9 +359,9 @@ function splitLargeTokens(tokens: ViewLineToken[]): ViewLineToken[] {
const piecesCount = Math.ceil(diff / Constants.LongToken);
for (let j = 1; j < piecesCount; j++) {
let pieceEndIndex = lastTokenEndIndex + (j * Constants.LongToken);
result[resultLen++] = new ViewLineToken(pieceEndIndex, tokenType);
result[resultLen++] = new LinePart(pieceEndIndex, tokenType);
}
result[resultLen++] = new ViewLineToken(tokenEndIndex, tokenType);
result[resultLen++] = new LinePart(tokenEndIndex, tokenType);
} else {
result[resultLen++] = token;
}
@@ -369,15 +376,15 @@ function splitLargeTokens(tokens: ViewLineToken[]): ViewLineToken[] {
* Moreover, a token is created for every visual indent because on some fonts the glyphs used for rendering whitespace (&rarr; or &middot;) do not have the same width as &nbsp;.
* The rendering phase will generate `style="width:..."` for these tokens.
*/
function _applyRenderWhitespace(lineContent: string, len: number, tokens: ViewLineToken[], fauxIndentLength: number, tabSize: number, useMonospaceOptimizations: boolean, onlyBoundary: boolean): ViewLineToken[] {
function _applyRenderWhitespace(lineContent: string, len: number, tokens: LinePart[], fauxIndentLength: number, tabSize: number, useMonospaceOptimizations: boolean, onlyBoundary: boolean): LinePart[] {
let result: ViewLineToken[] = [], resultLen = 0;
let result: LinePart[] = [], resultLen = 0;
let tokenIndex = 0;
let tokenType = tokens[tokenIndex].type;
let tokenEndIndex = tokens[tokenIndex].endIndex;
if (fauxIndentLength > 0) {
result[resultLen++] = new ViewLineToken(fauxIndentLength, '');
result[resultLen++] = new LinePart(fauxIndentLength, '');
}
let firstNonWhitespaceIndex = strings.firstNonWhitespaceIndex(lineContent);
@@ -433,13 +440,13 @@ function _applyRenderWhitespace(lineContent: string, len: number, tokens: ViewLi
// was in whitespace token
if (!isInWhitespace || (!useMonospaceOptimizations && tmpIndent >= tabSize)) {
// leaving whitespace token or entering a new indent
result[resultLen++] = new ViewLineToken(charIndex, 'vs-whitespace');
result[resultLen++] = new LinePart(charIndex, 'vs-whitespace');
tmpIndent = tmpIndent % tabSize;
}
} else {
// was in regular token
if (charIndex === tokenEndIndex || (isInWhitespace && charIndex > fauxIndentLength)) {
result[resultLen++] = new ViewLineToken(charIndex, tokenType);
result[resultLen++] = new LinePart(charIndex, tokenType);
tmpIndent = tmpIndent % tabSize;
}
}
@@ -461,10 +468,10 @@ function _applyRenderWhitespace(lineContent: string, len: number, tokens: ViewLi
if (wasInWhitespace) {
// was in whitespace token
result[resultLen++] = new ViewLineToken(len, 'vs-whitespace');
result[resultLen++] = new LinePart(len, 'vs-whitespace');
} else {
// was in regular token
result[resultLen++] = new ViewLineToken(len, tokenType);
result[resultLen++] = new LinePart(len, tokenType);
}
return result;
@@ -474,13 +481,13 @@ function _applyRenderWhitespace(lineContent: string, len: number, tokens: ViewLi
* Inline decorations are "merged" on top of tokens.
* Special care must be taken when multiple inline decorations are at play and they overlap.
*/
function _applyInlineDecorations(lineContent: string, len: number, tokens: ViewLineToken[], _lineDecorations: Decoration[]): ViewLineToken[] {
function _applyInlineDecorations(lineContent: string, len: number, tokens: LinePart[], _lineDecorations: Decoration[]): LinePart[] {
_lineDecorations.sort(Decoration.compare);
const lineDecorations = LineDecorationsNormalizer.normalize(_lineDecorations);
const lineDecorationsLen = lineDecorations.length;
let lineDecorationIndex = 0;
let result: ViewLineToken[] = [], resultLen = 0, lastResultEndIndex = 0;
let result: LinePart[] = [], resultLen = 0, lastResultEndIndex = 0;
for (let tokenIndex = 0, len = tokens.length; tokenIndex < len; tokenIndex++) {
const token = tokens[tokenIndex];
const tokenEndIndex = token.endIndex;
@@ -491,25 +498,25 @@ function _applyInlineDecorations(lineContent: string, len: number, tokens: ViewL
if (lineDecoration.startOffset > lastResultEndIndex) {
lastResultEndIndex = lineDecoration.startOffset;
result[resultLen++] = new ViewLineToken(lastResultEndIndex, tokenType);
result[resultLen++] = new LinePart(lastResultEndIndex, tokenType);
}
if (lineDecoration.endOffset + 1 <= tokenEndIndex) {
// This line decoration ends before this token ends
lastResultEndIndex = lineDecoration.endOffset + 1;
result[resultLen++] = new ViewLineToken(lastResultEndIndex, tokenType + ' ' + lineDecoration.className);
result[resultLen++] = new LinePart(lastResultEndIndex, tokenType + ' ' + lineDecoration.className);
lineDecorationIndex++;
} else {
// This line decoration continues on to the next token
lastResultEndIndex = tokenEndIndex;
result[resultLen++] = new ViewLineToken(lastResultEndIndex, tokenType + ' ' + lineDecoration.className);
result[resultLen++] = new LinePart(lastResultEndIndex, tokenType + ' ' + lineDecoration.className);
break;
}
}
if (tokenEndIndex > lastResultEndIndex) {
lastResultEndIndex = tokenEndIndex;
result[resultLen++] = new ViewLineToken(lastResultEndIndex, tokenType);
result[resultLen++] = new LinePart(lastResultEndIndex, tokenType);
}
}
@@ -526,33 +533,33 @@ function _renderLine(input: ResolvedRenderLineInput): RenderLineOutput {
const lineContent = input.lineContent;
const len = input.len;
const isOverflowing = input.isOverflowing;
const tokens = input.tokens;
const parts = input.parts;
const tabSize = input.tabSize;
const containsRTL = input.containsRTL;
const spaceWidth = input.spaceWidth;
const renderWhitespace = input.renderWhitespace;
const renderControlCharacters = input.renderControlCharacters;
const characterMapping = new CharacterMapping(len + 1, tokens.length);
const characterMapping = new CharacterMapping(len + 1, parts.length);
let charIndex = 0;
let tabsCharDelta = 0;
let charOffsetInPart = 0;
let out = '<span>';
for (let tokenIndex = 0, tokensLen = tokens.length; tokenIndex < tokensLen; tokenIndex++) {
const token = tokens[tokenIndex];
const tokenEndIndex = token.endIndex;
const tokenType = token.type;
const tokenRendersWhitespace = (renderWhitespace !== RenderWhitespace.None && (tokenType.indexOf('vs-whitespace') >= 0));
for (let partIndex = 0, tokensLen = parts.length; partIndex < tokensLen; partIndex++) {
const part = parts[partIndex];
const partEndIndex = part.endIndex;
const partType = part.type;
const partRendersWhitespace = (renderWhitespace !== RenderWhitespace.None && (partType.indexOf('vs-whitespace') >= 0));
charOffsetInPart = 0;
if (tokenRendersWhitespace) {
if (partRendersWhitespace) {
let partContentCnt = 0;
let partContent = '';
for (; charIndex < tokenEndIndex; charIndex++) {
characterMapping.setPartData(charIndex, tokenIndex, charOffsetInPart);
for (; charIndex < partEndIndex; charIndex++) {
characterMapping.setPartData(charIndex, partIndex, charOffsetInPart);
const charCode = lineContent.charCodeAt(charIndex);
if (charCode === CharCode.Tab) {
@@ -578,11 +585,11 @@ function _renderLine(input: ResolvedRenderLineInput): RenderLineOutput {
charOffsetInPart++;
}
characterMapping.setPartLength(tokenIndex, partContentCnt);
characterMapping.setPartLength(partIndex, partContentCnt);
if (fontIsMonospace) {
out += `<span class="${tokenType}">${partContent}</span>`;
out += `<span class="${partType}">${partContent}</span>`;
} else {
out += `<span class="${tokenType}" style="width:${spaceWidth * partContentCnt}px">${partContent}</span>`;
out += `<span class="${partType}" style="width:${spaceWidth * partContentCnt}px">${partContent}</span>`;
}
} else {
@@ -590,8 +597,8 @@ function _renderLine(input: ResolvedRenderLineInput): RenderLineOutput {
let partContentCnt = 0;
let partContent = '';
for (; charIndex < tokenEndIndex; charIndex++) {
characterMapping.setPartData(charIndex, tokenIndex, charOffsetInPart);
for (; charIndex < partEndIndex; charIndex++) {
characterMapping.setPartData(charIndex, partIndex, charOffsetInPart);
const charCode = lineContent.charCodeAt(charIndex);
switch (charCode) {
@@ -656,11 +663,11 @@ function _renderLine(input: ResolvedRenderLineInput): RenderLineOutput {
charOffsetInPart++;
}
characterMapping.setPartLength(tokenIndex, partContentCnt);
characterMapping.setPartLength(partIndex, partContentCnt);
if (containsRTL) {
out += `<span dir="ltr" class="${tokenType}">${partContent}</span>`;
out += `<span dir="ltr" class="${partType}">${partContent}</span>`;
} else {
out += `<span class="${tokenType}">${partContent}</span>`;
out += `<span class="${partType}">${partContent}</span>`;
}
}
@@ -668,7 +675,7 @@ function _renderLine(input: ResolvedRenderLineInput): RenderLineOutput {
// When getting client rects for the last character, we will position the
// text range at the end of the span, insteaf of at the beginning of next span
characterMapping.setPartData(len, tokens.length - 1, charOffsetInPart);
characterMapping.setPartData(len, parts.length - 1, charOffsetInPart);
if (isOverflowing) {
out += `<span class="vs-whitespace">&hellip;</span>`;
@@ -9,7 +9,7 @@ import { Range } from 'vs/editor/common/core/range';
import * as editorCommon from 'vs/editor/common/editorCommon';
import { LineTokens } from 'vs/editor/common/core/lineTokens';
import { PrefixSumComputerWithCache } from 'vs/editor/common/viewModel/prefixSumComputer';
import { ViewLineToken } from 'vs/editor/common/core/viewLineToken';
import { ViewLineData } from 'vs/editor/common/viewModel/viewModel';
export class OutputPosition {
_outputPositionBrand: void;
@@ -48,7 +48,8 @@ export interface ISplitLine {
getViewLineContent(model: IModel, modelLineNumber: number, outputLineIndex: number): string;
getViewLineMinColumn(model: IModel, modelLineNumber: number, outputLineIndex: number): number;
getViewLineMaxColumn(model: IModel, modelLineNumber: number, outputLineIndex: number): number;
getViewLineRenderingData(model: IModel, modelLineNumber: number, outputLineIndex: number): OutputLineRenderingData;
getViewLineData(model: IModel, modelLineNumber: number, outputLineIndex: number): ViewLineData;
getViewLinesData(model: IModel, modelLineNumber: number, fromOuputLineIndex: number, toOutputLineIndex: number, globalStartIndex: number, needed: boolean[], result: ViewLineData[]): void;
getModelColumnOfViewPosition(outputLineIndex: number, outputColumn: number): number;
getViewPositionOfModelPosition(deltaLineNumber: number, inputColumn: number): Position;
@@ -87,10 +88,10 @@ class VisibleIdentitySplitLine implements ISplitLine {
return model.getLineMaxColumn(modelLineNumber);
}
public getViewLineRenderingData(model: IModel, modelLineNumber: number, outputLineIndex: number): OutputLineRenderingData {
public getViewLineData(model: IModel, modelLineNumber: number, outputLineIndex: number): ViewLineData {
let lineTokens = model.getLineTokens(modelLineNumber, true);
let lineContent = lineTokens.getLineContent();
return new OutputLineRenderingData(
return new ViewLineData(
lineContent,
1,
lineContent.length + 1,
@@ -98,6 +99,14 @@ class VisibleIdentitySplitLine implements ISplitLine {
);
}
public getViewLinesData(model: IModel, modelLineNumber: number, fromOuputLineIndex: number, toOutputLineIndex: number, globalStartIndex: number, needed: boolean[], result: ViewLineData[]): void {
if (!needed[globalStartIndex]) {
result[globalStartIndex] = null;
return;
}
result[globalStartIndex] = this.getViewLineData(model, modelLineNumber, 0);
}
public getModelColumnOfViewPosition(outputLineIndex: number, outputColumn: number): number {
return outputColumn;
}
@@ -140,7 +149,11 @@ class InvisibleIdentitySplitLine implements ISplitLine {
throw new Error('Not supported');
}
public getViewLineRenderingData(model: IModel, modelLineNumber: number, outputLineIndex: number): OutputLineRenderingData {
public getViewLineData(model: IModel, modelLineNumber: number, outputLineIndex: number): ViewLineData {
throw new Error('Not supported');
}
public getViewLinesData(model: IModel, modelLineNumber: number, fromOuputLineIndex: number, toOutputLineIndex: number, globalStartIndex: number, needed: boolean[], result: ViewLineData[]): void {
throw new Error('Not supported');
}
@@ -229,7 +242,7 @@ export class SplitLine implements ISplitLine {
return this.getViewLineContent(model, modelLineNumber, outputLineIndex).length + 1;
}
public getViewLineRenderingData(model: IModel, modelLineNumber: number, outputLineIndex: number): OutputLineRenderingData {
public getViewLineData(model: IModel, modelLineNumber: number, outputLineIndex: number): ViewLineData {
if (!this._isVisible) {
throw new Error('Not supported');
}
@@ -251,7 +264,7 @@ export class SplitLine implements ISplitLine {
}
let lineTokens = model.getLineTokens(modelLineNumber, true);
return new OutputLineRenderingData(
return new ViewLineData(
lineContent,
minColumn,
maxColumn,
@@ -259,6 +272,21 @@ export class SplitLine implements ISplitLine {
);
}
public getViewLinesData(model: IModel, modelLineNumber: number, fromOuputLineIndex: number, toOutputLineIndex: number, globalStartIndex: number, needed: boolean[], result: ViewLineData[]): void {
if (!this._isVisible) {
throw new Error('Not supported');
}
for (let outputLineIndex = fromOuputLineIndex; outputLineIndex < toOutputLineIndex; outputLineIndex++) {
let globalIndex = globalStartIndex + outputLineIndex - fromOuputLineIndex;
if (!needed[globalIndex]) {
result[globalIndex] = null;
continue;
}
result[globalIndex] = this.getViewLineData(model, modelLineNumber, outputLineIndex);
}
}
public getModelColumnOfViewPosition(outputLineIndex: number, outputColumn: number): number {
if (!this._isVisible) {
throw new Error('Not supported');
@@ -723,14 +751,53 @@ export class SplitLinesCollection {
return this.lines[lineIndex].getViewLineMaxColumn(this.model, lineIndex + 1, remainder);
}
public getViewLineRenderingData(viewLineNumber: number): OutputLineRenderingData {
public getViewLineData(viewLineNumber: number): ViewLineData {
this._ensureValidState();
viewLineNumber = this._toValidViewLineNumber(viewLineNumber);
let r = this.prefixSumComputer.getIndexOf(viewLineNumber - 1);
let lineIndex = r.index;
let remainder = r.remainder;
return this.lines[lineIndex].getViewLineRenderingData(this.model, lineIndex + 1, remainder);
return this.lines[lineIndex].getViewLineData(this.model, lineIndex + 1, remainder);
}
public getViewLinesData(viewStartLineNumber: number, viewEndLineNumber: number, needed: boolean[]): ViewLineData[] {
this._ensureValidState();
viewStartLineNumber = this._toValidViewLineNumber(viewStartLineNumber);
viewEndLineNumber = this._toValidViewLineNumber(viewEndLineNumber);
let start = this.prefixSumComputer.getIndexOf(viewStartLineNumber - 1);
let viewLineNumber = viewStartLineNumber;
let startModelLineIndex = start.index;
let startRemainder = start.remainder;
let result: ViewLineData[] = [];
for (let modelLineIndex = startModelLineIndex, len = this.model.getLineCount(); modelLineIndex < len; modelLineIndex++) {
let line = this.lines[modelLineIndex];
if (!line.isVisible()) {
continue;
}
let fromViewLineIndex = (modelLineIndex === startModelLineIndex ? startRemainder : 0);
let remainingViewLineCount = line.getViewLineCount() - fromViewLineIndex;
let lastLine = false;
if (viewLineNumber + remainingViewLineCount > viewEndLineNumber) {
lastLine = true;
remainingViewLineCount = viewEndLineNumber - viewLineNumber + 1;
}
let toViewLineIndex = fromViewLineIndex + remainingViewLineCount;
line.getViewLinesData(this.model, modelLineIndex + 1, fromViewLineIndex, toViewLineIndex, viewLineNumber - viewStartLineNumber, needed, result);
viewLineNumber += remainingViewLineCount;
if (lastLine) {
break;
}
}
return result;
}
public validateViewPosition(viewLineNumber: number, viewColumn: number, expectedModelPosition: Position): Position {
@@ -805,36 +872,3 @@ export class SplitLinesCollection {
return r;
}
}
export class OutputLineRenderingData {
_outputLineRenderingDataBrand: void;
/**
* The content at this view line.
*/
public readonly content: string;
/**
* The minimum allowed column at this view line.
*/
public readonly minColumn: number;
/**
* The maximum allowed column at this view line.
*/
public readonly maxColumn: number;
/**
* The tokens at this view line.
*/
public readonly tokens: ViewLineToken[];
constructor(
content: string,
minColumn: number,
maxColumn: number,
tokens: ViewLineToken[]
) {
this.content = content;
this.minColumn = minColumn;
this.maxColumn = maxColumn;
this.tokens = tokens;
}
}
@@ -72,6 +72,7 @@ export interface IViewModel extends IEventEmitter {
getDecorationsInViewport(visibleRange: Range): ViewModelDecoration[];
getViewLineRenderingData(visibleRange: Range, lineNumber: number): ViewLineRenderingData;
getMinimapLinesRenderingData(startLineNumber: number, endLineNumber: number, needed: boolean[]): MinimapLinesRenderingData;
getTabSize(): number;
getLineCount(): number;
@@ -87,6 +88,55 @@ export interface IViewModel extends IEventEmitter {
getModelLineContent(modelLineNumber: number): string;
getModelLineMaxColumn(modelLineNumber: number): number;
validateModelPosition(modelPosition: IPosition): Position;
getPlainTextToCopy(ranges: Range[], enableEmptySelectionClipboard: boolean): string;
getHTMLToCopy(ranges: Range[], enableEmptySelectionClipboard: boolean): string;
}
export class MinimapLinesRenderingData {
public readonly tabSize: number;
public readonly data: ViewLineData[];
constructor(
tabSize: number,
data: ViewLineData[]
) {
this.tabSize = tabSize;
this.data = data;
}
}
export class ViewLineData {
_viewLineDataBrand: void;
/**
* The content at this view line.
*/
public readonly content: string;
/**
* The minimum allowed column at this view line.
*/
public readonly minColumn: number;
/**
* The maximum allowed column at this view line.
*/
public readonly maxColumn: number;
/**
* The tokens at this view line.
*/
public readonly tokens: ViewLineToken[];
constructor(
content: string,
minColumn: number,
maxColumn: number,
tokens: ViewLineToken[]
) {
this.content = content;
this.minColumn = minColumn;
this.maxColumn = maxColumn;
this.tokens = tokens;
}
}
export class ViewLineRenderingData {
+104 -2
View File
@@ -11,9 +11,11 @@ import { Position } from 'vs/editor/common/core/position';
import { Range } from 'vs/editor/common/core/range';
import { Selection } from 'vs/editor/common/core/selection';
import * as editorCommon from 'vs/editor/common/editorCommon';
import { TokenizationRegistry } from 'vs/editor/common/modes';
import { tokenizeLineToHTML } from 'vs/editor/common/modes/textToHtmlTokenizer';
import { ViewModelCursors } from 'vs/editor/common/viewModel/viewModelCursors';
import { ViewModelDecorations } from 'vs/editor/common/viewModel/viewModelDecorations';
import { ViewLineRenderingData, ViewModelDecoration, IViewModel, ICoordinatesConverter } from 'vs/editor/common/viewModel/viewModel';
import { MinimapLinesRenderingData, ViewLineRenderingData, ViewModelDecoration, IViewModel, ICoordinatesConverter } from 'vs/editor/common/viewModel/viewModel';
import { SplitLinesCollection } from 'vs/editor/common/viewModel/splitLinesCollection';
export class CoordinatesConverter implements ICoordinatesConverter {
@@ -518,7 +520,7 @@ export class ViewModel extends EventEmitter implements IViewModel {
let mightContainRTL = this.model.mightContainRTL();
let mightContainNonBasicASCII = this.model.mightContainNonBasicASCII();
let tabSize = this.getTabSize();
let lineData = this.lines.getViewLineRenderingData(lineNumber);
let lineData = this.lines.getViewLineData(lineNumber);
let allInlineDecorations = this.decorations.getDecorationsViewportData(visibleRange).inlineDecorations;
let inlineDecorations = allInlineDecorations[lineNumber - visibleRange.startLineNumber];
@@ -534,6 +536,14 @@ export class ViewModel extends EventEmitter implements IViewModel {
);
}
public getMinimapLinesRenderingData(startLineNumber: number, endLineNumber: number, needed: boolean[]): MinimapLinesRenderingData {
let result = this.lines.getViewLinesData(startLineNumber, endLineNumber, needed);
return new MinimapLinesRenderingData(
this.getTabSize(),
result
);
}
public getAllOverviewRulerDecorations(): ViewModelDecoration[] {
return this.decorations.getAllOverviewRulerDecorations();
}
@@ -558,4 +568,96 @@ export class ViewModel extends EventEmitter implements IViewModel {
public validateModelPosition(position: editorCommon.IPosition): Position {
return this.model.validatePosition(position);
}
public getPlainTextToCopy(ranges: Range[], enableEmptySelectionClipboard: boolean): string {
let newLineCharacter = this.getEOL();
if (ranges.length === 1) {
let range: Range = ranges[0];
if (range.isEmpty()) {
if (enableEmptySelectionClipboard) {
let modelLineNumber = this.coordinatesConverter.convertViewPositionToModelPosition(new Position(range.startLineNumber, 1)).lineNumber;
return this.getModelLineContent(modelLineNumber) + newLineCharacter;
} else {
return '';
}
}
return this.getValueInRange(range, editorCommon.EndOfLinePreference.TextDefined);
} else {
ranges = ranges.slice(0).sort(Range.compareRangesUsingStarts);
let result: string[] = [];
for (let i = 0; i < ranges.length; i++) {
result.push(this.getValueInRange(ranges[i], editorCommon.EndOfLinePreference.TextDefined));
}
return result.join(newLineCharacter);
}
}
public getHTMLToCopy(ranges: Range[], enableEmptySelectionClipboard: boolean): string {
// TODO: adopt new view line tokens.
let rules: { [key: string]: string } = {};
let colorMap = TokenizationRegistry.getColorMap();
for (let i = 1, len = colorMap.length; i < len; i++) {
let color = colorMap[i];
rules[`mtk${i}`] = `color: ${color.toRGBHex()};`;
}
rules['mtki'] = 'font-style: italic;';
rules['mtkb'] = 'font-weight: bold;';
rules['mtku'] = 'text-decoration: underline;';
let defaultForegroundColor = colorMap[1].toRGBHex();
let defaultBackgroundColor = colorMap[2].toRGBHex();
let fontInfo = this.configuration.editor.fontInfo;
let output = `<div style="color: ${defaultForegroundColor}; background-color: ${defaultBackgroundColor};` +
`font-family: ${fontInfo.fontFamily}; font-weight: ${fontInfo.fontWeight}; font-size: ${fontInfo.fontSize}px; line-height: ${fontInfo.lineHeight}px">`;
if (ranges.length === 1) {
let range: Range = ranges[0];
if (range.isEmpty()) {
if (enableEmptySelectionClipboard) {
let modelLineNumber = this.coordinatesConverter.convertViewPositionToModelPosition(new Position(range.startLineNumber, 1)).lineNumber;
let viewLineStart = new Position(range.startLineNumber, 1);
let viewLineEnd = new Position(range.startLineNumber, this.getLineMaxColumn(range.startLineNumber));
let startOffset = this.coordinatesConverter.convertViewPositionToModelPosition(viewLineStart).column - 1;
let endOffset = this.coordinatesConverter.convertViewPositionToModelPosition(viewLineEnd).column - 1;
let viewLineRenderingData = this.getViewLineRenderingData(new Range(viewLineStart.lineNumber, viewLineStart.column, viewLineEnd.lineNumber, viewLineEnd.column), modelLineNumber);
let html = tokenizeLineToHTML(this.getModelLineContent(modelLineNumber),
viewLineRenderingData.tokens,
rules,
{
startOffset: startOffset,
endOffset: endOffset,
tabSize: this.getTabSize()
});
output += `${html}`;
} else {
return '';
}
} else {
for (let i = 0, lineCount = range.endLineNumber - range.startLineNumber; i <= lineCount; i++) {
let viewLineRenderingData = this.getViewLineRenderingData(range, range.startLineNumber + i);
let lineContent = viewLineRenderingData.content;
let startOffset = i === 0 ? range.startColumn - 1 : 0;
let endOffset = i === lineCount ? range.endColumn - 1 : lineContent.length;
let html = tokenizeLineToHTML(lineContent, viewLineRenderingData.tokens, rules,
{
startOffset: startOffset,
endOffset: endOffset,
tabSize: this.getTabSize()
});
output += `${html}`;
}
}
}
output += '</div>';
return output;
}
}
@@ -13,6 +13,7 @@ import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation
import { findFocusedEditor } from 'vs/editor/common/config/config';
import * as editorCommon from 'vs/editor/common/editorCommon';
import { editorAction, IActionOptions, EditorAction } from 'vs/editor/common/editorCommonExtensions';
import { CopyOptions } from 'vs/editor/common/controller/textAreaHandler';
import EditorContextKeys = editorCommon.EditorContextKeys;
@@ -25,6 +26,14 @@ function conditionalEditorAction(testCommand: string) {
return editorAction;
}
function conditionalCopyWithSyntaxHighlighting() {
if (browser.isEdgeOrIE || !browser.supportsExecCommand('copy')) {
return () => { };
}
return editorAction;
}
abstract class ExecCommandAction extends EditorAction {
private browserCommand: string;
@@ -136,3 +145,32 @@ class ExecCommandPasteAction extends ExecCommandAction {
});
}
}
@conditionalCopyWithSyntaxHighlighting()
class ExecCommandCopyWithSyntaxHighlightingAction extends ExecCommandAction {
constructor() {
super('copy', {
id: 'editor.action.clipboardCopyWithSyntaxHighlightingAction',
label: nls.localize('actions.clipboard.copyWithSyntaxHighlightingLabel', "Copy With Syntax Highlighting"),
alias: 'Copy With Syntax Highlighting',
precondition: null,
kbOpts: {
kbExpr: EditorContextKeys.TextFocus,
primary: null
}
});
}
public run(accessor: ServicesAccessor, editor: editorCommon.ICommonCodeEditor): void {
var enableEmptySelectionClipboard = editor.getConfiguration().contribInfo.emptySelectionClipboard && browser.enableEmptySelectionClipboard;
if (!enableEmptySelectionClipboard && editor.getSelection().isEmpty()) {
return;
}
CopyOptions.forceCopyWithSyntaxHighlighting = true;
super.run(accessor, editor);
CopyOptions.forceCopyWithSyntaxHighlighting = false;
}
}
@@ -505,7 +505,7 @@ abstract class FoldingAction<T> extends EditorAction {
if (!foldingController) {
return;
}
this.reportTelemetry(accessor);
this.reportTelemetry(accessor, editor);
this.invoke(foldingController, editor, args);
}
@@ -118,7 +118,7 @@ export class DefinitionActionConfig {
public openToSide = false,
public openInPeek = false,
public filterCurrent = true,
public noResultsMessage = nls.localize('generic.noResults', "Could not find anything")
public noResultsMessage = nls.localize('generic.noResults', "No definition found")
) {
//
}
@@ -322,7 +322,7 @@ class MarkerNavigationAction extends EditorAction {
}
let model = controller.getOrCreateModel();
telemetryService.publicLog('zoneWidgetShown', { mode: 'go to error' });
telemetryService.publicLog('zoneWidgetShown', { mode: 'go to error', ...editor.getTelemetryData() });
if (model) {
if (this._isNext) {
model.next();
@@ -23,6 +23,7 @@ import { TokenizationRegistry, LanguageIdentifier, FontStyle, StandardTokenType
import { CharCode } from 'vs/base/common/charCode';
import { findMatchingThemeRule } from 'vs/editor/electron-browser/textMate/TMHelper';
import { IThemeService } from 'vs/workbench/services/themes/common/themeService';
import { Color } from 'vs/base/common/color';
@editorContribution
class InspectTMScopesController extends Disposable implements IEditorContribution {
@@ -114,8 +115,8 @@ interface IDecodedMetadata {
languageIdentifier: LanguageIdentifier;
tokenType: StandardTokenType;
fontStyle: FontStyle;
foreground: string;
background: string;
foreground: Color;
background: Color;
}
function renderTokenText(tokenText: string): string {
@@ -237,8 +238,8 @@ class InspectTMScopesWidget extends Disposable implements IContentWidget {
result += `<tr><td class="tm-metadata-key">language</td><td class="tm-metadata-value">${escape(metadata.languageIdentifier.language)}</td>`;
result += `<tr><td class="tm-metadata-key">token type</td><td class="tm-metadata-value">${this._tokenTypeToString(metadata.tokenType)}</td>`;
result += `<tr><td class="tm-metadata-key">font style</td><td class="tm-metadata-value">${this._fontStyleToString(metadata.fontStyle)}</td>`;
result += `<tr><td class="tm-metadata-key">foreground</td><td class="tm-metadata-value">${metadata.foreground}</td>`;
result += `<tr><td class="tm-metadata-key">background</td><td class="tm-metadata-value">${metadata.background}</td>`;
result += `<tr><td class="tm-metadata-key">foreground</td><td class="tm-metadata-value">${metadata.foreground.toRGBAHex()}</td>`;
result += `<tr><td class="tm-metadata-key">background</td><td class="tm-metadata-value">${metadata.background.toRGBAHex()}</td>`;
result += `</tbody></table>`;
let theme = this._themeService.getColorTheme();
@@ -20,6 +20,7 @@ import { CharCode } from 'vs/base/common/charCode';
import { IStandaloneColorService } from 'vs/editor/common/services/standaloneColorService';
import { NULL_STATE, nullTokenize, nullTokenize2 } from 'vs/editor/common/modes/nullMode';
import { Token } from 'vs/editor/common/core/token';
import { Color } from 'vs/base/common/color';
@editorContribution
class InspectTokensController extends Disposable implements IEditorContribution {
@@ -109,8 +110,8 @@ interface IDecodedMetadata {
languageIdentifier: LanguageIdentifier;
tokenType: StandardTokenType;
fontStyle: FontStyle;
foreground: string;
background: string;
foreground: Color;
background: Color;
}
function renderTokenText(tokenText: string): string {
@@ -235,8 +236,8 @@ class InspectTokensWidget extends Disposable implements IContentWidget {
result += `<tr><td class="tm-metadata-key">language</td><td class="tm-metadata-value">${escape(metadata.languageIdentifier.language)}</td>`;
result += `<tr><td class="tm-metadata-key">token type</td><td class="tm-metadata-value">${this._tokenTypeToString(metadata.tokenType)}</td>`;
result += `<tr><td class="tm-metadata-key">font style</td><td class="tm-metadata-value">${this._fontStyleToString(metadata.fontStyle)}</td>`;
result += `<tr><td class="tm-metadata-key">foreground</td><td class="tm-metadata-value">${metadata.foreground}</td>`;
result += `<tr><td class="tm-metadata-key">background</td><td class="tm-metadata-value">${metadata.background}</td>`;
result += `<tr><td class="tm-metadata-key">foreground</td><td class="tm-metadata-value">${metadata.foreground.toRGBHex()}</td>`;
result += `<tr><td class="tm-metadata-key">background</td><td class="tm-metadata-value">${metadata.background.toRGBHex()}</td>`;
result += `</tbody></table>`;
result += `<hr/>`;
@@ -593,7 +593,7 @@ export class SuggestWidget implements IContentWidget, IDelegate<ICompletionItem>
} else {
const { stats } = this.completionModel;
stats['wasAutomaticallyTriggered'] = !!isAuto;
this.telemetryService.publicLog('suggestWidget', stats);
this.telemetryService.publicLog('suggestWidget', { ...stats, ...this.editor.getTelemetryData() });
this.list.splice(0, this.list.length, this.completionModel.items);
this.list.setFocus([this.completionModel.topScoreIdx]);
@@ -694,7 +694,7 @@ export class SuggestWidget implements IContentWidget, IDelegate<ICompletionItem>
this.setState(State.Details);
this.editor.focus();
this.telemetryService.publicLog('suggestWidget:toggleDetails');
this.telemetryService.publicLog('suggestWidget:toggleDetails', this.editor.getTelemetryData());
}
private show(): void {
@@ -21,7 +21,8 @@ import { grammarsExtPoint, IEmbeddedLanguagesMap, ITMSyntaxExtensionPoint } from
import { TokenizationResult, TokenizationResult2 } from 'vs/editor/common/core/token';
import { TokenMetadata } from 'vs/editor/common/model/tokensBinaryEncoding';
import { nullTokenize2 } from 'vs/editor/common/modes/nullMode';
import { hexToCSS } from 'vs/base/common/color';
import { generateTokensCSSForColorMap } from 'vs/editor/common/modes/supports/tokenization';
import { Color } from 'vs/base/common/color';
export class TMScopeRegistry {
@@ -150,23 +151,19 @@ export class MainProcessTextMateSyntax implements ITextMateService {
});
}
private static _generateCSS(colorMap: string[]): string {
let rules: string[] = [];
private static _toColorMap(colorMap: string[]): Color[] {
let result: Color[] = [null];
for (let i = 1, len = colorMap.length; i < len; i++) {
let color = colorMap[i];
rules[i] = `.mtk${i} { color: ${hexToCSS(color)}; }`;
result[i] = Color.fromHex(colorMap[i]);
}
rules.push('.mtki { font-style: italic; }');
rules.push('.mtkb { font-weight: bold; }');
rules.push('.mtku { text-decoration: underline; }');
return rules.join('\n');
return result;
}
private _updateTheme(): void {
let colorTheme = this._themeService.getColorTheme();
this._grammarRegistry.setTheme({ name: colorTheme.label, settings: colorTheme.settings });
let colorMap = this._grammarRegistry.getColorMap();
let cssRules = MainProcessTextMateSyntax._generateCSS(colorMap);
let colorMap = MainProcessTextMateSyntax._toColorMap(this._grammarRegistry.getColorMap());
let cssRules = generateTokensCSSForColorMap(colorMap);
this._styleElement.innerHTML = cssRules;
TokenizationRegistry.setColorMap(colorMap);
}
@@ -53,6 +53,14 @@ class SingleLineTestModel implements ISimpleModel {
getLineCount(): number {
return 1;
}
public getPlainTextToCopy(ranges: Range[], enableEmptySelectionClipboard: boolean): string {
return '';
}
public getHTMLToCopy(ranges: Range[], enableEmptySelectionClipboard: boolean): string {
return '';
}
}
class TestView {
@@ -180,4 +188,4 @@ const TESTS = [
TESTS.forEach((t) => {
document.body.appendChild(doCreateTest(TextAreaStrategy.NVDA, t.description, t.in, t.out));
document.body.appendChild(doCreateTest(TextAreaStrategy.IENarrator, t.description, t.in, t.out));
});
});
@@ -4,9 +4,9 @@
*--------------------------------------------------------------------------------------------*/
'use strict';
import { Constants, MinimapCharRenderer } from 'vs/editor/common/view/minimapCharRenderer';
import { Constants, MinimapCharRenderer, ParsedColor } from 'vs/editor/common/view/minimapCharRenderer';
import { MinimapCharRendererFactory } from 'vs/editor/test/common/view/minimapCharRendererFactory';
import { createMinimapCharRenderer } from 'vs/editor/common/view/runtimeMinimapCharRenderer';
import { getOrCreateMinimapCharRenderer } from 'vs/editor/common/view/runtimeMinimapCharRenderer';
let canvas = <HTMLCanvasElement>document.getElementById('my-canvas');
let ctx = canvas.getContext('2d');
@@ -26,23 +26,55 @@ for (let chCode = Constants.START_CH_CODE; chCode <= Constants.END_CH_CODE; chCo
let sampleData = ctx.getImageData(0, 4, Constants.SAMPLED_CHAR_WIDTH * Constants.CHAR_COUNT, Constants.SAMPLED_CHAR_HEIGHT);
let minimapCharRenderer = MinimapCharRendererFactory.create(sampleData.data);
renderImageData(sampleData.data, sampleData.width, sampleData.height, 10, 100);
renderImageData(sampleData, 10, 100);
renderMinimapCharRenderer(minimapCharRenderer, 400);
renderMinimapCharRenderer(createMinimapCharRenderer(), 600);
renderMinimapCharRenderer(getOrCreateMinimapCharRenderer(), 600);
function createFakeImageData(width: number, height: number): ImageData {
return {
width: width,
height: height,
data: new Uint8ClampedArray(width * height * Constants.RGBA_CHANNELS_CNT)
};
}
function renderMinimapCharRenderer(minimapCharRenderer: MinimapCharRenderer, y: number): void {
let x2 = new Uint8ClampedArray(Constants.x2_CHAR_HEIGHT * Constants.x2_CHAR_WIDTH * Constants.RGBA_CHANNELS_CNT * Constants.CHAR_COUNT);
for (let chCode = Constants.START_CH_CODE; chCode <= Constants.END_CH_CODE; chCode++) {
minimapCharRenderer.x2RenderChar(x2, Constants.CHAR_COUNT, 0, chCode - Constants.START_CH_CODE, chCode);
}
renderImageData(x2, Constants.x2_CHAR_WIDTH * Constants.CHAR_COUNT, Constants.x2_CHAR_HEIGHT, 10, y);
let background = new ParsedColor(0, 0, 0);
let color = new ParsedColor(255, 255, 255);
let x1 = new Uint8ClampedArray(Constants.x1_CHAR_HEIGHT * Constants.x1_CHAR_WIDTH * Constants.RGBA_CHANNELS_CNT * Constants.CHAR_COUNT);
for (let chCode = Constants.START_CH_CODE; chCode <= Constants.END_CH_CODE; chCode++) {
minimapCharRenderer.x1RenderChar(x1, Constants.CHAR_COUNT, 0, chCode - Constants.START_CH_CODE, chCode);
{
let x2 = createFakeImageData(Constants.x2_CHAR_WIDTH * Constants.CHAR_COUNT, Constants.x2_CHAR_HEIGHT);
// set the background color
for (let i = 0, len = x2.data.length / 4; i < len; i++) {
x2.data[4 * i + 0] = background.r;
x2.data[4 * i + 1] = background.g;
x2.data[4 * i + 2] = background.b;
x2.data[4 * i + 3] = 255;
}
let dx = 0;
for (let chCode = Constants.START_CH_CODE; chCode <= Constants.END_CH_CODE; chCode++) {
minimapCharRenderer.x2RenderChar(x2, dx, 0, chCode, color, background);
dx += Constants.x2_CHAR_WIDTH;
}
renderImageData(x2, 10, y);
}
{
let x1 = createFakeImageData(Constants.x1_CHAR_WIDTH * Constants.CHAR_COUNT, Constants.x1_CHAR_HEIGHT);
// set the background color
for (let i = 0, len = x1.data.length / 4; i < len; i++) {
x1.data[4 * i + 0] = background.r;
x1.data[4 * i + 1] = background.g;
x1.data[4 * i + 2] = background.b;
x1.data[4 * i + 3] = 255;
}
let dx = 0;
for (let chCode = Constants.START_CH_CODE; chCode <= Constants.END_CH_CODE; chCode++) {
minimapCharRenderer.x1RenderChar(x1, dx, 0, chCode, color, background);
dx += Constants.x1_CHAR_WIDTH;
}
renderImageData(x1, 10, y + 100);
}
renderImageData(x1, Constants.x1_CHAR_WIDTH * Constants.CHAR_COUNT, Constants.x1_CHAR_HEIGHT, 10, y + 100);
}
(function () {
@@ -51,8 +83,8 @@ function renderMinimapCharRenderer(minimapCharRenderer: MinimapCharRenderer, y:
let charCode = charIndex + Constants.START_CH_CODE;
r += '\n\n// ' + String.fromCharCode(charCode);
for (let i = 0; i < Constants.x2_CHAR_HEIGHT * Constants.x2_CHAR_WIDTH * Constants.CA_CHANNELS_CNT; i++) {
if (i % 4 === 0) {
for (let i = 0; i < Constants.x2_CHAR_HEIGHT * Constants.x2_CHAR_WIDTH; i++) {
if (i % 2 === 0) {
r += '\n';
}
r += minimapCharRenderer.x2charData[offset] + ',';
@@ -70,10 +102,8 @@ function renderMinimapCharRenderer(minimapCharRenderer: MinimapCharRenderer, y:
let charCode = charIndex + Constants.START_CH_CODE;
r += '\n\n// ' + String.fromCharCode(charCode);
for (let i = 0; i < Constants.x1_CHAR_HEIGHT * Constants.x1_CHAR_WIDTH * Constants.CA_CHANNELS_CNT; i++) {
if (i % 2 === 0) {
r += '\n';
}
for (let i = 0; i < Constants.x1_CHAR_HEIGHT * Constants.x1_CHAR_WIDTH; i++) {
r += '\n';
r += minimapCharRenderer.x1charData[offset] + ',';
offset++;
}
@@ -85,16 +115,16 @@ function renderMinimapCharRenderer(minimapCharRenderer: MinimapCharRenderer, y:
function renderImageData(data: Uint8ClampedArray, width: number, height: number, left: number, top: number): void {
function renderImageData(imageData: ImageData, left: number, top: number): void {
let output = '';
var offset = 0;
var PX_SIZE = 15;
for (var i = 0; i < height; i++) {
for (var j = 0; j < width; j++) {
var R = data[offset];
var G = data[offset + 1];
var B = data[offset + 2];
var A = data[offset + 3];
for (var i = 0; i < imageData.height; i++) {
for (var j = 0; j < imageData.width; j++) {
var R = imageData.data[offset];
var G = imageData.data[offset + 1];
var B = imageData.data[offset + 2];
var A = imageData.data[offset + 3];
offset += 4;
output += `<div style="position:absolute;top:${PX_SIZE * i}px;left:${PX_SIZE * j}px;width:${PX_SIZE}px;height:${PX_SIZE}px;background:rgba(${R},${G},${B},${A / 256})"></div>`;
@@ -105,8 +135,8 @@ function renderImageData(data: Uint8ClampedArray, width: number, height: number,
domNode.style.position = 'absolute';
domNode.style.top = top + 'px';
domNode.style.left = left + 'px';
domNode.style.width = (width * PX_SIZE) + 'px';
domNode.style.height = (height * PX_SIZE) + 'px';
domNode.style.width = (imageData.width * PX_SIZE) + 'px';
domNode.style.height = (imageData.height * PX_SIZE) + 'px';
domNode.style.border = '1px solid #ccc';
domNode.style.background = '#000000';
domNode.innerHTML = output;
@@ -910,6 +910,112 @@ suite('Editor Controller - Cursor', () => {
model.dispose();
});
test('issue #20087: column select with mouse', () => {
let model = Model.createFromString([
'<property id="SomeThing" key="SomeKey" value="000"/>',
'<property id="SomeThing" key="SomeKey" value="000"/>',
'<property id="SomeThing" Key="SomeKey" value="000"/>',
'<property id="SomeThing" key="SomeKey" value="000"/>',
'<property id="SomeThing" key="SoMEKEy" value="000"/>',
'<property id="SomeThing" key="SomeKey" value="000"/>',
'<property id="SomeThing" key="SomeKey" value="000"/>',
'<property id="SomeThing" key="SomeKey" valuE="000"/>',
'<property id="SomeThing" key="SomeKey" value="000"/>',
'<property id="SomeThing" key="SomeKey" value="00X"/>',
].join('\n'));
let cursor = new Cursor(new MockConfiguration(null), model, viewModelHelper(model), true);
moveTo(cursor, 10, 10, false);
assertCursor(cursor, new Position(10, 10));
cursorCommand(cursor, H.ColumnSelect, {
position: new Position(1, 1),
viewPosition: new Position(1, 1),
mouseColumn: 1
});
assertCursor(cursor, [
new Selection(10, 10, 10, 1),
new Selection(9, 10, 9, 1),
new Selection(8, 10, 8, 1),
new Selection(7, 10, 7, 1),
new Selection(6, 10, 6, 1),
new Selection(5, 10, 5, 1),
new Selection(4, 10, 4, 1),
new Selection(3, 10, 3, 1),
new Selection(2, 10, 2, 1),
new Selection(1, 10, 1, 1),
]);
cursorCommand(cursor, H.ColumnSelect, {
position: new Position(1, 1),
viewPosition: new Position(1, 1),
mouseColumn: 1
});
assertCursor(cursor, [
new Selection(10, 10, 10, 1),
new Selection(9, 10, 9, 1),
new Selection(8, 10, 8, 1),
new Selection(7, 10, 7, 1),
new Selection(6, 10, 6, 1),
new Selection(5, 10, 5, 1),
new Selection(4, 10, 4, 1),
new Selection(3, 10, 3, 1),
new Selection(2, 10, 2, 1),
new Selection(1, 10, 1, 1),
]);
cursor.dispose();
model.dispose();
});
test('issue #20087: column select with keyboard', () => {
let model = Model.createFromString([
'<property id="SomeThing" key="SomeKey" value="000"/>',
'<property id="SomeThing" key="SomeKey" value="000"/>',
'<property id="SomeThing" Key="SomeKey" value="000"/>',
'<property id="SomeThing" key="SomeKey" value="000"/>',
'<property id="SomeThing" key="SoMEKEy" value="000"/>',
'<property id="SomeThing" key="SomeKey" value="000"/>',
'<property id="SomeThing" key="SomeKey" value="000"/>',
'<property id="SomeThing" key="SomeKey" valuE="000"/>',
'<property id="SomeThing" key="SomeKey" value="000"/>',
'<property id="SomeThing" key="SomeKey" value="00X"/>',
].join('\n'));
let cursor = new Cursor(new MockConfiguration(null), model, viewModelHelper(model), true);
moveTo(cursor, 10, 10, false);
assertCursor(cursor, new Position(10, 10));
cursorCommand(cursor, H.CursorColumnSelectLeft);
assertCursor(cursor, [
new Selection(10, 10, 10, 9)
]);
cursorCommand(cursor, H.CursorColumnSelectLeft);
assertCursor(cursor, [
new Selection(10, 10, 10, 8)
]);
cursorCommand(cursor, H.CursorColumnSelectRight);
assertCursor(cursor, [
new Selection(10, 10, 10, 9)
]);
cursorCommand(cursor, H.CursorColumnSelectUp);
assertCursor(cursor, [
new Selection(10, 10, 10, 9),
new Selection(9, 10, 9, 9),
]);
cursorCommand(cursor, H.CursorColumnSelectDown);
assertCursor(cursor, [
new Selection(10, 10, 10, 9)
]);
cursor.dispose();
model.dispose();
});
test('column select with keyboard', () => {
let model = Model.createFromString([
'var gulp = require("gulp");',
@@ -472,4 +472,12 @@ class SimpleModel implements ISimpleModel {
public getLineCount(): number {
return this._lines.length;
}
public getPlainTextToCopy(ranges: Range[], enableEmptySelectionClipboard: boolean): string {
return '';
}
public getHTMLToCopy(ranges: Range[], enableEmptySelectionClipboard: boolean): string {
return '';
}
}
@@ -0,0 +1,292 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as assert from 'assert';
import { LineTokens } from 'vs/editor/common/core/lineTokens';
import { MetadataConsts } from 'vs/editor/common/modes';
import { ViewLineToken } from 'vs/editor/common/core/viewLineToken';
suite('LineTokens', () => {
interface ILineToken {
startIndex: number;
foreground: number;
}
function createLineTokens(text: string, tokens: ILineToken[]): LineTokens {
let binTokens = new Uint32Array(tokens.length << 1);
for (let i = 0, len = tokens.length; i < len; i++) {
let token = tokens[i];
binTokens[(i << 1)] = token.startIndex;
binTokens[(i << 1) + 1] = (
token.foreground << MetadataConsts.FOREGROUND_OFFSET
) >>> 0;
}
return new LineTokens(binTokens, text);
}
function createTestLineTokens(): LineTokens {
return createLineTokens(
'Hello world, this is a lovely day',
[
{ startIndex: 0, foreground: 1 }, // Hello_
{ startIndex: 6, foreground: 2 }, // world,_
{ startIndex: 13, foreground: 3 }, // this_
{ startIndex: 18, foreground: 4 }, // is_
{ startIndex: 21, foreground: 5 }, // a_
{ startIndex: 23, foreground: 6 }, // lovely_
{ startIndex: 30, foreground: 7 }, // day
]
);
}
test('basics', () => {
const lineTokens = createTestLineTokens();
assert.equal(lineTokens.getLineContent(), 'Hello world, this is a lovely day');
assert.equal(lineTokens.getLineLength(), 33);
assert.equal(lineTokens.getTokenCount(), 7);
assert.equal(lineTokens.getTokenStartOffset(0), 0);
assert.equal(lineTokens.getTokenEndOffset(0), 6);
assert.equal(lineTokens.getTokenStartOffset(1), 6);
assert.equal(lineTokens.getTokenEndOffset(1), 13);
assert.equal(lineTokens.getTokenStartOffset(2), 13);
assert.equal(lineTokens.getTokenEndOffset(2), 18);
assert.equal(lineTokens.getTokenStartOffset(3), 18);
assert.equal(lineTokens.getTokenEndOffset(3), 21);
assert.equal(lineTokens.getTokenStartOffset(4), 21);
assert.equal(lineTokens.getTokenEndOffset(4), 23);
assert.equal(lineTokens.getTokenStartOffset(5), 23);
assert.equal(lineTokens.getTokenEndOffset(5), 30);
assert.equal(lineTokens.getTokenStartOffset(6), 30);
assert.equal(lineTokens.getTokenEndOffset(6), 33);
});
test('findToken', () => {
const lineTokens = createTestLineTokens();
assert.equal(lineTokens.findTokenIndexAtOffset(0), 0);
assert.equal(lineTokens.findTokenIndexAtOffset(1), 0);
assert.equal(lineTokens.findTokenIndexAtOffset(2), 0);
assert.equal(lineTokens.findTokenIndexAtOffset(3), 0);
assert.equal(lineTokens.findTokenIndexAtOffset(4), 0);
assert.equal(lineTokens.findTokenIndexAtOffset(5), 0);
assert.equal(lineTokens.findTokenIndexAtOffset(6), 1);
assert.equal(lineTokens.findTokenIndexAtOffset(7), 1);
assert.equal(lineTokens.findTokenIndexAtOffset(8), 1);
assert.equal(lineTokens.findTokenIndexAtOffset(9), 1);
assert.equal(lineTokens.findTokenIndexAtOffset(10), 1);
assert.equal(lineTokens.findTokenIndexAtOffset(11), 1);
assert.equal(lineTokens.findTokenIndexAtOffset(12), 1);
assert.equal(lineTokens.findTokenIndexAtOffset(13), 2);
assert.equal(lineTokens.findTokenIndexAtOffset(14), 2);
assert.equal(lineTokens.findTokenIndexAtOffset(15), 2);
assert.equal(lineTokens.findTokenIndexAtOffset(16), 2);
assert.equal(lineTokens.findTokenIndexAtOffset(17), 2);
assert.equal(lineTokens.findTokenIndexAtOffset(18), 3);
assert.equal(lineTokens.findTokenIndexAtOffset(19), 3);
assert.equal(lineTokens.findTokenIndexAtOffset(20), 3);
assert.equal(lineTokens.findTokenIndexAtOffset(21), 4);
assert.equal(lineTokens.findTokenIndexAtOffset(22), 4);
assert.equal(lineTokens.findTokenIndexAtOffset(23), 5);
assert.equal(lineTokens.findTokenIndexAtOffset(24), 5);
assert.equal(lineTokens.findTokenIndexAtOffset(25), 5);
assert.equal(lineTokens.findTokenIndexAtOffset(26), 5);
assert.equal(lineTokens.findTokenIndexAtOffset(27), 5);
assert.equal(lineTokens.findTokenIndexAtOffset(28), 5);
assert.equal(lineTokens.findTokenIndexAtOffset(29), 5);
assert.equal(lineTokens.findTokenIndexAtOffset(30), 6);
assert.equal(lineTokens.findTokenIndexAtOffset(31), 6);
assert.equal(lineTokens.findTokenIndexAtOffset(32), 6);
assert.equal(lineTokens.findTokenIndexAtOffset(33), 6);
assert.equal(lineTokens.findTokenIndexAtOffset(34), 6);
assert.equal(lineTokens.findTokenAtOffset(7).startOffset, 6);
assert.equal(lineTokens.findTokenAtOffset(7).endOffset, 13);
assert.equal(lineTokens.findTokenAtOffset(7).foregroundId, 2);
assert.equal(lineTokens.findTokenAtOffset(30).startOffset, 30);
assert.equal(lineTokens.findTokenAtOffset(30).endOffset, 33);
assert.equal(lineTokens.findTokenAtOffset(30).foregroundId, 7);
});
test('iterate forward', () => {
const lineTokens = createTestLineTokens();
let token = lineTokens.firstToken();
assert.equal(token.startOffset, 0);
assert.equal(token.endOffset, 6);
assert.equal(token.foregroundId, 1);
token = token.next();
assert.equal(token.startOffset, 6);
assert.equal(token.endOffset, 13);
assert.equal(token.foregroundId, 2);
token = token.next();
assert.equal(token.startOffset, 13);
assert.equal(token.endOffset, 18);
assert.equal(token.foregroundId, 3);
token = token.next();
assert.equal(token.startOffset, 18);
assert.equal(token.endOffset, 21);
assert.equal(token.foregroundId, 4);
token = token.next();
assert.equal(token.startOffset, 21);
assert.equal(token.endOffset, 23);
assert.equal(token.foregroundId, 5);
token = token.next();
assert.equal(token.startOffset, 23);
assert.equal(token.endOffset, 30);
assert.equal(token.foregroundId, 6);
token = token.next();
assert.equal(token.startOffset, 30);
assert.equal(token.endOffset, 33);
assert.equal(token.foregroundId, 7);
token = token.next();
assert.equal(token, null);
});
test('iterate backward', () => {
const lineTokens = createTestLineTokens();
let token = lineTokens.lastToken();
assert.equal(token.startOffset, 30);
assert.equal(token.endOffset, 33);
assert.equal(token.foregroundId, 7);
token = token.prev();
assert.equal(token.startOffset, 23);
assert.equal(token.endOffset, 30);
assert.equal(token.foregroundId, 6);
token = token.prev();
assert.equal(token.startOffset, 21);
assert.equal(token.endOffset, 23);
assert.equal(token.foregroundId, 5);
token = token.prev();
assert.equal(token.startOffset, 18);
assert.equal(token.endOffset, 21);
assert.equal(token.foregroundId, 4);
token = token.prev();
assert.equal(token.startOffset, 13);
assert.equal(token.endOffset, 18);
assert.equal(token.foregroundId, 3);
token = token.prev();
assert.equal(token.startOffset, 6);
assert.equal(token.endOffset, 13);
assert.equal(token.foregroundId, 2);
token = token.prev();
assert.equal(token.startOffset, 0);
assert.equal(token.endOffset, 6);
assert.equal(token.foregroundId, 1);
token = token.prev();
assert.equal(token, null);
});
interface ITestViewLineToken {
endIndex: number;
foreground: number;
}
function assertViewLineTokens(actual: ViewLineToken[], expected: ITestViewLineToken[]): void {
assert.deepEqual(actual.map(token => {
return {
endIndex: token.endIndex,
foreground: token.getForeground()
};
}), expected);
}
test('inflate', () => {
const lineTokens = createTestLineTokens();
assertViewLineTokens(lineTokens.inflate(), [
{ endIndex: 6, foreground: 1 },
{ endIndex: 13, foreground: 2 },
{ endIndex: 18, foreground: 3 },
{ endIndex: 21, foreground: 4 },
{ endIndex: 23, foreground: 5 },
{ endIndex: 30, foreground: 6 },
{ endIndex: 33, foreground: 7 },
]);
});
test('sliceAndInflate', () => {
const lineTokens = createTestLineTokens();
assertViewLineTokens(lineTokens.sliceAndInflate(0, 33, 0), [
{ endIndex: 6, foreground: 1 },
{ endIndex: 13, foreground: 2 },
{ endIndex: 18, foreground: 3 },
{ endIndex: 21, foreground: 4 },
{ endIndex: 23, foreground: 5 },
{ endIndex: 30, foreground: 6 },
{ endIndex: 33, foreground: 7 },
]);
assertViewLineTokens(lineTokens.sliceAndInflate(0, 32, 0), [
{ endIndex: 6, foreground: 1 },
{ endIndex: 13, foreground: 2 },
{ endIndex: 18, foreground: 3 },
{ endIndex: 21, foreground: 4 },
{ endIndex: 23, foreground: 5 },
{ endIndex: 30, foreground: 6 },
{ endIndex: 32, foreground: 7 },
]);
assertViewLineTokens(lineTokens.sliceAndInflate(0, 30, 0), [
{ endIndex: 6, foreground: 1 },
{ endIndex: 13, foreground: 2 },
{ endIndex: 18, foreground: 3 },
{ endIndex: 21, foreground: 4 },
{ endIndex: 23, foreground: 5 },
{ endIndex: 30, foreground: 6 }
]);
assertViewLineTokens(lineTokens.sliceAndInflate(0, 30, 1), [
{ endIndex: 7, foreground: 1 },
{ endIndex: 14, foreground: 2 },
{ endIndex: 19, foreground: 3 },
{ endIndex: 22, foreground: 4 },
{ endIndex: 24, foreground: 5 },
{ endIndex: 31, foreground: 6 }
]);
assertViewLineTokens(lineTokens.sliceAndInflate(6, 18, 0), [
{ endIndex: 7, foreground: 2 },
{ endIndex: 12, foreground: 3 }
]);
assertViewLineTokens(lineTokens.sliceAndInflate(7, 18, 0), [
{ endIndex: 6, foreground: 2 },
{ endIndex: 11, foreground: 3 }
]);
assertViewLineTokens(lineTokens.sliceAndInflate(6, 17, 0), [
{ endIndex: 7, foreground: 2 },
{ endIndex: 11, foreground: 3 }
]);
assertViewLineTokens(lineTokens.sliceAndInflate(6, 19, 0), [
{ endIndex: 7, foreground: 2 },
{ endIndex: 12, foreground: 3 },
{ endIndex: 13, foreground: 4 },
]);
});
});
@@ -30,6 +30,10 @@ export class MockConfiguration extends CommonEditorConfiguration {
return true;
}
protected _getPixelRatio(): number {
return 1;
}
protected readConfiguration(styling: BareFontInfo): FontInfo {
return new FontInfo({
fontFamily: 'mockFont',
@@ -9,12 +9,18 @@ import { LineTokens } from 'vs/editor/common/core/lineTokens';
import { ModelLine, ILineEdit, LineMarker, MarkersTracker } from 'vs/editor/common/model/modelLine';
import { MetadataConsts } from 'vs/editor/common/modes';
import { Position } from 'vs/editor/common/core/position';
import { TokenMetadata } from 'vs/editor/common/model/tokensBinaryEncoding';
import { ViewLineToken, ViewLineTokenFactory } from 'vs/editor/common/core/viewLineToken';
function assertLineTokens(_actual: LineTokens, _expected: TestToken[]): void {
let expected = TokenMetadata.inflateArr(TestToken.toTokens(_expected), _actual.getLineLength());
let expected = ViewLineTokenFactory.inflateArr(TestToken.toTokens(_expected), _actual.getLineLength());
let actual = _actual.inflate();
assert.deepEqual(actual, expected);
let decode = (token: ViewLineToken) => {
return {
endIndex: token.endIndex,
type: token.getType()
};
};
assert.deepEqual(actual.map(decode), expected.map(decode));
}
const NO_TAB_SIZE = 0;
@@ -298,7 +304,7 @@ suite('Editor Model - modelLine.applyEdits text & tokens', () => {
line.applyEdits(new MarkersTracker(), edits, NO_TAB_SIZE);
assert.equal(line.text, expectedText);
assertLineTokens(line.getTokens(0, []), expectedTokens);
assertLineTokens(line.getTokens(0), expectedTokens);
}
test('insertion on empty line', () => {
@@ -309,7 +315,7 @@ suite('Editor Model - modelLine.applyEdits text & tokens', () => {
line.setTokens(0, new Uint32Array(0));
line.applyEdits(new MarkersTracker(), [{ startColumn: 1, endColumn: 1, text: 'a', forceMoveMarkers: false }], NO_TAB_SIZE);
assertLineTokens(line.getTokens(0, []), [new TestToken(0, 1)]);
assertLineTokens(line.getTokens(0), [new TestToken(0, 1)]);
});
test('updates tokens on insertion 1', () => {
@@ -871,7 +877,7 @@ suite('Editor Model - modelLine.split text & tokens', () => {
assert.equal(line.text, expectedText1);
assert.equal(other.text, expectedText2);
assertLineTokens(line.getTokens(0, []), expectedTokens);
assertLineTokens(line.getTokens(0), expectedTokens);
}
test('split at the beginning', () => {
@@ -957,7 +963,7 @@ suite('Editor Model - modelLine.append text & tokens', () => {
a.append(new MarkersTracker(), b, NO_TAB_SIZE);
assert.equal(a.text, expectedText);
assertLineTokens(a.getTokens(0, []), expectedTokens);
assertLineTokens(a.getTokens(0), expectedTokens);
}
test('append empty 1', () => {
@@ -265,7 +265,13 @@ suite('TextModelWithTokens regression tests', () => {
test('Microsoft/monaco-editor#122: Unhandled Exception: TypeError: Unable to get property \'replace\' of undefined or null reference', () => {
function assertViewLineTokens(model: Model, lineNumber: number, forceTokenization: boolean, expected: ViewLineToken[]): void {
let actual = model.getLineTokens(lineNumber, !forceTokenization).inflate();
assert.deepEqual(actual, expected);
let decode = (token: ViewLineToken) => {
return {
endIndex: token.endIndex,
foreground: token.getForeground()
};
};
assert.deepEqual(actual.map(decode), expected.map(decode));
}
let _tokenId = 10;
@@ -293,24 +299,32 @@ suite('TextModelWithTokens regression tests', () => {
let model = Model.createFromString('A model with\ntwo lines');
assertViewLineTokens(model, 1, true, [new ViewLineToken(12, 'mtk1')]);
assertViewLineTokens(model, 2, true, [new ViewLineToken(9, 'mtk1')]);
assertViewLineTokens(model, 1, true, [createViewLineToken(12, 1)]);
assertViewLineTokens(model, 2, true, [createViewLineToken(9, 1)]);
model.setMode(languageIdentifier1);
assertViewLineTokens(model, 1, true, [new ViewLineToken(12, 'mtk11')]);
assertViewLineTokens(model, 2, true, [new ViewLineToken(9, 'mtk12')]);
assertViewLineTokens(model, 1, true, [createViewLineToken(12, 11)]);
assertViewLineTokens(model, 2, true, [createViewLineToken(9, 12)]);
model.setMode(languageIdentifier2);
assertViewLineTokens(model, 1, false, [new ViewLineToken(12, 'mtk1')]);
assertViewLineTokens(model, 2, false, [new ViewLineToken(9, 'mtk1')]);
assertViewLineTokens(model, 1, false, [createViewLineToken(12, 1)]);
assertViewLineTokens(model, 2, false, [createViewLineToken(9, 1)]);
model.dispose();
registration1.dispose();
registration2.dispose();
function createViewLineToken(endIndex: number, foreground: number): ViewLineToken {
let metadata = (
(foreground << MetadataConsts.FOREGROUND_OFFSET)
) >>> 0;
return new ViewLineToken(endIndex, metadata);
}
});
test('Microsoft/monaco-editor#133: Error: Cannot read property \'modeId\' of undefined', () => {
const languageIdentifier = new LanguageIdentifier('testMode', LanguageId.PlainText);

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