mirror of
https://github.com/Microsoft/vscode
synced 2024-08-28 05:19:39 +00:00
Merge branch 'master' into gitattributes
This commit is contained in:
commit
0dfe0c4c3e
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
|
@ -38,5 +38,6 @@
|
|||
}
|
||||
}
|
||||
],
|
||||
"typescript.tsdk": "node_modules/typescript/lib"
|
||||
"typescript.tsdk": "node_modules/typescript/lib",
|
||||
"npm.exclude": "**/extensions/**"
|
||||
}
|
|
@ -81,7 +81,8 @@ const indentationFilter = [
|
|||
'!build/{lib,tslintRules}/**/*.js',
|
||||
'!build/**/*.sh',
|
||||
'!build/tfs/**/*.js',
|
||||
'!**/Dockerfile'
|
||||
'!**/Dockerfile',
|
||||
'!extensions/markdown/media/*.js'
|
||||
];
|
||||
|
||||
const copyrightFilter = [
|
||||
|
|
|
@ -78,7 +78,7 @@ const vscodeResources = [
|
|||
'out-build/vs/workbench/electron-browser/bootstrap/**',
|
||||
'out-build/vs/workbench/parts/debug/**/*.json',
|
||||
'out-build/vs/workbench/parts/execution/**/*.scpt',
|
||||
'out-build/vs/workbench/parts/html/browser/webview-pre.js',
|
||||
'out-build/vs/workbench/parts/html/electron-browser/webview-pre.js',
|
||||
'out-build/vs/**/markdown.css',
|
||||
'out-build/vs/workbench/parts/tasks/**/*.json',
|
||||
'out-build/vs/workbench/parts/terminal/electron-browser/terminalProcess.js',
|
||||
|
|
|
@ -134,6 +134,10 @@
|
|||
"name": "vs/workbench/parts/watermark",
|
||||
"project": "vscode-workbench"
|
||||
},
|
||||
{
|
||||
"name": "vs/workbench/parts/webview",
|
||||
"project": "vscode-workbench"
|
||||
},
|
||||
{
|
||||
"name": "vs/workbench/parts/welcome",
|
||||
"project": "vscode-workbench"
|
||||
|
|
|
@ -24,6 +24,13 @@ interface ExpandAbbreviationInput {
|
|||
filter?: string;
|
||||
}
|
||||
|
||||
interface PreviewRangesWithContent {
|
||||
previewRange: vscode.Range;
|
||||
originalRange: vscode.Range;
|
||||
originalContent: string;
|
||||
textToWrapInPreview: string[];
|
||||
}
|
||||
|
||||
export function wrapWithAbbreviation(args: any) {
|
||||
if (!validate(false) || !vscode.window.activeTextEditor) {
|
||||
return;
|
||||
|
@ -32,50 +39,145 @@ export function wrapWithAbbreviation(args: any) {
|
|||
const editor = vscode.window.activeTextEditor;
|
||||
let rootNode = parseDocument(editor.document, false);
|
||||
|
||||
const syntax = getSyntaxFromArgs({ language: editor.document.languageId });
|
||||
const syntax = getSyntaxFromArgs({ language: editor.document.languageId }) || '';
|
||||
if (!syntax) {
|
||||
return;
|
||||
}
|
||||
|
||||
const abbreviationPromise = (args && args['abbreviation']) ? Promise.resolve(args['abbreviation']) : vscode.window.showInputBox({ prompt: 'Enter Abbreviation' });
|
||||
let inPreview = false;
|
||||
|
||||
// Fetch general information for the succesive expansions. i.e. the ranges to replace and its contents
|
||||
let rangesToReplace: PreviewRangesWithContent[] = [];
|
||||
|
||||
editor.selections.sort((a: vscode.Selection, b: vscode.Selection) => { return a.start.line - b.start.line; }).forEach(selection => {
|
||||
let rangeToReplace: vscode.Range = selection.isReversed ? new vscode.Range(selection.active, selection.anchor) : selection;
|
||||
if (rangeToReplace.isEmpty) {
|
||||
let { active } = selection;
|
||||
let currentNode = getNode(rootNode, active, true);
|
||||
if (currentNode && (currentNode.start.line === active.line || currentNode.end.line === active.line)) {
|
||||
rangeToReplace = new vscode.Range(currentNode.start, currentNode.end);
|
||||
} else {
|
||||
rangeToReplace = new vscode.Range(rangeToReplace.start.line, 0, rangeToReplace.start.line, editor.document.lineAt(rangeToReplace.start.line).text.length);
|
||||
}
|
||||
}
|
||||
|
||||
const firstLineOfSelection = editor.document.lineAt(rangeToReplace.start).text.substr(rangeToReplace.start.character);
|
||||
const matches = firstLineOfSelection.match(/^(\s*)/);
|
||||
const extraWhiteSpaceSelected = matches ? matches[1].length : 0;
|
||||
|
||||
rangeToReplace = new vscode.Range(rangeToReplace.start.line, rangeToReplace.start.character + extraWhiteSpaceSelected, rangeToReplace.end.line, rangeToReplace.end.character);
|
||||
|
||||
const wholeFirstLine = editor.document.lineAt(rangeToReplace.start).text;
|
||||
const otherMatches = wholeFirstLine.match(/^(\s*)/);
|
||||
const preceedingWhiteSpace = otherMatches ? otherMatches[1] : '';
|
||||
let textToReplace = editor.document.getText(rangeToReplace);
|
||||
let textToWrapInPreview = rangeToReplace.isSingleLine ? [textToReplace] : ['\n\t' + textToReplace.split('\n' + preceedingWhiteSpace).join('\n\t') + '\n'];
|
||||
rangesToReplace.push({ previewRange: rangeToReplace, originalRange: rangeToReplace, originalContent: textToReplace, textToWrapInPreview });
|
||||
});
|
||||
|
||||
let abbreviationPromise;
|
||||
let currentValue = '';
|
||||
|
||||
function inputChanged(value: string): string {
|
||||
if (value !== currentValue) {
|
||||
currentValue = value;
|
||||
makeChanges(value, inPreview, false).then((out) => {
|
||||
if (typeof out === 'boolean') {
|
||||
inPreview = out;
|
||||
}
|
||||
});
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
abbreviationPromise = (args && args['abbreviation']) ? Promise.resolve(args['abbreviation']) : vscode.window.showInputBox({ prompt: 'Enter Abbreviation', validateInput: inputChanged });
|
||||
const helper = getEmmetHelper();
|
||||
|
||||
return abbreviationPromise.then(inputAbbreviation => {
|
||||
if (!inputAbbreviation || !inputAbbreviation.trim() || !helper.isAbbreviationValid(syntax, inputAbbreviation)) { return false; }
|
||||
function makeChanges(inputAbbreviation: string | undefined, inPreview: boolean, definitive: boolean): Thenable<boolean> {
|
||||
if (!inputAbbreviation || !inputAbbreviation.trim() || !helper.isAbbreviationValid(syntax, inputAbbreviation)) {
|
||||
return inPreview ? revertPreview(editor, rangesToReplace).then(() => { return false; }) : Promise.resolve(inPreview);
|
||||
}
|
||||
|
||||
let extractedResults = helper.extractAbbreviationFromText(inputAbbreviation);
|
||||
if (!extractedResults) {
|
||||
return false;
|
||||
return Promise.resolve(inPreview);
|
||||
} else if (extractedResults.abbreviation !== inputAbbreviation) {
|
||||
// Not clear what should we do in this case. Warn the user? How?
|
||||
}
|
||||
|
||||
let { abbreviation, filter } = extractedResults;
|
||||
if (definitive) {
|
||||
const revertPromise = inPreview ? revertPreview(editor, rangesToReplace) : Promise.resolve();
|
||||
return revertPromise.then(() => {
|
||||
const expandAbbrList: ExpandAbbreviationInput[] = rangesToReplace.map(rangesAndContent => {
|
||||
let rangeToReplace = rangesAndContent.originalRange;
|
||||
let textToWrap = rangeToReplace.isSingleLine ? ['$TM_SELECTED_TEXT'] : ['\n\t$TM_SELECTED_TEXT\n'];
|
||||
return { syntax, abbreviation, rangeToReplace, textToWrap, filter };
|
||||
});
|
||||
return expandAbbreviationInRange(editor, expandAbbrList, true).then(() => { return true; });
|
||||
});
|
||||
}
|
||||
|
||||
let expandAbbrList: ExpandAbbreviationInput[] = [];
|
||||
|
||||
editor.selections.forEach(selection => {
|
||||
let rangeToReplace: vscode.Range = selection.isReversed ? new vscode.Range(selection.active, selection.anchor) : selection;
|
||||
if (rangeToReplace.isEmpty) {
|
||||
let { active } = selection;
|
||||
let currentNode = getNode(rootNode, active, true);
|
||||
if (currentNode && (currentNode.start.line === active.line || currentNode.end.line === active.line)) {
|
||||
rangeToReplace = new vscode.Range(currentNode.start, currentNode.end);
|
||||
} else {
|
||||
rangeToReplace = new vscode.Range(rangeToReplace.start.line, 0, rangeToReplace.start.line, editor.document.lineAt(rangeToReplace.start.line).text.length);
|
||||
}
|
||||
}
|
||||
|
||||
const firstLineOfSelection = editor.document.lineAt(rangeToReplace.start).text.substr(rangeToReplace.start.character);
|
||||
const matches = firstLineOfSelection.match(/^(\s*)/);
|
||||
const preceedingWhiteSpace = matches ? matches[1].length : 0;
|
||||
|
||||
rangeToReplace = new vscode.Range(rangeToReplace.start.line, rangeToReplace.start.character + preceedingWhiteSpace, rangeToReplace.end.line, rangeToReplace.end.character);
|
||||
let textToWrap = rangeToReplace.isSingleLine ? ['$TM_SELECTED_TEXT'] : ['\n\t$TM_SELECTED_TEXT\n'];
|
||||
expandAbbrList.push({ syntax, abbreviation, rangeToReplace, textToWrap, filter });
|
||||
const expandAbbrList: ExpandAbbreviationInput[] = rangesToReplace.map(rangesAndContent => {
|
||||
return { syntax, abbreviation, rangeToReplace: rangesAndContent.originalRange, textToWrap: rangesAndContent.textToWrapInPreview, filter };
|
||||
});
|
||||
|
||||
return expandAbbreviationInRange(editor, expandAbbrList, true);
|
||||
return applyPreview(editor, expandAbbrList, rangesToReplace);
|
||||
}
|
||||
|
||||
// On inputBox closing
|
||||
return abbreviationPromise.then(inputAbbreviation => {
|
||||
return makeChanges(inputAbbreviation, inPreview, true);
|
||||
});
|
||||
}
|
||||
|
||||
function revertPreview(editor: vscode.TextEditor, rangesToReplace: PreviewRangesWithContent[]): Thenable<any> {
|
||||
return editor.edit(builder => {
|
||||
for (let i = 0; i < rangesToReplace.length; i++) {
|
||||
builder.replace(rangesToReplace[i].previewRange, rangesToReplace[i].originalContent);
|
||||
rangesToReplace[i].previewRange = rangesToReplace[i].originalRange;
|
||||
}
|
||||
}, { undoStopBefore: false, undoStopAfter: false });
|
||||
}
|
||||
|
||||
function applyPreview(editor: vscode.TextEditor, expandAbbrList: ExpandAbbreviationInput[], rangesToReplace: PreviewRangesWithContent[]): Thenable<boolean> {
|
||||
let totalLinesInserted = 0;
|
||||
|
||||
return editor.edit(builder => {
|
||||
for (let i = 0; i < rangesToReplace.length; i++) {
|
||||
const expandedText = expandAbbr(expandAbbrList[i]) || '';
|
||||
if (!expandedText) {
|
||||
// Failed to expand text. We already showed an error inside expandAbbr.
|
||||
break;
|
||||
}
|
||||
|
||||
const oldPreviewRange = rangesToReplace[i].previewRange;
|
||||
const preceedingText = editor.document.getText(new vscode.Range(oldPreviewRange.start.line, 0, oldPreviewRange.start.line, oldPreviewRange.start.character));
|
||||
const indentPrefix = (preceedingText.match(/^(\s*)/) || ['', ''])[1];
|
||||
|
||||
let newText = expandedText.replace(/\n/g, '\n' + indentPrefix); // Adding indentation on each line of expanded text
|
||||
newText = newText.replace(/\$\{[\d]*\}/g, '|'); // Removing Tabstops
|
||||
newText = newText.replace(/\$\{[\d]*(:[^}]*)?\}/g, (match) => { // Replacing Placeholders
|
||||
return match.replace(/^\$\{[\d]*:/, '').replace('}', '');
|
||||
});
|
||||
builder.replace(oldPreviewRange, newText);
|
||||
|
||||
const expandedTextLines = newText.split('\n');
|
||||
const oldPreviewLines = oldPreviewRange.end.line - oldPreviewRange.start.line + 1;
|
||||
const newLinesInserted = expandedTextLines.length - oldPreviewLines;
|
||||
|
||||
let lastLineEnd = expandedTextLines[expandedTextLines.length - 1].length;
|
||||
if (expandedTextLines.length === 1) {
|
||||
// If the expandedText is single line, add the length of preceeding whitespace as it will not be included in line length.
|
||||
lastLineEnd += oldPreviewRange.start.character;
|
||||
}
|
||||
|
||||
rangesToReplace[i].previewRange = new vscode.Range(oldPreviewRange.start.line + totalLinesInserted, oldPreviewRange.start.character, oldPreviewRange.end.line + totalLinesInserted + newLinesInserted, lastLineEnd);
|
||||
totalLinesInserted += newLinesInserted;
|
||||
}
|
||||
}, { undoStopBefore: false, undoStopAfter: false });
|
||||
}
|
||||
|
||||
export function wrapIndividualLinesWithAbbreviation(args: any) {
|
||||
if (!validate(false) || !vscode.window.activeTextEditor) {
|
||||
return;
|
||||
|
|
|
@ -309,7 +309,7 @@ export function sameNodes(node1: Node, node2: Node): boolean {
|
|||
export function getEmmetConfiguration(syntax: string) {
|
||||
const emmetConfig = vscode.workspace.getConfiguration('emmet');
|
||||
const syntaxProfiles = Object.assign({}, emmetConfig['syntaxProfiles'] || {});
|
||||
|
||||
const preferences = Object.assign({}, emmetConfig['preferences'] || {});
|
||||
// jsx, xml and xsl syntaxes need to have self closing tags unless otherwise configured by user
|
||||
if (syntax === 'jsx' || syntax === 'xml' || syntax === 'xsl') {
|
||||
syntaxProfiles[syntax] = syntaxProfiles[syntax] || {};
|
||||
|
@ -322,7 +322,7 @@ export function getEmmetConfiguration(syntax: string) {
|
|||
}
|
||||
|
||||
return {
|
||||
preferences: emmetConfig['preferences'],
|
||||
preferences,
|
||||
showExpandedAbbreviation: emmetConfig['showExpandedAbbreviation'],
|
||||
showAbbreviationSuggestions: emmetConfig['showAbbreviationSuggestions'],
|
||||
syntaxProfiles,
|
||||
|
|
37
extensions/html/server/src/test/utils.test.ts
Normal file
37
extensions/html/server/src/test/utils.test.ts
Normal file
|
@ -0,0 +1,37 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { applyEdits } from '../utils/edits';
|
||||
import { TextDocument, TextEdit, Position, Range } from 'vscode-languageserver-types';
|
||||
|
||||
suite('Edits', () => {
|
||||
|
||||
test('inserts', function (): any {
|
||||
let input = TextDocument.create('foo://bar/f', 'html', 0, '012345678901234567890123456789');
|
||||
assert.equal(applyEdits(input, [TextEdit.insert(Position.create(0, 0), 'Hello')]), 'Hello012345678901234567890123456789');
|
||||
assert.equal(applyEdits(input, [TextEdit.insert(Position.create(0, 1), 'Hello')]), '0Hello12345678901234567890123456789');
|
||||
assert.equal(applyEdits(input, [TextEdit.insert(Position.create(0, 1), 'Hello'), TextEdit.insert(Position.create(0, 1), 'World')]), '0HelloWorld12345678901234567890123456789');
|
||||
assert.equal(applyEdits(input, [TextEdit.insert(Position.create(0, 2), 'One'), TextEdit.insert(Position.create(0, 1), 'Hello'), TextEdit.insert(Position.create(0, 1), 'World'), TextEdit.insert(Position.create(0, 2), 'Two'), TextEdit.insert(Position.create(0, 2), 'Three')]), '0HelloWorld1OneTwoThree2345678901234567890123456789');
|
||||
});
|
||||
|
||||
test('replace', function (): any {
|
||||
let input = TextDocument.create('foo://bar/f', 'html', 0, '012345678901234567890123456789');
|
||||
assert.equal(applyEdits(input, [TextEdit.replace(Range.create(Position.create(0, 3), Position.create(0, 6)), 'Hello')]), '012Hello678901234567890123456789');
|
||||
assert.equal(applyEdits(input, [TextEdit.replace(Range.create(Position.create(0, 3), Position.create(0, 6)), 'Hello'), TextEdit.replace(Range.create(Position.create(0, 6), Position.create(0, 9)), 'World')]), '012HelloWorld901234567890123456789');
|
||||
assert.equal(applyEdits(input, [TextEdit.replace(Range.create(Position.create(0, 3), Position.create(0, 6)), 'Hello'), TextEdit.insert(Position.create(0, 6), 'World')]), '012HelloWorld678901234567890123456789');
|
||||
assert.equal(applyEdits(input, [TextEdit.insert(Position.create(0, 6), 'World'), TextEdit.replace(Range.create(Position.create(0, 3), Position.create(0, 6)), 'Hello')]), '012HelloWorld678901234567890123456789');
|
||||
assert.equal(applyEdits(input, [TextEdit.insert(Position.create(0, 3), 'World'), TextEdit.replace(Range.create(Position.create(0, 3), Position.create(0, 6)), 'Hello')]), '012WorldHello678901234567890123456789');
|
||||
|
||||
});
|
||||
|
||||
test('overlap', function (): any {
|
||||
let input = TextDocument.create('foo://bar/f', 'html', 0, '012345678901234567890123456789');
|
||||
assert.throws(_ => applyEdits(input, [TextEdit.replace(Range.create(Position.create(0, 3), Position.create(0, 6)), 'Hello'), TextEdit.insert(Position.create(0, 3), 'World')]));
|
||||
assert.throws(_ => applyEdits(input, [TextEdit.replace(Range.create(Position.create(0, 3), Position.create(0, 6)), 'Hello'), TextEdit.insert(Position.create(0, 4), 'World')]));
|
||||
});
|
||||
|
||||
});
|
|
@ -15,3 +15,45 @@ export function pushAll<T>(to: T[], from: T[]) {
|
|||
export function contains<T>(arr: T[], val: T) {
|
||||
return arr.indexOf(val) !== -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Like `Array#sort` but always stable. Usually runs a little slower `than Array#sort`
|
||||
* so only use this when actually needing stable sort.
|
||||
*/
|
||||
export function mergeSort<T>(data: T[], compare: (a: T, b: T) => number): T[] {
|
||||
_divideAndMerge(data, compare);
|
||||
return data;
|
||||
}
|
||||
|
||||
function _divideAndMerge<T>(data: T[], compare: (a: T, b: T) => number): void {
|
||||
if (data.length <= 1) {
|
||||
// sorted
|
||||
return;
|
||||
}
|
||||
const p = (data.length / 2) | 0;
|
||||
const left = data.slice(0, p);
|
||||
const right = data.slice(p);
|
||||
|
||||
_divideAndMerge(left, compare);
|
||||
_divideAndMerge(right, compare);
|
||||
|
||||
let leftIdx = 0;
|
||||
let rightIdx = 0;
|
||||
let i = 0;
|
||||
while (leftIdx < left.length && rightIdx < right.length) {
|
||||
let ret = compare(left[leftIdx], right[rightIdx]);
|
||||
if (ret <= 0) {
|
||||
// smaller_equal -> take left to preserve order
|
||||
data[i++] = left[leftIdx++];
|
||||
} else {
|
||||
// greater -> take right
|
||||
data[i++] = right[rightIdx++];
|
||||
}
|
||||
}
|
||||
while (leftIdx < left.length) {
|
||||
data[i++] = left[leftIdx++];
|
||||
}
|
||||
while (rightIdx < right.length) {
|
||||
data[i++] = right[rightIdx++];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,29 +4,29 @@
|
|||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import { TextDocument, TextEdit, Position } from 'vscode-languageserver-types';
|
||||
import { TextDocument, TextEdit } from 'vscode-languageserver-types';
|
||||
import { mergeSort } from './arrays';
|
||||
|
||||
export function applyEdits(document: TextDocument, edits: TextEdit[]): string {
|
||||
let text = document.getText();
|
||||
let sortedEdits = edits.sort((a, b) => {
|
||||
let startDiff = comparePositions(a.range.start, b.range.start);
|
||||
if (startDiff === 0) {
|
||||
return comparePositions(a.range.end, b.range.end);
|
||||
let sortedEdits = mergeSort(edits, (a, b) => {
|
||||
let diff = a.range.start.line - b.range.start.line;
|
||||
if (diff === 0) {
|
||||
return a.range.start.character - b.range.start.character;
|
||||
}
|
||||
return startDiff;
|
||||
return 0;
|
||||
});
|
||||
sortedEdits.forEach(e => {
|
||||
let lastModifiedOffset = text.length;
|
||||
for (let i = sortedEdits.length - 1; i >= 0; i--) {
|
||||
let e = sortedEdits[i];
|
||||
let startOffset = document.offsetAt(e.range.start);
|
||||
let endOffset = document.offsetAt(e.range.end);
|
||||
text = text.substring(0, startOffset) + e.newText + text.substring(endOffset, text.length);
|
||||
});
|
||||
if (endOffset <= lastModifiedOffset) {
|
||||
text = text.substring(0, startOffset) + e.newText + text.substring(endOffset, text.length);
|
||||
} else {
|
||||
throw new Error('Ovelapping edit');
|
||||
}
|
||||
lastModifiedOffset = startOffset;
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
function comparePositions(p1: Position, p2: Position) {
|
||||
let diff = p2.line - p1.line;
|
||||
if (diff === 0) {
|
||||
return p2.character - p1.character;
|
||||
}
|
||||
return diff;
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "json",
|
||||
"displayName": "%displayName%",
|
||||
"description": "%description%",
|
||||
"description": "%description%",
|
||||
"version": "1.0.0",
|
||||
"publisher": "vscode",
|
||||
"aiKey": "AIF-d9b70cd4-b9f9-4d70-929b-a071c400b217",
|
||||
|
@ -171,7 +171,7 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"vscode-extension-telemetry": "0.0.15",
|
||||
"vscode-languageclient": "4.0.0-next.9",
|
||||
"vscode-languageclient": "^4.0.0",
|
||||
"vscode-nls": "^3.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
|
@ -8,10 +8,10 @@
|
|||
"node": "*"
|
||||
},
|
||||
"dependencies": {
|
||||
"jsonc-parser": "^1.0.1",
|
||||
"jsonc-parser": "^1.0.2",
|
||||
"request-light": "^0.2.2",
|
||||
"vscode-json-languageservice": "^3.0.7",
|
||||
"vscode-languageserver": "4.0.0-next.3",
|
||||
"vscode-json-languageservice": "^3.0.8",
|
||||
"vscode-languageserver": "^4.0.0",
|
||||
"vscode-nls": "^3.2.1",
|
||||
"vscode-uri": "^1.0.1"
|
||||
},
|
||||
|
|
|
@ -7,11 +7,9 @@
|
|||
import {
|
||||
createConnection, IConnection,
|
||||
TextDocuments, TextDocument, InitializeParams, InitializeResult, NotificationType, RequestType,
|
||||
DocumentRangeFormattingRequest, Disposable, ServerCapabilities
|
||||
DocumentRangeFormattingRequest, Disposable, ServerCapabilities, DocumentColorRequest, ColorPresentationRequest,
|
||||
} from 'vscode-languageserver';
|
||||
|
||||
import { DocumentColorRequest, ServerCapabilities as CPServerCapabilities, ColorPresentationRequest } from 'vscode-languageserver-protocol/lib/protocol.colorProvider.proposed';
|
||||
|
||||
import { xhr, XHRResponse, configure as configureHttpRequests, getErrorStatusDescription } from 'request-light';
|
||||
import fs = require('fs');
|
||||
import URI from 'vscode-uri';
|
||||
|
@ -74,7 +72,7 @@ connection.onInitialize((params: InitializeParams): InitializeResult => {
|
|||
|
||||
clientSnippetSupport = hasClientCapability('textDocument', 'completion', 'completionItem', 'snippetSupport');
|
||||
clientDynamicRegisterSupport = hasClientCapability('workspace', 'symbol', 'dynamicRegistration');
|
||||
let capabilities: ServerCapabilities & CPServerCapabilities & FoldingProviderServerCapabilities = {
|
||||
let capabilities: ServerCapabilities & FoldingProviderServerCapabilities = {
|
||||
// Tell the client that the server works in FULL text document sync mode
|
||||
textDocumentSync: documents.syncKind,
|
||||
completionProvider: clientSnippetSupport ? { resolveProvider: true, triggerCharacters: ['"', ':'] } : void 0,
|
||||
|
|
|
@ -48,9 +48,9 @@ https-proxy-agent@2.1.1:
|
|||
agent-base "^4.1.0"
|
||||
debug "^3.1.0"
|
||||
|
||||
jsonc-parser@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-1.0.1.tgz#7f8f296414e6e7c4a33b9e4914fc8c47e4421675"
|
||||
jsonc-parser@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-1.0.2.tgz#3fe86c0237db206fe5693872d93eec3bc3b05055"
|
||||
|
||||
ms@2.0.0:
|
||||
version "2.0.0"
|
||||
|
@ -64,35 +64,35 @@ request-light@^0.2.2:
|
|||
https-proxy-agent "2.1.1"
|
||||
vscode-nls "^2.0.2"
|
||||
|
||||
vscode-json-languageservice@^3.0.7:
|
||||
version "3.0.7"
|
||||
resolved "https://registry.yarnpkg.com/vscode-json-languageservice/-/vscode-json-languageservice-3.0.7.tgz#dc00117d51d4a7ac3bde9204afa701f962f00736"
|
||||
vscode-json-languageservice@^3.0.8:
|
||||
version "3.0.8"
|
||||
resolved "https://registry.yarnpkg.com/vscode-json-languageservice/-/vscode-json-languageservice-3.0.8.tgz#0f585c2a0e75de224c89a4ee516bbf1be15699aa"
|
||||
dependencies:
|
||||
jsonc-parser "^1.0.1"
|
||||
vscode-languageserver-types "^3.6.0-next.1"
|
||||
vscode-nls "^2.0.2"
|
||||
jsonc-parser "^1.0.2"
|
||||
vscode-languageserver-types "^3.6.0"
|
||||
vscode-nls "^3.2.1"
|
||||
vscode-uri "^1.0.1"
|
||||
|
||||
vscode-jsonrpc@^3.6.0-next.1:
|
||||
version "3.6.0-next.1"
|
||||
resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-3.6.0-next.1.tgz#3cb463dffe5842d6aec16718ca9252708cd6aabe"
|
||||
vscode-jsonrpc@^3.6.0:
|
||||
version "3.6.0"
|
||||
resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-3.6.0.tgz#848d56995d5168950d84feb5d9c237ae5c6a02d4"
|
||||
|
||||
vscode-languageserver-protocol@^3.6.0-next.3:
|
||||
version "3.6.0-next.4"
|
||||
resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.6.0-next.4.tgz#5b9940e4d6afafd5b63f9731dbd3a9bcc65b3719"
|
||||
vscode-languageserver-protocol@^3.6.0:
|
||||
version "3.6.0"
|
||||
resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.6.0.tgz#579642cdcccf74b0cd771c33daa3239acb40d040"
|
||||
dependencies:
|
||||
vscode-jsonrpc "^3.6.0-next.1"
|
||||
vscode-languageserver-types "^3.6.0-next.1"
|
||||
vscode-jsonrpc "^3.6.0"
|
||||
vscode-languageserver-types "^3.6.0"
|
||||
|
||||
vscode-languageserver-types@^3.6.0-next.1:
|
||||
version "3.6.0-next.1"
|
||||
resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.6.0-next.1.tgz#98e488d3f87b666b4ee1a3d89f0023e246d358f3"
|
||||
vscode-languageserver-types@^3.6.0:
|
||||
version "3.6.0"
|
||||
resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.6.0.tgz#0bba63b0fa82a714394a4478f55a596ee4ed7d0a"
|
||||
|
||||
vscode-languageserver@4.0.0-next.3:
|
||||
version "4.0.0-next.3"
|
||||
resolved "https://registry.yarnpkg.com/vscode-languageserver/-/vscode-languageserver-4.0.0-next.3.tgz#89a9ce5078e3a86a78e3551c3766194ce4295611"
|
||||
vscode-languageserver@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/vscode-languageserver/-/vscode-languageserver-4.0.0.tgz#8b792f0d6d10acfe363d02371ed4ce53d08af88a"
|
||||
dependencies:
|
||||
vscode-languageserver-protocol "^3.6.0-next.3"
|
||||
vscode-languageserver-protocol "^3.6.0"
|
||||
vscode-uri "^1.0.1"
|
||||
|
||||
vscode-nls@^2.0.2:
|
||||
|
|
|
@ -34,26 +34,26 @@ vscode-extension-telemetry@0.0.15:
|
|||
dependencies:
|
||||
applicationinsights "1.0.1"
|
||||
|
||||
vscode-jsonrpc@^3.6.0-next.1:
|
||||
version "3.6.0-next.1"
|
||||
resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-3.6.0-next.1.tgz#3cb463dffe5842d6aec16718ca9252708cd6aabe"
|
||||
vscode-jsonrpc@^3.6.0:
|
||||
version "3.6.0"
|
||||
resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-3.6.0.tgz#848d56995d5168950d84feb5d9c237ae5c6a02d4"
|
||||
|
||||
vscode-languageclient@4.0.0-next.9:
|
||||
version "4.0.0-next.9"
|
||||
resolved "https://registry.yarnpkg.com/vscode-languageclient/-/vscode-languageclient-4.0.0-next.9.tgz#2a06568f46ee9de3490f85e227d3740a21a03d3a"
|
||||
vscode-languageclient@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/vscode-languageclient/-/vscode-languageclient-4.0.0.tgz#635f5bfbcfa1385dae489b394857f1db8b459a7d"
|
||||
dependencies:
|
||||
vscode-languageserver-protocol "^3.6.0-next.5"
|
||||
vscode-languageserver-protocol "^3.6.0"
|
||||
|
||||
vscode-languageserver-protocol@^3.6.0-next.5:
|
||||
version "3.6.0-next.5"
|
||||
resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.6.0-next.5.tgz#ed2ec2db759826f753c0a13977dfb2bedc4d31b3"
|
||||
vscode-languageserver-protocol@^3.6.0:
|
||||
version "3.6.0"
|
||||
resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.6.0.tgz#579642cdcccf74b0cd771c33daa3239acb40d040"
|
||||
dependencies:
|
||||
vscode-jsonrpc "^3.6.0-next.1"
|
||||
vscode-languageserver-types "^3.6.0-next.1"
|
||||
vscode-jsonrpc "^3.6.0"
|
||||
vscode-languageserver-types "^3.6.0"
|
||||
|
||||
vscode-languageserver-types@^3.6.0-next.1:
|
||||
version "3.6.0-next.1"
|
||||
resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.6.0-next.1.tgz#98e488d3f87b666b4ee1a3d89f0023e246d358f3"
|
||||
vscode-languageserver-types@^3.6.0:
|
||||
version "3.6.0"
|
||||
resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.6.0.tgz#0bba63b0fa82a714394a4478f55a596ee4ed7d0a"
|
||||
|
||||
vscode-nls@^3.2.1:
|
||||
version "3.2.1"
|
||||
|
|
|
@ -26,6 +26,7 @@
|
|||
"name": "lessc",
|
||||
"label": "Lessc compiler",
|
||||
"owner": "lessc",
|
||||
"source": "less",
|
||||
"fileLocation": "absolute",
|
||||
"pattern": {
|
||||
"regexp": "(.*)\\sin\\s(.*)\\son line\\s(\\d+),\\scolumn\\s(\\d+)",
|
||||
|
|
|
@ -1,49 +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';
|
||||
|
||||
(function () {
|
||||
const settings = JSON.parse(document.getElementById('vscode-markdown-preview-data').getAttribute('data-settings'));
|
||||
const strings = JSON.parse(document.getElementById('vscode-markdown-preview-data').getAttribute('data-strings'));
|
||||
|
||||
let didShow = false;
|
||||
|
||||
const showCspWarning = () => {
|
||||
if (didShow || settings.disableSecurityWarnings) {
|
||||
return;
|
||||
}
|
||||
didShow = true;
|
||||
|
||||
const notification = document.createElement('a');
|
||||
notification.innerText = strings.cspAlertMessageText;
|
||||
notification.setAttribute('id', 'code-csp-warning');
|
||||
notification.setAttribute('title', strings.cspAlertMessageTitle);
|
||||
|
||||
notification.setAttribute('role', 'button');
|
||||
notification.setAttribute('aria-label', strings.cspAlertMessageLabel);
|
||||
notification.onclick = () => {
|
||||
window.parent.postMessage({
|
||||
type: 'command',
|
||||
source: settings.source,
|
||||
body: {
|
||||
command: 'markdown.showPreviewSecuritySelector',
|
||||
args: [settings.source]
|
||||
}
|
||||
}, '*');
|
||||
};
|
||||
document.body.appendChild(notification);
|
||||
};
|
||||
|
||||
document.addEventListener('securitypolicyviolation', () => {
|
||||
showCspWarning();
|
||||
});
|
||||
|
||||
window.addEventListener('message', (event) => {
|
||||
if (event && event.data && event.data.name === 'vscode-did-block-svg') {
|
||||
showCspWarning();
|
||||
}
|
||||
});
|
||||
}());
|
943
extensions/markdown/media/index.js
Normal file
943
extensions/markdown/media/index.js
Normal file
File diff suppressed because one or more lines are too long
|
@ -1,40 +0,0 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
// @ts-check
|
||||
|
||||
'use strict';
|
||||
|
||||
(function () {
|
||||
const unloadedStyles = [];
|
||||
|
||||
const settings = JSON.parse(document.getElementById('vscode-markdown-preview-data').getAttribute('data-settings'));
|
||||
|
||||
const onStyleLoadError = (event) => {
|
||||
const source = event.target.dataset.source;
|
||||
unloadedStyles.push(source);
|
||||
};
|
||||
|
||||
window.addEventListener('DOMContentLoaded', () => {
|
||||
for (const link of document.getElementsByClassName('code-user-style')) {
|
||||
if (link.dataset.source) {
|
||||
link.onerror = onStyleLoadError;
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
window.addEventListener('load', () => {
|
||||
if (!unloadedStyles.length) {
|
||||
return;
|
||||
}
|
||||
window.parent.postMessage({
|
||||
type: 'command',
|
||||
source: settings.source,
|
||||
body: {
|
||||
command: '_markdown.onPreviewStyleLoadError',
|
||||
args: [unloadedStyles]
|
||||
}
|
||||
}, '*');
|
||||
});
|
||||
}());
|
|
@ -1,344 +0,0 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
// @ts-check
|
||||
'use strict';
|
||||
|
||||
(function () {
|
||||
// From https://remysharp.com/2010/07/21/throttling-function-calls
|
||||
function throttle(fn, threshhold, scope) {
|
||||
threshhold || (threshhold = 250);
|
||||
var last, deferTimer;
|
||||
return function () {
|
||||
var context = scope || this;
|
||||
|
||||
var now = +new Date,
|
||||
args = arguments;
|
||||
if (last && now < last + threshhold) {
|
||||
// hold on to it
|
||||
clearTimeout(deferTimer);
|
||||
deferTimer = setTimeout(function () {
|
||||
last = now;
|
||||
fn.apply(context, args);
|
||||
}, threshhold + last - now);
|
||||
} else {
|
||||
last = now;
|
||||
fn.apply(context, args);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} min
|
||||
* @param {number} max
|
||||
* @param {number} value
|
||||
*/
|
||||
function clamp(min, max, value) {
|
||||
return Math.min(max, Math.max(min, value));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} line
|
||||
*/
|
||||
function clampLine(line) {
|
||||
return clamp(0, settings.lineCount - 1, line);
|
||||
}
|
||||
|
||||
/**
|
||||
* Post a message to the markdown extension
|
||||
*
|
||||
* @param {string} type
|
||||
* @param {object} body
|
||||
*/
|
||||
function postMessage(type, body) {
|
||||
window.parent.postMessage({
|
||||
type,
|
||||
source: settings.source,
|
||||
body
|
||||
}, '*');
|
||||
}
|
||||
|
||||
/**
|
||||
* Post a command to be executed to the markdown extension
|
||||
*
|
||||
* @param {string} command
|
||||
* @param {any[]} args
|
||||
*/
|
||||
function postCommand(command, args) {
|
||||
postMessage('command', { command, args });
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {{ element: Element, line: number }} CodeLineElement
|
||||
*/
|
||||
|
||||
/**
|
||||
* @return {CodeLineElement[]}
|
||||
*/
|
||||
const getCodeLineElements = (() => {
|
||||
/** @type {CodeLineElement[]} */
|
||||
let elements;
|
||||
return () => {
|
||||
if (!elements) {
|
||||
elements = Array.prototype.map.call(
|
||||
document.getElementsByClassName('code-line'),
|
||||
element => {
|
||||
const line = +element.getAttribute('data-line');
|
||||
return { element, line }
|
||||
})
|
||||
.filter(x => !isNaN(x.line));
|
||||
}
|
||||
return elements;
|
||||
};
|
||||
})()
|
||||
|
||||
/**
|
||||
* Find the html elements that map to a specific target line in the editor.
|
||||
*
|
||||
* If an exact match, returns a single element. If the line is between elements,
|
||||
* returns the element prior to and the element after the given line.
|
||||
*
|
||||
* @param {number} targetLine
|
||||
*
|
||||
* @returns {{ previous: CodeLineElement, next?: CodeLineElement }}
|
||||
*/
|
||||
function getElementsForSourceLine(targetLine) {
|
||||
const lineNumber = Math.floor(targetLine)
|
||||
const lines = getCodeLineElements();
|
||||
let previous = lines[0] || null;
|
||||
for (const entry of lines) {
|
||||
if (entry.line === lineNumber) {
|
||||
return { previous: entry, next: null };
|
||||
} else if (entry.line > lineNumber) {
|
||||
return { previous, next: entry };
|
||||
}
|
||||
previous = entry;
|
||||
}
|
||||
return { previous };
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the html elements that are at a specific pixel offset on the page.
|
||||
*
|
||||
* @returns {{ previous: CodeLineElement, next?: CodeLineElement }}
|
||||
*/
|
||||
function getLineElementsAtPageOffset(offset) {
|
||||
const lines = getCodeLineElements()
|
||||
|
||||
const position = offset - window.scrollY;
|
||||
|
||||
let lo = -1;
|
||||
let hi = lines.length - 1;
|
||||
while (lo + 1 < hi) {
|
||||
const mid = Math.floor((lo + hi) / 2);
|
||||
const bounds = lines[mid].element.getBoundingClientRect();
|
||||
if (bounds.top + bounds.height >= position) {
|
||||
hi = mid;
|
||||
} else {
|
||||
lo = mid;
|
||||
}
|
||||
}
|
||||
|
||||
const hiElement = lines[hi];
|
||||
const hiBounds = hiElement.element.getBoundingClientRect();
|
||||
|
||||
if (hi >= 1 && hiBounds.top > position) {
|
||||
const loElement = lines[lo];
|
||||
return { previous: loElement, next: hiElement };
|
||||
}
|
||||
|
||||
return { previous: hiElement };
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to reveal the element for a source line in the editor.
|
||||
*
|
||||
* @param {number} line
|
||||
*/
|
||||
function scrollToRevealSourceLine(line) {
|
||||
const { previous, next } = getElementsForSourceLine(line);
|
||||
if (previous && settings.scrollPreviewWithEditor) {
|
||||
let scrollTo = 0;
|
||||
const rect = previous.element.getBoundingClientRect();
|
||||
const previousTop = rect.top;
|
||||
|
||||
if (next && next.line !== previous.line) {
|
||||
// Between two elements. Go to percentage offset between them.
|
||||
const betweenProgress = (line - previous.line) / (next.line - previous.line);
|
||||
const elementOffset = next.element.getBoundingClientRect().top - previousTop;
|
||||
scrollTo = previousTop + betweenProgress * elementOffset;
|
||||
} else {
|
||||
scrollTo = previousTop;
|
||||
}
|
||||
|
||||
window.scroll(0, Math.max(1, window.scrollY + scrollTo));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} offset
|
||||
*/
|
||||
function getEditorLineNumberForPageOffset(offset) {
|
||||
const { previous, next } = getLineElementsAtPageOffset(offset);
|
||||
if (previous) {
|
||||
const previousBounds = previous.element.getBoundingClientRect();
|
||||
const offsetFromPrevious = (offset - window.scrollY - previousBounds.top);
|
||||
|
||||
if (next) {
|
||||
const progressBetweenElements = offsetFromPrevious / (next.element.getBoundingClientRect().top - previousBounds.top);
|
||||
const line = previous.line + progressBetweenElements * (next.line - previous.line);
|
||||
return clampLine(line);
|
||||
} else {
|
||||
const progressWithinElement = offsetFromPrevious / (previousBounds.height);
|
||||
const line = previous.line + progressWithinElement;
|
||||
return clampLine(line);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
class ActiveLineMarker {
|
||||
onDidChangeTextEditorSelection(line) {
|
||||
const { previous } = getElementsForSourceLine(line);
|
||||
this._update(previous && previous.element);
|
||||
}
|
||||
|
||||
_update(before) {
|
||||
this._unmarkActiveElement(this._current);
|
||||
this._markActiveElement(before);
|
||||
this._current = before;
|
||||
}
|
||||
|
||||
_unmarkActiveElement(element) {
|
||||
if (!element) {
|
||||
return;
|
||||
}
|
||||
element.className = element.className.replace(/\bcode-active-line\b/g);
|
||||
}
|
||||
|
||||
_markActiveElement(element) {
|
||||
if (!element) {
|
||||
return;
|
||||
}
|
||||
element.className += ' code-active-line';
|
||||
}
|
||||
}
|
||||
|
||||
var scrollDisabled = true;
|
||||
const marker = new ActiveLineMarker();
|
||||
const settings = JSON.parse(document.getElementById('vscode-markdown-preview-data').getAttribute('data-settings'));
|
||||
|
||||
function onLoad() {
|
||||
if (settings.scrollPreviewWithEditor) {
|
||||
setTimeout(() => {
|
||||
const initialLine = +settings.line;
|
||||
if (!isNaN(initialLine)) {
|
||||
scrollDisabled = true;
|
||||
scrollToRevealSourceLine(initialLine);
|
||||
}
|
||||
}, 0);
|
||||
}
|
||||
}
|
||||
|
||||
const onUpdateView = (() => {
|
||||
const doScroll = throttle(line => {
|
||||
scrollDisabled = true;
|
||||
scrollToRevealSourceLine(line);
|
||||
}, 50);
|
||||
|
||||
return (line, settings) => {
|
||||
if (!isNaN(line)) {
|
||||
settings.line = line;
|
||||
doScroll(line);
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
|
||||
if (document.readyState === 'loading' || document.readyState === 'uninitialized') {
|
||||
document.addEventListener('DOMContentLoaded', onLoad);
|
||||
} else {
|
||||
onLoad();
|
||||
}
|
||||
|
||||
|
||||
window.addEventListener('resize', () => {
|
||||
scrollDisabled = true;
|
||||
}, true);
|
||||
|
||||
window.addEventListener('message', event => {
|
||||
if (event.data.source !== settings.source) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (event.data.type) {
|
||||
case 'onDidChangeTextEditorSelection':
|
||||
marker.onDidChangeTextEditorSelection(event.data.line);
|
||||
break;
|
||||
|
||||
case 'updateView':
|
||||
onUpdateView(event.data.line, settings);
|
||||
break;
|
||||
}
|
||||
}, false);
|
||||
|
||||
document.addEventListener('dblclick', event => {
|
||||
if (!settings.doubleClickToSwitchToEditor) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Ignore clicks on links
|
||||
for (let node = /** @type {HTMLElement} */(event.target); node; node = /** @type {HTMLElement} */(node.parentNode)) {
|
||||
if (node.tagName === "A") {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const offset = event.pageY;
|
||||
const line = getEditorLineNumberForPageOffset(offset);
|
||||
if (!isNaN(line)) {
|
||||
postMessage('didClick', { line });
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('click', event => {
|
||||
if (!event) {
|
||||
return;
|
||||
}
|
||||
|
||||
const baseElement = document.getElementsByTagName('base')[0];
|
||||
|
||||
/** @type {*} */
|
||||
let node = event.target;
|
||||
while (node) {
|
||||
if (node.tagName && node.tagName === 'A' && node.href) {
|
||||
if (node.getAttribute('href').startsWith('#')) {
|
||||
break;
|
||||
}
|
||||
if (node.href.startsWith('file://') || node.href.startsWith('vscode-workspace-resource:')) {
|
||||
const [path, fragment] = node.href.replace(/^(file:\/\/|vscode-workspace-resource:)/i, '').split('#');
|
||||
postCommand('_markdown.openDocumentLink', [{ path, fragment }]);
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
node = node.parentNode;
|
||||
}
|
||||
}, true);
|
||||
|
||||
if (settings.scrollEditorWithPreview) {
|
||||
window.addEventListener('scroll', throttle(() => {
|
||||
if (scrollDisabled) {
|
||||
scrollDisabled = false;
|
||||
} else {
|
||||
const line = getEditorLineNumberForPageOffset(window.scrollY);
|
||||
if (!isNaN(line)) {
|
||||
postMessage('revealLine', { line });
|
||||
}
|
||||
}
|
||||
}, 50));
|
||||
}
|
||||
}());
|
297
extensions/markdown/media/pre.js
Normal file
297
extensions/markdown/media/pre.js
Normal file
File diff suppressed because one or more lines are too long
8463
extensions/markdown/package-lock.json
generated
Normal file
8463
extensions/markdown/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
|
@ -270,7 +270,11 @@
|
|||
]
|
||||
},
|
||||
"scripts": {
|
||||
"vscode:prepublish": "node ../../node_modules/gulp/bin/gulp.js --gulpfile ../../build/gulpfile.extensions.js compile-extension:markdown ./tsconfig.json"
|
||||
"compile": "gulp compile-extension:markdown && npm run build-preview",
|
||||
"watch": "npm run build-preview && gulp watch-extension:markdown",
|
||||
"vscode:prepublish": "npm run build-ext && npm run build-preview",
|
||||
"build-ext": "node ../../node_modules/gulp/bin/gulp.js --gulpfile ../../build/gulpfile.extensions.js compile-extension:markdown ./tsconfig.json",
|
||||
"build-preview": "webpack --mode development"
|
||||
},
|
||||
"dependencies": {
|
||||
"highlight.js": "9.5.0",
|
||||
|
@ -281,8 +285,14 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"@types/highlight.js": "9.1.10",
|
||||
"@types/lodash.throttle": "^4.1.3",
|
||||
"@types/markdown-it": "0.0.2",
|
||||
"@types/node": "7.0.43",
|
||||
"vscode": "^1.1.10"
|
||||
"lodash.throttle": "^4.1.1",
|
||||
"ts-loader": "^4.0.1",
|
||||
"typescript": "^2.7.2",
|
||||
"vscode": "^1.1.10",
|
||||
"webpack": "^4.1.0",
|
||||
"webpack-cli": "^2.0.10"
|
||||
}
|
||||
}
|
34
extensions/markdown/preview-src/activeLineMarker.ts
Normal file
34
extensions/markdown/preview-src/activeLineMarker.ts
Normal file
|
@ -0,0 +1,34 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import { getElementsForSourceLine } from './scroll-sync';
|
||||
|
||||
export class ActiveLineMarker {
|
||||
private _current: any;
|
||||
|
||||
onDidChangeTextEditorSelection(line: number) {
|
||||
const { previous } = getElementsForSourceLine(line);
|
||||
this._update(previous && previous.element);
|
||||
}
|
||||
|
||||
_update(before: HTMLElement | undefined) {
|
||||
this._unmarkActiveElement(this._current);
|
||||
this._markActiveElement(before);
|
||||
this._current = before;
|
||||
}
|
||||
|
||||
_unmarkActiveElement(element: HTMLElement | undefined) {
|
||||
if (!element) {
|
||||
return;
|
||||
}
|
||||
element.className = element.className.replace(/\bcode-active-line\b/g, '');
|
||||
}
|
||||
|
||||
_markActiveElement(element: HTMLElement | undefined) {
|
||||
if (!element) {
|
||||
return;
|
||||
}
|
||||
element.className += ' code-active-line';
|
||||
}
|
||||
}
|
49
extensions/markdown/preview-src/csp.ts
Normal file
49
extensions/markdown/preview-src/csp.ts
Normal file
|
@ -0,0 +1,49 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { getSettings } from './settings';
|
||||
import { getStrings } from './strings';
|
||||
import { postCommand } from './messaging';
|
||||
|
||||
/**
|
||||
* Shows an alert when there is a content security policy violation.
|
||||
*/
|
||||
export class CspAlerter {
|
||||
private didShow = false;
|
||||
|
||||
constructor() {
|
||||
document.addEventListener('securitypolicyviolation', () => {
|
||||
this.showCspWarning();
|
||||
});
|
||||
|
||||
window.addEventListener('message', (event) => {
|
||||
if (event && event.data && event.data.name === 'vscode-did-block-svg') {
|
||||
this.showCspWarning();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private showCspWarning() {
|
||||
const strings = getStrings();
|
||||
const settings = getSettings();
|
||||
|
||||
if (this.didShow || settings.disableSecurityWarnings) {
|
||||
return;
|
||||
}
|
||||
this.didShow = true;
|
||||
|
||||
const notification = document.createElement('a');
|
||||
notification.innerText = strings.cspAlertMessageText;
|
||||
notification.setAttribute('id', 'code-csp-warning');
|
||||
notification.setAttribute('title', strings.cspAlertMessageTitle);
|
||||
|
||||
notification.setAttribute('role', 'button');
|
||||
notification.setAttribute('aria-label', strings.cspAlertMessageLabel);
|
||||
notification.onclick = () => {
|
||||
postCommand('markdown.showPreviewSecuritySelector', [settings.source]);
|
||||
};
|
||||
document.body.appendChild(notification);
|
||||
}
|
||||
}
|
|
@ -3,6 +3,10 @@
|
|||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
.htmlPreviewPart {
|
||||
overflow: hidden;
|
||||
export function onceDocumentLoaded(f: () => void) {
|
||||
if (document.readyState === 'loading' || document.readyState === 'uninitialized') {
|
||||
document.addEventListener('DOMContentLoaded', f);
|
||||
} else {
|
||||
f();
|
||||
}
|
||||
}
|
117
extensions/markdown/preview-src/index.ts
Normal file
117
extensions/markdown/preview-src/index.ts
Normal file
|
@ -0,0 +1,117 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { getSettings } from './settings';
|
||||
import { postCommand, postMessage } from './messaging';
|
||||
import { onceDocumentLoaded } from './events';
|
||||
import { getEditorLineNumberForPageOffset, scrollToRevealSourceLine } from './scroll-sync';
|
||||
import { ActiveLineMarker } from './activeLineMarker';
|
||||
import throttle = require('lodash.throttle');
|
||||
|
||||
var scrollDisabled = true;
|
||||
const marker = new ActiveLineMarker();
|
||||
const settings = getSettings();
|
||||
|
||||
onceDocumentLoaded(() => {
|
||||
if (settings.scrollPreviewWithEditor) {
|
||||
setTimeout(() => {
|
||||
const initialLine = +settings.line;
|
||||
if (!isNaN(initialLine)) {
|
||||
scrollDisabled = true;
|
||||
scrollToRevealSourceLine(initialLine);
|
||||
}
|
||||
}, 0);
|
||||
}
|
||||
});
|
||||
|
||||
const onUpdateView = (() => {
|
||||
const doScroll = throttle((line: number) => {
|
||||
scrollDisabled = true;
|
||||
scrollToRevealSourceLine(line);
|
||||
}, 50);
|
||||
|
||||
return (line: number, settings: any) => {
|
||||
if (!isNaN(line)) {
|
||||
settings.line = line;
|
||||
doScroll(line);
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
window.addEventListener('resize', () => {
|
||||
scrollDisabled = true;
|
||||
}, true);
|
||||
|
||||
window.addEventListener('message', event => {
|
||||
if (event.data.source !== settings.source) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (event.data.type) {
|
||||
case 'onDidChangeTextEditorSelection':
|
||||
marker.onDidChangeTextEditorSelection(event.data.line);
|
||||
break;
|
||||
|
||||
case 'updateView':
|
||||
onUpdateView(event.data.line, settings);
|
||||
break;
|
||||
}
|
||||
}, false);
|
||||
|
||||
document.addEventListener('dblclick', event => {
|
||||
if (!settings.doubleClickToSwitchToEditor) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Ignore clicks on links
|
||||
for (let node = event.target as HTMLElement; node; node = node.parentNode as HTMLElement) {
|
||||
if (node.tagName === 'A') {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const offset = event.pageY;
|
||||
const line = getEditorLineNumberForPageOffset(offset);
|
||||
if (typeof line === 'number' && !isNaN(line)) {
|
||||
postMessage('didClick', { line });
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('click', event => {
|
||||
if (!event) {
|
||||
return;
|
||||
}
|
||||
|
||||
let node: any = event.target;
|
||||
while (node) {
|
||||
if (node.tagName && node.tagName === 'A' && node.href) {
|
||||
if (node.getAttribute('href').startsWith('#')) {
|
||||
break;
|
||||
}
|
||||
if (node.href.startsWith('file://') || node.href.startsWith('vscode-workspace-resource:')) {
|
||||
const [path, fragment] = node.href.replace(/^(file:\/\/|vscode-workspace-resource:)/i, '').split('#');
|
||||
postCommand('_markdown.openDocumentLink', [{ path, fragment }]);
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
node = node.parentNode;
|
||||
}
|
||||
}, true);
|
||||
|
||||
if (settings.scrollEditorWithPreview) {
|
||||
window.addEventListener('scroll', throttle(() => {
|
||||
if (scrollDisabled) {
|
||||
scrollDisabled = false;
|
||||
} else {
|
||||
const line = getEditorLineNumberForPageOffset(window.scrollY);
|
||||
if (typeof line === 'number' && !isNaN(line)) {
|
||||
postMessage('revealLine', { line });
|
||||
}
|
||||
}
|
||||
}, 50));
|
||||
}
|
31
extensions/markdown/preview-src/loading.ts
Normal file
31
extensions/markdown/preview-src/loading.ts
Normal file
|
@ -0,0 +1,31 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import { postCommand } from './messaging';
|
||||
|
||||
export class StyleLoadingMonitor {
|
||||
private unloadedStyles: string[] = [];
|
||||
|
||||
constructor() {
|
||||
const onStyleLoadError = (event: any) => {
|
||||
const source = event.target.dataset.source;
|
||||
this.unloadedStyles.push(source);
|
||||
};
|
||||
|
||||
window.addEventListener('DOMContentLoaded', () => {
|
||||
for (const link of document.getElementsByClassName('code-user-style') as HTMLCollectionOf<HTMLElement>) {
|
||||
if (link.dataset.source) {
|
||||
link.onerror = onStyleLoadError;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
window.addEventListener('load', () => {
|
||||
if (!this.unloadedStyles.length) {
|
||||
return;
|
||||
}
|
||||
postCommand('_markdown.onPreviewStyleLoadError', [this.unloadedStyles]);
|
||||
});
|
||||
}
|
||||
}
|
24
extensions/markdown/preview-src/messaging.ts
Normal file
24
extensions/markdown/preview-src/messaging.ts
Normal file
|
@ -0,0 +1,24 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { getSettings } from './settings';
|
||||
|
||||
/**
|
||||
* Post a message to the markdown extension
|
||||
*/
|
||||
export function postMessage(type: string, body: object) {
|
||||
window.parent.postMessage({
|
||||
type,
|
||||
source: getSettings().source,
|
||||
body
|
||||
}, '*');
|
||||
}
|
||||
|
||||
/**
|
||||
* Post a command to be executed to the markdown extension
|
||||
*/
|
||||
export function postCommand(command: string, args: any[]) {
|
||||
postMessage('command', { command, args });
|
||||
}
|
13
extensions/markdown/preview-src/pre.ts
Normal file
13
extensions/markdown/preview-src/pre.ts
Normal file
|
@ -0,0 +1,13 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { CspAlerter } from './csp';
|
||||
import { StyleLoadingMonitor } from './loading';
|
||||
|
||||
// tslint:disable-next-line:no-unused-expression
|
||||
new CspAlerter();
|
||||
|
||||
// tslint:disable-next-line:no-unused-expression
|
||||
new StyleLoadingMonitor();
|
127
extensions/markdown/preview-src/scroll-sync.ts
Normal file
127
extensions/markdown/preview-src/scroll-sync.ts
Normal file
|
@ -0,0 +1,127 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { getSettings } from './settings';
|
||||
|
||||
|
||||
function clamp(min: number, max: number, value: number) {
|
||||
return Math.min(max, Math.max(min, value));
|
||||
}
|
||||
|
||||
function clampLine(line: number) {
|
||||
return clamp(0, getSettings().lineCount - 1, line);
|
||||
}
|
||||
|
||||
|
||||
export interface CodeLineElement {
|
||||
element: HTMLElement;
|
||||
line: number;
|
||||
}
|
||||
|
||||
const getCodeLineElements = (() => {
|
||||
let elements: CodeLineElement[];
|
||||
return () => {
|
||||
if (!elements) {
|
||||
elements = Array.prototype.map.call(
|
||||
document.getElementsByClassName('code-line'),
|
||||
(element: any) => {
|
||||
const line = +element.getAttribute('data-line');
|
||||
return { element, line };
|
||||
})
|
||||
.filter((x: any) => !isNaN(x.line));
|
||||
}
|
||||
return elements;
|
||||
};
|
||||
})();
|
||||
|
||||
/**
|
||||
* Find the html elements that map to a specific target line in the editor.
|
||||
*
|
||||
* If an exact match, returns a single element. If the line is between elements,
|
||||
* returns the element prior to and the element after the given line.
|
||||
*/
|
||||
export function getElementsForSourceLine(targetLine: number): { previous: CodeLineElement; next?: CodeLineElement; } {
|
||||
const lineNumber = Math.floor(targetLine);
|
||||
const lines = getCodeLineElements();
|
||||
let previous = lines[0] || null;
|
||||
for (const entry of lines) {
|
||||
if (entry.line === lineNumber) {
|
||||
return { previous: entry, next: undefined };
|
||||
}
|
||||
else if (entry.line > lineNumber) {
|
||||
return { previous, next: entry };
|
||||
}
|
||||
previous = entry;
|
||||
}
|
||||
return { previous };
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the html elements that are at a specific pixel offset on the page.
|
||||
*/
|
||||
export function getLineElementsAtPageOffset(offset: number): { previous: CodeLineElement; next?: CodeLineElement; } {
|
||||
const lines = getCodeLineElements();
|
||||
const position = offset - window.scrollY;
|
||||
let lo = -1;
|
||||
let hi = lines.length - 1;
|
||||
while (lo + 1 < hi) {
|
||||
const mid = Math.floor((lo + hi) / 2);
|
||||
const bounds = lines[mid].element.getBoundingClientRect();
|
||||
if (bounds.top + bounds.height >= position) {
|
||||
hi = mid;
|
||||
}
|
||||
else {
|
||||
lo = mid;
|
||||
}
|
||||
}
|
||||
const hiElement = lines[hi];
|
||||
const hiBounds = hiElement.element.getBoundingClientRect();
|
||||
if (hi >= 1 && hiBounds.top > position) {
|
||||
const loElement = lines[lo];
|
||||
return { previous: loElement, next: hiElement };
|
||||
}
|
||||
return { previous: hiElement };
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to reveal the element for a source line in the editor.
|
||||
*/
|
||||
export function scrollToRevealSourceLine(line: number) {
|
||||
const { previous, next } = getElementsForSourceLine(line);
|
||||
if (previous && getSettings().scrollPreviewWithEditor) {
|
||||
let scrollTo = 0;
|
||||
const rect = previous.element.getBoundingClientRect();
|
||||
const previousTop = rect.top;
|
||||
if (next && next.line !== previous.line) {
|
||||
// Between two elements. Go to percentage offset between them.
|
||||
const betweenProgress = (line - previous.line) / (next.line - previous.line);
|
||||
const elementOffset = next.element.getBoundingClientRect().top - previousTop;
|
||||
scrollTo = previousTop + betweenProgress * elementOffset;
|
||||
}
|
||||
else {
|
||||
scrollTo = previousTop;
|
||||
}
|
||||
window.scroll(0, Math.max(1, window.scrollY + scrollTo));
|
||||
}
|
||||
}
|
||||
|
||||
export function getEditorLineNumberForPageOffset(offset: number) {
|
||||
const { previous, next } = getLineElementsAtPageOffset(offset);
|
||||
if (previous) {
|
||||
const previousBounds = previous.element.getBoundingClientRect();
|
||||
const offsetFromPrevious = (offset - window.scrollY - previousBounds.top);
|
||||
if (next) {
|
||||
const progressBetweenElements = offsetFromPrevious / (next.element.getBoundingClientRect().top - previousBounds.top);
|
||||
const line = previous.line + progressBetweenElements * (next.line - previous.line);
|
||||
return clampLine(line);
|
||||
}
|
||||
else {
|
||||
const progressWithinElement = offsetFromPrevious / (previousBounds.height);
|
||||
const line = previous.line + progressWithinElement;
|
||||
return clampLine(line);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
33
extensions/markdown/preview-src/settings.ts
Normal file
33
extensions/markdown/preview-src/settings.ts
Normal file
|
@ -0,0 +1,33 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
export interface PreviewSettings {
|
||||
source: string;
|
||||
line: number;
|
||||
lineCount: number;
|
||||
scrollPreviewWithEditor?: boolean;
|
||||
scrollEditorWithPreview: boolean;
|
||||
disableSecurityWarnings: boolean;
|
||||
doubleClickToSwitchToEditor: boolean;
|
||||
}
|
||||
|
||||
let cachedSettings: PreviewSettings | undefined = undefined;
|
||||
|
||||
export function getSettings(): PreviewSettings {
|
||||
if (cachedSettings) {
|
||||
return cachedSettings;
|
||||
}
|
||||
|
||||
const element = document.getElementById('vscode-markdown-preview-data');
|
||||
if (element) {
|
||||
const data = element.getAttribute('data-settings');
|
||||
if (data) {
|
||||
cachedSettings = JSON.parse(data);
|
||||
return cachedSettings!;
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error('Could not load settings');
|
||||
}
|
15
extensions/markdown/preview-src/strings.ts
Normal file
15
extensions/markdown/preview-src/strings.ts
Normal file
|
@ -0,0 +1,15 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
export function getStrings(): { [key: string]: string } {
|
||||
const store = document.getElementById('vscode-markdown-preview-data');
|
||||
if (store) {
|
||||
const data = store.getAttribute('data-strings');
|
||||
if (data) {
|
||||
return JSON.parse(data);
|
||||
}
|
||||
}
|
||||
throw new Error('Could not load strings');
|
||||
}
|
12
extensions/markdown/preview-src/tsconfig.json
Normal file
12
extensions/markdown/preview-src/tsconfig.json
Normal file
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"outDir": "./dist/",
|
||||
"module": "commonjs",
|
||||
"target": "es6",
|
||||
"jsx": "react",
|
||||
"sourceMap": true,
|
||||
"strict": true,
|
||||
"noImplicitAny": true,
|
||||
"noUnusedLocals": true
|
||||
}
|
||||
}
|
|
@ -33,11 +33,12 @@ export class OpenDocumentLinkCommand implements Command {
|
|||
) { }
|
||||
|
||||
public execute(args: OpenDocumentLinkArgs) {
|
||||
return this.tryOpen(args.path, args).catch(() => {
|
||||
if (path.extname(args.path) === '') {
|
||||
return this.tryOpen(args.path + '.md', args);
|
||||
const p = decodeURIComponent(args.path);
|
||||
return this.tryOpen(p, args).catch(() => {
|
||||
if (path.extname(p) === '') {
|
||||
return this.tryOpen(p + '.md', args);
|
||||
}
|
||||
const resource = vscode.Uri.file(args.path);
|
||||
const resource = vscode.Uri.file(p);
|
||||
return Promise.resolve(void 0)
|
||||
.then(() => vscode.commands.executeCommand('vscode.open', resource))
|
||||
.then(() => void 0);
|
||||
|
|
|
@ -83,8 +83,7 @@ export class MarkdownContentProvider {
|
|||
<meta http-equiv="Content-type" content="text/html;charset=UTF-8">
|
||||
${csp}
|
||||
<meta id="vscode-markdown-preview-data" data-settings="${JSON.stringify(initialData).replace(/"/g, '"')}" data-strings="${JSON.stringify(previewStrings).replace(/"/g, '"')}">
|
||||
<script src="${this.extensionResourcePath('csp.js')}" nonce="${nonce}"></script>
|
||||
<script src="${this.extensionResourcePath('loading.js')}" nonce="${nonce}"></script>
|
||||
<script src="${this.extensionResourcePath('pre.js')}" nonce="${nonce}"></script>
|
||||
${this.getStyles(sourceUri, nonce, config)}
|
||||
<base href="${markdownDocument.uri.with({ scheme: 'vscode-workspace-resource' }).toString(true)}">
|
||||
</head>
|
||||
|
@ -165,7 +164,7 @@ export class MarkdownContentProvider {
|
|||
}
|
||||
|
||||
private getScripts(nonce: string): string {
|
||||
const scripts = [this.extensionResourcePath('main.js')].concat(this.extraScripts.map(resource => resource.toString()));
|
||||
const scripts = [this.extensionResourcePath('index.js')].concat(this.extraScripts.map(resource => resource.toString()));
|
||||
return scripts
|
||||
.map(source => `<script async src="${source}" nonce="${nonce}" charset="UTF-8"></script>`)
|
||||
.join('\n');
|
||||
|
@ -174,14 +173,14 @@ export class MarkdownContentProvider {
|
|||
private getCspForResource(resource: vscode.Uri, nonce: string): string {
|
||||
switch (this.cspArbiter.getSecurityLevelForResource(resource)) {
|
||||
case MarkdownPreviewSecurityLevel.AllowInsecureContent:
|
||||
return `<meta http-equiv="Content-Security-Policy" content="default-src 'none'; img-src vscode-workspace-resource: vscode-extension-resource: http: https: data:; media-src vscode-workspace-resource: vscode-extension-resource: http: https: data:; script-src 'nonce-${nonce}'; style-src vscode-workspace-resource: 'unsafe-inline' http: https: data: vscode-extension-resource:; font-src vscode-workspace-resource: vscode-workspace-resource: http: https: data:;">`;
|
||||
return `<meta http-equiv="Content-Security-Policy" content="default-src 'none'; img-src vscode-workspace-resource: vscode-extension-resource: http: https: data:; media-src vscode-workspace-resource: vscode-extension-resource: http: https: data:; script-src 'nonce-${nonce}'; style-src vscode-workspace-resource: 'unsafe-inline' http: https: data: vscode-extension-resource:; font-src vscode-workspace-resource: vscode-extension-resource: http: https: data:;">`;
|
||||
|
||||
case MarkdownPreviewSecurityLevel.AllowScriptsAndAllContent:
|
||||
return '';
|
||||
|
||||
case MarkdownPreviewSecurityLevel.Strict:
|
||||
default:
|
||||
return `<meta http-equiv="Content-Security-Policy" content="default-src 'none'; img-src vscode-workspace-resource: vscode-extension-resource: https: data:; media-src vscode-workspace-resource: vscode-extension-resource: https: data:; script-src 'nonce-${nonce}'; style-src vscode-workspace-resource: 'unsafe-inline' https: data: vscode-extension-resource:; font-src vscode-workspace-resource: vscode-workspace-resource: https: data:;">`;
|
||||
return `<meta http-equiv="Content-Security-Policy" content="default-src 'none'; img-src vscode-workspace-resource: vscode-extension-resource: https: data:; media-src vscode-workspace-resource: vscode-extension-resource: https: data:; script-src 'nonce-${nonce}'; style-src vscode-workspace-resource: 'unsafe-inline' https: data: vscode-extension-resource:; font-src vscode-workspace-resource: vscode-extension-resource: https: data:;">`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -150,9 +150,15 @@ export class MarkdownEngine {
|
|||
}
|
||||
|
||||
if (fragment) {
|
||||
uri = uri.with({ fragment });
|
||||
uri = uri.with({
|
||||
fragment: Slug.fromHeading(fragment).value
|
||||
});
|
||||
}
|
||||
return normalizeLink(uri.with({ scheme: 'vscode-workspace-resource' }).toString(true));
|
||||
} else if (!uri.scheme && !uri.path && uri.fragment) {
|
||||
return normalizeLink(uri.with({
|
||||
fragment: Slug.fromHeading(uri.fragment).value
|
||||
}).toString(true));
|
||||
}
|
||||
} catch (e) {
|
||||
// noop
|
||||
|
|
29
extensions/markdown/webpack.config.js
Normal file
29
extensions/markdown/webpack.config.js
Normal file
|
@ -0,0 +1,29 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
const path = require('path');
|
||||
|
||||
module.exports = {
|
||||
entry: {
|
||||
index: './preview-src/index.ts',
|
||||
pre: './preview-src/pre.ts'
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.tsx?$/,
|
||||
use: 'ts-loader',
|
||||
exclude: /node_modules/
|
||||
}
|
||||
]
|
||||
},
|
||||
resolve: {
|
||||
extensions: ['.tsx', '.ts', '.js']
|
||||
},
|
||||
devtool: 'inline-source-map',
|
||||
output: {
|
||||
filename: '[name].js',
|
||||
path: path.resolve(__dirname, 'media')
|
||||
}
|
||||
};
|
File diff suppressed because it is too large
Load diff
|
@ -7,6 +7,8 @@ are treated as build tasks.
|
|||
|
||||
To run scripts as tasks you use the `Tasks` menu.
|
||||
|
||||
For more information about auto detection of Tasks pls see the [documentation](https://code.visualstudio.com/Docs/editor/tasks#_task-autodetection).
|
||||
|
||||
## Settings
|
||||
- `npm.autoDetect` enable detecting scripts as tasks, the default is `on`.
|
||||
- `npm.runSilent` run npm script with the `--silent` option, the default is `false`.
|
||||
|
|
|
@ -409,10 +409,6 @@
|
|||
"debugExceptionWidget.background": "#051336",
|
||||
"debugExceptionWidget.border": "#AB395B",
|
||||
|
||||
// Workbench: Notifications
|
||||
"notification.background": "#182543",
|
||||
// "notification.foreground": "",
|
||||
|
||||
// Workbench: Quick Open
|
||||
"pickerGroup.border": "#596F99",
|
||||
"pickerGroup.foreground": "#596F99",
|
||||
|
|
|
@ -37,7 +37,6 @@
|
|||
"peekViewResult.background": "#362712",
|
||||
"peekViewEditor.background": "#221a14",
|
||||
"peekViewEditor.matchHighlightBackground": "#84613daa",
|
||||
"notification.background": "#473a29",
|
||||
"button.background": "#6e583b",
|
||||
"inputValidation.infoBorder": "#1b60a5",
|
||||
"inputValidation.infoBackground": "#2b2a42",
|
||||
|
|
|
@ -35,7 +35,6 @@
|
|||
"activityBarBadge.background": "#3655b5",
|
||||
"sideBar.background": "#272727",
|
||||
"sideBarSectionHeader.background": "#505050",
|
||||
"notification.background": "#353535",
|
||||
"pickerGroup.foreground": "#b0b0b0",
|
||||
"terminal.ansiWhite": "#ffffff",
|
||||
"inputOption.activeBorder": "#3655b5",
|
||||
|
|
|
@ -519,7 +519,6 @@
|
|||
"activityBarBadge.background": "#705697",
|
||||
"titleBar.activeBackground": "#c4b7d7",
|
||||
"button.background": "#705697",
|
||||
"notification.background": "#442e66",
|
||||
"editorGroup.dropBackground": "#C9D0D988",
|
||||
"inputValidation.infoBorder": "#4ec1e5",
|
||||
"inputValidation.infoBackground": "#f2fcff",
|
||||
|
|
|
@ -49,7 +49,6 @@
|
|||
"list.dropBackground": "#662222",
|
||||
"list.focusBackground": "#660000",
|
||||
"list.highlightForeground": "#ff4444",
|
||||
"notification.background": "#662222",
|
||||
"pickerGroup.foreground": "#cc9999",
|
||||
"pickerGroup.border": "#ff000033",
|
||||
"badge.background": "#cc3333",
|
||||
|
|
|
@ -457,10 +457,6 @@
|
|||
"debugExceptionWidget.background": "#00212B",
|
||||
"debugExceptionWidget.border": "#AB395B",
|
||||
|
||||
// Workbench: Notifications
|
||||
"notification.background": "#003847",
|
||||
// "notification.foreground": "",
|
||||
|
||||
// Workbench: Quick Open
|
||||
"pickerGroup.foreground": "#2AA19899",
|
||||
"pickerGroup.border": "#2AA19899",
|
||||
|
|
|
@ -456,10 +456,6 @@
|
|||
"debugExceptionWidget.background": "#DDD6C1",
|
||||
"debugExceptionWidget.border": "#AB395B",
|
||||
|
||||
// Workbench: Notifications
|
||||
"notification.background": "#999178",
|
||||
// "notification.foreground": "",
|
||||
|
||||
// Workbench: Quick Open
|
||||
"pickerGroup.border": "#2AA19899",
|
||||
"pickerGroup.foreground": "#2AA19899",
|
||||
|
|
|
@ -552,6 +552,7 @@
|
|||
"name": "tsc",
|
||||
"label": "%typescript.problemMatchers.tsc.label%",
|
||||
"owner": "typescript",
|
||||
"source": "ts",
|
||||
"applyTo": "closedDocuments",
|
||||
"fileLocation": [
|
||||
"relative",
|
||||
|
@ -563,6 +564,7 @@
|
|||
"name": "tsc-watch",
|
||||
"label": "%typescript.problemMatchers.tscWatch.label%",
|
||||
"owner": "typescript",
|
||||
"source": "ts",
|
||||
"applyTo": "closedDocuments",
|
||||
"fileLocation": [
|
||||
"relative",
|
||||
|
|
|
@ -46,7 +46,7 @@
|
|||
"vscode-debugprotocol": "1.27.0",
|
||||
"vscode-ripgrep": "0.7.1-patch.0.1",
|
||||
"vscode-textmate": "^3.2.0",
|
||||
"vscode-xterm": "3.2.0-beta7",
|
||||
"vscode-xterm": "3.3.0-beta2",
|
||||
"yauzl": "2.8.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
|
@ -18,7 +18,7 @@ if %errorlevel% neq 0 exit /b %errorlevel%
|
|||
call .\scripts\code.bat %~dp0\..\extensions\vscode-colorize-tests\test --extensionDevelopmentPath=%~dp0\..\extensions\vscode-colorize-tests --extensionTestsPath=%~dp0\..\extensions\vscode-colorize-tests\out --disableExtensions --user-data-dir=%VSCODEUSERDATADIR%
|
||||
if %errorlevel% neq 0 exit /b %errorlevel%
|
||||
|
||||
call .\scripts\code.bat $%~dp0\..\extensions\emmet\test-fixtures --extensionDevelopmentPath=%~dp0\..\extensions\emmet --extensionTestsPath=%~dp0\..\extensions\emmet\out\test --disableExtensions --user-data-dir=%VSCODEUSERDATADIR%
|
||||
call .\scripts\code.bat $%~dp0\..\extensions\emmet\test-fixtures --extensionDevelopmentPath=%~dp0\..\extensions\emmet --extensionTestsPath=%~dp0\..\extensions\emmet\out\test --disableExtensions --user-data-dir=%VSCODEUSERDATADIR% .
|
||||
if %errorlevel% neq 0 exit /b %errorlevel%
|
||||
|
||||
:: Integration & performance tests in AMD
|
||||
|
|
|
@ -16,7 +16,9 @@ cd $ROOT
|
|||
./scripts/code.sh $ROOT/extensions/vscode-api-tests/testWorkspace --extensionDevelopmentPath=$ROOT/extensions/vscode-api-tests --extensionTestsPath=$ROOT/extensions/vscode-api-tests/out/singlefolder-tests --disableExtensions --user-data-dir=$VSCODEUSERDATADIR --skip-getting-started
|
||||
./scripts/code.sh $ROOT/extensions/vscode-api-tests/testworkspace.code-workspace --extensionDevelopmentPath=$ROOT/extensions/vscode-api-tests --extensionTestsPath=$ROOT/extensions/vscode-api-tests/out/workspace-tests --disableExtensions --user-data-dir=$VSCODEUSERDATADIR --skip-getting-started
|
||||
./scripts/code.sh $ROOT/extensions/vscode-colorize-tests/test --extensionDevelopmentPath=$ROOT/extensions/vscode-colorize-tests --extensionTestsPath=$ROOT/extensions/vscode-colorize-tests/out --disableExtensions --user-data-dir=$VSCODEUSERDATADIR --skip-getting-started
|
||||
./scripts/code.sh $ROOT/extensions/emmet/test-fixtures --extensionDevelopmentPath=$ROOT/extensions/emmet --extensionTestsPath=$ROOT/extensions/emmet/out/test --disableExtensions --user-data-dir=$VSCODEUSERDATADIR --skip-getting-started
|
||||
mkdir $ROOT/extensions/emmet/test-fixtures
|
||||
./scripts/code.sh $ROOT/extensions/emmet/test-fixtures --extensionDevelopmentPath=$ROOT/extensions/emmet --extensionTestsPath=$ROOT/extensions/emmet/out/test --disableExtensions --user-data-dir=$VSCODEUSERDATADIR --skip-getting-started .
|
||||
rm -r $ROOT/extensions/emmet/test-fixtures
|
||||
|
||||
# Integration tests in AMD
|
||||
./scripts/test.sh --runGlob **/*.integrationTest.js "$@"
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
"noImplicitReturns": true,
|
||||
"noUnusedLocals": true,
|
||||
"noImplicitThis": true,
|
||||
"alwaysStrict": true,
|
||||
"baseUrl": ".",
|
||||
"outDir": "../out",
|
||||
"typeRoots": [
|
||||
|
|
2
src/typings/windows-process-tree.d.ts
vendored
2
src/typings/windows-process-tree.d.ts
vendored
|
@ -9,6 +9,8 @@ declare module 'windows-process-tree' {
|
|||
name: string,
|
||||
children: ProcessTreeNode[]
|
||||
}
|
||||
|
||||
function get(rootPid: number, callback: (tree: ProcessTreeNode) => void): void;
|
||||
|
||||
export = get;
|
||||
}
|
|
@ -15,11 +15,11 @@ import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
|||
import { Color } from 'vs/base/common/color';
|
||||
|
||||
export interface ICheckboxOpts extends ICheckboxStyles {
|
||||
actionClassName: string;
|
||||
title: string;
|
||||
isChecked: boolean;
|
||||
onChange: (viaKeyboard: boolean) => void;
|
||||
onKeyDown?: (e: IKeyboardEvent) => void;
|
||||
readonly actionClassName: string;
|
||||
readonly title: string;
|
||||
readonly isChecked: boolean;
|
||||
readonly onChange: (viaKeyboard: boolean) => void;
|
||||
readonly onKeyDown?: (e: IKeyboardEvent) => void;
|
||||
}
|
||||
|
||||
export interface ICheckboxStyles {
|
||||
|
@ -32,8 +32,8 @@ const defaultOpts = {
|
|||
|
||||
export class Checkbox extends Widget {
|
||||
|
||||
private _opts: ICheckboxOpts;
|
||||
public domNode: HTMLElement;
|
||||
private readonly _opts: ICheckboxOpts;
|
||||
public readonly domNode: HTMLElement;
|
||||
|
||||
private _checked: boolean;
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@ import { Gesture, EventType as GestureEventType } from 'vs/base/browser/touch';
|
|||
import { ActionRunner, IAction, IActionRunner } from 'vs/base/common/actions';
|
||||
import { BaseActionItem, IActionItemProvider } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { IContextViewProvider } from 'vs/base/browser/ui/contextview/contextview';
|
||||
import { IContextViewProvider, IAnchor } from 'vs/base/browser/ui/contextview/contextview';
|
||||
import { IMenuOptions } from 'vs/base/browser/ui/menu/menu';
|
||||
import { ResolvedKeybinding } from 'vs/base/common/keyCodes';
|
||||
import { EventHelper, EventType } from 'vs/base/browser/dom';
|
||||
|
@ -33,6 +33,7 @@ export class BaseDropdown extends ActionRunner {
|
|||
private $boxContainer: Builder;
|
||||
private $label: Builder;
|
||||
private $contents: Builder;
|
||||
private visible: boolean;
|
||||
|
||||
constructor(container: HTMLElement, options: IBaseDropdownOptions) {
|
||||
super();
|
||||
|
@ -58,7 +59,11 @@ export class BaseDropdown extends ActionRunner {
|
|||
return; // prevent multiple clicks to open multiple context menus (https://github.com/Microsoft/vscode/issues/41363)
|
||||
}
|
||||
|
||||
this.show();
|
||||
if (this.visible) {
|
||||
this.hide();
|
||||
} else {
|
||||
this.show();
|
||||
}
|
||||
}).appendTo(this.$el);
|
||||
|
||||
let cleanupFn = labelRenderer(this.$label.getHTMLElement());
|
||||
|
@ -87,11 +92,11 @@ export class BaseDropdown extends ActionRunner {
|
|||
}
|
||||
|
||||
public show(): void {
|
||||
// noop
|
||||
this.visible = true;
|
||||
}
|
||||
|
||||
public hide(): void {
|
||||
// noop
|
||||
this.visible = false;
|
||||
}
|
||||
|
||||
protected onEvent(e: Event, activeElement: HTMLElement): void {
|
||||
|
@ -135,10 +140,12 @@ export class Dropdown extends BaseDropdown {
|
|||
}
|
||||
|
||||
public show(): void {
|
||||
super.show();
|
||||
|
||||
this.element.addClass('active');
|
||||
|
||||
this.contextViewProvider.showContextView({
|
||||
getAnchor: () => this.element.getHTMLElement(),
|
||||
getAnchor: () => this.getAnchor(),
|
||||
|
||||
render: (container) => {
|
||||
return this.renderContents(container);
|
||||
|
@ -148,13 +155,21 @@ export class Dropdown extends BaseDropdown {
|
|||
this.onEvent(e, activeElement);
|
||||
},
|
||||
|
||||
onHide: () => {
|
||||
this.element.removeClass('active');
|
||||
}
|
||||
onHide: () => this.onHide()
|
||||
});
|
||||
}
|
||||
|
||||
protected getAnchor(): HTMLElement | IAnchor {
|
||||
return this.element.getHTMLElement();
|
||||
}
|
||||
|
||||
protected onHide(): void {
|
||||
this.element.removeClass('active');
|
||||
}
|
||||
|
||||
public hide(): void {
|
||||
super.hide();
|
||||
|
||||
if (this.contextViewProvider) {
|
||||
this.contextViewProvider.hideContextView();
|
||||
}
|
||||
|
@ -217,6 +232,8 @@ export class DropdownMenu extends BaseDropdown {
|
|||
}
|
||||
|
||||
public show(): void {
|
||||
super.show();
|
||||
|
||||
this.element.addClass('active');
|
||||
|
||||
this._contextMenuProvider.showContextMenu({
|
||||
|
@ -232,7 +249,7 @@ export class DropdownMenu extends BaseDropdown {
|
|||
}
|
||||
|
||||
public hide(): void {
|
||||
// noop
|
||||
super.hide();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -20,14 +20,14 @@ import { Color } from 'vs/base/common/color';
|
|||
import { ICheckboxStyles } from 'vs/base/browser/ui/checkbox/checkbox';
|
||||
|
||||
export interface IFindInputOptions extends IFindInputStyles {
|
||||
placeholder?: string;
|
||||
width?: number;
|
||||
validation?: IInputValidator;
|
||||
label: string;
|
||||
readonly placeholder?: string;
|
||||
readonly width?: number;
|
||||
readonly validation?: IInputValidator;
|
||||
readonly label: string;
|
||||
|
||||
appendCaseSensitiveLabel?: string;
|
||||
appendWholeWordsLabel?: string;
|
||||
appendRegexLabel?: string;
|
||||
readonly appendCaseSensitiveLabel?: string;
|
||||
readonly appendWholeWordsLabel?: string;
|
||||
readonly appendRegexLabel?: string;
|
||||
}
|
||||
|
||||
export interface IFindInputStyles extends IInputBoxStyles {
|
||||
|
@ -38,7 +38,7 @@ const NLS_DEFAULT_LABEL = nls.localize('defaultLabel', "input");
|
|||
|
||||
export class FindInput extends Widget {
|
||||
|
||||
static OPTION_CHANGE: string = 'optionChange';
|
||||
static readonly OPTION_CHANGE: string = 'optionChange';
|
||||
|
||||
private contextViewProvider: IContextViewProvider;
|
||||
private width: number;
|
||||
|
|
|
@ -12,11 +12,11 @@ import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
|||
import { Color } from 'vs/base/common/color';
|
||||
|
||||
export interface IFindInputCheckboxOpts {
|
||||
appendTitle: string;
|
||||
isChecked: boolean;
|
||||
onChange: (viaKeyboard: boolean) => void;
|
||||
onKeyDown?: (e: IKeyboardEvent) => void;
|
||||
inputActiveOptionBorder?: Color;
|
||||
readonly appendTitle: string;
|
||||
readonly isChecked: boolean;
|
||||
readonly onChange: (viaKeyboard: boolean) => void;
|
||||
readonly onKeyDown?: (e: IKeyboardEvent) => void;
|
||||
readonly inputActiveOptionBorder?: Color;
|
||||
}
|
||||
|
||||
const NLS_CASE_SENSITIVE_CHECKBOX_LABEL = nls.localize('caseDescription', "Match Case");
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import * as dom from 'vs/base/browser/dom';
|
||||
import * as objects from 'vs/base/common/objects';
|
||||
import { render as renderOcticons } from 'vs/base/browser/ui/octiconLabel/octiconLabel';
|
||||
import { renderOcticons } from 'vs/base/browser/ui/octiconLabel/octiconLabel';
|
||||
|
||||
export interface IHighlight {
|
||||
start: number;
|
||||
|
|
|
@ -329,21 +329,22 @@ export class InputBox extends Widget {
|
|||
}
|
||||
|
||||
public validate(): boolean {
|
||||
let result: IMessage = null;
|
||||
let errorMsg: IMessage = null;
|
||||
|
||||
if (this.validation) {
|
||||
result = this.validation(this.value);
|
||||
errorMsg = this.validation(this.value);
|
||||
|
||||
if (!result) {
|
||||
if (errorMsg) {
|
||||
this.inputElement.setAttribute('aria-invalid', 'true');
|
||||
this.showMessage(errorMsg);
|
||||
}
|
||||
else if (this.inputElement.hasAttribute('aria-invalid')) {
|
||||
this.inputElement.removeAttribute('aria-invalid');
|
||||
this.hideMessage();
|
||||
} else {
|
||||
this.inputElement.setAttribute('aria-invalid', 'true');
|
||||
this.showMessage(result);
|
||||
}
|
||||
}
|
||||
|
||||
return !result;
|
||||
return !errorMsg;
|
||||
}
|
||||
|
||||
private stylesForType(type: MessageType): { border: Color; background: Color } {
|
||||
|
|
|
@ -339,7 +339,8 @@ class DOMFocusController<T> implements IDisposable {
|
|||
.filter(e => !isInputElement(e.target as HTMLElement))
|
||||
.map(e => new StandardKeyboardEvent(e));
|
||||
|
||||
onKeyDown.filter(e => e.keyCode === KeyCode.Tab).on(this.onTab, this, this.disposables);
|
||||
onKeyDown.filter(e => e.keyCode === KeyCode.Tab && !e.ctrlKey && !e.metaKey && !e.shiftKey && !e.altKey)
|
||||
.on(this.onTab, this, this.disposables);
|
||||
}
|
||||
|
||||
private onTab(e: StandardKeyboardEvent): void {
|
||||
|
|
|
@ -24,7 +24,7 @@ class MockOcticonLabel {
|
|||
}
|
||||
|
||||
var mock: typeof octiconLabel = {
|
||||
render: render,
|
||||
renderOcticons: render,
|
||||
OcticonLabel: <any>MockOcticonLabel
|
||||
};
|
||||
export = mock;
|
|
@ -14,20 +14,20 @@ function expand(text: string): string {
|
|||
});
|
||||
}
|
||||
|
||||
export function render(label: string): string {
|
||||
export function renderOcticons(label: string): string {
|
||||
return expand(escape(label));
|
||||
}
|
||||
|
||||
export class OcticonLabel {
|
||||
|
||||
private _container: HTMLElement;
|
||||
private readonly _container: HTMLElement;
|
||||
|
||||
constructor(container: HTMLElement) {
|
||||
this._container = container;
|
||||
}
|
||||
|
||||
set text(text: string) {
|
||||
this._container.innerHTML = render(text || '');
|
||||
this._container.innerHTML = renderOcticons(text || '');
|
||||
}
|
||||
|
||||
set title(title: string) {
|
||||
|
|
|
@ -378,9 +378,12 @@ export class SelectBoxList implements ISelectBoxDelegate, IDelegate<ISelectOptio
|
|||
}
|
||||
|
||||
private renderSelectDropDown(container: HTMLElement): IDisposable {
|
||||
dom.append(container, this.selectDropDownContainer);
|
||||
container.appendChild(this.selectDropDownContainer);
|
||||
|
||||
this.layoutSelectDropDown();
|
||||
return null;
|
||||
return {
|
||||
dispose: () => container.removeChild(this.selectDropDownContainer) // remove to take out the CSS rules we add
|
||||
};
|
||||
}
|
||||
|
||||
private layoutSelectDropDown() {
|
||||
|
|
|
@ -10,15 +10,20 @@
|
|||
height: 100%;
|
||||
}
|
||||
|
||||
.monaco-split-view2 > .split-view-view {
|
||||
.monaco-split-view2 > .split-view-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.monaco-split-view2 > .split-view-container > .split-view-view {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.monaco-split-view2.vertical > .split-view-view {
|
||||
.monaco-split-view2.vertical > .split-view-container > .split-view-view {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.monaco-split-view2.horizontal > .split-view-view {
|
||||
.monaco-split-view2.horizontal > .split-view-container > .split-view-view {
|
||||
height: 100%;
|
||||
display: inline-block;
|
||||
}
|
||||
|
|
|
@ -82,6 +82,7 @@ export class SplitView implements IDisposable {
|
|||
|
||||
private orientation: Orientation;
|
||||
private el: HTMLElement;
|
||||
private viewContainer: HTMLElement;
|
||||
private size = 0;
|
||||
private contentSize = 0;
|
||||
private viewItems: IViewItem[] = [];
|
||||
|
@ -105,6 +106,10 @@ export class SplitView implements IDisposable {
|
|||
dom.addClass(this.el, 'monaco-split-view2');
|
||||
dom.addClass(this.el, this.orientation === Orientation.VERTICAL ? 'vertical' : 'horizontal');
|
||||
container.appendChild(this.el);
|
||||
|
||||
this.viewContainer = document.createElement('div');
|
||||
dom.addClass(this.viewContainer, 'split-view-container');
|
||||
this.el.appendChild(this.viewContainer);
|
||||
}
|
||||
|
||||
addView(view: IView, size: number, index = this.viewItems.length): void {
|
||||
|
@ -118,13 +123,13 @@ export class SplitView implements IDisposable {
|
|||
const container = dom.$('.split-view-view');
|
||||
|
||||
if (index === this.viewItems.length) {
|
||||
this.el.appendChild(container);
|
||||
this.viewContainer.appendChild(container);
|
||||
} else {
|
||||
this.el.insertBefore(container, this.el.children.item(index));
|
||||
this.viewContainer.insertBefore(container, this.viewContainer.children.item(index));
|
||||
}
|
||||
|
||||
const onChangeDisposable = view.onDidChange(size => this.onViewChange(item, size));
|
||||
const containerDisposable = toDisposable(() => this.el.removeChild(container));
|
||||
const containerDisposable = toDisposable(() => this.viewContainer.removeChild(container));
|
||||
const disposable = combinedDisposable([onChangeDisposable, containerDisposable]);
|
||||
|
||||
const layoutContainer = this.orientation === Orientation.VERTICAL
|
||||
|
@ -218,9 +223,9 @@ export class SplitView implements IDisposable {
|
|||
this.viewItems.splice(to, 0, viewItem);
|
||||
|
||||
if (to + 1 < this.viewItems.length) {
|
||||
this.el.insertBefore(viewItem.container, this.viewItems[to + 1].container);
|
||||
this.viewContainer.insertBefore(viewItem.container, this.viewItems[to + 1].container);
|
||||
} else {
|
||||
this.el.appendChild(viewItem.container);
|
||||
this.viewContainer.appendChild(viewItem.container);
|
||||
}
|
||||
|
||||
this.layoutViews();
|
||||
|
|
|
@ -12,6 +12,7 @@ import { IDisposable, dispose, toDisposable } from 'vs/base/common/lifecycle';
|
|||
import Event, { Emitter } from 'vs/base/common/event';
|
||||
import * as json from 'vs/base/common/json';
|
||||
import * as extfs from 'vs/base/node/extfs';
|
||||
import { isWindows } from 'vs/base/common/platform';
|
||||
|
||||
export interface IConfigurationChangeEvent<T> {
|
||||
config: T;
|
||||
|
@ -165,8 +166,18 @@ export class ConfigWatcher<T> implements IConfigWatcher<T>, IDisposable {
|
|||
}
|
||||
|
||||
private onConfigFileChange(eventType: string, filename: string, isParentFolder: boolean): void {
|
||||
if (isParentFolder && filename !== this.configName) {
|
||||
return; // a change to a sibling file that is not our config file
|
||||
if (isParentFolder) {
|
||||
|
||||
// Windows: in some cases the filename contains artifacts from the absolute path
|
||||
// see https://github.com/nodejs/node/issues/19170
|
||||
// As such, we have to ensure that the filename basename is used for comparison.
|
||||
if (isWindows && filename !== this.configName) {
|
||||
filename = basename(filename);
|
||||
}
|
||||
|
||||
if (filename !== this.configName) {
|
||||
return; // a change to a sibling file that is not our config file
|
||||
}
|
||||
}
|
||||
|
||||
if (this.timeoutHandle) {
|
||||
|
|
|
@ -78,7 +78,7 @@ suite('Splitview', () => {
|
|||
|
||||
test('empty splitview has empty DOM', () => {
|
||||
const splitview = new SplitView(container);
|
||||
assert.equal(container.firstElementChild.childElementCount, 0, 'split view should be empty');
|
||||
assert.equal(container.firstElementChild.firstElementChild.childElementCount, 0, 'split view should be empty');
|
||||
splitview.dispose();
|
||||
});
|
||||
|
||||
|
@ -92,7 +92,7 @@ suite('Splitview', () => {
|
|||
splitview.addView(view2, 20);
|
||||
splitview.addView(view3, 20);
|
||||
|
||||
let viewQuery = container.querySelectorAll('.monaco-split-view2 > .split-view-view');
|
||||
let viewQuery = container.querySelectorAll('.monaco-split-view2 > .split-view-container > .split-view-view');
|
||||
assert.equal(viewQuery.length, 3, 'split view should have 3 views');
|
||||
|
||||
let sashQuery = container.querySelectorAll('.monaco-split-view2 > .monaco-sash');
|
||||
|
@ -100,7 +100,7 @@ suite('Splitview', () => {
|
|||
|
||||
splitview.removeView(2);
|
||||
|
||||
viewQuery = container.querySelectorAll('.monaco-split-view2 > .split-view-view');
|
||||
viewQuery = container.querySelectorAll('.monaco-split-view2 > .split-view-container > .split-view-view');
|
||||
assert.equal(viewQuery.length, 2, 'split view should have 2 views');
|
||||
|
||||
sashQuery = container.querySelectorAll('.monaco-split-view2 > .monaco-sash');
|
||||
|
@ -108,7 +108,7 @@ suite('Splitview', () => {
|
|||
|
||||
splitview.removeView(0);
|
||||
|
||||
viewQuery = container.querySelectorAll('.monaco-split-view2 > .split-view-view');
|
||||
viewQuery = container.querySelectorAll('.monaco-split-view2 > .split-view-container > .split-view-view');
|
||||
assert.equal(viewQuery.length, 1, 'split view should have 1 view');
|
||||
|
||||
sashQuery = container.querySelectorAll('.monaco-split-view2 > .monaco-sash');
|
||||
|
@ -116,7 +116,7 @@ suite('Splitview', () => {
|
|||
|
||||
splitview.removeView(0);
|
||||
|
||||
viewQuery = container.querySelectorAll('.monaco-split-view2 > .split-view-view');
|
||||
viewQuery = container.querySelectorAll('.monaco-split-view2 > .split-view-container > .split-view-view');
|
||||
assert.equal(viewQuery.length, 0, 'split view should have no views');
|
||||
|
||||
sashQuery = container.querySelectorAll('.monaco-split-view2 > .monaco-sash');
|
||||
|
|
|
@ -1055,7 +1055,10 @@ export class CodeMenu {
|
|||
return [new MenuItem({
|
||||
label: nls.localize('miCheckForUpdates', "Check for Updates..."), click: () => setTimeout(() => {
|
||||
this.reportMenuActionTelemetry('CheckForUpdate');
|
||||
this.updateService.checkForUpdates(true);
|
||||
|
||||
const focusedWindow = this.windowsMainService.getFocusedWindow();
|
||||
const context = focusedWindow ? { windowId: focusedWindow.id } : null;
|
||||
this.updateService.checkForUpdates(context);
|
||||
}, 0)
|
||||
})];
|
||||
|
||||
|
|
|
@ -5,8 +5,6 @@
|
|||
|
||||
'use strict';
|
||||
|
||||
import * as path from 'path';
|
||||
import * as fs from 'fs';
|
||||
import * as platform from 'vs/base/common/platform';
|
||||
import * as paths from 'vs/base/common/paths';
|
||||
import { OpenContext } from 'vs/platform/windows/common/windows';
|
||||
|
@ -35,22 +33,9 @@ export interface IBestWindowOrFolderOptions<W extends ISimpleWindow> {
|
|||
export function findBestWindowOrFolderForFile<W extends ISimpleWindow>({ windows, newWindow, reuseWindow, context, filePath, userHome, codeSettingsFolder, workspaceResolver }: IBestWindowOrFolderOptions<W>): W | string {
|
||||
if (!newWindow && filePath && (context === OpenContext.DESKTOP || context === OpenContext.CLI || context === OpenContext.DOCK)) {
|
||||
const windowOnFilePath = findWindowOnFilePath(windows, filePath, workspaceResolver);
|
||||
|
||||
// 1) window wins if it has a workspace opened
|
||||
if (windowOnFilePath && !!windowOnFilePath.openedWorkspace) {
|
||||
if (windowOnFilePath) {
|
||||
return windowOnFilePath;
|
||||
}
|
||||
|
||||
// 2) window wins if it has a folder opened that is more specific than settings folder
|
||||
const folderWithCodeSettings = !reuseWindow && findFolderWithCodeSettings(filePath, userHome, codeSettingsFolder);
|
||||
if (windowOnFilePath && !(folderWithCodeSettings && folderWithCodeSettings.length > windowOnFilePath.openedFolderPath.length)) {
|
||||
return windowOnFilePath;
|
||||
}
|
||||
|
||||
// 3) finally return path to folder with settings
|
||||
if (folderWithCodeSettings) {
|
||||
return folderWithCodeSettings;
|
||||
}
|
||||
}
|
||||
|
||||
return !newWindow ? getLastActiveWindow(windows) : null;
|
||||
|
@ -77,40 +62,6 @@ function findWindowOnFilePath<W extends ISimpleWindow>(windows: W[], filePath: s
|
|||
return null;
|
||||
}
|
||||
|
||||
function findFolderWithCodeSettings(filePath: string, userHome?: string, codeSettingsFolder?: string): string {
|
||||
let folder = path.dirname(paths.normalize(filePath, true));
|
||||
let homeFolder = userHome && paths.normalize(userHome, true);
|
||||
if (!platform.isLinux) {
|
||||
homeFolder = homeFolder && homeFolder.toLowerCase();
|
||||
}
|
||||
|
||||
let previous = null;
|
||||
while (folder !== previous) {
|
||||
if (hasCodeSettings(folder, homeFolder, codeSettingsFolder)) {
|
||||
return folder;
|
||||
}
|
||||
|
||||
previous = folder;
|
||||
folder = path.dirname(folder);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function hasCodeSettings(folder: string, normalizedUserHome?: string, codeSettingsFolder = '.vscode') {
|
||||
try {
|
||||
if ((platform.isLinux ? folder : folder.toLowerCase()) === normalizedUserHome) {
|
||||
return fs.statSync(path.join(folder, codeSettingsFolder, 'settings.json')).isFile(); // ~/.vscode/extensions is used for extensions
|
||||
}
|
||||
|
||||
return fs.statSync(path.join(folder, codeSettingsFolder)).isDirectory();
|
||||
} catch (err) {
|
||||
// assume impossible to access
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
export function getLastActiveWindow<W extends ISimpleWindow>(windows: W[]): W {
|
||||
const lastFocusedDate = Math.max.apply(Math, windows.map(window => window.lastFocusTime));
|
||||
|
||||
|
|
|
@ -48,25 +48,22 @@ suite('WindowsFinder', () => {
|
|||
})), null);
|
||||
assert.equal(findBestWindowOrFolderForFile(options({
|
||||
filePath: path.join(fixturesFolder, 'vscode_folder', 'file.txt'),
|
||||
newWindow: true // We assume this implies 'editor' work mode, might need separate CLI option later.
|
||||
newWindow: true
|
||||
})), null);
|
||||
assert.equal(findBestWindowOrFolderForFile(options({
|
||||
filePath: path.join(fixturesFolder, 'vscode_folder', 'file.txt'),
|
||||
reuseWindow: true // We assume this implies 'editor' work mode, might need separate CLI option later.
|
||||
reuseWindow: true
|
||||
})), null);
|
||||
assert.equal(findBestWindowOrFolderForFile(options({
|
||||
filePath: path.join(fixturesFolder, 'vscode_folder', 'file.txt'),
|
||||
context: OpenContext.API
|
||||
})), null);
|
||||
});
|
||||
|
||||
test('New window with folder when no windows exist', () => {
|
||||
assert.equal(findBestWindowOrFolderForFile(options({
|
||||
filePath: path.join(fixturesFolder, 'vscode_folder', 'file.txt')
|
||||
})), path.join(fixturesFolder, 'vscode_folder'));
|
||||
})), null);
|
||||
assert.equal(findBestWindowOrFolderForFile(options({
|
||||
filePath: path.join(fixturesFolder, 'vscode_folder', 'new_folder', 'new_file.txt')
|
||||
})), path.join(fixturesFolder, 'vscode_folder'));
|
||||
})), null);
|
||||
});
|
||||
|
||||
test('New window without folder when windows exist', () => {
|
||||
|
@ -106,19 +103,11 @@ suite('WindowsFinder', () => {
|
|||
windows,
|
||||
filePath: path.join(fixturesFolder, 'vscode_folder', 'file.txt')
|
||||
})), vscodeFolderWindow);
|
||||
});
|
||||
|
||||
test('Existing window wins over vscode folder if more specific', () => {
|
||||
const window = { lastFocusTime: 1, openedFolderPath: path.join(fixturesFolder, 'vscode_folder', 'nested_folder') };
|
||||
assert.equal(findBestWindowOrFolderForFile(options({
|
||||
windows: [window],
|
||||
filePath: path.join(fixturesFolder, 'vscode_folder', 'nested_folder', 'subfolder', 'file.txt')
|
||||
})), window);
|
||||
// check
|
||||
assert.equal(findBestWindowOrFolderForFile(options({
|
||||
windows: [window],
|
||||
filePath: path.join(fixturesFolder, 'vscode_folder', 'nested_folder2', 'subfolder', 'file.txt')
|
||||
})), path.join(fixturesFolder, 'vscode_folder'));
|
||||
});
|
||||
|
||||
test('More specific existing window wins', () => {
|
||||
|
@ -130,37 +119,6 @@ suite('WindowsFinder', () => {
|
|||
})), nestedFolderWindow);
|
||||
});
|
||||
|
||||
test('VSCode folder wins over existing window if more specific', () => {
|
||||
const window = { lastFocusTime: 1, openedFolderPath: path.join(fixturesFolder, 'vscode_folder') };
|
||||
assert.equal(findBestWindowOrFolderForFile(options({
|
||||
windows: [window],
|
||||
filePath: path.join(fixturesFolder, 'vscode_folder', 'nested_vscode_folder', 'subfolder', 'file.txt')
|
||||
})), path.join(fixturesFolder, 'vscode_folder', 'nested_vscode_folder'));
|
||||
// check
|
||||
assert.equal(findBestWindowOrFolderForFile(options({
|
||||
windows: [window],
|
||||
filePath: path.join(fixturesFolder, 'vscode_folder', 'nested_folder', 'subfolder', 'file.txt')
|
||||
})), window);
|
||||
});
|
||||
|
||||
test('More specific VSCode folder wins', () => {
|
||||
assert.equal(findBestWindowOrFolderForFile(options({
|
||||
filePath: path.join(fixturesFolder, 'vscode_folder', 'nested_vscode_folder', 'subfolder', 'file.txt')
|
||||
})), path.join(fixturesFolder, 'vscode_folder', 'nested_vscode_folder'));
|
||||
});
|
||||
|
||||
test('VSCode folder in home folder needs settings.json', () => {
|
||||
// Because ~/.vscode/extensions is used for extensions, ~/.vscode is not enough as a hint.
|
||||
assert.equal(findBestWindowOrFolderForFile(options({
|
||||
filePath: path.join(fixturesFolder, 'vscode_folder', 'file.txt'),
|
||||
userHome: path.join(fixturesFolder, 'vscode_folder')
|
||||
})), null);
|
||||
assert.equal(findBestWindowOrFolderForFile(options({
|
||||
filePath: path.join(fixturesFolder, 'vscode_home_folder', 'file.txt'),
|
||||
userHome: path.join(fixturesFolder, 'vscode_home_folder')
|
||||
})), path.join(fixturesFolder, 'vscode_home_folder'));
|
||||
});
|
||||
|
||||
test('Workspace folder wins', () => {
|
||||
const window = { lastFocusTime: 1, openedWorkspace: testWorkspace };
|
||||
assert.equal(findBestWindowOrFolderForFile(options({
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
user-select: none;
|
||||
}
|
||||
|
||||
.monaco-shell .colorpicker-hover[tabindex="0"]:focus {
|
||||
.monaco-editor .colorpicker-hover:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
|
|
|
@ -71,14 +71,6 @@
|
|||
font-size: 13px;
|
||||
}
|
||||
|
||||
.monaco-editor .find-widget.visible.noanimation {
|
||||
-webkit-transition: none;
|
||||
-o-transition: none;
|
||||
-moz-transition: none;
|
||||
-ms-transition: none;
|
||||
transition: none;
|
||||
}
|
||||
|
||||
.monaco-editor .find-widget > .find-part,
|
||||
.monaco-editor .find-widget > .replace-part {
|
||||
margin: 4px 0 0 17px;
|
||||
|
|
|
@ -65,10 +65,10 @@ const FIND_REPLACE_AREA_HEIGHT = 64; // The height of Find Widget when Replace I
|
|||
|
||||
|
||||
export class FindWidgetViewZone implements IViewZone {
|
||||
public afterLineNumber: number;
|
||||
public readonly afterLineNumber: number;
|
||||
public heightInPx: number;
|
||||
public suppressMouseDown: boolean;
|
||||
public domNode: HTMLElement;
|
||||
public readonly suppressMouseDown: boolean;
|
||||
public readonly domNode: HTMLElement;
|
||||
|
||||
constructor(afterLineNumber: number) {
|
||||
this.afterLineNumber = afterLineNumber;
|
||||
|
@ -82,11 +82,11 @@ export class FindWidgetViewZone implements IViewZone {
|
|||
|
||||
export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSashLayoutProvider {
|
||||
private static readonly ID = 'editor.contrib.findWidget';
|
||||
private _codeEditor: ICodeEditor;
|
||||
private readonly _codeEditor: ICodeEditor;
|
||||
private _state: FindReplaceState;
|
||||
private _controller: IFindController;
|
||||
private _contextViewProvider: IContextViewProvider;
|
||||
private _keybindingService: IKeybindingService;
|
||||
private readonly _contextViewProvider: IContextViewProvider;
|
||||
private readonly _keybindingService: IKeybindingService;
|
||||
|
||||
private _domNode: HTMLElement;
|
||||
private _findInput: FindInput;
|
||||
|
@ -382,12 +382,6 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas
|
|||
setTimeout(() => {
|
||||
dom.addClass(this._domNode, 'visible');
|
||||
this._domNode.setAttribute('aria-hidden', 'false');
|
||||
if (!animate) {
|
||||
dom.addClass(this._domNode, 'noanimation');
|
||||
setTimeout(() => {
|
||||
dom.removeClass(this._domNode, 'noanimation');
|
||||
}, 200);
|
||||
}
|
||||
}, 0);
|
||||
this._codeEditor.layoutOverlayWidget(this);
|
||||
|
||||
|
@ -726,8 +720,7 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas
|
|||
className: 'previous',
|
||||
onTrigger: () => {
|
||||
this._codeEditor.getAction(FIND_IDS.PreviousMatchFindAction).run().done(null, onUnexpectedError);
|
||||
},
|
||||
onKeyDown: (e) => { }
|
||||
}
|
||||
}));
|
||||
|
||||
// Next button
|
||||
|
@ -736,8 +729,7 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas
|
|||
className: 'next',
|
||||
onTrigger: () => {
|
||||
this._codeEditor.getAction(FIND_IDS.NextMatchFindAction).run().done(null, onUnexpectedError);
|
||||
},
|
||||
onKeyDown: (e) => { }
|
||||
}
|
||||
}));
|
||||
|
||||
let findPart = document.createElement('div');
|
||||
|
@ -828,8 +820,7 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas
|
|||
className: 'replace-all',
|
||||
onTrigger: () => {
|
||||
this._controller.replaceAll();
|
||||
},
|
||||
onKeyDown: (e) => { }
|
||||
}
|
||||
}));
|
||||
|
||||
let replacePart = document.createElement('div');
|
||||
|
@ -858,8 +849,7 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas
|
|||
this._replaceInputBox.width = this._findInput.inputBox.width;
|
||||
}
|
||||
this._showViewZone();
|
||||
},
|
||||
onKeyDown: (e) => { }
|
||||
}
|
||||
}));
|
||||
this._toggleReplaceBtn.toggleClass('expand', this._isReplaceVisible);
|
||||
this._toggleReplaceBtn.toggleClass('collapse', !this._isReplaceVisible);
|
||||
|
@ -911,19 +901,19 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas
|
|||
}
|
||||
|
||||
interface ISimpleCheckboxOpts {
|
||||
parent: HTMLElement;
|
||||
title: string;
|
||||
onChange: () => void;
|
||||
readonly parent: HTMLElement;
|
||||
readonly title: string;
|
||||
readonly onChange: () => void;
|
||||
}
|
||||
|
||||
class SimpleCheckbox extends Widget {
|
||||
|
||||
private static _COUNTER = 0;
|
||||
|
||||
private _opts: ISimpleCheckboxOpts;
|
||||
private _domNode: HTMLElement;
|
||||
private _checkbox: HTMLInputElement;
|
||||
private _label: HTMLLabelElement;
|
||||
private readonly _opts: ISimpleCheckboxOpts;
|
||||
private readonly _domNode: HTMLElement;
|
||||
private readonly _checkbox: HTMLInputElement;
|
||||
private readonly _label: HTMLLabelElement;
|
||||
|
||||
constructor(opts: ISimpleCheckboxOpts) {
|
||||
super();
|
||||
|
@ -992,16 +982,16 @@ class SimpleCheckbox extends Widget {
|
|||
}
|
||||
|
||||
export interface ISimpleButtonOpts {
|
||||
label: string;
|
||||
className: string;
|
||||
onTrigger: () => void;
|
||||
onKeyDown: (e: IKeyboardEvent) => void;
|
||||
readonly label: string;
|
||||
readonly className: string;
|
||||
readonly onTrigger: () => void;
|
||||
readonly onKeyDown?: (e: IKeyboardEvent) => void;
|
||||
}
|
||||
|
||||
export class SimpleButton extends Widget {
|
||||
|
||||
private _opts: ISimpleButtonOpts;
|
||||
private _domNode: HTMLElement;
|
||||
private readonly _opts: ISimpleButtonOpts;
|
||||
private readonly _domNode: HTMLElement;
|
||||
|
||||
constructor(opts: ISimpleButtonOpts) {
|
||||
super();
|
||||
|
@ -1018,13 +1008,16 @@ export class SimpleButton extends Widget {
|
|||
this._opts.onTrigger();
|
||||
e.preventDefault();
|
||||
});
|
||||
|
||||
this.onkeydown(this._domNode, (e) => {
|
||||
if (e.equals(KeyCode.Space) || e.equals(KeyCode.Enter)) {
|
||||
this._opts.onTrigger();
|
||||
e.preventDefault();
|
||||
return;
|
||||
}
|
||||
this._opts.onKeyDown(e);
|
||||
if (this._opts.onKeyDown) {
|
||||
this._opts.onKeyDown(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -23,20 +23,20 @@ const NLS_NEXT_MATCH_BTN_LABEL = nls.localize('label.nextMatchButton', "Next mat
|
|||
const NLS_CLOSE_BTN_LABEL = nls.localize('label.closeButton', "Close");
|
||||
|
||||
export abstract class SimpleFindWidget extends Widget {
|
||||
protected _findInput: FindInput;
|
||||
protected _domNode: HTMLElement;
|
||||
protected _innerDomNode: HTMLElement;
|
||||
protected _isVisible: boolean;
|
||||
protected _focusTracker: dom.IFocusTracker;
|
||||
protected _findInputFocusTracker: dom.IFocusTracker;
|
||||
protected _findHistory: HistoryNavigator<string>;
|
||||
protected _updateHistoryDelayer: Delayer<void>;
|
||||
private _findInput: FindInput;
|
||||
private _domNode: HTMLElement;
|
||||
private _innerDomNode: HTMLElement;
|
||||
private _isVisible: boolean;
|
||||
private _focusTracker: dom.IFocusTracker;
|
||||
private _findInputFocusTracker: dom.IFocusTracker;
|
||||
private _findHistory: HistoryNavigator<string>;
|
||||
private _updateHistoryDelayer: Delayer<void>;
|
||||
|
||||
constructor(
|
||||
@IContextViewService private readonly _contextViewService: IContextViewService,
|
||||
private animate: boolean = true
|
||||
@IContextViewService private readonly _contextViewService: IContextViewService
|
||||
) {
|
||||
super();
|
||||
|
||||
this._findInput = this._register(new FindInput(null, this._contextViewService, {
|
||||
label: NLS_FIND_INPUT_LABEL,
|
||||
placeholder: NLS_FIND_INPUT_PLACEHOLDER,
|
||||
|
@ -65,31 +65,28 @@ export abstract class SimpleFindWidget extends Widget {
|
|||
}
|
||||
}));
|
||||
|
||||
let prevBtn = new SimpleButton({
|
||||
const prevBtn = new SimpleButton({
|
||||
label: NLS_PREVIOUS_MATCH_BTN_LABEL,
|
||||
className: 'previous',
|
||||
onTrigger: () => {
|
||||
this.find(true);
|
||||
},
|
||||
onKeyDown: (e) => { }
|
||||
}
|
||||
});
|
||||
|
||||
let nextBtn = new SimpleButton({
|
||||
const nextBtn = new SimpleButton({
|
||||
label: NLS_NEXT_MATCH_BTN_LABEL,
|
||||
className: 'next',
|
||||
onTrigger: () => {
|
||||
this.find(false);
|
||||
},
|
||||
onKeyDown: (e) => { }
|
||||
}
|
||||
});
|
||||
|
||||
let closeBtn = new SimpleButton({
|
||||
const closeBtn = new SimpleButton({
|
||||
label: NLS_CLOSE_BTN_LABEL,
|
||||
className: 'close-fw',
|
||||
onTrigger: () => {
|
||||
this.hide();
|
||||
},
|
||||
onKeyDown: (e) => { }
|
||||
}
|
||||
});
|
||||
|
||||
this._innerDomNode = document.createElement('div');
|
||||
|
@ -136,8 +133,8 @@ export abstract class SimpleFindWidget extends Widget {
|
|||
return this._findInput.getValue();
|
||||
}
|
||||
|
||||
public updateTheme(theme?: ITheme): void {
|
||||
let inputStyles = {
|
||||
public updateTheme(theme: ITheme): void {
|
||||
const inputStyles = {
|
||||
inputActiveOptionBorder: theme.getColor(inputActiveOptionBorder),
|
||||
inputBackground: theme.getColor(inputBackground),
|
||||
inputForeground: theme.getColor(inputForeground),
|
||||
|
@ -152,6 +149,14 @@ export abstract class SimpleFindWidget extends Widget {
|
|||
this._findInput.style(inputStyles);
|
||||
}
|
||||
|
||||
dipose() {
|
||||
super.dispose();
|
||||
|
||||
if (this._domNode && this._domNode.parentElement) {
|
||||
this._domNode.parentElement.removeChild(this._domNode);
|
||||
}
|
||||
}
|
||||
|
||||
public getDomNode(): HTMLElement {
|
||||
return this._domNode;
|
||||
}
|
||||
|
@ -171,11 +176,7 @@ export abstract class SimpleFindWidget extends Widget {
|
|||
setTimeout(() => {
|
||||
dom.addClass(this._innerDomNode, 'visible');
|
||||
this._innerDomNode.setAttribute('aria-hidden', 'false');
|
||||
if (!this.animate) {
|
||||
dom.addClass(this._innerDomNode, 'noanimation');
|
||||
}
|
||||
setTimeout(() => {
|
||||
dom.removeClass(this._innerDomNode, 'noanimation');
|
||||
this._findInput.select();
|
||||
}, 200);
|
||||
}, 0);
|
||||
|
@ -201,14 +202,14 @@ export abstract class SimpleFindWidget extends Widget {
|
|||
}
|
||||
|
||||
public showNextFindTerm() {
|
||||
let next = this._findHistory.next();
|
||||
const next = this._findHistory.next();
|
||||
if (next) {
|
||||
this._findInput.setValue(next);
|
||||
}
|
||||
}
|
||||
|
||||
public showPreviousFindTerm() {
|
||||
let previous = this._findHistory.previous();
|
||||
const previous = this._findHistory.previous();
|
||||
if (previous) {
|
||||
this._findInput.setValue(previous);
|
||||
}
|
||||
|
@ -222,7 +223,7 @@ registerThemingParticipant((theme, collector) => {
|
|||
collector.addRule(`.monaco-workbench .simple-find-part { background-color: ${findWidgetBGColor} !important; }`);
|
||||
}
|
||||
|
||||
let widgetShadowColor = theme.getColor(widgetShadow);
|
||||
const widgetShadowColor = theme.getColor(widgetShadow);
|
||||
if (widgetShadowColor) {
|
||||
collector.addRule(`.monaco-workbench .simple-find-part { box-shadow: 0 2px 8px ${widgetShadowColor}; }`);
|
||||
}
|
||||
|
|
|
@ -5,14 +5,12 @@
|
|||
|
||||
'use strict';
|
||||
|
||||
import 'vs/css!./gotoError';
|
||||
import * as nls from 'vs/nls';
|
||||
import { Emitter } from 'vs/base/common/event';
|
||||
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
|
||||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import Severity from 'vs/base/common/severity';
|
||||
import URI from 'vs/base/common/uri';
|
||||
import * as dom from 'vs/base/browser/dom';
|
||||
import { RawContextKey, IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IMarker, IMarkerService } from 'vs/platform/markers/common/markers';
|
||||
import { Position } from 'vs/editor/common/core/position';
|
||||
|
@ -20,16 +18,11 @@ import { Range } from 'vs/editor/common/core/range';
|
|||
import * as editorCommon from 'vs/editor/common/editorCommon';
|
||||
import { registerEditorAction, registerEditorContribution, ServicesAccessor, IActionOptions, EditorAction, EditorCommand, registerEditorCommand } from 'vs/editor/browser/editorExtensions';
|
||||
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { ZoneWidget } from 'vs/editor/contrib/zoneWidget/zoneWidget';
|
||||
import { registerColor, oneOf } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { IThemeService, ITheme } from 'vs/platform/theme/common/themeService';
|
||||
import { Color } from 'vs/base/common/color';
|
||||
import { } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
|
||||
import { AccessibilitySupport } from 'vs/base/common/platform';
|
||||
import { editorErrorForeground, editorErrorBorder, editorWarningForeground, editorWarningBorder, editorInfoForeground, editorInfoBorder } from 'vs/editor/common/view/editorColorRegistry';
|
||||
import { ScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement';
|
||||
import { ScrollbarVisibility } from 'vs/base/common/scrollable';
|
||||
import { KeybindingsRegistry } from 'vs/platform/keybinding/common/keybindingsRegistry';
|
||||
import { MarkerNavigationWidget } from './gotoErrorWidget';
|
||||
|
||||
class MarkerModel {
|
||||
|
||||
|
@ -169,224 +162,11 @@ class MarkerModel {
|
|||
return 1 + this._markers.indexOf(marker);
|
||||
}
|
||||
|
||||
public reveal(): void {
|
||||
|
||||
if (this._nextIdx === -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.withoutWatchingEditorPosition(() => {
|
||||
const pos = new Position(this._markers[this._nextIdx].startLineNumber, this._markers[this._nextIdx].startColumn);
|
||||
this._editor.setPosition(pos);
|
||||
this._editor.revealPositionInCenter(pos, editorCommon.ScrollType.Smooth);
|
||||
});
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this._toUnbind = dispose(this._toUnbind);
|
||||
}
|
||||
}
|
||||
|
||||
class MessageWidget {
|
||||
|
||||
lines: number = 0;
|
||||
longestLineLength: number = 0;
|
||||
|
||||
private readonly _editor: ICodeEditor;
|
||||
private readonly _domNode: HTMLElement;
|
||||
private readonly _scrollable: ScrollableElement;
|
||||
private readonly _disposables: IDisposable[] = [];
|
||||
|
||||
constructor(parent: HTMLElement, editor: ICodeEditor) {
|
||||
this._editor = editor;
|
||||
|
||||
this._domNode = document.createElement('span');
|
||||
this._domNode.className = 'descriptioncontainer';
|
||||
this._domNode.setAttribute('aria-live', 'assertive');
|
||||
this._domNode.setAttribute('role', 'alert');
|
||||
|
||||
this._scrollable = new ScrollableElement(this._domNode, {
|
||||
horizontal: ScrollbarVisibility.Auto,
|
||||
vertical: ScrollbarVisibility.Hidden,
|
||||
useShadows: false,
|
||||
horizontalScrollbarSize: 3
|
||||
});
|
||||
dom.addClass(this._scrollable.getDomNode(), 'block');
|
||||
parent.appendChild(this._scrollable.getDomNode());
|
||||
this._disposables.push(this._scrollable.onScroll(e => this._domNode.style.left = `-${e.scrollLeft}px`));
|
||||
this._disposables.push(this._scrollable);
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
dispose(this._disposables);
|
||||
}
|
||||
|
||||
update({ source, message }: IMarker): void {
|
||||
|
||||
if (source) {
|
||||
this.lines = 0;
|
||||
this.longestLineLength = 0;
|
||||
const indent = new Array(source.length + 3 + 1).join(' ');
|
||||
const lines = message.split(/\r\n|\r|\n/g);
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
let line = lines[i];
|
||||
this.lines += 1;
|
||||
this.longestLineLength = Math.max(line.length, this.longestLineLength);
|
||||
if (i === 0) {
|
||||
message = `[${source}] ${line}`;
|
||||
} else {
|
||||
message += `\n${indent}${line}`;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.lines = 1;
|
||||
this.longestLineLength = message.length;
|
||||
}
|
||||
|
||||
this._domNode.innerText = message;
|
||||
this._editor.applyFontInfo(this._domNode);
|
||||
const width = Math.floor(this._editor.getConfiguration().fontInfo.typicalFullwidthCharacterWidth * this.longestLineLength);
|
||||
this._scrollable.setScrollDimensions({ scrollWidth: width });
|
||||
}
|
||||
|
||||
layout(height: number, width: number): void {
|
||||
this._scrollable.setScrollDimensions({ width });
|
||||
}
|
||||
}
|
||||
|
||||
class MarkerNavigationWidget extends ZoneWidget {
|
||||
|
||||
private _parentContainer: HTMLElement;
|
||||
private _container: HTMLElement;
|
||||
private _title: HTMLElement;
|
||||
private _message: MessageWidget;
|
||||
private _callOnDispose: IDisposable[] = [];
|
||||
private _severity: Severity;
|
||||
private _backgroundColor: Color;
|
||||
|
||||
constructor(
|
||||
editor: ICodeEditor,
|
||||
private _model: MarkerModel,
|
||||
private _themeService: IThemeService
|
||||
) {
|
||||
super(editor, { showArrow: true, showFrame: true, isAccessible: true });
|
||||
this._severity = Severity.Warning;
|
||||
this._backgroundColor = Color.white;
|
||||
|
||||
this._applyTheme(_themeService.getTheme());
|
||||
this._callOnDispose.push(_themeService.onThemeChange(this._applyTheme.bind(this)));
|
||||
|
||||
this.create();
|
||||
this._wireModelAndView();
|
||||
}
|
||||
|
||||
private _applyTheme(theme: ITheme) {
|
||||
this._backgroundColor = theme.getColor(editorMarkerNavigationBackground);
|
||||
let colorId = editorMarkerNavigationError;
|
||||
if (this._severity === Severity.Warning) {
|
||||
colorId = editorMarkerNavigationWarning;
|
||||
} else if (this._severity === Severity.Info) {
|
||||
colorId = editorMarkerNavigationInfo;
|
||||
}
|
||||
let frameColor = theme.getColor(colorId);
|
||||
this.style({
|
||||
arrowColor: frameColor,
|
||||
frameColor: frameColor
|
||||
}); // style() will trigger _applyStyles
|
||||
}
|
||||
|
||||
protected _applyStyles(): void {
|
||||
if (this._parentContainer) {
|
||||
this._parentContainer.style.backgroundColor = this._backgroundColor.toString();
|
||||
}
|
||||
super._applyStyles();
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this._callOnDispose = dispose(this._callOnDispose);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
focus(): void {
|
||||
this._parentContainer.focus();
|
||||
}
|
||||
|
||||
protected _fillContainer(container: HTMLElement): void {
|
||||
this._parentContainer = container;
|
||||
dom.addClass(container, 'marker-widget');
|
||||
this._parentContainer.tabIndex = 0;
|
||||
this._parentContainer.setAttribute('role', 'tooltip');
|
||||
|
||||
this._container = document.createElement('div');
|
||||
container.appendChild(this._container);
|
||||
|
||||
this._title = document.createElement('div');
|
||||
this._title.className = 'block title';
|
||||
this._container.appendChild(this._title);
|
||||
|
||||
this._message = new MessageWidget(this._container, this.editor);
|
||||
this._disposables.push(this._message);
|
||||
}
|
||||
|
||||
show(where: Position, heightInLines: number): void {
|
||||
super.show(where, heightInLines);
|
||||
if (this.editor.getConfiguration().accessibilitySupport !== AccessibilitySupport.Disabled) {
|
||||
this.focus();
|
||||
}
|
||||
}
|
||||
|
||||
private _wireModelAndView(): void {
|
||||
// listen to events
|
||||
this._model.onCurrentMarkerChanged(this.showAtMarker, this, this._callOnDispose);
|
||||
this._model.onMarkerSetChanged(this._onMarkersChanged, this, this._callOnDispose);
|
||||
}
|
||||
|
||||
public showAtMarker(marker: IMarker): void {
|
||||
|
||||
if (!marker) {
|
||||
return;
|
||||
}
|
||||
|
||||
// update:
|
||||
// * title
|
||||
// * message
|
||||
this._container.classList.remove('stale');
|
||||
this._title.innerHTML = nls.localize('title.wo_source', "({0}/{1})", this._model.indexOf(marker), this._model.total);
|
||||
this._message.update(marker);
|
||||
|
||||
this._model.withoutWatchingEditorPosition(() => {
|
||||
// update frame color (only applied on 'show')
|
||||
this._severity = marker.severity;
|
||||
this._applyTheme(this._themeService.getTheme());
|
||||
|
||||
this.show(new Position(marker.startLineNumber, marker.startColumn), this.computeRequiredHeight());
|
||||
});
|
||||
}
|
||||
|
||||
private _onMarkersChanged(): void {
|
||||
const marker = this._model.findMarkerAtPosition(this.position);
|
||||
if (marker) {
|
||||
this._container.classList.remove('stale');
|
||||
this._message.update(marker);
|
||||
} else {
|
||||
this._container.classList.add('stale');
|
||||
}
|
||||
this._relayout();
|
||||
}
|
||||
|
||||
protected _doLayout(heightInPixel: number, widthInPixel: number): void {
|
||||
this._message.layout(heightInPixel, widthInPixel);
|
||||
}
|
||||
|
||||
protected _relayout(): void {
|
||||
super._relayout(this.computeRequiredHeight());
|
||||
}
|
||||
|
||||
private computeRequiredHeight() {
|
||||
return 1 + this._message.lines;
|
||||
}
|
||||
}
|
||||
|
||||
class MarkerNavigationAction extends EditorAction {
|
||||
|
||||
private _isNext: boolean;
|
||||
|
@ -402,14 +182,11 @@ class MarkerNavigationAction extends EditorAction {
|
|||
return;
|
||||
}
|
||||
|
||||
let model = controller.getOrCreateModel();
|
||||
if (model) {
|
||||
if (this._isNext) {
|
||||
model.next();
|
||||
} else {
|
||||
model.previous();
|
||||
}
|
||||
model.reveal();
|
||||
const model = controller.getOrCreateModel();
|
||||
if (this._isNext) {
|
||||
model.next();
|
||||
} else {
|
||||
model.previous();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -424,9 +201,9 @@ class MarkerController implements editorCommon.IEditorContribution {
|
|||
|
||||
private _editor: ICodeEditor;
|
||||
private _model: MarkerModel;
|
||||
private _zone: MarkerNavigationWidget;
|
||||
private _callOnClose: IDisposable[] = [];
|
||||
private _markersNavigationVisible: IContextKey<boolean>;
|
||||
private _widget: MarkerNavigationWidget;
|
||||
private _widgetVisible: IContextKey<boolean>;
|
||||
private _disposeOnClose: IDisposable[] = [];
|
||||
|
||||
constructor(
|
||||
editor: ICodeEditor,
|
||||
|
@ -435,7 +212,7 @@ class MarkerController implements editorCommon.IEditorContribution {
|
|||
@IThemeService private readonly _themeService: IThemeService
|
||||
) {
|
||||
this._editor = editor;
|
||||
this._markersNavigationVisible = CONTEXT_MARKERS_NAVIGATION_VISIBLE.bindTo(this._contextKeyService);
|
||||
this._widgetVisible = CONTEXT_MARKERS_NAVIGATION_VISIBLE.bindTo(this._contextKeyService);
|
||||
}
|
||||
|
||||
public getId(): string {
|
||||
|
@ -447,9 +224,9 @@ class MarkerController implements editorCommon.IEditorContribution {
|
|||
}
|
||||
|
||||
private _cleanUp(): void {
|
||||
this._markersNavigationVisible.reset();
|
||||
this._callOnClose = dispose(this._callOnClose);
|
||||
this._zone = null;
|
||||
this._widgetVisible.reset();
|
||||
this._disposeOnClose = dispose(this._disposeOnClose);
|
||||
this._widget = null;
|
||||
this._model = null;
|
||||
}
|
||||
|
||||
|
@ -461,15 +238,33 @@ class MarkerController implements editorCommon.IEditorContribution {
|
|||
|
||||
const markers = this._getMarkers();
|
||||
this._model = new MarkerModel(this._editor, markers);
|
||||
this._zone = new MarkerNavigationWidget(this._editor, this._model, this._themeService);
|
||||
this._markersNavigationVisible.set(true);
|
||||
this._markerService.onMarkerChanged(this._onMarkerChanged, this, this._disposeOnClose);
|
||||
|
||||
this._callOnClose.push(this._model);
|
||||
this._callOnClose.push(this._zone);
|
||||
this._widget = new MarkerNavigationWidget(this._editor, this._themeService);
|
||||
this._widgetVisible.set(true);
|
||||
|
||||
this._disposeOnClose.push(this._model);
|
||||
this._disposeOnClose.push(this._widget);
|
||||
this._disposeOnClose.push(this._editor.onDidChangeModel(() => this._cleanUp()));
|
||||
|
||||
this._disposeOnClose.push(this._model.onCurrentMarkerChanged(marker => {
|
||||
if (!marker) {
|
||||
this._cleanUp();
|
||||
} else {
|
||||
this._model.withoutWatchingEditorPosition(() => {
|
||||
this._widget.showAtMarker(marker, this._model.indexOf(marker), this._model.total);
|
||||
});
|
||||
}
|
||||
}));
|
||||
this._disposeOnClose.push(this._model.onMarkerSetChanged(() => {
|
||||
const marker = this._model.findMarkerAtPosition(this._widget.position);
|
||||
if (marker) {
|
||||
this._widget.updateMarker(marker);
|
||||
} else {
|
||||
this._widget.showStale();
|
||||
}
|
||||
}));
|
||||
|
||||
this._callOnClose.push(this._editor.onDidChangeModel(() => this._cleanUp()));
|
||||
this._model.onCurrentMarkerChanged(marker => !marker && this._cleanUp(), undefined, this._callOnClose);
|
||||
this._markerService.onMarkerChanged(this._onMarkerChanged, this, this._callOnClose);
|
||||
return this._model;
|
||||
}
|
||||
|
||||
|
@ -539,14 +334,3 @@ registerEditorCommand(new MarkerCommand({
|
|||
secondary: [KeyMod.Shift | KeyCode.Escape]
|
||||
}
|
||||
}));
|
||||
|
||||
// theming
|
||||
|
||||
let errorDefault = oneOf(editorErrorForeground, editorErrorBorder);
|
||||
let warningDefault = oneOf(editorWarningForeground, editorWarningBorder);
|
||||
let infoDefault = oneOf(editorInfoForeground, editorInfoBorder);
|
||||
|
||||
export const editorMarkerNavigationError = registerColor('editorMarkerNavigationError.background', { dark: errorDefault, light: errorDefault, hc: errorDefault }, nls.localize('editorMarkerNavigationError', 'Editor marker navigation widget error color.'));
|
||||
export const editorMarkerNavigationWarning = registerColor('editorMarkerNavigationWarning.background', { dark: warningDefault, light: warningDefault, hc: warningDefault }, nls.localize('editorMarkerNavigationWarning', 'Editor marker navigation widget warning color.'));
|
||||
export const editorMarkerNavigationInfo = registerColor('editorMarkerNavigationInfo.background', { dark: infoDefault, light: infoDefault, hc: infoDefault }, nls.localize('editorMarkerNavigationInfo', 'Editor marker navigation widget info color.'));
|
||||
export const editorMarkerNavigationBackground = registerColor('editorMarkerNavigation.background', { dark: '#2D2D30', light: Color.white, hc: '#0C141F' }, nls.localize('editorMarkerNavigationBackground', 'Editor marker navigation widget background.'));
|
||||
|
|
226
src/vs/editor/contrib/gotoError/gotoErrorWidget.ts
Normal file
226
src/vs/editor/contrib/gotoError/gotoErrorWidget.ts
Normal file
|
@ -0,0 +1,226 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* 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!./gotoErrorWidget';
|
||||
import * as nls from 'vs/nls';
|
||||
import * as dom from 'vs/base/browser/dom';
|
||||
import Severity from 'vs/base/common/severity';
|
||||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { IMarker } from 'vs/platform/markers/common/markers';
|
||||
import { Position } from 'vs/editor/common/core/position';
|
||||
import { Range } from 'vs/editor/common/core/range';
|
||||
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { ZoneWidget } from 'vs/editor/contrib/zoneWidget/zoneWidget';
|
||||
import { registerColor, oneOf } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { IThemeService, ITheme } from 'vs/platform/theme/common/themeService';
|
||||
import { Color } from 'vs/base/common/color';
|
||||
import { AccessibilitySupport } from 'vs/base/common/platform';
|
||||
import { editorErrorForeground, editorErrorBorder, editorWarningForeground, editorWarningBorder, editorInfoForeground, editorInfoBorder } from 'vs/editor/common/view/editorColorRegistry';
|
||||
import { ScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement';
|
||||
import { ScrollbarVisibility } from 'vs/base/common/scrollable';
|
||||
import { ScrollType } from 'vs/editor/common/editorCommon';
|
||||
|
||||
class MessageWidget {
|
||||
|
||||
lines: number = 0;
|
||||
longestLineLength: number = 0;
|
||||
|
||||
private readonly _editor: ICodeEditor;
|
||||
private readonly _domNode: HTMLElement;
|
||||
private readonly _scrollable: ScrollableElement;
|
||||
private readonly _disposables: IDisposable[] = [];
|
||||
|
||||
constructor(parent: HTMLElement, editor: ICodeEditor) {
|
||||
this._editor = editor;
|
||||
|
||||
this._domNode = document.createElement('span');
|
||||
this._domNode.className = 'descriptioncontainer';
|
||||
this._domNode.setAttribute('aria-live', 'assertive');
|
||||
this._domNode.setAttribute('role', 'alert');
|
||||
|
||||
this._scrollable = new ScrollableElement(this._domNode, {
|
||||
horizontal: ScrollbarVisibility.Auto,
|
||||
vertical: ScrollbarVisibility.Hidden,
|
||||
useShadows: false,
|
||||
horizontalScrollbarSize: 3
|
||||
});
|
||||
dom.addClass(this._scrollable.getDomNode(), 'block');
|
||||
parent.appendChild(this._scrollable.getDomNode());
|
||||
this._disposables.push(this._scrollable.onScroll(e => this._domNode.style.left = `-${e.scrollLeft}px`));
|
||||
this._disposables.push(this._scrollable);
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
dispose(this._disposables);
|
||||
}
|
||||
|
||||
update({ source, message }: IMarker): void {
|
||||
|
||||
if (source) {
|
||||
this.lines = 0;
|
||||
this.longestLineLength = 0;
|
||||
const indent = new Array(source.length + 3 + 1).join(' ');
|
||||
const lines = message.split(/\r\n|\r|\n/g);
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
let line = lines[i];
|
||||
this.lines += 1;
|
||||
this.longestLineLength = Math.max(line.length, this.longestLineLength);
|
||||
if (i === 0) {
|
||||
message = `[${source}] ${line}`;
|
||||
} else {
|
||||
message += `\n${indent}${line}`;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.lines = 1;
|
||||
this.longestLineLength = message.length;
|
||||
}
|
||||
|
||||
this._domNode.innerText = message;
|
||||
this._editor.applyFontInfo(this._domNode);
|
||||
const width = Math.floor(this._editor.getConfiguration().fontInfo.typicalFullwidthCharacterWidth * this.longestLineLength);
|
||||
this._scrollable.setScrollDimensions({ scrollWidth: width });
|
||||
}
|
||||
|
||||
layout(height: number, width: number): void {
|
||||
this._scrollable.setScrollDimensions({ width });
|
||||
}
|
||||
}
|
||||
|
||||
export class MarkerNavigationWidget extends ZoneWidget {
|
||||
|
||||
private _parentContainer: HTMLElement;
|
||||
private _container: HTMLElement;
|
||||
private _title: HTMLElement;
|
||||
private _message: MessageWidget;
|
||||
private _callOnDispose: IDisposable[] = [];
|
||||
private _severity: Severity;
|
||||
private _backgroundColor: Color;
|
||||
|
||||
constructor(
|
||||
editor: ICodeEditor,
|
||||
private _themeService: IThemeService
|
||||
) {
|
||||
super(editor, { showArrow: true, showFrame: true, isAccessible: true });
|
||||
this._severity = Severity.Warning;
|
||||
this._backgroundColor = Color.white;
|
||||
|
||||
this._applyTheme(_themeService.getTheme());
|
||||
this._callOnDispose.push(_themeService.onThemeChange(this._applyTheme.bind(this)));
|
||||
|
||||
this.create();
|
||||
}
|
||||
|
||||
private _applyTheme(theme: ITheme) {
|
||||
this._backgroundColor = theme.getColor(editorMarkerNavigationBackground);
|
||||
let colorId = editorMarkerNavigationError;
|
||||
if (this._severity === Severity.Warning) {
|
||||
colorId = editorMarkerNavigationWarning;
|
||||
} else if (this._severity === Severity.Info) {
|
||||
colorId = editorMarkerNavigationInfo;
|
||||
}
|
||||
let frameColor = theme.getColor(colorId);
|
||||
this.style({
|
||||
arrowColor: frameColor,
|
||||
frameColor: frameColor
|
||||
}); // style() will trigger _applyStyles
|
||||
}
|
||||
|
||||
protected _applyStyles(): void {
|
||||
if (this._parentContainer) {
|
||||
this._parentContainer.style.backgroundColor = this._backgroundColor.toString();
|
||||
}
|
||||
super._applyStyles();
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this._callOnDispose = dispose(this._callOnDispose);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
focus(): void {
|
||||
this._parentContainer.focus();
|
||||
}
|
||||
|
||||
protected _fillContainer(container: HTMLElement): void {
|
||||
this._parentContainer = container;
|
||||
dom.addClass(container, 'marker-widget');
|
||||
this._parentContainer.tabIndex = 0;
|
||||
this._parentContainer.setAttribute('role', 'tooltip');
|
||||
|
||||
this._container = document.createElement('div');
|
||||
container.appendChild(this._container);
|
||||
|
||||
this._title = document.createElement('div');
|
||||
this._title.className = 'block title';
|
||||
this._container.appendChild(this._title);
|
||||
|
||||
this._message = new MessageWidget(this._container, this.editor);
|
||||
this._disposables.push(this._message);
|
||||
}
|
||||
|
||||
show(where: Position, heightInLines: number): void {
|
||||
throw new Error('call showAtMarker');
|
||||
}
|
||||
|
||||
showAtMarker(marker: IMarker, markerIdx: number, markerCount: number): void {
|
||||
// update:
|
||||
// * title
|
||||
// * message
|
||||
this._container.classList.remove('stale');
|
||||
this._title.innerHTML = nls.localize('title.wo_source', "({0}/{1})", markerIdx, markerCount);
|
||||
this._message.update(marker);
|
||||
|
||||
// update frame color (only applied on 'show')
|
||||
this._severity = marker.severity;
|
||||
this._applyTheme(this._themeService.getTheme());
|
||||
|
||||
// show
|
||||
let range = Range.lift(marker);
|
||||
let position = range.containsPosition(this.editor.getPosition()) ? this.editor.getPosition() : range.getStartPosition();
|
||||
super.show(position, this.computeRequiredHeight());
|
||||
|
||||
this.editor.revealPositionInCenter(position, ScrollType.Smooth);
|
||||
|
||||
if (this.editor.getConfiguration().accessibilitySupport !== AccessibilitySupport.Disabled) {
|
||||
this.focus();
|
||||
}
|
||||
}
|
||||
|
||||
updateMarker(marker: IMarker): void {
|
||||
this._container.classList.remove('stale');
|
||||
this._message.update(marker);
|
||||
}
|
||||
|
||||
showStale() {
|
||||
this._container.classList.add('stale');
|
||||
this._relayout();
|
||||
}
|
||||
|
||||
protected _doLayout(heightInPixel: number, widthInPixel: number): void {
|
||||
this._message.layout(heightInPixel, widthInPixel);
|
||||
}
|
||||
|
||||
protected _relayout(): void {
|
||||
super._relayout(this.computeRequiredHeight());
|
||||
}
|
||||
|
||||
private computeRequiredHeight() {
|
||||
return 1 + this._message.lines;
|
||||
}
|
||||
}
|
||||
|
||||
// theming
|
||||
|
||||
let errorDefault = oneOf(editorErrorForeground, editorErrorBorder);
|
||||
let warningDefault = oneOf(editorWarningForeground, editorWarningBorder);
|
||||
let infoDefault = oneOf(editorInfoForeground, editorInfoBorder);
|
||||
|
||||
export const editorMarkerNavigationError = registerColor('editorMarkerNavigationError.background', { dark: errorDefault, light: errorDefault, hc: errorDefault }, nls.localize('editorMarkerNavigationError', 'Editor marker navigation widget error color.'));
|
||||
export const editorMarkerNavigationWarning = registerColor('editorMarkerNavigationWarning.background', { dark: warningDefault, light: warningDefault, hc: warningDefault }, nls.localize('editorMarkerNavigationWarning', 'Editor marker navigation widget warning color.'));
|
||||
export const editorMarkerNavigationInfo = registerColor('editorMarkerNavigationInfo.background', { dark: infoDefault, light: infoDefault, hc: infoDefault }, nls.localize('editorMarkerNavigationInfo', 'Editor marker navigation widget info color.'));
|
||||
export const editorMarkerNavigationBackground = registerColor('editorMarkerNavigation.background', { dark: '#2D2D30', light: Color.white, hc: '#0C141F' }, nls.localize('editorMarkerNavigationBackground', 'Editor marker navigation widget background.'));
|
|
@ -361,7 +361,6 @@ export class SuggestWidget implements IContentWidget, IDelegate<ICompletionItem>
|
|||
private isAuto: boolean;
|
||||
private loadingTimeout: number;
|
||||
private currentSuggestionDetails: TPromise<void>;
|
||||
private focusedItemIndex: number;
|
||||
private focusedItem: ICompletionItem;
|
||||
private ignoreFocusEvents = false;
|
||||
private completionModel: CompletionModel;
|
||||
|
@ -591,28 +590,12 @@ export class SuggestWidget implements IContentWidget, IDelegate<ICompletionItem>
|
|||
|
||||
this.suggestionSupportsAutoAccept.set(!item.suggestion.noAutoAccept);
|
||||
|
||||
const oldFocus = this.focusedItem;
|
||||
const oldFocusIndex = this.focusedItemIndex;
|
||||
this.focusedItemIndex = index;
|
||||
this.focusedItem = item;
|
||||
|
||||
if (oldFocus) {
|
||||
this.ignoreFocusEvents = true;
|
||||
this.list.splice(oldFocusIndex, 1, [oldFocus]);
|
||||
this.ignoreFocusEvents = false;
|
||||
}
|
||||
|
||||
this.list.reveal(index);
|
||||
|
||||
this.currentSuggestionDetails = item.resolve()
|
||||
.then(() => {
|
||||
this.ignoreFocusEvents = true;
|
||||
this.list.splice(index, 1, [item]);
|
||||
this.ignoreFocusEvents = false;
|
||||
|
||||
this.list.setFocus([index]);
|
||||
this.list.reveal(index);
|
||||
|
||||
if (this.expandDocsSettingFromStorage()) {
|
||||
this.showDetails();
|
||||
} else {
|
||||
|
@ -738,7 +721,6 @@ export class SuggestWidget implements IContentWidget, IDelegate<ICompletionItem>
|
|||
this.telemetryService.publicLog('suggestWidget', { ...stats, ...this.editor.getTelemetryData() });
|
||||
|
||||
this.focusedItem = null;
|
||||
this.focusedItemIndex = null;
|
||||
this.list.splice(0, this.list.length, this.completionModel.items);
|
||||
|
||||
if (isFrozen) {
|
||||
|
|
|
@ -39,7 +39,7 @@ import { OS } from 'vs/base/common/platform';
|
|||
import { IRange } from 'vs/editor/common/core/range';
|
||||
import { ITextModel } from 'vs/editor/common/model';
|
||||
import { INotificationService, INotification, INotificationHandle, NoOpNotification } from 'vs/platform/notification/common/notification';
|
||||
import { IConfirmation, IConfirmationResult, IConfirmationService } from 'vs/platform/dialogs/common/dialogs';
|
||||
import { IConfirmation, IConfirmationResult, IDialogService } from 'vs/platform/dialogs/common/dialogs';
|
||||
import { IPosition, Position as Pos } from 'vs/editor/common/core/position';
|
||||
|
||||
export class SimpleEditor implements IEditor {
|
||||
|
@ -236,11 +236,20 @@ export class SimpleProgressService implements IProgressService {
|
|||
}
|
||||
}
|
||||
|
||||
export class SimpleConfirmationService implements IConfirmationService {
|
||||
export class SimpleDialogService implements IDialogService {
|
||||
|
||||
public _serviceBrand: any;
|
||||
|
||||
public confirm(confirmation: IConfirmation): TPromise<boolean> {
|
||||
public confirm(confirmation: IConfirmation): TPromise<IConfirmationResult> {
|
||||
return this.doConfirm(confirmation).then(confirmed => {
|
||||
return {
|
||||
confirmed,
|
||||
checkboxChecked: false // unsupported
|
||||
} as IConfirmationResult;
|
||||
});
|
||||
}
|
||||
|
||||
private doConfirm(confirmation: IConfirmation): TPromise<boolean> {
|
||||
let messageText = confirmation.message;
|
||||
if (confirmation.detail) {
|
||||
messageText = messageText + '\n\n' + confirmation.detail;
|
||||
|
@ -248,15 +257,6 @@ export class SimpleConfirmationService implements IConfirmationService {
|
|||
|
||||
return TPromise.wrap(window.confirm(messageText));
|
||||
}
|
||||
|
||||
public confirmWithCheckbox(confirmation: IConfirmation): TPromise<IConfirmationResult> {
|
||||
return this.confirm(confirmation).then(confirmed => {
|
||||
return {
|
||||
confirmed,
|
||||
checkboxChecked: false // unsupported
|
||||
} as IConfirmationResult;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export class SimpleNotificationService implements INotificationService {
|
||||
|
|
|
@ -33,7 +33,7 @@ import { CodeEditorServiceImpl } from 'vs/editor/browser/services/codeEditorServ
|
|||
import {
|
||||
SimpleConfigurationService, SimpleResourceConfigurationService, SimpleMenuService,
|
||||
SimpleProgressService, StandaloneCommandService, StandaloneKeybindingService, SimpleNotificationService,
|
||||
StandaloneTelemetryService, SimpleWorkspaceContextService, SimpleConfirmationService
|
||||
StandaloneTelemetryService, SimpleWorkspaceContextService, SimpleDialogService
|
||||
} from 'vs/editor/standalone/browser/simpleServices';
|
||||
import { ContextKeyService } from 'vs/platform/contextkey/browser/contextKeyService';
|
||||
import { IMenuService } from 'vs/platform/actions/common/actions';
|
||||
|
@ -41,7 +41,7 @@ import { IStandaloneThemeService } from 'vs/editor/standalone/common/standaloneT
|
|||
import { StandaloneThemeServiceImpl } from 'vs/editor/standalone/browser/standaloneThemeServiceImpl';
|
||||
import { ILogService, NullLogService } from 'vs/platform/log/common/log';
|
||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
import { IConfirmationService } from 'vs/platform/dialogs/common/dialogs';
|
||||
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
|
||||
|
||||
export interface IEditorContextViewService extends IContextViewService {
|
||||
dispose(): void;
|
||||
|
@ -126,7 +126,7 @@ export module StaticServices {
|
|||
|
||||
export const telemetryService = define(ITelemetryService, () => new StandaloneTelemetryService());
|
||||
|
||||
export const confirmationService = define(IConfirmationService, () => new SimpleConfirmationService());
|
||||
export const dialogService = define(IDialogService, () => new SimpleDialogService());
|
||||
|
||||
export const notificationService = define(INotificationService, () => new SimpleNotificationService());
|
||||
|
||||
|
|
|
@ -1864,8 +1864,6 @@ suite('Editor Controller - Regression tests', () => {
|
|||
const viewModel = new ViewModel(0, config, model, null);
|
||||
const cursor = new Cursor(config, model, viewModel);
|
||||
|
||||
console.log(viewModel.getLineCount());
|
||||
|
||||
moveTo(cursor, 1, 43, false);
|
||||
moveTo(cursor, 1, 147, true);
|
||||
assertCursor(cursor, new Selection(1, 43, 1, 147));
|
||||
|
|
|
@ -286,7 +286,8 @@ export class Configuration {
|
|||
private _workspaceConfiguration: ConfigurationModel = new ConfigurationModel(),
|
||||
private _folderConfigurations: StrictResourceMap<ConfigurationModel> = new StrictResourceMap<ConfigurationModel>(),
|
||||
private _memoryConfiguration: ConfigurationModel = new ConfigurationModel(),
|
||||
private _memoryConfigurationByResource: StrictResourceMap<ConfigurationModel> = new StrictResourceMap<ConfigurationModel>()) {
|
||||
private _memoryConfigurationByResource: StrictResourceMap<ConfigurationModel> = new StrictResourceMap<ConfigurationModel>(),
|
||||
private _freeze: boolean = true) {
|
||||
}
|
||||
|
||||
getValue(section: string, overrides: IConfigurationOverrides, workspace: Workspace): any {
|
||||
|
@ -421,7 +422,10 @@ export class Configuration {
|
|||
|
||||
private getWorkspaceConsolidatedConfiguration(): ConfigurationModel {
|
||||
if (!this._workspaceConsolidatedConfiguration) {
|
||||
this._workspaceConsolidatedConfiguration = this._defaultConfiguration.merge(this._userConfiguration).merge(this._workspaceConfiguration).merge(this._memoryConfiguration).freeze();
|
||||
this._workspaceConsolidatedConfiguration = this._defaultConfiguration.merge(this._userConfiguration, this._workspaceConfiguration, this._memoryConfiguration);
|
||||
if (this._freeze) {
|
||||
this._workspaceConfiguration = this._workspaceConfiguration.freeze();
|
||||
}
|
||||
}
|
||||
return this._workspaceConsolidatedConfiguration;
|
||||
}
|
||||
|
@ -432,7 +436,10 @@ export class Configuration {
|
|||
const workspaceConsolidateConfiguration = this.getWorkspaceConsolidatedConfiguration();
|
||||
const folderConfiguration = this._folderConfigurations.get(folder);
|
||||
if (folderConfiguration) {
|
||||
folderConsolidatedConfiguration = workspaceConsolidateConfiguration.merge(folderConfiguration).freeze();
|
||||
folderConsolidatedConfiguration = workspaceConsolidateConfiguration.merge(folderConfiguration);
|
||||
if (this._freeze) {
|
||||
folderConsolidatedConfiguration = folderConsolidatedConfiguration.freeze();
|
||||
}
|
||||
this._foldersConsolidatedConfigurations.set(folder, folderConsolidatedConfiguration);
|
||||
} else {
|
||||
folderConsolidatedConfiguration = workspaceConsolidateConfiguration;
|
||||
|
@ -493,21 +500,6 @@ export class Configuration {
|
|||
}
|
||||
return all;
|
||||
}
|
||||
|
||||
public static parse(data: IConfigurationData): Configuration {
|
||||
const defaultConfiguration = Configuration.parseConfigurationModel(data.defaults);
|
||||
const userConfiguration = Configuration.parseConfigurationModel(data.user);
|
||||
const workspaceConfiguration = Configuration.parseConfigurationModel(data.workspace);
|
||||
const folders: StrictResourceMap<ConfigurationModel> = Object.keys(data.folders).reduce((result, key) => {
|
||||
result.set(URI.parse(key), Configuration.parseConfigurationModel(data.folders[key]));
|
||||
return result;
|
||||
}, new StrictResourceMap<ConfigurationModel>());
|
||||
return new Configuration(defaultConfiguration, userConfiguration, workspaceConfiguration, folders);
|
||||
}
|
||||
|
||||
private static parseConfigurationModel(model: IConfigurationModel): ConfigurationModel {
|
||||
return new ConfigurationModel(model.contents, model.keys, model.overrides).freeze();
|
||||
}
|
||||
}
|
||||
|
||||
export class AbstractConfigurationChangeEvent {
|
||||
|
|
|
@ -22,25 +22,30 @@ export interface IConfirmation {
|
|||
}
|
||||
|
||||
export interface IConfirmationResult {
|
||||
|
||||
/**
|
||||
* Will be true if the dialog was confirmed with the primary button
|
||||
* pressed.
|
||||
*/
|
||||
confirmed: boolean;
|
||||
|
||||
/**
|
||||
* This will only be defined if the confirmation was created
|
||||
* with the checkox option defined.
|
||||
*/
|
||||
checkboxChecked?: boolean;
|
||||
}
|
||||
|
||||
export const IConfirmationService = createDecorator<IConfirmationService>('confirmationService');
|
||||
export const IDialogService = createDecorator<IDialogService>('dialogService');
|
||||
|
||||
export interface IConfirmationService {
|
||||
export interface IDialogService {
|
||||
|
||||
_serviceBrand: any;
|
||||
|
||||
/**
|
||||
* Ask the user for confirmation with a modal dialog.
|
||||
*/
|
||||
confirm(confirmation: IConfirmation): TPromise<boolean>;
|
||||
|
||||
/**
|
||||
* Ask the user for confirmation with a checkbox in a modal dialog.
|
||||
*/
|
||||
confirmWithCheckbox(confirmation: IConfirmation): TPromise<IConfirmationResult>;
|
||||
confirm(confirmation: IConfirmation): TPromise<IConfirmationResult>;
|
||||
}
|
||||
|
||||
export const IChoiceService = createDecorator<IChoiceService>('choiceService');
|
||||
|
|
|
@ -5,11 +5,10 @@
|
|||
|
||||
import { localize } from 'vs/nls';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { distinct, coalesce } from 'vs/base/common/arrays';
|
||||
import Event, { Emitter } from 'vs/base/common/event';
|
||||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { IExtensionManagementService, DidUninstallExtensionEvent, IExtensionEnablementService, IExtensionIdentifier, EnablementState, ILocalExtension, isIExtensionIdentifier } from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
import { adoptToGalleryExtensionId, getIdFromLocalExtensionId, areSameExtensions, getGalleryExtensionIdFromLocal } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
|
||||
import { IExtensionManagementService, DidUninstallExtensionEvent, IExtensionEnablementService, IExtensionIdentifier, EnablementState, ILocalExtension, isIExtensionIdentifier, LocalExtensionType } from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
import { getIdFromLocalExtensionId, areSameExtensions, getGalleryExtensionIdFromLocal } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
|
||||
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
|
||||
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
|
@ -58,10 +57,11 @@ export class ExtensionEnablementService implements IExtensionEnablementService {
|
|||
return TPromise.as(result);
|
||||
}
|
||||
|
||||
getEnablementState(identifier: IExtensionIdentifier): EnablementState {
|
||||
if (this.environmentService.disableExtensions) {
|
||||
getEnablementState(extension: ILocalExtension): EnablementState {
|
||||
if (this.environmentService.disableExtensions && extension.type === LocalExtensionType.User) {
|
||||
return EnablementState.Disabled;
|
||||
}
|
||||
const identifier = this._getIdentifier(extension);
|
||||
if (this.hasWorkspace) {
|
||||
if (this._getEnabledExtensions(StorageScope.WORKSPACE).filter(e => areSameExtensions(e, identifier))[0]) {
|
||||
return EnablementState.WorkspaceEnabled;
|
||||
|
@ -82,14 +82,14 @@ export class ExtensionEnablementService implements IExtensionEnablementService {
|
|||
}
|
||||
|
||||
setEnablement(arg: ILocalExtension | IExtensionIdentifier, newState: EnablementState): TPromise<boolean> {
|
||||
let identifier;
|
||||
let identifier: IExtensionIdentifier;
|
||||
if (isIExtensionIdentifier(arg)) {
|
||||
identifier = arg;
|
||||
} else {
|
||||
if (!this.canChangeEnablement(arg)) {
|
||||
return TPromise.wrap(false);
|
||||
}
|
||||
identifier = { id: getGalleryExtensionIdFromLocal(arg), uuid: arg.identifier.uuid };
|
||||
identifier = this._getIdentifier(arg);
|
||||
}
|
||||
|
||||
const workspace = newState === EnablementState.WorkspaceDisabled || newState === EnablementState.WorkspaceEnabled;
|
||||
|
@ -97,7 +97,7 @@ export class ExtensionEnablementService implements IExtensionEnablementService {
|
|||
return TPromise.wrapError<boolean>(new Error(localize('noWorkspace', "No workspace.")));
|
||||
}
|
||||
|
||||
const currentState = this.getEnablementState(identifier);
|
||||
const currentState = this._getEnablementState(identifier);
|
||||
|
||||
if (currentState === newState) {
|
||||
return TPromise.as(false);
|
||||
|
@ -123,16 +123,29 @@ export class ExtensionEnablementService implements IExtensionEnablementService {
|
|||
return TPromise.as(true);
|
||||
}
|
||||
|
||||
isEnabled(identifier: IExtensionIdentifier): boolean {
|
||||
const enablementState = this.getEnablementState(identifier);
|
||||
isEnabled(extension: ILocalExtension): boolean {
|
||||
const enablementState = this.getEnablementState(extension);
|
||||
return enablementState === EnablementState.WorkspaceEnabled || enablementState === EnablementState.Enabled;
|
||||
}
|
||||
|
||||
migrateToIdentifiers(installed: IExtensionIdentifier[]): void {
|
||||
this._migrateDisabledExtensions(installed, StorageScope.GLOBAL);
|
||||
private _getEnablementState(identifier: IExtensionIdentifier): EnablementState {
|
||||
if (this.hasWorkspace) {
|
||||
this._migrateDisabledExtensions(installed, StorageScope.WORKSPACE);
|
||||
if (this._getEnabledExtensions(StorageScope.WORKSPACE).filter(e => areSameExtensions(e, identifier))[0]) {
|
||||
return EnablementState.WorkspaceEnabled;
|
||||
}
|
||||
|
||||
if (this._getDisabledExtensions(StorageScope.WORKSPACE).filter(e => areSameExtensions(e, identifier))[0]) {
|
||||
return EnablementState.WorkspaceDisabled;
|
||||
}
|
||||
}
|
||||
if (this._getDisabledExtensions(StorageScope.GLOBAL).filter(e => areSameExtensions(e, identifier))[0]) {
|
||||
return EnablementState.Disabled;
|
||||
}
|
||||
return EnablementState.Enabled;
|
||||
}
|
||||
|
||||
private _getIdentifier(extension: ILocalExtension): IExtensionIdentifier {
|
||||
return { id: getGalleryExtensionIdFromLocal(extension), uuid: extension.identifier.uuid };
|
||||
}
|
||||
|
||||
private _enableExtension(identifier: IExtensionIdentifier): void {
|
||||
|
@ -227,8 +240,8 @@ export class ExtensionEnablementService implements IExtensionEnablementService {
|
|||
return this._getExtensions(DISABLED_EXTENSIONS_STORAGE_PATH, scope);
|
||||
}
|
||||
|
||||
private _setDisabledExtensions(disabledExtensions: IExtensionIdentifier[], scope: StorageScope, extension: IExtensionIdentifier, fireEvent = true): void {
|
||||
this._setExtensions(DISABLED_EXTENSIONS_STORAGE_PATH, disabledExtensions, scope, extension, fireEvent);
|
||||
private _setDisabledExtensions(disabledExtensions: IExtensionIdentifier[], scope: StorageScope, extension: IExtensionIdentifier): void {
|
||||
this._setExtensions(DISABLED_EXTENSIONS_STORAGE_PATH, disabledExtensions, scope, extension);
|
||||
}
|
||||
|
||||
private _getExtensions(storageId: string, scope: StorageScope): IExtensionIdentifier[] {
|
||||
|
@ -239,30 +252,12 @@ export class ExtensionEnablementService implements IExtensionEnablementService {
|
|||
return value ? JSON.parse(value) : [];
|
||||
}
|
||||
|
||||
private _setExtensions(storageId: string, extensions: IExtensionIdentifier[], scope: StorageScope, extension: IExtensionIdentifier, fireEvent = true): void {
|
||||
private _setExtensions(storageId: string, extensions: IExtensionIdentifier[], scope: StorageScope, extension: IExtensionIdentifier): void {
|
||||
if (extensions.length) {
|
||||
this.storageService.store(storageId, JSON.stringify(extensions.map(({ id, uuid }) => (<IExtensionIdentifier>{ id, uuid }))), scope);
|
||||
} else {
|
||||
this.storageService.remove(storageId, scope);
|
||||
}
|
||||
if (fireEvent) {
|
||||
this._onEnablementChanged.fire(extension);
|
||||
}
|
||||
}
|
||||
|
||||
private _migrateDisabledExtensions(installedExtensions: IExtensionIdentifier[], scope: StorageScope): void {
|
||||
const oldValue = this.storageService.get('extensions/disabled', scope, '');
|
||||
if (oldValue) {
|
||||
const extensionIdentifiers = coalesce(distinct(oldValue.split(',')).map(id => {
|
||||
id = adoptToGalleryExtensionId(id);
|
||||
const matched = installedExtensions.filter(installed => areSameExtensions({ id }, { id: installed.id }))[0];
|
||||
return matched ? { id: matched.id, uuid: matched.uuid } : null;
|
||||
}));
|
||||
if (extensionIdentifiers.length) {
|
||||
this.storageService.store(DISABLED_EXTENSIONS_STORAGE_PATH, JSON.stringify(extensionIdentifiers), scope);
|
||||
}
|
||||
}
|
||||
this.storageService.remove('extensions/disabled', scope);
|
||||
}
|
||||
|
||||
private _onDidUninstallExtension({ identifier, error }: DidUninstallExtensionEvent): void {
|
||||
|
|
|
@ -271,10 +271,10 @@ export interface IExtensionManagementService {
|
|||
onUninstallExtension: Event<IExtensionIdentifier>;
|
||||
onDidUninstallExtension: Event<DidUninstallExtensionEvent>;
|
||||
|
||||
install(zipPath: string): TPromise<void>;
|
||||
installFromGallery(extension: IGalleryExtension): TPromise<void>;
|
||||
install(zipPath: string): TPromise<ILocalExtension>;
|
||||
installFromGallery(extension: IGalleryExtension): TPromise<ILocalExtension>;
|
||||
uninstall(extension: ILocalExtension, force?: boolean): TPromise<void>;
|
||||
reinstall(extension: ILocalExtension): TPromise<void>;
|
||||
reinstall(extension: ILocalExtension): TPromise<ILocalExtension>;
|
||||
getInstalled(type?: LocalExtensionType): TPromise<ILocalExtension[]>;
|
||||
getExtensionsReport(): TPromise<IReportedExtension[]>;
|
||||
|
||||
|
@ -308,7 +308,7 @@ export interface IExtensionEnablementService {
|
|||
/**
|
||||
* Returns the enablement state for the given extension
|
||||
*/
|
||||
getEnablementState(identifier: IExtensionIdentifier): EnablementState;
|
||||
getEnablementState(extension: ILocalExtension): EnablementState;
|
||||
|
||||
/**
|
||||
* Returns `true` if the enablement can be changed.
|
||||
|
@ -318,7 +318,7 @@ export interface IExtensionEnablementService {
|
|||
/**
|
||||
* Returns `true` if the given extension identifier is enabled.
|
||||
*/
|
||||
isEnabled(identifier: IExtensionIdentifier): boolean;
|
||||
isEnabled(extension: ILocalExtension): boolean;
|
||||
|
||||
/**
|
||||
* Enable or disable the given extension.
|
||||
|
@ -330,12 +330,6 @@ export interface IExtensionEnablementService {
|
|||
* Throws error if enablement is requested for workspace and there is no workspace
|
||||
*/
|
||||
setEnablement(extension: ILocalExtension, state: EnablementState): TPromise<boolean>;
|
||||
/**
|
||||
* TODO: @Sandy. Use setEnablement(extension: ILocalExtension, state: EnablementState): TPromise<boolean>. Use one model for extension management and runtime
|
||||
*/
|
||||
setEnablement(identifier: IExtensionIdentifier, state: EnablementState): TPromise<boolean>;
|
||||
|
||||
migrateToIdentifiers(installed: IExtensionIdentifier[]): void;
|
||||
}
|
||||
|
||||
export const IExtensionTipsService = createDecorator<IExtensionTipsService>('extensionTipsService');
|
||||
|
|
|
@ -15,9 +15,10 @@ export interface IExtensionManagementChannel extends IChannel {
|
|||
call(command: 'event:onDidInstallExtension'): TPromise<void>;
|
||||
call(command: 'event:onUninstallExtension'): TPromise<void>;
|
||||
call(command: 'event:onDidUninstallExtension'): TPromise<void>;
|
||||
call(command: 'install', path: string): TPromise<void>;
|
||||
call(command: 'installFromGallery', extension: IGalleryExtension): TPromise<void>;
|
||||
call(command: 'install', path: string): TPromise<ILocalExtension>;
|
||||
call(command: 'installFromGallery', extension: IGalleryExtension): TPromise<ILocalExtension>;
|
||||
call(command: 'uninstall', args: [ILocalExtension, boolean]): TPromise<void>;
|
||||
call(command: 'reinstall', args: [ILocalExtension]): TPromise<ILocalExtension>;
|
||||
call(command: 'getInstalled'): TPromise<ILocalExtension[]>;
|
||||
call(command: 'getExtensionsReport'): TPromise<IReportedExtension[]>;
|
||||
call(command: string, arg?: any): TPromise<any>;
|
||||
|
@ -73,11 +74,11 @@ export class ExtensionManagementChannelClient implements IExtensionManagementSer
|
|||
private _onDidUninstallExtension = eventFromCall<DidUninstallExtensionEvent>(this.channel, 'event:onDidUninstallExtension');
|
||||
get onDidUninstallExtension(): Event<DidUninstallExtensionEvent> { return this._onDidUninstallExtension; }
|
||||
|
||||
install(zipPath: string): TPromise<void> {
|
||||
install(zipPath: string): TPromise<ILocalExtension> {
|
||||
return this.channel.call('install', zipPath);
|
||||
}
|
||||
|
||||
installFromGallery(extension: IGalleryExtension): TPromise<void> {
|
||||
installFromGallery(extension: IGalleryExtension): TPromise<ILocalExtension> {
|
||||
return this.channel.call('installFromGallery', [extension]);
|
||||
}
|
||||
|
||||
|
@ -85,7 +86,7 @@ export class ExtensionManagementChannelClient implements IExtensionManagementSer
|
|||
return this.channel.call('uninstall', [extension, force]);
|
||||
}
|
||||
|
||||
reinstall(extension: ILocalExtension): TPromise<void> {
|
||||
reinstall(extension: ILocalExtension): TPromise<ILocalExtension> {
|
||||
return this.channel.call('reinstall', [extension]);
|
||||
}
|
||||
|
||||
|
|
|
@ -25,7 +25,7 @@ import {
|
|||
import { getGalleryExtensionIdFromLocal, adoptToGalleryExtensionId, areSameExtensions, getGalleryExtensionId, groupByExtension, getMaliciousExtensionsSet, getLocalExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
|
||||
import { localizeManifest } from '../common/extensionNls';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { Limiter } from 'vs/base/common/async';
|
||||
import { Limiter, always } from 'vs/base/common/async';
|
||||
import Event, { Emitter } from 'vs/base/common/event';
|
||||
import * as semver from 'semver';
|
||||
import URI from 'vs/base/common/uri';
|
||||
|
@ -49,8 +49,6 @@ const INSTALL_ERROR_GALLERY = 'gallery';
|
|||
const INSTALL_ERROR_LOCAL = 'local';
|
||||
const INSTALL_ERROR_EXTRACTING = 'extracting';
|
||||
const INSTALL_ERROR_DELETING = 'deleting';
|
||||
const INSTALL_ERROR_READING_EXTENSION_FROM_DISK = 'readingExtension';
|
||||
const INSTALL_ERROR_SAVING_METADATA = 'savingMetadata';
|
||||
const INSTALL_ERROR_UNKNOWN = 'unknown';
|
||||
|
||||
export class ExtensionManagementError extends Error {
|
||||
|
@ -141,7 +139,7 @@ export class ExtensionManagementService extends Disposable implements IExtension
|
|||
this.extensionLifecycle = this._register(new ExtensionsLifecycle(this.logService));
|
||||
}
|
||||
|
||||
install(zipPath: string): TPromise<void> {
|
||||
install(zipPath: string): TPromise<ILocalExtension> {
|
||||
zipPath = path.resolve(zipPath);
|
||||
|
||||
return validateLocalExtension(zipPath)
|
||||
|
@ -159,7 +157,7 @@ export class ExtensionManagementService extends Disposable implements IExtension
|
|||
metadata => this.installFromZipPath(identifier, zipPath, metadata, manifest),
|
||||
error => this.installFromZipPath(identifier, zipPath, null, manifest))
|
||||
.then(
|
||||
() => this.logService.info('Successfully installed the extension:', identifier.id),
|
||||
local => { this.logService.info('Successfully installed the extension:', identifier.id); return local; },
|
||||
e => {
|
||||
this.logService.error('Failed to install the extension:', identifier.id, e.message);
|
||||
return TPromise.wrapError(e);
|
||||
|
@ -208,7 +206,7 @@ export class ExtensionManagementService extends Disposable implements IExtension
|
|||
});
|
||||
}
|
||||
|
||||
private installFromZipPath(identifier: IExtensionIdentifier, zipPath: string, metadata: IGalleryMetadata, manifest: IExtensionManifest): TPromise<void> {
|
||||
private installFromZipPath(identifier: IExtensionIdentifier, zipPath: string, metadata: IGalleryMetadata, manifest: IExtensionManifest): TPromise<ILocalExtension> {
|
||||
return this.installExtension({ zipPath, id: identifier.id, metadata })
|
||||
.then(local => {
|
||||
if (this.galleryService.isEnabled() && local.manifest.extensionDependencies && local.manifest.extensionDependencies.length) {
|
||||
|
@ -222,12 +220,12 @@ export class ExtensionManagementService extends Disposable implements IExtension
|
|||
return local;
|
||||
})
|
||||
.then(
|
||||
local => this._onDidInstallExtension.fire({ identifier, zipPath, local }),
|
||||
local => { this._onDidInstallExtension.fire({ identifier, zipPath, local }); return local; },
|
||||
error => { this._onDidInstallExtension.fire({ identifier, zipPath, error }); return TPromise.wrapError(error); }
|
||||
);
|
||||
}
|
||||
|
||||
installFromGallery(extension: IGalleryExtension): TPromise<void> {
|
||||
installFromGallery(extension: IGalleryExtension): TPromise<ILocalExtension> {
|
||||
this.onInstallExtensions([extension]);
|
||||
return this.collectExtensionsToInstall(extension)
|
||||
.then(
|
||||
|
@ -237,13 +235,14 @@ export class ExtensionManagementService extends Disposable implements IExtension
|
|||
}
|
||||
return this.downloadAndInstallExtensions(extensionsToInstall)
|
||||
.then(
|
||||
locals => this.onDidInstallExtensions(extensionsToInstall, locals, []),
|
||||
locals => this.onDidInstallExtensions(extensionsToInstall, locals, [])
|
||||
.then(() => locals.filter(l => areSameExtensions({ id: getGalleryExtensionIdFromLocal(l), uuid: l.identifier.uuid }, extension.identifier)[0])),
|
||||
errors => this.onDidInstallExtensions(extensionsToInstall, [], errors));
|
||||
},
|
||||
error => this.onDidInstallExtensions([extension], [], [error]));
|
||||
}
|
||||
|
||||
reinstall(extension: ILocalExtension): TPromise<void> {
|
||||
reinstall(extension: ILocalExtension): TPromise<ILocalExtension> {
|
||||
if (!this.galleryService.isEnabled()) {
|
||||
return TPromise.wrapError(new Error(nls.localize('MarketPlaceDisabled', "Marketplace is not enabled")));
|
||||
}
|
||||
|
@ -256,7 +255,7 @@ export class ExtensionManagementService extends Disposable implements IExtension
|
|||
() => this.installFromGallery(galleryExtension),
|
||||
e => TPromise.wrapError(new Error(nls.localize('removeError', "Error while removing the extension: {0}. Please Quit and Start VS Code before trying again.", toErrorMessage(e))))));
|
||||
}
|
||||
return TPromise.wrapError(new Error(nls.localize('Not Market place extension', "Only Market place Extensions can be reinstalled")));
|
||||
return TPromise.wrapError(new Error(nls.localize('Not a Marketplace extension', "Only Marketplace Extensions can be reinstalled")));
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -410,43 +409,39 @@ export class ExtensionManagementService extends Disposable implements IExtension
|
|||
}
|
||||
|
||||
private extractAndInstall({ zipPath, id, metadata }: InstallableExtension): TPromise<ILocalExtension> {
|
||||
const extensionPath = path.join(this.extensionsPath, id);
|
||||
return pfs.rimraf(extensionPath)
|
||||
.then(() => {
|
||||
this.logService.trace(`Started extracting the extension from ${zipPath} to ${extensionPath}`);
|
||||
return extract(zipPath, extensionPath, { sourcePath: 'extension', overwrite: true })
|
||||
.then(
|
||||
() => {
|
||||
this.logService.info(`Extracted extension to ${extensionPath}:`, id);
|
||||
return this.completeInstall(id, extensionPath, metadata);
|
||||
},
|
||||
e => TPromise.wrapError(new ExtensionManagementError(e.message, INSTALL_ERROR_EXTRACTING)))
|
||||
.then(null, e => {
|
||||
this.logService.info('Deleting the extracted extension', id);
|
||||
return pfs.rimraf(extensionPath).then(() => TPromise.wrapError(e), () => TPromise.wrapError(e));
|
||||
});
|
||||
}, e => TPromise.wrapError(new ExtensionManagementError(this.joinErrors(e).message, INSTALL_ERROR_DELETING)));
|
||||
const extractPath = path.join(this.extensionsPath, `.${id}`); // Extract to temp path
|
||||
return this.extract(id, zipPath, extractPath, { sourcePath: 'extension', overwrite: true })
|
||||
.then(() => this.completeInstall(id, extractPath))
|
||||
.then(() => this.scanExtension(id, this.extensionsPath, LocalExtensionType.User))
|
||||
.then(local => {
|
||||
if (metadata) {
|
||||
local.metadata = metadata;
|
||||
return this.saveMetadataForLocalExtension(local);
|
||||
}
|
||||
return local;
|
||||
});
|
||||
}
|
||||
|
||||
private completeInstall(id: string, extensionPath: string, metadata: IGalleryMetadata): TPromise<ILocalExtension> {
|
||||
return TPromise.join([readManifest(extensionPath), pfs.readdir(extensionPath)])
|
||||
.then(null, e => TPromise.wrapError(new ExtensionManagementError(this.joinErrors(e).message, INSTALL_ERROR_READING_EXTENSION_FROM_DISK)))
|
||||
.then(([{ manifest }, children]) => {
|
||||
const readme = children.filter(child => /^readme(\.txt|\.md|)$/i.test(child))[0];
|
||||
const readmeUrl = readme ? URI.file(path.join(extensionPath, readme)).toString() : null;
|
||||
const changelog = children.filter(child => /^changelog(\.txt|\.md|)$/i.test(child))[0];
|
||||
const changelogUrl = changelog ? URI.file(path.join(extensionPath, changelog)).toString() : null;
|
||||
const type = LocalExtensionType.User;
|
||||
const identifier = { id, uuid: metadata ? metadata.id : null };
|
||||
private extract(id: string, zipPath: string, extractPath: string, options: any): TPromise<void> {
|
||||
this.logService.trace(`Started extracting the extension from ${zipPath} to ${extractPath}`);
|
||||
return pfs.rimraf(extractPath)
|
||||
.then(
|
||||
() => extract(zipPath, extractPath, options)
|
||||
.then(
|
||||
() => this.logService.info(`Extracted extension to ${extractPath}:`, id),
|
||||
e => always(pfs.rimraf(extractPath), () => null)
|
||||
.then(() => TPromise.wrapError(new ExtensionManagementError(e.message, INSTALL_ERROR_EXTRACTING)))),
|
||||
e => TPromise.wrapError(new ExtensionManagementError(this.joinErrors(e).message, INSTALL_ERROR_DELETING)));
|
||||
}
|
||||
|
||||
const local: ILocalExtension = { type, identifier, manifest, metadata, path: extensionPath, readmeUrl, changelogUrl };
|
||||
|
||||
this.logService.trace(`Updating metadata of the extension:`, id);
|
||||
return this.saveMetadataForLocalExtension(local)
|
||||
.then(() => {
|
||||
this.logService.info(`Updated metadata of the extension:`, id);
|
||||
return local;
|
||||
}, e => TPromise.wrapError(new ExtensionManagementError(this.joinErrors(e).message, INSTALL_ERROR_SAVING_METADATA)));
|
||||
private completeInstall(id: string, extractPath: string): TPromise<void> {
|
||||
return pfs.rename(extractPath, path.join(this.extensionsPath, id))
|
||||
.then(
|
||||
() => this.logService.info('Installation compelted.', id),
|
||||
e => {
|
||||
this.logService.info('Deleting the extracted extension', id);
|
||||
return always(pfs.rimraf(extractPath), () => null)
|
||||
.then(() => TPromise.wrapError(e));
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -721,6 +716,9 @@ export class ExtensionManagementService extends Disposable implements IExtension
|
|||
}
|
||||
|
||||
private scanExtension(folderName: string, root: string, type: LocalExtensionType): TPromise<ILocalExtension> {
|
||||
if (type === LocalExtensionType.User && folderName.indexOf('.') === 0) { // Do not consider user exension folder starting with `.`
|
||||
return TPromise.as(null);
|
||||
}
|
||||
const extensionPath = path.join(root, folderName);
|
||||
return pfs.readdir(extensionPath)
|
||||
.then(children => readManifest(extensionPath)
|
||||
|
|
|
@ -98,13 +98,13 @@ suite('ExtensionEnablementService Test', () => {
|
|||
|
||||
test('test state of globally disabled extension', () => {
|
||||
return testObject.setEnablement(aLocalExtension('pub.a'), EnablementState.Disabled)
|
||||
.then(() => assert.equal(testObject.getEnablementState({ id: 'pub.a' }), EnablementState.Disabled));
|
||||
.then(() => assert.equal(testObject.getEnablementState(aLocalExtension('pub.a')), EnablementState.Disabled));
|
||||
});
|
||||
|
||||
test('test state of globally enabled extension', () => {
|
||||
return testObject.setEnablement(aLocalExtension('pub.a'), EnablementState.Disabled)
|
||||
.then(() => testObject.setEnablement(aLocalExtension('pub.a'), EnablementState.Enabled))
|
||||
.then(() => assert.equal(testObject.getEnablementState({ id: 'pub.a' }), EnablementState.Enabled));
|
||||
.then(() => assert.equal(testObject.getEnablementState(aLocalExtension('pub.a')), EnablementState.Enabled));
|
||||
});
|
||||
|
||||
test('test disable an extension for workspace', () => {
|
||||
|
@ -126,59 +126,59 @@ suite('ExtensionEnablementService Test', () => {
|
|||
|
||||
test('test state of workspace disabled extension', () => {
|
||||
return testObject.setEnablement(aLocalExtension('pub.a'), EnablementState.WorkspaceDisabled)
|
||||
.then(() => assert.equal(testObject.getEnablementState({ id: 'pub.a' }), EnablementState.WorkspaceDisabled));
|
||||
.then(() => assert.equal(testObject.getEnablementState(aLocalExtension('pub.a')), EnablementState.WorkspaceDisabled));
|
||||
});
|
||||
|
||||
test('test state of workspace and globally disabled extension', () => {
|
||||
return testObject.setEnablement(aLocalExtension('pub.a'), EnablementState.Disabled)
|
||||
.then(() => testObject.setEnablement(aLocalExtension('pub.a'), EnablementState.WorkspaceDisabled))
|
||||
.then(() => assert.equal(testObject.getEnablementState({ id: 'pub.a' }), EnablementState.WorkspaceDisabled));
|
||||
.then(() => assert.equal(testObject.getEnablementState(aLocalExtension('pub.a')), EnablementState.WorkspaceDisabled));
|
||||
});
|
||||
|
||||
test('test state of workspace enabled extension', () => {
|
||||
return testObject.setEnablement(aLocalExtension('pub.a'), EnablementState.WorkspaceDisabled)
|
||||
.then(() => testObject.setEnablement(aLocalExtension('pub.a'), EnablementState.WorkspaceEnabled))
|
||||
.then(() => assert.equal(testObject.getEnablementState({ id: 'pub.a' }), EnablementState.WorkspaceEnabled));
|
||||
.then(() => assert.equal(testObject.getEnablementState(aLocalExtension('pub.a')), EnablementState.WorkspaceEnabled));
|
||||
});
|
||||
|
||||
test('test state of globally disabled and workspace enabled extension', () => {
|
||||
return testObject.setEnablement(aLocalExtension('pub.a'), EnablementState.Disabled)
|
||||
.then(() => testObject.setEnablement(aLocalExtension('pub.a'), EnablementState.WorkspaceDisabled))
|
||||
.then(() => testObject.setEnablement(aLocalExtension('pub.a'), EnablementState.WorkspaceEnabled))
|
||||
.then(() => assert.equal(testObject.getEnablementState({ id: 'pub.a' }), EnablementState.WorkspaceEnabled));
|
||||
.then(() => assert.equal(testObject.getEnablementState(aLocalExtension('pub.a')), EnablementState.WorkspaceEnabled));
|
||||
});
|
||||
|
||||
test('test state of an extension when disabled for workspace from workspace enabled', () => {
|
||||
return testObject.setEnablement(aLocalExtension('pub.a'), EnablementState.WorkspaceDisabled)
|
||||
.then(() => testObject.setEnablement(aLocalExtension('pub.a'), EnablementState.WorkspaceEnabled))
|
||||
.then(() => testObject.setEnablement(aLocalExtension('pub.a'), EnablementState.WorkspaceDisabled))
|
||||
.then(() => assert.equal(testObject.getEnablementState({ id: 'pub.a' }), EnablementState.WorkspaceDisabled));
|
||||
.then(() => assert.equal(testObject.getEnablementState(aLocalExtension('pub.a')), EnablementState.WorkspaceDisabled));
|
||||
});
|
||||
|
||||
test('test state of an extension when disabled globally from workspace enabled', () => {
|
||||
return testObject.setEnablement(aLocalExtension('pub.a'), EnablementState.WorkspaceDisabled)
|
||||
.then(() => testObject.setEnablement(aLocalExtension('pub.a'), EnablementState.WorkspaceEnabled))
|
||||
.then(() => testObject.setEnablement(aLocalExtension('pub.a'), EnablementState.Disabled))
|
||||
.then(() => assert.equal(testObject.getEnablementState({ id: 'pub.a' }), EnablementState.Disabled));
|
||||
.then(() => assert.equal(testObject.getEnablementState(aLocalExtension('pub.a')), EnablementState.Disabled));
|
||||
});
|
||||
|
||||
test('test state of an extension when disabled globally from workspace disabled', () => {
|
||||
return testObject.setEnablement(aLocalExtension('pub.a'), EnablementState.WorkspaceDisabled)
|
||||
.then(() => testObject.setEnablement(aLocalExtension('pub.a'), EnablementState.Disabled))
|
||||
.then(() => assert.equal(testObject.getEnablementState({ id: 'pub.a' }), EnablementState.Disabled));
|
||||
.then(() => assert.equal(testObject.getEnablementState(aLocalExtension('pub.a')), EnablementState.Disabled));
|
||||
});
|
||||
|
||||
test('test state of an extension when enabled globally from workspace enabled', () => {
|
||||
return testObject.setEnablement(aLocalExtension('pub.a'), EnablementState.WorkspaceDisabled)
|
||||
.then(() => testObject.setEnablement(aLocalExtension('pub.a'), EnablementState.WorkspaceEnabled))
|
||||
.then(() => testObject.setEnablement(aLocalExtension('pub.a'), EnablementState.Enabled))
|
||||
.then(() => assert.equal(testObject.getEnablementState({ id: 'pub.a' }), EnablementState.Enabled));
|
||||
.then(() => assert.equal(testObject.getEnablementState(aLocalExtension('pub.a')), EnablementState.Enabled));
|
||||
});
|
||||
|
||||
test('test state of an extension when enabled globally from workspace disabled', () => {
|
||||
return testObject.setEnablement(aLocalExtension('pub.a'), EnablementState.WorkspaceDisabled)
|
||||
.then(() => testObject.setEnablement(aLocalExtension('pub.a'), EnablementState.Enabled))
|
||||
.then(() => assert.equal(testObject.getEnablementState({ id: 'pub.a' }), EnablementState.Enabled));
|
||||
.then(() => assert.equal(testObject.getEnablementState(aLocalExtension('pub.a')), EnablementState.Enabled));
|
||||
});
|
||||
|
||||
test('test disable an extension for workspace and then globally', () => {
|
||||
|
@ -311,18 +311,18 @@ suite('ExtensionEnablementService Test', () => {
|
|||
|
||||
test('test isEnabled return false extension is disabled globally', () => {
|
||||
return testObject.setEnablement(aLocalExtension('pub.a'), EnablementState.Disabled)
|
||||
.then(() => assert.ok(!testObject.isEnabled({ id: 'pub.a' })));
|
||||
.then(() => assert.ok(!testObject.isEnabled(aLocalExtension('pub.a'))));
|
||||
});
|
||||
|
||||
test('test isEnabled return false extension is disabled in workspace', () => {
|
||||
return testObject.setEnablement(aLocalExtension('pub.a'), EnablementState.WorkspaceDisabled)
|
||||
.then(() => assert.ok(!testObject.isEnabled({ id: 'pub.a' })));
|
||||
.then(() => assert.ok(!testObject.isEnabled(aLocalExtension('pub.a'))));
|
||||
});
|
||||
|
||||
test('test isEnabled return true extension is not disabled', () => {
|
||||
return testObject.setEnablement(aLocalExtension('pub.a'), EnablementState.WorkspaceDisabled)
|
||||
.then(() => testObject.setEnablement(aLocalExtension('pub.c'), EnablementState.Disabled))
|
||||
.then(() => assert.ok(testObject.isEnabled({ id: 'pub.b' })));
|
||||
.then(() => assert.ok(testObject.isEnabled(aLocalExtension('pub.b'))));
|
||||
});
|
||||
|
||||
test('test canChangeEnablement return false for language packs', () => {
|
||||
|
|
|
@ -44,7 +44,7 @@ export interface INotification {
|
|||
* close automatically when invoking a secondary action.
|
||||
*
|
||||
* **Note:** If your intent is to show a message with actions to the user, consider
|
||||
* the `IChoiceService` and `IConfirmationService` instead which are optimized for
|
||||
* the `IChoiceService` or `IDialogService` instead which are optimized for
|
||||
* this usecase and much easier to use!
|
||||
*/
|
||||
actions?: INotificationActions;
|
||||
|
@ -129,7 +129,7 @@ export interface INotificationService {
|
|||
* can be used to control the notification afterwards.
|
||||
*
|
||||
* **Note:** If your intent is to show a message with actions to the user, consider
|
||||
* the `IChoiceService` and `IConfirmationService` instead which are optimized for
|
||||
* the `IChoiceService` or `IDialogService` instead which are optimized for
|
||||
* this usecase and much easier to use!
|
||||
*/
|
||||
notify(notification: INotification): INotificationHandle;
|
||||
|
|
|
@ -11,7 +11,7 @@ import { IRequestOptions, IRequestContext, IRequestFunction, request } from 'vs/
|
|||
import { getProxyAgent } from 'vs/base/node/proxy';
|
||||
import { IRequestService, IHTTPConfiguration } from 'vs/platform/request/node/request';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { ILogService } from '../../log/common/log';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
|
||||
/**
|
||||
* This service exposes the `request` API, while using the global
|
||||
|
|
|
@ -50,7 +50,7 @@ export enum StateType {
|
|||
|
||||
export type Uninitialized = { type: StateType.Uninitialized };
|
||||
export type Idle = { type: StateType.Idle };
|
||||
export type CheckingForUpdates = { type: StateType.CheckingForUpdates, explicit: boolean };
|
||||
export type CheckingForUpdates = { type: StateType.CheckingForUpdates, context: any };
|
||||
export type AvailableForDownload = { type: StateType.AvailableForDownload, update: IUpdate };
|
||||
export type Downloading = { type: StateType.Downloading, update: IUpdate };
|
||||
export type Downloaded = { type: StateType.Downloaded, update: IUpdate };
|
||||
|
@ -62,7 +62,7 @@ export type State = Uninitialized | Idle | CheckingForUpdates | AvailableForDown
|
|||
export const State = {
|
||||
Uninitialized: { type: StateType.Uninitialized } as Uninitialized,
|
||||
Idle: { type: StateType.Idle } as Idle,
|
||||
CheckingForUpdates: (explicit: boolean) => ({ type: StateType.CheckingForUpdates, explicit } as CheckingForUpdates),
|
||||
CheckingForUpdates: (context: any) => ({ type: StateType.CheckingForUpdates, context } as CheckingForUpdates),
|
||||
AvailableForDownload: (update: IUpdate) => ({ type: StateType.AvailableForDownload, update } as AvailableForDownload),
|
||||
Downloading: (update: IUpdate) => ({ type: StateType.Downloading, update } as Downloading),
|
||||
Downloaded: (update: IUpdate) => ({ type: StateType.Downloaded, update } as Downloaded),
|
||||
|
@ -85,7 +85,7 @@ export interface IUpdateService {
|
|||
readonly onStateChange: Event<State>;
|
||||
readonly state: State;
|
||||
|
||||
checkForUpdates(explicit: boolean): TPromise<void>;
|
||||
checkForUpdates(context: any): TPromise<void>;
|
||||
downloadUpdate(): TPromise<void>;
|
||||
applyUpdate(): TPromise<void>;
|
||||
quitAndInstall(): TPromise<void>;
|
||||
|
|
|
@ -12,7 +12,7 @@ import { onUnexpectedError } from 'vs/base/common/errors';
|
|||
import { IUpdateService, State } from './update';
|
||||
|
||||
export interface IUpdateChannel extends IChannel {
|
||||
call(command: 'checkForUpdates', arg: boolean): TPromise<void>;
|
||||
call(command: 'checkForUpdates', arg: any): TPromise<void>;
|
||||
call(command: 'downloadUpdate'): TPromise<void>;
|
||||
call(command: 'applyUpdate'): TPromise<void>;
|
||||
call(command: 'quitAndInstall'): TPromise<void>;
|
||||
|
@ -62,8 +62,8 @@ export class UpdateChannelClient implements IUpdateService {
|
|||
}, onUnexpectedError);
|
||||
}
|
||||
|
||||
checkForUpdates(explicit: boolean): TPromise<void> {
|
||||
return this.channel.call('checkForUpdates', explicit);
|
||||
checkForUpdates(context: any): TPromise<void> {
|
||||
return this.channel.call('checkForUpdates', context);
|
||||
}
|
||||
|
||||
downloadUpdate(): TPromise<void> {
|
||||
|
|
|
@ -81,7 +81,7 @@ export abstract class AbstractUpdateService implements IUpdateService {
|
|||
|
||||
private scheduleCheckForUpdates(delay = 60 * 60 * 1000): TPromise<void> {
|
||||
return TPromise.timeout(delay)
|
||||
.then(() => this.checkForUpdates())
|
||||
.then(() => this.checkForUpdates(null))
|
||||
.then(update => {
|
||||
if (update) {
|
||||
// Update found, no need to check more
|
||||
|
@ -93,14 +93,14 @@ export abstract class AbstractUpdateService implements IUpdateService {
|
|||
});
|
||||
}
|
||||
|
||||
checkForUpdates(explicit = false): TPromise<void> {
|
||||
checkForUpdates(context: any): TPromise<void> {
|
||||
this.logService.trace('update#checkForUpdates, state = ', this.state.type);
|
||||
|
||||
if (this.state.type !== StateType.Idle) {
|
||||
return TPromise.as(null);
|
||||
}
|
||||
|
||||
return this.throttler.queue(() => TPromise.as(this.doCheckForUpdates(explicit)));
|
||||
return this.throttler.queue(() => TPromise.as(this.doCheckForUpdates(context)));
|
||||
}
|
||||
|
||||
downloadUpdate(): TPromise<void> {
|
||||
|
@ -158,5 +158,5 @@ export abstract class AbstractUpdateService implements IUpdateService {
|
|||
}
|
||||
|
||||
protected abstract setUpdateFeedUrl(quality: string): boolean;
|
||||
protected abstract doCheckForUpdates(explicit: boolean): void;
|
||||
protected abstract doCheckForUpdates(context: any): void;
|
||||
}
|
||||
|
|
|
@ -59,8 +59,8 @@ export class DarwinUpdateService extends AbstractUpdateService {
|
|||
return true;
|
||||
}
|
||||
|
||||
protected doCheckForUpdates(explicit: boolean): void {
|
||||
this.setState(State.CheckingForUpdates(explicit));
|
||||
protected doCheckForUpdates(context: any): void {
|
||||
this.setState(State.CheckingForUpdates(context));
|
||||
electron.autoUpdater.checkForUpdates();
|
||||
}
|
||||
|
||||
|
@ -97,7 +97,7 @@ export class DarwinUpdateService extends AbstractUpdateService {
|
|||
"explicit" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
|
||||
}
|
||||
*/
|
||||
this.telemetryService.publicLog('update:notAvailable', { explicit: this.state.explicit });
|
||||
this.telemetryService.publicLog('update:notAvailable', { explicit: !!this.state.context });
|
||||
|
||||
this.setState(State.Idle);
|
||||
}
|
||||
|
|
|
@ -40,12 +40,12 @@ export class LinuxUpdateService extends AbstractUpdateService {
|
|||
return true;
|
||||
}
|
||||
|
||||
protected doCheckForUpdates(explicit: boolean): void {
|
||||
protected doCheckForUpdates(context: any): void {
|
||||
if (!this.url) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState(State.CheckingForUpdates(explicit));
|
||||
this.setState(State.CheckingForUpdates(context));
|
||||
|
||||
this.requestService.request({ url: this.url })
|
||||
.then<IUpdate>(asJson)
|
||||
|
@ -56,7 +56,7 @@ export class LinuxUpdateService extends AbstractUpdateService {
|
|||
"explicit" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
|
||||
}
|
||||
*/
|
||||
this.telemetryService.publicLog('update:notAvailable', { explicit });
|
||||
this.telemetryService.publicLog('update:notAvailable', { explicit: !!context });
|
||||
|
||||
this.setState(State.Idle);
|
||||
} else {
|
||||
|
@ -71,7 +71,7 @@ export class LinuxUpdateService extends AbstractUpdateService {
|
|||
"explicit" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
|
||||
}
|
||||
*/
|
||||
this.telemetryService.publicLog('update:notAvailable', { explicit });
|
||||
this.telemetryService.publicLog('update:notAvailable', { explicit: !!context });
|
||||
this.setState(State.Idle);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -76,12 +76,12 @@ export class Win32UpdateService extends AbstractUpdateService {
|
|||
return true;
|
||||
}
|
||||
|
||||
protected doCheckForUpdates(explicit: boolean): void {
|
||||
protected doCheckForUpdates(context: any): void {
|
||||
if (!this.url) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState(State.CheckingForUpdates(explicit));
|
||||
this.setState(State.CheckingForUpdates(context));
|
||||
|
||||
this.requestService.request({ url: this.url })
|
||||
.then<IUpdate>(asJson)
|
||||
|
@ -92,7 +92,7 @@ export class Win32UpdateService extends AbstractUpdateService {
|
|||
"explicit" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
|
||||
}
|
||||
*/
|
||||
this.telemetryService.publicLog('update:notAvailable', { explicit });
|
||||
this.telemetryService.publicLog('update:notAvailable', { explicit: !!context });
|
||||
|
||||
this.setState(State.Idle);
|
||||
return TPromise.as(null);
|
||||
|
@ -137,7 +137,7 @@ export class Win32UpdateService extends AbstractUpdateService {
|
|||
"explicit" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
|
||||
}
|
||||
*/
|
||||
this.telemetryService.publicLog('update:notAvailable', { explicit });
|
||||
this.telemetryService.publicLog('update:notAvailable', { explicit: !!context });
|
||||
this.setState(State.Idle);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ import { IURLService } from 'vs/platform/url/common/url';
|
|||
import product from 'vs/platform/node/product';
|
||||
import { app } from 'electron';
|
||||
import URI from 'vs/base/common/uri';
|
||||
import { ILogService } from '../../log/common/log';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
|
||||
export class URLService implements IURLService {
|
||||
|
||||
|
|
|
@ -24,7 +24,7 @@ import { ICommandAction } from 'vs/platform/actions/common/actions';
|
|||
import { Schemas } from 'vs/base/common/network';
|
||||
import { mnemonicButtonLabel } from 'vs/base/common/labels';
|
||||
import { isWindows } from 'vs/base/common/platform';
|
||||
import { ILogService } from '../../log/common/log';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
|
||||
export class WindowsService implements IWindowsService, IDisposable {
|
||||
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue