提交 de5bca37 编写于 作者: D Dirk Baeumer

Merge branch 'master' into dbaeumer/27078

......@@ -121,7 +121,7 @@ const config = {
name: product.nameLong + ' document',
role: 'Editor',
ostypes: ["TEXT", "utxt", "TUTX", "****"],
extensions: ["ascx", "asp", "aspx", "bash", "bash_login", "bash_logout", "bash_profile", "bashrc", "bat", "bowerrc", "c", "cc", "clj", "cljs", "cljx", "clojure", "cmd", "coffee", "config", "cpp", "cs", "cshtml", "csproj", "css", "csx", "ctp", "cxx", "dockerfile", "dot", "dtd", "editorconfig", "edn", "eyaml", "eyml", "fs", "fsi", "fsscript", "fsx", "gemspec", "gitattributes", "gitconfig", "gitignore", "go", "h", "handlebars", "hbs", "hh", "hpp", "htm", "html", "hxx", "ini", "jade", "jav", "java", "js", "jscsrc", "jshintrc", "jshtm", "json", "jsp", "less", "lua", "m", "makefile", "markdown", "md", "mdoc", "mdown", "mdtext", "mdtxt", "mdwn", "mkd", "mkdn", "ml", "mli", "php", "phtml", "pl", "pl6", "pm", "pm6", "pod", "pp", "profile", "properties", "ps1", "psd1", "psgi", "psm1", "py", "r", "rb", "rhistory", "rprofile", "rs", "rt", "scss", "sh", "shtml", "sql", "svg", "svgz", "t", "ts", "txt", "vb", "wxi", "wxl", "wxs", "xaml", "xml", "yaml", "yml", "zlogin", "zlogout", "zprofile", "zsh", "zshenv", "zshrc"],
extensions: ["ascx", "asp", "aspx", "bash", "bash_login", "bash_logout", "bash_profile", "bashrc", "bat", "bowerrc", "c", "cc", "clj", "cljs", "cljx", "clojure", "cmd", "coffee", "config", "cpp", "cs", "cshtml", "csproj", "css", "csx", "ctp", "cxx", "dockerfile", "dot", "dtd", "editorconfig", "edn", "eyaml", "eyml", "fs", "fsi", "fsscript", "fsx", "gemspec", "gitattributes", "gitconfig", "gitignore", "go", "h", "handlebars", "hbs", "hh", "hpp", "htm", "html", "hxx", "ini", "jade", "jav", "java", "js", "jscsrc", "jshintrc", "jshtm", "json", "jsp", "less", "lua", "m", "makefile", "markdown", "md", "mdoc", "mdown", "mdtext", "mdtxt", "mdwn", "mkd", "mkdn", "ml", "mli", "php", "phtml", "pl", "pl6", "pm", "pm6", "pod", "pp", "profile", "properties", "ps1", "psd1", "psgi", "psm1", "py", "r", "rb", "rhistory", "rprofile", "rs", "rt", "scss", "sh", "shtml", "sql", "svg", "svgz", "t", "ts", "txt", "vb", "wxi", "wxl", "wxs", "xaml", "xcodeproj", "xcworkspace", "xml", "yaml", "yml", "zlogin", "zlogout", "zprofile", "zsh", "zshenv", "zshrc"],
iconFile: 'resources/darwin/code_file.icns'
}],
darwinBundleURLTypes: [{
......
......@@ -34,7 +34,9 @@ const extensions = [
'git',
'gulp',
'grunt',
'jake'
'jake',
'merge-conflict',
'emmet'
];
extensions.forEach(extension => npmInstall(`extensions/${extension}`));
......
{
"name": "emmet",
"displayName": "emmet",
"description": "Emmet support for VS Code",
"version": "0.0.1",
"publisher": "vscode",
"engines": {
"vscode": "^1.10.0"
},
"categories": [
"Other"
],
"repository": {
"type": "git",
"url": "https://github.com/Microsoft/vscode-emmet"
},
"activationEvents": [
"onLanguage:html",
"onLanguage:jade",
"onLanguage:slim",
"onLanguage:haml",
"onLanguage:xml",
"onLanguage:xsl",
"onLanguage:css",
"onLanguage:scss",
"onLanguage:sass",
"onLanguage:less",
"onLanguage:stylus",
"onLanguage:javascriptreact",
"onLanguage:typescriptreact"
],
"main": "./out/extension",
"contributes": {
"configuration": {
"type": "object",
"title": "Emmet configuration",
"properties": {
"emmet.suggestExpandedAbbreviation": {
"type": "boolean",
"default": true,
"description": "Shows expanded emmet abbreviations as suggestions"
},
"emmet.suggestAbbreviations": {
"type": "boolean",
"default": true,
"description": "Shows possible emmet abbreviations as suggestions"
}
}
}
},
"scripts": {
"vscode:prepublish": "tsc -p ./",
"compile": "tsc -watch -p ./",
"postinstall": "node ./node_modules/vscode/bin/install",
"test": "node ./node_modules/vscode/bin/test"
},
"devDependencies": {
"typescript": "^2.0.3",
"vscode": "^1.0.0",
"mocha": "^2.3.3",
"@types/node": "^6.0.40",
"@types/mocha": "^2.2.32"
},
"dependencies": {
"@emmetio/expand-abbreviation": "^0.5.4",
"@emmetio/extract-abbreviation": "^0.1.1",
"@emmetio/html-matcher": "^0.3.1",
"@emmetio/css-parser": "^0.3.0"
}
}
\ No newline at end of file
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import { expand } from '@emmetio/expand-abbreviation';
import { getSyntax, getProfile, extractAbbreviation } from './util';
const field = (index, placeholder) => `\${${index}${placeholder ? ':' + placeholder : ''}}`;
export function wrapWithAbbreviation() {
let editor = vscode.window.activeTextEditor;
if (!editor) {
vscode.window.showInformationMessage('No editor is active');
return;
}
let rangeToReplace: vscode.Range = editor.selection;
if (rangeToReplace.isEmpty) {
rangeToReplace = new vscode.Range(rangeToReplace.start.line, 0, rangeToReplace.start.line, editor.document.lineAt(rangeToReplace.start.line).text.length);
}
let textToReplace = editor.document.getText(rangeToReplace);
let options = {
field: field,
syntax: getSyntax(editor.document),
profile: getProfile(getSyntax(editor.document)),
text: textToReplace
};
vscode.window.showInputBox({ prompt: 'Enter Abbreviation' }).then(abbr => {
if (!abbr || !abbr.trim()) { return; }
let expandedText = expand(abbr, options);
editor.insertSnippet(new vscode.SnippetString(expandedText), rangeToReplace);
});
}
export function expandAbbreviation() {
let editor = vscode.window.activeTextEditor;
if (!editor) {
vscode.window.showInformationMessage('No editor is active');
return;
}
let rangeToReplace: vscode.Range = editor.selection;
let abbr = editor.document.getText(rangeToReplace);
if (rangeToReplace.isEmpty) {
[rangeToReplace, abbr] = extractAbbreviation(rangeToReplace.start);
}
let options = {
field: field,
syntax: getSyntax(editor.document),
profile: getProfile(getSyntax(editor.document))
};
let expandedText = expand(abbr, options);
editor.insertSnippet(new vscode.SnippetString(expandedText), rangeToReplace);
}
\ No newline at end of file
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import { getNode, getNodeOuterSelection, getNodeInnerSelection, isStyleSheet } from './util';
import parse from '@emmetio/html-matcher';
import Node from '@emmetio/node';
export function balanceOut() {
balance(true);
}
export function balanceIn() {
balance(false);
}
function balance(out: boolean) {
let editor = vscode.window.activeTextEditor;
if (!editor) {
vscode.window.showInformationMessage('No editor is active');
return;
}
if (isStyleSheet(editor.document.languageId)) {
return;
}
let getRangeFunction = out ? getRangeToBalanceOut : getRangeToBalanceIn;
let rootNode: Node = parse(editor.document.getText());
let newSelections: vscode.Selection[] = [];
editor.selections.forEach(selection => {
let range = getRangeFunction(editor.document, selection, rootNode);
if (range) {
newSelections.push(range);
}
});
editor.selection = newSelections[0];
editor.selections = newSelections;
}
function getRangeToBalanceOut(document: vscode.TextDocument, selection: vscode.Selection, rootNode: Node): vscode.Selection {
let offset = document.offsetAt(selection.start);
let nodeToBalance = getNode(rootNode, offset);
let innerSelection = getNodeInnerSelection(document, nodeToBalance);
let outerSelection = getNodeOuterSelection(document, nodeToBalance);
if (innerSelection.contains(selection) && !innerSelection.isEqual(selection)) {
return innerSelection;
}
if (outerSelection.contains(selection) && !outerSelection.isEqual(selection)) {
return outerSelection;
}
return;
}
function getRangeToBalanceIn(document: vscode.TextDocument, selection: vscode.Selection, rootNode: Node): vscode.Selection {
let offset = document.offsetAt(selection.start);
let nodeToBalance: Node = getNode(rootNode, offset);
if (!nodeToBalance.firstChild) {
return selection;
}
if (nodeToBalance.firstChild.start === offset && nodeToBalance.firstChild.end === document.offsetAt(selection.end)) {
return getNodeInnerSelection(document, nodeToBalance.firstChild);
}
return new vscode.Selection(document.positionAt(nodeToBalance.firstChild.start), document.positionAt(nodeToBalance.firstChild.end));
}
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import { validate } from './util';
export function fetchEditPoint(direction: string): void {
let editor = vscode.window.activeTextEditor;
if (!validate()) {
return;
}
let newSelections: vscode.Selection[] = [];
editor.selections.forEach(selection => {
let updatedSelection = direction === 'next' ? nextEditPoint(selection.anchor, editor) : prevEditPoint(selection.anchor, editor);
newSelections.push(updatedSelection);
});
editor.selections = newSelections;
}
function nextEditPoint(position: vscode.Position, editor: vscode.TextEditor): vscode.Selection {
for (let lineNum = position.line; lineNum < editor.document.lineCount; lineNum++) {
let updatedSelection = findEditPoint(lineNum, editor, position, 'next');
if (updatedSelection) {
return updatedSelection;
}
}
}
function prevEditPoint(position: vscode.Position, editor: vscode.TextEditor): vscode.Selection {
for (let lineNum = position.line; lineNum >= 0; lineNum--) {
let updatedSelection = findEditPoint(lineNum, editor, position, 'prev');
if (updatedSelection) {
return updatedSelection;
}
}
}
function findEditPoint(lineNum: number, editor: vscode.TextEditor, position: vscode.Position, direction: string): vscode.Selection {
let line = editor.document.lineAt(lineNum);
if (lineNum !== position.line && line.isEmptyOrWhitespace) {
editor.selection = new vscode.Selection(lineNum, 0, lineNum, 0);
return;
}
let lineContent = line.text;
if (lineNum === position.line && direction === 'prev') {
lineContent = lineContent.substr(0, position.character);
}
let emptyAttrIndex = direction === 'next' ? lineContent.indexOf('""', lineNum === position.line ? position.character : 0) : lineContent.lastIndexOf('""');
let emptyTagIndex = direction === 'next' ? lineContent.indexOf('><', lineNum === position.line ? position.character : 0) : lineContent.lastIndexOf('><');
let winner = -1;
if (emptyAttrIndex > -1 && emptyTagIndex > -1) {
winner = direction === 'next' ? Math.min(emptyAttrIndex, emptyTagIndex) : Math.max(emptyAttrIndex, emptyTagIndex);
} else if (emptyAttrIndex > -1) {
winner = emptyAttrIndex;
} else {
winner = emptyTagIndex;
}
if (winner > -1) {
return new vscode.Selection(lineNum, winner + 1, lineNum, winner + 1);
}
}
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import { expand, createSnippetsRegistry } from '@emmetio/expand-abbreviation';
import { getSyntax, isStyleSheet, getProfile, extractAbbreviation } from './util';
const field = (index, placeholder) => `\${${index}${placeholder ? ':' + placeholder : ''}}`;
const snippetCompletionsCache = new Map<string, vscode.CompletionItem[]>();
export class EmmetCompletionItemProvider implements vscode.CompletionItemProvider {
public provideCompletionItems(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken): Thenable<vscode.CompletionList> {
if (!vscode.workspace.getConfiguration('emmet')['useModules']) {
return Promise.resolve(null);
}
let currentWord = getCurrentWord(document, position);
let expandedAbbr = getExpandedAbbreviation(document, position);
let abbreviationSuggestions = getAbbreviationSuggestions(getSyntax(document), currentWord, (expandedAbbr && currentWord === expandedAbbr.label));
let completionItems = expandedAbbr ? [expandedAbbr, ...abbreviationSuggestions] : abbreviationSuggestions;
return Promise.resolve(new vscode.CompletionList(completionItems, true));
}
}
function getExpandedAbbreviation(document: vscode.TextDocument, position: vscode.Position): vscode.CompletionItem {
if (!vscode.workspace.getConfiguration('emmet')['suggestExpandedAbbreviation']) {
return;
}
let [rangeToReplace, wordToExpand] = extractAbbreviation(position);
if (!rangeToReplace || !wordToExpand) {
return;
}
let syntax = getSyntax(document);
let expandedWord = expand(wordToExpand, {
field: field,
syntax: syntax,
profile: getProfile(syntax)
});
let completionitem = new vscode.CompletionItem(wordToExpand);
completionitem.insertText = new vscode.SnippetString(expandedWord);
completionitem.documentation = removeTabStops(expandedWord);
completionitem.range = rangeToReplace;
completionitem.detail = 'Expand Emmet Abbreviation';
// In non stylesheet like syntax, this extension returns expanded abbr plus posssible abbr completions
// To differentiate between the 2, the former is given CompletionItemKind.Value so that it gets a different icon
if (!isStyleSheet(syntax)) {
completionitem.kind = vscode.CompletionItemKind.Value;
}
return completionitem;
}
function getCurrentWord(document: vscode.TextDocument, position: vscode.Position): string {
let wordAtPosition = document.getWordRangeAtPosition(position);
let currentWord = '';
if (wordAtPosition && wordAtPosition.start.character < position.character) {
let word = document.getText(wordAtPosition);
currentWord = word.substr(0, position.character - wordAtPosition.start.character);
}
return currentWord;
}
function removeTabStops(expandedWord: string): string {
return expandedWord.replace(/\$\{\d+\}/g, '').replace(/\$\{\d+:([^\}]+)\}/g, '$1');
}
function getAbbreviationSuggestions(syntax: string, prefix: string, skipExactMatch: boolean) {
if (!vscode.workspace.getConfiguration('emmet')['suggestAbbreviations'] || !prefix || isStyleSheet(syntax)) {
return [];
}
if (!snippetCompletionsCache.has(syntax)) {
let registry = createSnippetsRegistry(syntax);
let completions: vscode.CompletionItem[] = registry.all({ type: 'string' }).map(snippet => {
let expandedWord = expand(snippet.value, {
field: field,
syntax: syntax
});
let item = new vscode.CompletionItem(snippet.key);
item.documentation = removeTabStops(expandedWord);
item.detail = 'Complete Emmet Abbreviation';
item.insertText = snippet.key;
return item;
});
snippetCompletionsCache.set(syntax, completions);
}
let snippetCompletions = snippetCompletionsCache.get(syntax);
snippetCompletions = snippetCompletions.filter(x => x.label.startsWith(prefix) && (!skipExactMatch || x.label !== prefix));
return snippetCompletions;
}
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import { EmmetCompletionItemProvider } from './emmetCompletionProvider';
import { expandAbbreviation, wrapWithAbbreviation } from './abbreviationActions';
import { removeTag } from './removeTag';
import { updateTag } from './updateTag';
import { matchTag } from './matchTag';
import { balanceOut, balanceIn } from './balance';
import { splitJoinTag } from './splitJoinTag';
import { mergeLines } from './mergeLines';
import { toggleComment } from './toggleComment';
import { fetchEditPoint } from './editPoint';
import { fetchSelectItem } from './selectItem';
interface ISupportedLanguageMode {
id: string;
triggerCharacters: string[];
}
const SUPPORTED_LANGUAGE_MODES: ISupportedLanguageMode[] = [
{ id: 'html', triggerCharacters: ['!', '.'] },
{ id: 'jade', triggerCharacters: ['!', '.'] },
{ id: 'slim', triggerCharacters: ['!', '.'] },
{ id: 'haml', triggerCharacters: ['!', '.'] },
{ id: 'xml', triggerCharacters: ['.'] },
{ id: 'xsl', triggerCharacters: ['.'] },
{ id: 'css', triggerCharacters: [':'] },
{ id: 'scss', triggerCharacters: [':'] },
{ id: 'sass', triggerCharacters: [':'] },
{ id: 'less', triggerCharacters: [':'] },
{ id: 'stylus', triggerCharacters: [':'] },
{ id: 'javascriptreact', triggerCharacters: ['.'] },
{ id: 'typescriptreact', triggerCharacters: ['.'] }
];
export function activate(context: vscode.ExtensionContext) {
let completionProvider = new EmmetCompletionItemProvider();
for (let language of SUPPORTED_LANGUAGE_MODES) {
const selector: vscode.DocumentFilter = { language: language.id, scheme: 'file' };
const provider = vscode.languages.registerCompletionItemProvider(selector, completionProvider, ...language.triggerCharacters);
context.subscriptions.push(provider);
}
context.subscriptions.push(vscode.commands.registerCommand('emmet.wrapWithAbbreviation', () => {
wrapWithAbbreviation();
}));
context.subscriptions.push(vscode.commands.registerCommand('emmet.expandAbbreviation', () => {
expandAbbreviation();
}));
context.subscriptions.push(vscode.commands.registerCommand('emmet.removeTag', () => {
removeTag();
}));
context.subscriptions.push(vscode.commands.registerCommand('emmet.updateTag', () => {
vscode.window.showInputBox({ prompt: 'Enter Tag' }).then(tagName => {
updateTag(tagName);
});
}));
context.subscriptions.push(vscode.commands.registerCommand('emmet.matchTag', () => {
matchTag();
}));
context.subscriptions.push(vscode.commands.registerCommand('emmet.balanceOut', () => {
balanceOut();
}));
context.subscriptions.push(vscode.commands.registerCommand('emmet.balanceIn', () => {
balanceIn();
}));
context.subscriptions.push(vscode.commands.registerCommand('emmet.splitJoinTag', () => {
splitJoinTag();
}));
context.subscriptions.push(vscode.commands.registerCommand('emmet.mergeLines', () => {
mergeLines();
}));
context.subscriptions.push(vscode.commands.registerCommand('emmet.toggleComment', () => {
toggleComment();
}));
context.subscriptions.push(vscode.commands.registerCommand('emmet.nextEditPoint', () => {
fetchEditPoint('next');
}));
context.subscriptions.push(vscode.commands.registerCommand('emmet.prevEditPoint', () => {
fetchEditPoint('prev');
}));
context.subscriptions.push(vscode.commands.registerCommand('emmet.selectNextItem', () => {
fetchSelectItem('next');
}));
context.subscriptions.push(vscode.commands.registerCommand('emmet.selectPrevItem', () => {
fetchSelectItem('prev');
}));
}
export function deactivate() {
}
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import { getNode } from './util';
import parse from '@emmetio/html-matcher';
import Node from '@emmetio/node';
export function matchTag() {
let editor = vscode.window.activeTextEditor;
if (!editor) {
vscode.window.showInformationMessage('No editor is active');
return;
}
let rootNode: Node = parse(editor.document.getText());
let updatedSelections = [];
editor.selections.forEach(selection => {
let updatedSelection = getUpdatedSelections(editor, editor.document.offsetAt(selection.start), rootNode);
if (updatedSelection) {
updatedSelections.push(updatedSelection);
}
});
if (updatedSelections.length > 0) {
editor.selections = updatedSelections;
}
}
function getUpdatedSelections(editor: vscode.TextEditor, offset: number, rootNode: Node): vscode.Selection {
let currentNode = getNode(rootNode, offset);
// If no closing tag or cursor is between open and close tag, then no-op
if (!currentNode.close || (currentNode.open.end < offset && currentNode.close.start > offset)) {
return;
}
if (offset <= currentNode.open.end) {
let matchingPosition = editor.document.positionAt(currentNode.close.start);
return new vscode.Selection(matchingPosition, matchingPosition);
} else {
let matchingPosition = editor.document.positionAt(currentNode.open.start);
return new vscode.Selection(matchingPosition, matchingPosition);
}
}
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import { isStyleSheet, getNode } from './util';
import parse from '@emmetio/html-matcher';
import Node from '@emmetio/node';
export function mergeLines() {
let editor = vscode.window.activeTextEditor;
if (!editor) {
vscode.window.showInformationMessage('No editor is active');
return;
}
if (isStyleSheet(editor.document.languageId)) {
return;
}
let rootNode: Node = parse(editor.document.getText());
editor.edit(editBuilder => {
editor.selections.reverse().forEach(selection => {
let [rangeToReplace, textToReplaceWith] = getRangesToReplace(editor.document, selection, rootNode);
editBuilder.replace(rangeToReplace, textToReplaceWith);
});
});
}
function getRangesToReplace(document: vscode.TextDocument, selection: vscode.Selection, rootNode: Node): [vscode.Range, string] {
let startNodeToUpdate: Node;
let endNodeToUpdate: Node;
if (selection.isEmpty) {
startNodeToUpdate = endNodeToUpdate = getNode(rootNode, document.offsetAt(selection.start));
} else {
startNodeToUpdate = getNode(rootNode, document.offsetAt(selection.start), true);
endNodeToUpdate = getNode(rootNode, document.offsetAt(selection.end), true);
}
let rangeToReplace = new vscode.Range(document.positionAt(startNodeToUpdate.start), document.positionAt(endNodeToUpdate.end));
let textToReplaceWith = document.getText(rangeToReplace).replace(/\r\n|\n/g, '').replace(/>\s*</g, '><');
return [rangeToReplace, textToReplaceWith];
}
\ No newline at end of file
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import { getOpenCloseRange } from './util';
export function removeTag() {
let editor = vscode.window.activeTextEditor;
if (!editor) {
vscode.window.showInformationMessage('No editor is active');
return;
}
let indentInSpaces = '';
for (let i = 0; i < editor.options.tabSize; i++) {
indentInSpaces += ' ';
}
let rangesToRemove = [];
editor.selections.reverse().forEach(selection => {
rangesToRemove = rangesToRemove.concat(getRangeToRemove(editor, selection, indentInSpaces));
});
editor.edit(editBuilder => {
rangesToRemove.forEach(range => {
editBuilder.replace(range, '');
});
});
}
function getRangeToRemove(editor: vscode.TextEditor, selection: vscode.Selection, indentInSpaces: string): vscode.Range[] {
let offset = editor.document.offsetAt(selection.start);
let [openRange, closeRange] = getOpenCloseRange(editor.document, offset);
if (!openRange.contains(selection.start) && !closeRange.contains(selection.start)) {
return [];
}
let ranges = [openRange];
if (closeRange) {
for (let i = openRange.start.line + 1; i <= closeRange.start.line; i++) {
let lineContent = editor.document.lineAt(i).text;
if (lineContent.startsWith('\t')) {
ranges.push(new vscode.Range(i, 0, i, 1));
} else if (lineContent.startsWith(indentInSpaces)) {
ranges.push(new vscode.Range(i, 0, i, indentInSpaces.length));
}
}
ranges.push(closeRange);
}
return ranges;
}
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import { validate, isStyleSheet } from './util';
import { nextItemHTML, prevItemHTML } from './selectItemHTML';
import { nextItemStylesheet, prevItemStylesheet } from './selectItemStylesheet';
import parseStylesheet from '@emmetio/css-parser';
import parse from '@emmetio/html-matcher';
import Node from '@emmetio/node';
export function fetchSelectItem(direction: string): void {
let editor = vscode.window.activeTextEditor;
if (!validate()) {
return;
}
let nextItem;
let prevItem;
let parseContent;
if (isStyleSheet(editor.document.languageId)) {
nextItem = nextItemStylesheet;
prevItem = prevItemStylesheet;
parseContent = parseStylesheet;
} else {
nextItem = nextItemHTML;
prevItem = prevItemHTML;
parseContent = parse;
}
let rootNode: Node = parseContent(editor.document.getText());
let newSelections: vscode.Selection[] = [];
editor.selections.forEach(selection => {
let updatedSelection = direction === 'next' ? nextItem(selection, editor, rootNode) : prevItem(selection, editor, rootNode);
newSelections.push(updatedSelection ? updatedSelection : selection);
});
editor.selections = newSelections;
}
\ No newline at end of file
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import { getNode, getDeepestNode } from './util';
import Node from '@emmetio/node';
export function nextItemHTML(selection: vscode.Selection, editor: vscode.TextEditor, rootNode: Node): vscode.Selection {
let offset = editor.document.offsetAt(selection.active);
let currentNode = getNode(rootNode, offset);
// Cursor is in the open tag, look for attributes
if (offset < currentNode.open.end) {
let attrSelection = getNextAttribute(selection, editor.document, currentNode);
if (attrSelection) {
return attrSelection;
}
}
// Get the first child of current node which is right after the cursor
let nextNode = currentNode.firstChild;
while (nextNode && nextNode.start < offset) {
nextNode = nextNode.nextSibling;
}
// Get next sibling of current node or the parent
while (!nextNode && currentNode) {
nextNode = currentNode.nextSibling;
currentNode = currentNode.parent;
}
return getSelectionFromNode(nextNode, editor.document);
}
export function prevItemHTML(selection: vscode.Selection, editor: vscode.TextEditor, rootNode: Node): vscode.Selection {
let offset = editor.document.offsetAt(selection.active);
let currentNode = getNode(rootNode, offset);
let prevNode: Node;
// Cursor is in the open tag after the tag name
if (offset > currentNode.open.start + currentNode.name.length + 1 && offset <= currentNode.open.end) {
prevNode = currentNode;
}
// Cursor is inside the tag
if (!prevNode && offset > currentNode.open.end) {
if (!currentNode.firstChild) {
// No children, so current node should be selected
prevNode = currentNode;
} else {
// Select the child that appears just before the cursor
prevNode = currentNode.firstChild;
while (prevNode.nextSibling && prevNode.nextSibling.end < offset) {
prevNode = prevNode.nextSibling;
}
if (prevNode) {
prevNode = getDeepestNode(prevNode);
}
}
}
if (!prevNode && currentNode.previousSibling) {
prevNode = getDeepestNode(currentNode.previousSibling);
}
if (!prevNode && currentNode.parent) {
prevNode = currentNode.parent;
}
let attrSelection = getPrevAttribute(selection, editor.document, prevNode);
return attrSelection ? attrSelection : getSelectionFromNode(prevNode, editor.document);
}
function getSelectionFromNode(node: Node, document: vscode.TextDocument): vscode.Selection {
if (node && node.open) {
let selectionStart = document.positionAt(node.open.start + 1);
let selectionEnd = node.type === 'comment' ? document.positionAt(node.open.end - 1) : selectionStart.translate(0, node.name.length);
return new vscode.Selection(selectionStart, selectionEnd);
}
}
function getNextAttribute(selection: vscode.Selection, document: vscode.TextDocument, node: Node): vscode.Selection {
if (!node.attributes || node.attributes.length === 0 || node.type === 'comment') {
return;
}
let selectionStart = document.offsetAt(selection.anchor);
let selectionEnd = document.offsetAt(selection.active);
for (let i = 0; i < node.attributes.length; i++) {
let attr = node.attributes[i];
if (selectionEnd < attr.start) {
// select full attr
return new vscode.Selection(document.positionAt(attr.start), document.positionAt(attr.end));
}
if ((attr.value.start !== attr.value.end) && ((selectionStart === attr.start && selectionEnd === attr.end) || selectionEnd < attr.end - 1)) {
// select attr value
return new vscode.Selection(document.positionAt(attr.value.start), document.positionAt(attr.value.end));
}
}
}
function getPrevAttribute(selection: vscode.Selection, document: vscode.TextDocument, node: Node): vscode.Selection {
if (!node.attributes || node.attributes.length === 0 || node.type === 'comment') {
return;
}
let selectionStart = document.offsetAt(selection.anchor);
for (let i = node.attributes.length - 1; i >= 0; i--) {
let attr = node.attributes[i];
if (selectionStart > attr.value.start) {
// select attr value
return new vscode.Selection(document.positionAt(attr.value.start), document.positionAt(attr.value.end));
}
if (selectionStart > attr.start) {
// select full attr
return new vscode.Selection(document.positionAt(attr.start), document.positionAt(attr.end));
}
}
}
\ No newline at end of file
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import { getNode, getDeepestNode } from './util';
import Node from '@emmetio/node';
export function nextItemStylesheet(selection: vscode.Selection, editor: vscode.TextEditor, rootNode: Node): vscode.Selection {
let startOffset = editor.document.offsetAt(selection.anchor);
let endOffset = editor.document.offsetAt(selection.active);
let currentNode = getNode(rootNode, endOffset, true);
// Full property is selected, so select property value next
if (currentNode.type === 'property' && startOffset === currentNode.start && endOffset === currentNode.end) {
return getSelectionFromNode(currentNode, editor.document, true);
}
// Cursor is in the selector or in a property
if ((currentNode.type === 'rule' && endOffset < currentNode.selectorToken.end)
|| (currentNode.type === 'property' && endOffset < currentNode.valueToken.end)) {
return getSelectionFromNode(currentNode, editor.document);
}
// Get the first child of current node which is right after the cursor
let nextNode = currentNode.firstChild;
while (nextNode && endOffset >= nextNode.end) {
nextNode = nextNode.nextSibling;
}
// Get next sibling of current node or the parent
while (!nextNode && currentNode) {
nextNode = currentNode.nextSibling;
currentNode = currentNode.parent;
}
return getSelectionFromNode(nextNode, editor.document);
}
export function prevItemStylesheet(selection: vscode.Selection, editor: vscode.TextEditor, rootNode: Node): vscode.Selection {
let startOffset = editor.document.offsetAt(selection.anchor);
let currentNode = getNode(rootNode, startOffset);
if (!currentNode) {
currentNode = rootNode;
}
if (currentNode.type === 'property' || !currentNode.firstChild || (currentNode.type === 'rule' && startOffset <= currentNode.firstChild.start)) {
return getSelectionFromNode(currentNode, editor.document);;
}
// Select the child that appears just before the cursor
let prevNode = currentNode.firstChild;
while (prevNode.nextSibling && prevNode.nextSibling.end <= startOffset) {
prevNode = prevNode.nextSibling;
}
prevNode = getDeepestNode(prevNode);
return getSelectionFromNode(prevNode, editor.document, true);
}
function getSelectionFromNode(node: Node, document: vscode.TextDocument, selectPropertyValue: boolean = false): vscode.Selection {
if (!node) {
return;
}
let nodeToSelect = node.type === 'rule' ? node.selectorToken : node;
let selectionStart = (node.type === 'property' && selectPropertyValue) ? node.valueToken.start : nodeToSelect.start;
let selectionEnd = (node.type === 'property' && selectPropertyValue) ? node.valueToken.end : nodeToSelect.end;
return new vscode.Selection(document.positionAt(selectionStart), document.positionAt(selectionEnd));
}
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import { isStyleSheet, getNode } from './util';
import parse from '@emmetio/html-matcher';
import Node from '@emmetio/node';
export function splitJoinTag() {
let editor = vscode.window.activeTextEditor;
if (!editor) {
vscode.window.showInformationMessage('No editor is active');
return;
}
if (isStyleSheet(editor.document.languageId)) {
return;
}
let rootNode: Node = parse(editor.document.getText());
editor.edit(editBuilder => {
editor.selections.reverse().forEach(selection => {
let [rangeToReplace, textToReplaceWith] = getRangesToReplace(editor.document, selection, rootNode);
editBuilder.replace(rangeToReplace, textToReplaceWith);
});
});
}
function getRangesToReplace(document: vscode.TextDocument, selection: vscode.Selection, rootNode: Node): [vscode.Range, string] {
let offset = document.offsetAt(selection.start);
let nodeToUpdate: Node = getNode(rootNode, offset);
let rangeToReplace: vscode.Range;
let textToReplaceWith: string;
if (!nodeToUpdate.close) {
// Split Tag
let nodeText = document.getText(new vscode.Range(document.positionAt(nodeToUpdate.start), document.positionAt(nodeToUpdate.end)));
let m = nodeText.match(/(\s*\/)?>$/);
let end = nodeToUpdate.open.end;
let start = m ? end - m[0].length : end;
rangeToReplace = new vscode.Range(document.positionAt(start), document.positionAt(end));
textToReplaceWith = `></${nodeToUpdate.name}>`;
} else {
// Join Tag
rangeToReplace = new vscode.Range(document.positionAt(nodeToUpdate.open.end - 1), document.positionAt(nodeToUpdate.close.end));
textToReplaceWith = '/>';
}
return [rangeToReplace, textToReplaceWith];
}
\ No newline at end of file
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import { getNode, isStyleSheet } from './util';
import parse from '@emmetio/html-matcher';
import parseStylesheet from '@emmetio/css-parser';
import Node from '@emmetio/node';
const startCommentStylesheet = '/*';
const endCommentStylesheet = '*/';
const startCommentHTML = '<!--';
const endCommentHTML = '-->';
export function toggleComment() {
let editor = vscode.window.activeTextEditor;
if (!editor) {
vscode.window.showInformationMessage('No editor is active');
return;
}
let toggleCommentInternal;
let startComment;
let endComment;
let parseContent;
if (isStyleSheet(editor.document.languageId)) {
parseContent = parseStylesheet;
toggleCommentInternal = toggleCommentStylesheet;
startComment = startCommentStylesheet;
endComment = endCommentStylesheet;
} else {
parseContent = parse;
toggleCommentInternal = toggleCommentHTML;
startComment = startCommentHTML;
endComment = endCommentHTML;
}
let rootNode = parseContent(editor.document.getText());
editor.edit(editBuilder => {
editor.selections.reverse().forEach(selection => {
let [rangesToUnComment, positionForCommentStart, positionForCommentEnd] = toggleCommentInternal(editor.document, selection, rootNode);
rangesToUnComment.forEach(rangeToDelete => {
editBuilder.delete(rangeToDelete);
});
if (positionForCommentStart) {
editBuilder.insert(positionForCommentStart, startComment);
}
if (positionForCommentEnd) {
editBuilder.insert(positionForCommentEnd, endComment);
}
});
});
}
function toggleCommentHTML(document: vscode.TextDocument, selection: vscode.Selection, rootNode: Node): [vscode.Range[], vscode.Position, vscode.Position] {
let offset = document.offsetAt(selection.start);
let nodeToUpdate = getNode(rootNode, offset);
let rangesToUnComment = getRangesToUnCommentHTML(nodeToUpdate, document);
if (nodeToUpdate.type === 'comment') {
return [rangesToUnComment, null, null];
}
let positionForCommentStart = document.positionAt(nodeToUpdate.start);
let positionForCommentEnd = document.positionAt(nodeToUpdate.end);
return [rangesToUnComment, positionForCommentStart, positionForCommentEnd];
}
function getRangesToUnCommentHTML(node: Node, document: vscode.TextDocument): vscode.Range[] {
let rangesToUnComment = [];
// If current node is commented, then uncomment and return
if (node.type === 'comment') {
rangesToUnComment.push(new vscode.Range(document.positionAt(node.start), document.positionAt(node.start + startCommentHTML.length)));
rangesToUnComment.push(new vscode.Range(document.positionAt(node.end), document.positionAt(node.end - endCommentHTML.length)));
return rangesToUnComment;
}
// All children of current node should be uncommented
node.children.forEach(childNode => {
rangesToUnComment = rangesToUnComment.concat(getRangesToUnCommentHTML(childNode, document));
});
return rangesToUnComment;
}
function toggleCommentStylesheet(document: vscode.TextDocument, selection: vscode.Selection, rootNode: Node): [vscode.Range[], vscode.Position, vscode.Position] {
let selectionStart = document.offsetAt(selection.anchor);
let selectionEnd = document.offsetAt(selection.active);
// If current node is commented, then uncomment and return
let rangesToUnComment = getRangesToUnCommentStylesheet(rootNode, selectionStart, selectionEnd, document, true);
if (rangesToUnComment.length > 0) {
return [rangesToUnComment, null, null];
}
// Uncomment children of current node and then comment the node
let nodeToComment = getNode(rootNode, selectionStart);
rangesToUnComment = getRangesToUnCommentStylesheet(rootNode, nodeToComment.start, nodeToComment.end, document, false);
let positionForCommentStart = document.positionAt(nodeToComment.start);
let positionForCommentEnd = document.positionAt(nodeToComment.end);
return [rangesToUnComment, positionForCommentStart, positionForCommentEnd];
}
function getRangesToUnCommentStylesheet(rootNode: Node, selectionStart: number, selectionEnd: number, document: vscode.TextDocument, selectionInsideComment: boolean): vscode.Range[] {
if (!rootNode.comments || rootNode.comments.length === 0) {
return [];
}
let rangesToUnComment = [];
rootNode.comments.forEach(comment => {
let foundComment = false;
if (selectionInsideComment) {
foundComment = comment.start <= selectionStart && comment.end >= selectionEnd;
} else {
foundComment = selectionStart <= comment.start && selectionEnd >= comment.end;
}
if (foundComment) {
rangesToUnComment.push(new vscode.Range(document.positionAt(comment.start), document.positionAt(comment.start + startCommentStylesheet.length)));
rangesToUnComment.push(new vscode.Range(document.positionAt(comment.end), document.positionAt(comment.end - endCommentStylesheet.length)));
}
});
return rangesToUnComment;
}
\ No newline at end of file
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import { getNode } from './util';
import parse from '@emmetio/html-matcher';
import Node from '@emmetio/node';
export function updateTag(tagName: string) {
let editor = vscode.window.activeTextEditor;
if (!editor) {
vscode.window.showInformationMessage('No editor is active');
return;
}
let rootNode: Node = parse(editor.document.getText());
let rangesToUpdate = [];
editor.selections.reverse().forEach(selection => {
rangesToUpdate = rangesToUpdate.concat(getRangesToUpdate(editor, selection, rootNode));
});
editor.edit(editBuilder => {
rangesToUpdate.forEach(range => {
editBuilder.replace(range, tagName);
});
});
}
function getRangesToUpdate(editor: vscode.TextEditor, selection: vscode.Selection, rootNode: Node): vscode.Range[] {
let offset = editor.document.offsetAt(selection.start);
let nodeToUpdate = getNode(rootNode, offset);
let openStart = editor.document.positionAt(nodeToUpdate.open.start + 1);
let openEnd = openStart.translate(0, nodeToUpdate.name.length);
let ranges = [new vscode.Range(openStart, openEnd)];
if (nodeToUpdate.close) {
let closeStart = editor.document.positionAt(nodeToUpdate.close.start + 2);
let closeEnd = editor.document.positionAt(nodeToUpdate.close.end - 1);
ranges.push(new vscode.Range(closeStart, closeEnd));
}
return ranges;
}
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import parse from '@emmetio/html-matcher';
import Node from '@emmetio/node';
import * as extract from '@emmetio/extract-abbreviation';
export function validate(allowStylesheet: boolean = true): boolean {
let editor = vscode.window.activeTextEditor;
if (!editor) {
vscode.window.showInformationMessage('No editor is active');
return false;
}
if (!allowStylesheet && isStyleSheet(editor.document.languageId)) {
return false;
}
return true;
}
export function getSyntax(document: vscode.TextDocument): string {
if (document.languageId === 'jade') {
return 'pug';
}
if (document.languageId === 'javascriptreact' || document.languageId === 'typescriptreact') {
return 'jsx';
}
return document.languageId;
}
export function isStyleSheet(syntax): boolean {
let stylesheetSyntaxes = ['css', 'scss', 'sass', 'less', 'stylus'];
return (stylesheetSyntaxes.indexOf(syntax) > -1);
}
export function getProfile(syntax: string): any {
let config = vscode.workspace.getConfiguration('emmet')['syntaxProfiles'] || {};
let options = config[syntax];
if (!options || typeof options === 'string') {
return {};
}
let newOptions = {};
for (let key in options) {
switch (key) {
case 'tag_case':
newOptions['tagCase'] = (options[key] === 'lower' || options[key] === 'upper') ? options[key] : '';
break;
case 'attr_case':
newOptions['attributeCase'] = (options[key] === 'lower' || options[key] === 'upper') ? options[key] : '';
break;
case 'attr_quotes':
newOptions['attributeQuotes'] = options[key];
break;
case 'tag_nl':
newOptions['format'] = (options[key] === 'true' || options[key] === 'false') ? options[key] : 'true';
break;
case 'indent':
newOptions['attrCase'] = (options[key] === 'true' || options[key] === 'false') ? '\t' : options[key];
break;
case 'inline_break':
newOptions['inlineBreak'] = options[key];
break;
case 'self_closing_tag':
if (options[key] === true) {
newOptions['selfClosingStyle'] = 'xml'; break;
}
if (options[key] === false) {
newOptions['selfClosingStyle'] = 'html'; break;
}
newOptions['selfClosingStyle'] = options[key];
break;
default:
newOptions[key] = options[key];
break;
}
}
return newOptions;
}
export function getOpenCloseRange(document: vscode.TextDocument, offset: number): [vscode.Range, vscode.Range] {
let rootNode: Node = parse(document.getText());
let nodeToUpdate = getNode(rootNode, offset);
let openRange = new vscode.Range(document.positionAt(nodeToUpdate.open.start), document.positionAt(nodeToUpdate.open.end));
let closeRange = null;
if (nodeToUpdate.close) {
closeRange = new vscode.Range(document.positionAt(nodeToUpdate.close.start), document.positionAt(nodeToUpdate.close.end));
}
return [openRange, closeRange];
}
export function getNode(root: Node, offset: number, includeNodeBoundary: boolean = false) {
let currentNode: Node = root.firstChild;
let foundNode: Node = null;
while (currentNode) {
if ((currentNode.start < offset && currentNode.end > offset)
|| (includeNodeBoundary && (currentNode.start <= offset && currentNode.end >= offset))) {
foundNode = currentNode;
// Dig deeper
currentNode = currentNode.firstChild;
} else {
currentNode = currentNode.nextSibling;
}
}
return foundNode;
}
export function getNodeOuterSelection(document: vscode.TextDocument, node: Node): vscode.Selection {
return new vscode.Selection(document.positionAt(node.start), document.positionAt(node.end));
}
export function getNodeInnerSelection(document: vscode.TextDocument, node: Node): vscode.Selection {
return new vscode.Selection(document.positionAt(node.open.end), document.positionAt(node.close.start));
}
export function extractAbbreviation(position: vscode.Position): [vscode.Range, string] {
let editor = vscode.window.activeTextEditor;
let currentLine = editor.document.lineAt(position.line).text;
let result = extract(currentLine, position.character, true);
if (!result) {
return [null, ''];
}
let rangeToReplace = new vscode.Range(position.line, result.location, position.line, result.location + result.abbreviation.length);
return [rangeToReplace, result.abbreviation];
}
export function getDeepestNode(node: Node): Node {
if (!node || !node.children || node.children.length === 0) {
return node;
}
return getDeepestNode(node.children[node.children.length - 1]);
}
\ No newline at end of file
{
"compilerOptions": {
"target": "es6",
"lib": [
"es2016"
],
"module": "commonjs",
"outDir": "./out"
},
"exclude": [
"node_modules",
".vscode-test"
],
"include": [
"src/**/*"
]
}
\ No newline at end of file
......@@ -13,7 +13,6 @@ import { CommandCenter } from './commands';
import { StatusBarCommands } from './statusbar';
import { GitContentProvider } from './contentProvider';
import { AutoFetcher } from './autofetch';
import { MergeDecorator } from './merge';
import { Askpass } from './askpass';
import { toDisposable } from './util';
import TelemetryReporter from 'vscode-extension-telemetry';
......@@ -58,14 +57,12 @@ async function init(context: ExtensionContext, disposables: Disposable[]): Promi
const provider = new GitSCMProvider(model, commandCenter, statusBarCommands);
const contentProvider = new GitContentProvider(model);
const autoFetcher = new AutoFetcher(model);
const mergeDecorator = new MergeDecorator(model);
disposables.push(
commandCenter,
provider,
contentProvider,
autoFetcher,
mergeDecorator,
model
);
......
/*---------------------------------------------------------------------------------------------
* 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 { window, workspace, Disposable, TextEditor, TextDocument, Range } from 'vscode';
import { Model, Status } from './model';
import { filterEvent } from './util';
import { debounce } from './decorators';
import { iterate } from './iterators';
function* lines(document: TextDocument): IterableIterator<string> {
for (let i = 0; i < document.lineCount; i++) {
yield document.lineAt(i).text;
}
}
const pattern = /^<<<<<<<|^=======|^>>>>>>>/;
function decorate(document: TextDocument): Range[] {
return iterate(lines(document))
.map((line, i) => pattern.test(line) ? i : null)
.filter(i => i !== null)
.map((i: number) => new Range(i, 1, i, 1))
.toArray();
}
class TextEditorMergeDecorator {
private static DecorationType = window.createTextEditorDecorationType({
backgroundColor: 'rgba(255, 139, 0, 0.3)',
isWholeLine: true,
dark: {
backgroundColor: 'rgba(235, 59, 0, 0.3)'
}
});
private uri: string;
private disposables: Disposable[] = [];
constructor(
private model: Model,
private editor: TextEditor
) {
this.uri = this.editor.document.uri.toString();
const onDidChange = filterEvent(workspace.onDidChangeTextDocument, e => e.document && e.document.uri.toString() === this.uri);
onDidChange(this.redecorate, this, this.disposables);
model.onDidChange(this.redecorate, this, this.disposables);
this.redecorate();
}
@debounce(300)
private redecorate(): void {
let decorations: Range[] = [];
if (window.visibleTextEditors.every(e => e !== this.editor)) {
this.dispose();
return;
}
if (this.model.mergeGroup.resources.some(r => r.type === Status.BOTH_MODIFIED && r.resourceUri.toString() === this.uri)) {
decorations = decorate(this.editor.document);
}
this.editor.setDecorations(TextEditorMergeDecorator.DecorationType, decorations);
}
dispose(): void {
this.disposables.forEach(d => d.dispose());
}
}
export class MergeDecorator {
private textEditorDecorators: TextEditorMergeDecorator[] = [];
private disposables: Disposable[] = [];
constructor(private model: Model) {
window.onDidChangeVisibleTextEditors(this.onDidChangeVisibleTextEditors, this, this.disposables);
this.onDidChangeVisibleTextEditors(window.visibleTextEditors);
}
private onDidChangeVisibleTextEditors(editors: TextEditor[]): void {
this.textEditorDecorators.forEach(d => d.dispose());
this.textEditorDecorators = editors.map(e => new TextEditorMergeDecorator(this.model, e));
}
dispose(): void {
this.textEditorDecorators.forEach(d => d.dispose());
this.disposables.forEach(d => d.dispose());
}
}
......@@ -7,7 +7,6 @@
"engines": {
"vscode": "*"
},
"enableProposedApi": true,
"categories": [
"Other"
],
......
......@@ -7,7 +7,6 @@
"engines": {
"vscode": "*"
},
"enableProposedApi": true,
"categories": [
"Other"
],
......
......@@ -7,7 +7,6 @@
"engines": {
"vscode": "*"
},
"enableProposedApi": true,
"categories": [
"Other"
],
......
{
"name": "merge-conflict",
"publisher": "vscode",
"displayName": "merge-conflict",
"description": "Merge Conflict",
"version": "0.7.0",
"aiKey": "AIF-d9b70cd4-b9f9-4d70-929b-a071c400b217",
"engines": {
"vscode": "^1.5.0"
},
"categories": [
"Other"
],
"activationEvents": [
"*"
],
"main": "./out/extension",
"scripts": {
"compile": "gulp compile-extension:merge-conflict",
"watch": "gulp watch-extension:merge-conflict"
},
"contributes": {
"commands": [
{
"category": "%command.category%",
"title": "%command.accept.all-incoming%",
"command": "merge-conflict.accept.all-incoming"
},
{
"category": "%command.category%",
"title": "%command.accept.all-both%",
"command": "merge-conflict.accept.all-both"
},
{
"category": "%command.category%",
"title": "%command.accept.current%",
"command": "merge-conflict.accept.current"
},
{
"category": "%command.category%",
"title": "%command.accept.incoming%",
"command": "merge-conflict.accept.incoming"
},
{
"category": "%command.category%",
"title": "Accept selection",
"command": "merge-conflict.accept.selection"
},
{
"category": "%command.category%",
"title": "%command.accept.both%",
"command": "merge-conflict.accept.both"
},
{
"category": "%command.category%",
"title": "%command.next%",
"command": "merge-conflict.next"
},
{
"category": "%command.category%",
"title": "%command.previous%",
"command": "merge-conflict.previous"
},
{
"category": "%command.category%",
"title": "%command.compare%",
"command": "merge-conflict.compare"
}
],
"keybindings": [
{
"command": "merge-conflict.next",
"when": "editorTextFocus",
"key": "alt+m down"
},
{
"command": "merge-conflict.previous",
"when": "editorTextFocus",
"key": "alt+m up"
},
{
"command": "merge-conflict.accept.selection",
"when": "editorTextFocus",
"key": "alt+m enter"
},
{
"command": "merge-conflict.accept.current",
"when": "editorTextFocus",
"key": "alt+m 1"
},
{
"command": "merge-conflict.accept.incoming",
"when": "editorTextFocus",
"key": "alt+m 2"
},
{
"command": "merge-conflict.accept.both",
"when": "editorTextFocus",
"key": "alt+m 3"
}
],
"configuration": {
"title": "%config.title%",
"properties": {
"merge-conflict.codeLens.enabled": {
"type": "boolean",
"description": "%config.codeLensEnabled%",
"default": true
},
"merge-conflict.decorators.enabled": {
"type": "boolean",
"description": "%config.decoratorsEnabled%",
"default": true
}
}
}
},
"dependencies": {
"vscode-extension-telemetry": "^0.0.7",
"vscode-nls": "^2.0.2"
},
"devDependencies": {
"@types/mocha": "^2.2.41",
"@types/node": "^7.0.4",
"mocha": "^3.2.0"
}
}
\ No newline at end of file
{
"command.category": "Merge Conflict",
"command.accept.all-incoming": "Accept all incoming",
"command.accept.all-both": "Accept all both",
"command.accept.current": "Accept current",
"command.accept.incoming": "Accept incoming",
"command.accept.selection": "Accept selection",
"command.accept.both": "Accept Both",
"command.next": "Next conflict",
"command.previous": "Previous conflict",
"command.compare": "Compare current conflict",
"config.title": "Merge Conflict",
"config.codeLensEnabled": "Enable/disable merge conflict block CodeLens within editor",
"config.decoratorsEnabled": "Enable/disable merge conflict decorators within editor"
}
\ No newline at end of file
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import * as interfaces from './interfaces';
import { loadMessageBundle } from 'vscode-nls';
const localize = loadMessageBundle();
export default class MergeConflictCodeLensProvider implements vscode.CodeLensProvider, vscode.Disposable {
private codeLensRegistrationHandle: vscode.Disposable | null;
private config: interfaces.IExtensionConfiguration;
private tracker: interfaces.IDocumentMergeConflictTracker;
constructor(private context: vscode.ExtensionContext, trackerService: interfaces.IDocumentMergeConflictTrackerService) {
this.tracker = trackerService.createTracker('codelens');
}
begin(config: interfaces.IExtensionConfiguration) {
this.config = config;
if (this.config.enableCodeLens) {
this.registerCodeLensProvider();
}
}
configurationUpdated(updatedConfig: interfaces.IExtensionConfiguration) {
if (updatedConfig.enableCodeLens === false && this.codeLensRegistrationHandle) {
this.codeLensRegistrationHandle.dispose();
this.codeLensRegistrationHandle = null;
}
else if (updatedConfig.enableCodeLens === true && !this.codeLensRegistrationHandle) {
this.registerCodeLensProvider();
}
this.config = updatedConfig;
}
dispose() {
if (this.codeLensRegistrationHandle) {
this.codeLensRegistrationHandle.dispose();
this.codeLensRegistrationHandle = null;
}
}
async provideCodeLenses(document: vscode.TextDocument, token: vscode.CancellationToken): Promise<vscode.CodeLens[] | null> {
if (!this.config || !this.config.enableCodeLens) {
return null;
}
let conflicts = await this.tracker.getConflicts(document);
if (!conflicts || conflicts.length === 0) {
return null;
}
let items: vscode.CodeLens[] = [];
conflicts.forEach(conflict => {
let acceptCurrentCommand: vscode.Command = {
command: 'merge-conflict.accept.current',
title: localize('acceptCurrentChange', 'Accept current change'),
arguments: ['known-conflict', conflict]
};
let acceptIncomingCommand: vscode.Command = {
command: 'merge-conflict.accept.incoming',
title: localize('acceptIncomingChange', 'Accept incoming change'),
arguments: ['known-conflict', conflict]
};
let acceptBothCommand: vscode.Command = {
command: 'merge-conflict.accept.both',
title: localize('acceptBothChanges', 'Accept both changes'),
arguments: ['known-conflict', conflict]
};
let diffCommand: vscode.Command = {
command: 'merge-conflict.compare',
title: localize('compareChanges', 'Compare changes'),
arguments: [conflict]
};
items.push(
new vscode.CodeLens(conflict.range, acceptCurrentCommand),
new vscode.CodeLens(conflict.range.with(conflict.range.start.with({ character: conflict.range.start.character + 1 })), acceptIncomingCommand),
new vscode.CodeLens(conflict.range.with(conflict.range.start.with({ character: conflict.range.start.character + 2 })), acceptBothCommand),
new vscode.CodeLens(conflict.range.with(conflict.range.start.with({ character: conflict.range.start.character + 3 })), diffCommand)
);
});
return items;
}
private registerCodeLensProvider() {
this.codeLensRegistrationHandle = vscode.languages.registerCodeLensProvider({ pattern: '**/*' }, this);
}
}
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import * as interfaces from './interfaces';
import ContentProvider from './contentProvider';
import * as path from 'path';
import { loadMessageBundle } from 'vscode-nls';
const localize = loadMessageBundle();
interface IDocumentMergeConflictNavigationResults {
canNavigate: boolean;
conflict?: interfaces.IDocumentMergeConflict;
}
enum NavigationDirection {
Forwards,
Backwards
}
export default class CommandHandler implements vscode.Disposable {
private disposables: vscode.Disposable[] = [];
private tracker: interfaces.IDocumentMergeConflictTracker;
constructor(private context: vscode.ExtensionContext, trackerService: interfaces.IDocumentMergeConflictTrackerService) {
this.tracker = trackerService.createTracker('commands');
}
begin() {
this.disposables.push(
vscode.commands.registerTextEditorCommand('merge-conflict.accept.current', this.acceptCurrent, this),
vscode.commands.registerTextEditorCommand('merge-conflict.accept.incoming', this.acceptIncoming, this),
vscode.commands.registerTextEditorCommand('merge-conflict.accept.selection', this.acceptSelection, this),
vscode.commands.registerTextEditorCommand('merge-conflict.accept.both', this.acceptBoth, this),
vscode.commands.registerTextEditorCommand('merge-conflict.accept.all-current', this.acceptAllCurrent, this),
vscode.commands.registerTextEditorCommand('merge-conflict.accept.all-incoming', this.acceptAllIncoming, this),
vscode.commands.registerTextEditorCommand('merge-conflict.accept.all-both', this.acceptAllBoth, this),
vscode.commands.registerTextEditorCommand('merge-conflict.next', this.navigateNext, this),
vscode.commands.registerTextEditorCommand('merge-conflict.previous', this.navigatePrevious, this),
vscode.commands.registerTextEditorCommand('merge-conflict.compare', this.compare, this)
);
}
acceptCurrent(editor: vscode.TextEditor, edit: vscode.TextEditorEdit, ...args): Promise<void> {
return this.accept(interfaces.CommitType.Current, editor, ...args);
}
acceptIncoming(editor: vscode.TextEditor, edit: vscode.TextEditorEdit, ...args): Promise<void> {
return this.accept(interfaces.CommitType.Incoming, editor, ...args);
}
acceptBoth(editor: vscode.TextEditor, edit: vscode.TextEditorEdit, ...args): Promise<void> {
return this.accept(interfaces.CommitType.Both, editor, ...args);
}
acceptAllCurrent(editor: vscode.TextEditor, edit: vscode.TextEditorEdit, ...args): Promise<void> {
return this.acceptAll(interfaces.CommitType.Current, editor);
}
acceptAllIncoming(editor: vscode.TextEditor, edit: vscode.TextEditorEdit, ...args): Promise<void> {
return this.acceptAll(interfaces.CommitType.Incoming, editor);
}
acceptAllBoth(editor: vscode.TextEditor, edit: vscode.TextEditorEdit, ...args): Promise<void> {
return this.acceptAll(interfaces.CommitType.Both, editor);
}
async compare(editor: vscode.TextEditor, edit: vscode.TextEditorEdit, conflict: interfaces.IDocumentMergeConflict | null, ...args) {
const fileName = path.basename(editor.document.uri.fsPath);
// No conflict, command executed from command palette
if (!conflict) {
conflict = await this.findConflictContainingSelection(editor);
// Still failed to find conflict, warn the user and exit
if (!conflict) {
vscode.window.showWarningMessage(localize('cursorNotInConflict', 'Editor cursor is not within a merge conflict'));
return;
}
}
let range = conflict.current.content;
const leftUri = editor.document.uri.with({
scheme: ContentProvider.scheme,
query: JSON.stringify(range)
});
range = conflict.incoming.content;
const rightUri = leftUri.with({ query: JSON.stringify(range) });
const title = localize('compareChangesTitle', '{0}: Current changes \u2194 Incoming changes', fileName);
vscode.commands.executeCommand('vscode.diff', leftUri, rightUri, title);
}
navigateNext(editor: vscode.TextEditor, edit: vscode.TextEditorEdit, ...args): Promise<void> {
return this.navigate(editor, NavigationDirection.Forwards);
}
navigatePrevious(editor: vscode.TextEditor, edit: vscode.TextEditorEdit, ...args): Promise<void> {
return this.navigate(editor, NavigationDirection.Backwards);
}
async acceptSelection(editor: vscode.TextEditor, edit: vscode.TextEditorEdit, ...args): Promise<void> {
let conflict = await this.findConflictContainingSelection(editor);
if (!conflict) {
vscode.window.showWarningMessage(localize('cursorNotInConflict', 'Editor cursor is not within a merge conflict'));
return;
}
let typeToAccept: interfaces.CommitType;
// Figure out if the cursor is in current or incoming, we do this by seeing if
// the active position is before or after the range of the splitter. We can
// use this trick as the previous check in findConflictByActiveSelection will
// ensure it's within the conflict range, so we don't falsely identify "current"
// or "incoming" if outside of a conflict range.
if (editor.selection.active.isBefore(conflict.splitter.start)) {
typeToAccept = interfaces.CommitType.Current;
}
else if (editor.selection.active.isAfter(conflict.splitter.end)) {
typeToAccept = interfaces.CommitType.Incoming;
}
else {
vscode.window.showWarningMessage(localize('cursorOnSplitterRange', 'Editor cursor is within the merge conflict splitter, please move it to either the "current" or "incoming" block'));
return;
}
this.tracker.forget(editor.document);
conflict.commitEdit(typeToAccept, editor);
}
dispose() {
this.disposables.forEach(disposable => disposable.dispose());
this.disposables = [];
}
private async navigate(editor: vscode.TextEditor, direction: NavigationDirection): Promise<void> {
let navigationResult = await this.findConflictForNavigation(editor, direction);
if (!navigationResult) {
vscode.window.showWarningMessage(localize('noConflicts', 'No merge conflicts found in this file'));
return;
}
else if (!navigationResult.canNavigate) {
vscode.window.showWarningMessage(localize('noOtherConflictsInThisFile', 'No other merge conflicts within this file'));
return;
}
else if (!navigationResult.conflict) {
// TODO: Show error message?
return;
}
// Move the selection to the first line of the conflict
editor.selection = new vscode.Selection(navigationResult.conflict.range.start, navigationResult.conflict.range.start);
editor.revealRange(navigationResult.conflict.range, vscode.TextEditorRevealType.Default);
}
private async accept(type: interfaces.CommitType, editor: vscode.TextEditor, ...args): Promise<void> {
let conflict: interfaces.IDocumentMergeConflict | null;
// If launched with known context, take the conflict from that
if (args[0] === 'known-conflict') {
conflict = args[1];
}
else {
// Attempt to find a conflict that matches the current curosr position
conflict = await this.findConflictContainingSelection(editor);
}
if (!conflict) {
vscode.window.showWarningMessage(localize('cursorNotInConflict', 'Editor cursor is not within a merge conflict'));
return;
}
// Tracker can forget as we know we are going to do an edit
this.tracker.forget(editor.document);
conflict.commitEdit(type, editor);
}
private async acceptAll(type: interfaces.CommitType, editor: vscode.TextEditor): Promise<void> {
let conflicts = await this.tracker.getConflicts(editor.document);
if (!conflicts || conflicts.length === 0) {
vscode.window.showWarningMessage(localize('noConflicts', 'No merge conflicts found in this file'));
return;
}
// For get the current state of the document, as we know we are doing to do a large edit
this.tracker.forget(editor.document);
// Apply all changes as one edit
await editor.edit((edit) => conflicts.forEach(conflict => {
conflict.applyEdit(type, editor, edit);
}));
}
private async findConflictContainingSelection(editor: vscode.TextEditor, conflicts?: interfaces.IDocumentMergeConflict[]): Promise<interfaces.IDocumentMergeConflict | null> {
if (!conflicts) {
conflicts = await this.tracker.getConflicts(editor.document);
}
if (!conflicts || conflicts.length === 0) {
return null;
}
for (let i = 0; i < conflicts.length; i++) {
if (conflicts[i].range.contains(editor.selection.active)) {
return conflicts[i];
}
}
return null;
}
private async findConflictForNavigation(editor: vscode.TextEditor, direction: NavigationDirection, conflicts?: interfaces.IDocumentMergeConflict[]): Promise<IDocumentMergeConflictNavigationResults | null> {
if (!conflicts) {
conflicts = await this.tracker.getConflicts(editor.document);
}
if (!conflicts || conflicts.length === 0) {
return null;
}
let selection = editor.selection.active;
if (conflicts.length === 1) {
if (conflicts[0].range.contains(selection)) {
return {
canNavigate: false
};
}
return {
canNavigate: true,
conflict: conflicts[0]
};
}
let predicate: (conflict) => boolean;
let fallback: () => interfaces.IDocumentMergeConflict;
if (direction === NavigationDirection.Forwards) {
predicate = (conflict) => selection.isBefore(conflict.range.start);
fallback = () => conflicts![0];
} else if (direction === NavigationDirection.Backwards) {
predicate = (conflict) => selection.isAfter(conflict.range.start);
fallback = () => conflicts![conflicts!.length - 1];
} else {
throw new Error(`Unsupported direction ${direction}`);
}
for (let i = 0; i < conflicts.length; i++) {
if (predicate(conflicts[i]) && !conflicts[i].range.contains(selection)) {
return {
canNavigate: true,
conflict: conflicts[i]
};
}
}
// Went all the way to the end, return the head
return {
canNavigate: true,
conflict: fallback()
};
}
}
\ No newline at end of file
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as vscode from 'vscode';
import * as interfaces from './interfaces';
export default class MergeConflictContentProvider implements vscode.TextDocumentContentProvider, vscode.Disposable {
static scheme = 'merge-conflict.conflict-diff';
constructor(private context: vscode.ExtensionContext) {
}
begin(config: interfaces.IExtensionConfiguration) {
this.context.subscriptions.push(
vscode.workspace.registerTextDocumentContentProvider(MergeConflictContentProvider.scheme, this)
);
}
dispose() {
}
async provideTextDocumentContent(uri: vscode.Uri): Promise<string | null> {
try {
const [start, end] = JSON.parse(uri.query) as { line: number, character: number }[];
const document = await vscode.workspace.openTextDocument(uri.with({ scheme: 'file', query: '' }));
const text = document.getText(new vscode.Range(start.line, start.character, end.line, end.character));
return text;
}
catch (ex) {
await vscode.window.showErrorMessage('Unable to show comparison');
return null;
}
}
}
\ No newline at end of file
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
export interface ITask<T> {
(): T;
}
export class Delayer<T> {
public defaultDelay: number;
private timeout: any; // Timer
private completionPromise: Promise<T> | null;
private onSuccess: ((value?: T | Thenable<T> | null) => void) | null;
private task: ITask<T> | null;
constructor(defaultDelay: number) {
this.defaultDelay = defaultDelay;
this.timeout = null;
this.completionPromise = null;
this.onSuccess = null;
this.task = null;
}
public trigger(task: ITask<T>, delay: number = this.defaultDelay): Promise<T> {
this.task = task;
if (delay >= 0) {
this.cancelTimeout();
}
if (!this.completionPromise) {
this.completionPromise = new Promise<T>((resolve) => {
this.onSuccess = resolve;
}).then(() => {
this.completionPromise = null;
this.onSuccess = null;
var result = this.task!();
this.task = null;
return result;
});
}
if (delay >= 0 || this.timeout === null) {
this.timeout = setTimeout(() => {
this.timeout = null;
this.onSuccess!(null);
}, delay >= 0 ? delay : this.defaultDelay);
}
return this.completionPromise;
}
public forceDelivery(): Promise<T> | null {
if (!this.completionPromise) {
return null;
}
this.cancelTimeout();
let result = this.completionPromise;
this.onSuccess!(null);
return result;
}
public isTriggered(): boolean {
return this.timeout !== null;
}
public cancel(): void {
this.cancelTimeout();
this.completionPromise = null;
}
private cancelTimeout(): void {
if (this.timeout !== null) {
clearTimeout(this.timeout);
this.timeout = null;
}
}
}
\ No newline at end of file
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as interfaces from './interfaces';
import * as vscode from 'vscode';
export class DocumentMergeConflict implements interfaces.IDocumentMergeConflict {
public range: vscode.Range;
public current: interfaces.IMergeRegion;
public incoming: interfaces.IMergeRegion;
public splitter: vscode.Range;
constructor(document: vscode.TextDocument, descriptor: interfaces.IDocumentMergeConflictDescriptor) {
this.range = descriptor.range;
this.current = descriptor.current;
this.incoming = descriptor.incoming;
this.splitter = descriptor.splitter;
}
public commitEdit(type: interfaces.CommitType, editor: vscode.TextEditor, edit?: vscode.TextEditorEdit): Thenable<boolean> {
if (edit) {
this.applyEdit(type, editor, edit);
return Promise.resolve(true);
};
return editor.edit((edit) => this.applyEdit(type, editor, edit));
}
public applyEdit(type: interfaces.CommitType, editor: vscode.TextEditor, edit: vscode.TextEditorEdit): void {
// Each conflict is a set of ranges as follows, note placements or newlines
// which may not in in spans
// [ Conflict Range -- (Entire content below)
// [ Current Header ]\n -- >>>>> Header
// [ Current Content ] -- (content)
// [ Splitter ]\n -- =====
// [ Incoming Content ] -- (content)
// [ Incoming Header ]\n -- <<<<< Incoming
// ]
if (type === interfaces.CommitType.Current) {
// Replace [ Conflict Range ] with [ Current Content ]
let content = editor.document.getText(this.current.content);
this.replaceRangeWithContent(content, edit);
}
else if (type === interfaces.CommitType.Incoming) {
let content = editor.document.getText(this.incoming.content);
this.replaceRangeWithContent(content, edit);
}
else if (type === interfaces.CommitType.Both) {
// Replace [ Conflict Range ] with [ Current Content ] + \n + [ Incoming Content ]
const currentContent = editor.document.getText(this.current.content);
const incomingContent = editor.document.getText(this.incoming.content);
edit.replace(this.range, currentContent.concat(incomingContent));
}
}
private replaceRangeWithContent(content: string, edit: vscode.TextEditorEdit) {
if (this.isNewlineOnly(content)) {
edit.replace(this.range, '');
return;
}
// Replace [ Conflict Range ] with [ Current Content ]
edit.replace(this.range, content);
}
private isNewlineOnly(text: string) {
return text === '\n' || text === '\r\n';
}
}
\ No newline at end of file
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import { MergeConflictParser } from './mergeConflictParser';
import * as interfaces from './interfaces';
import { Delayer } from './delayer';
class ScanTask {
public origins: Set<string> = new Set<string>();
public delayTask: Delayer<interfaces.IDocumentMergeConflict[]>;
constructor(delayTime: number, initialOrigin: string) {
this.origins.add(initialOrigin);
this.delayTask = new Delayer<interfaces.IDocumentMergeConflict[]>(delayTime);
}
public addOrigin(name: string): boolean {
if (this.origins.has(name)) {
return false;
}
return false;
}
public hasOrigin(name: string): boolean {
return this.origins.has(name);
}
}
class OriginDocumentMergeConflictTracker implements interfaces.IDocumentMergeConflictTracker {
constructor(private parent: DocumentMergeConflictTracker, private origin: string) {
}
getConflicts(document: vscode.TextDocument): PromiseLike<interfaces.IDocumentMergeConflict[]> {
return this.parent.getConflicts(document, this.origin);
}
isPending(document: vscode.TextDocument): boolean {
return this.parent.isPending(document, this.origin);
}
forget(document: vscode.TextDocument) {
this.parent.forget(document);
}
}
export default class DocumentMergeConflictTracker implements vscode.Disposable, interfaces.IDocumentMergeConflictTrackerService {
private cache: Map<string, ScanTask> = new Map();
private delayExpireTime: number = 250;
getConflicts(document: vscode.TextDocument, origin: string): PromiseLike<interfaces.IDocumentMergeConflict[]> {
// Attempt from cache
let key = this.getCacheKey(document);
if (!key) {
// Document doesnt have a uri, can't cache it, so return
return Promise.resolve(this.getConflictsOrEmpty(document, [origin]));
}
let cacheItem = this.cache.get(key);
if (!cacheItem) {
cacheItem = new ScanTask(this.delayExpireTime, origin);
this.cache.set(key, cacheItem);
}
else {
cacheItem.addOrigin(origin);
}
return cacheItem.delayTask.trigger(() => {
let conflicts = this.getConflictsOrEmpty(document, Array.from(cacheItem!.origins));
if (this.cache) {
this.cache.delete(key!);
}
return conflicts;
});
}
isPending(document: vscode.TextDocument, origin: string): boolean {
if (!document) {
return false;
}
let key = this.getCacheKey(document);
if (!key) {
return false;
}
var task = this.cache.get(key);
if (!task) {
return false;
}
return task.hasOrigin(origin);
}
createTracker(origin: string): interfaces.IDocumentMergeConflictTracker {
return new OriginDocumentMergeConflictTracker(this, origin);
}
forget(document: vscode.TextDocument) {
let key = this.getCacheKey(document);
if (key) {
this.cache.delete(key);
}
}
dispose() {
this.cache.clear();
}
private getConflictsOrEmpty(document: vscode.TextDocument, origins: string[]): interfaces.IDocumentMergeConflict[] {
const containsConflict = MergeConflictParser.containsConflict(document);
if (!containsConflict) {
return [];
}
const conflicts = MergeConflictParser.scanDocument(document);
return conflicts;
}
private getCacheKey(document: vscode.TextDocument): string | null {
if (document.uri && document.uri) {
return document.uri.toString();
}
return null;
}
}
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import MergeConflictServices from './services';
export function activate(context: vscode.ExtensionContext) {
// Register disposables
const services = new MergeConflictServices(context);
services.begin();
context.subscriptions.push(services);
}
export function deactivate() {
}
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
export interface IMergeRegion {
name: string;
header: vscode.Range;
content: vscode.Range;
decoratorContent: vscode.Range;
}
export enum CommitType {
Current,
Incoming,
Both
}
export interface IExtensionConfiguration {
enableCodeLens: boolean;
enableDecorations: boolean;
enableEditorOverview: boolean;
}
export interface IDocumentMergeConflict extends IDocumentMergeConflictDescriptor {
commitEdit(type: CommitType, editor: vscode.TextEditor, edit?: vscode.TextEditorEdit);
applyEdit(type: CommitType, editor: vscode.TextEditor, edit: vscode.TextEditorEdit);
}
export interface IDocumentMergeConflictDescriptor {
range: vscode.Range;
current: IMergeRegion;
incoming: IMergeRegion;
splitter: vscode.Range;
}
export interface IDocumentMergeConflictTracker {
getConflicts(document: vscode.TextDocument): PromiseLike<IDocumentMergeConflict[]>;
isPending(document: vscode.TextDocument): boolean;
forget(document: vscode.TextDocument);
}
export interface IDocumentMergeConflictTrackerService {
createTracker(origin: string): IDocumentMergeConflictTracker;
forget(document: vscode.TextDocument);
}
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import * as interfaces from './interfaces';
import { DocumentMergeConflict } from './documentMergeConflict';
const startHeaderMarker = '<<<<<<< ';
const splitterMarker = '=======';
const endFooterMarker = '>>>>>>> ';
interface IScanMergedConflict {
startHeader: vscode.TextLine;
splitter?: vscode.TextLine;
endFooter?: vscode.TextLine;
}
export class MergeConflictParser {
static scanDocument(document: vscode.TextDocument): interfaces.IDocumentMergeConflict[] {
// Scan each line in the document, we already know there is atleast a <<<<<<< and
// >>>>>> marker within the document, we need to group these into conflict ranges.
// We initially build a scan match, that references the lines of the header, splitter
// and footer. This is then converted into a full descriptor containing all required
// ranges.
let currentConflict: IScanMergedConflict | null = null;
const conflictDescriptors: interfaces.IDocumentMergeConflictDescriptor[] = [];
for (let i = 0; i < document.lineCount; i++) {
const line = document.lineAt(i);
// Ignore empty lines
if (!line || line.isEmptyOrWhitespace) {
continue;
}
// Is this a start line? <<<<<<<
if (line.text.startsWith(startHeaderMarker)) {
if (currentConflict !== null) {
// Error, we should not see a startMarker before we've seen an endMarker
currentConflict = null;
// Give up parsing, anything matched up this to this point will be decorated
// anything after will not
break;
}
// Create a new conflict starting at this line
currentConflict = { startHeader: line };
}
// Are we within a conflict block and is this a splitter? =======
else if (currentConflict && line.text.startsWith(splitterMarker)) {
currentConflict.splitter = line;
}
// Are we withon a conflict block and is this a footer? >>>>>>>
else if (currentConflict && line.text.startsWith(endFooterMarker)) {
currentConflict.endFooter = line;
// Create a full descriptor from the lines that we matched. This can return
// null if the descriptor could not be completed.
let completeDescriptor = MergeConflictParser.scanItemTolMergeConflictDescriptor(document, currentConflict);
if (completeDescriptor !== null) {
conflictDescriptors.push(completeDescriptor);
}
// Reset the current conflict to be empty, so we can match the next
// starting header marker.
currentConflict = null;
}
}
return conflictDescriptors
.filter(Boolean)
.map(descriptor => new DocumentMergeConflict(document, descriptor));
}
private static scanItemTolMergeConflictDescriptor(document: vscode.TextDocument, scanned: IScanMergedConflict): interfaces.IDocumentMergeConflictDescriptor | null {
// Validate we have all the required lines within the scan item.
if (!scanned.startHeader || !scanned.splitter || !scanned.endFooter) {
return null;
}
// Assume that descriptor.current.header, descriptor.incoming.header and descriptor.spliiter
// have valid ranges, fill in content and total ranges from these parts.
// NOTE: We need to shift the decortator range back one character so the splitter does not end up with
// two decoration colors (current and splitter), if we take the new line from the content into account
// the decorator will wrap to the next line.
return {
current: {
header: scanned.startHeader.range,
decoratorContent: new vscode.Range(
scanned.startHeader.rangeIncludingLineBreak.end,
MergeConflictParser.shiftBackOneCharacter(document, scanned.splitter.range.start)),
// Current content is range between header (shifted for linebreak) and splitter start
content: new vscode.Range(
scanned.startHeader.rangeIncludingLineBreak.end,
scanned.splitter.range.start),
name: scanned.startHeader.text.substring(startHeaderMarker.length)
},
splitter: scanned.splitter.range,
incoming: {
header: scanned.endFooter.range,
decoratorContent: new vscode.Range(
scanned.splitter.rangeIncludingLineBreak.end,
MergeConflictParser.shiftBackOneCharacter(document, scanned.endFooter.range.start)),
// Incoming content is range between splitter (shifted for linebreak) and footer start
content: new vscode.Range(
scanned.splitter.rangeIncludingLineBreak.end,
scanned.endFooter.range.start),
name: scanned.endFooter.text.substring(endFooterMarker.length)
},
// Entire range is between current header start and incoming header end (including line break)
range: new vscode.Range(scanned.startHeader.range.start, scanned.endFooter.rangeIncludingLineBreak.end)
};
}
static containsConflict(document: vscode.TextDocument): boolean {
if (!document) {
return false;
}
let text = document.getText();
return text.includes(startHeaderMarker) && text.includes(endFooterMarker);
}
private static shiftBackOneCharacter(document: vscode.TextDocument, range: vscode.Position): vscode.Position {
let line = range.line;
let character = range.character - 1;
if (character < 0) {
line--;
character = document.lineAt(line).range.end.character;
}
return new vscode.Position(line, character);
}
}
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import * as interfaces from './interfaces';
import { loadMessageBundle } from 'vscode-nls';
const localize = loadMessageBundle();
export default class MergeDectorator implements vscode.Disposable {
private decorations: { [key: string]: vscode.TextEditorDecorationType } = {};
private decorationUsesWholeLine: boolean = true; // Useful for debugging, set to false to see exact match ranges
// TODO: Move to config?
private currentColorRgb = `32,200,94`;
private incomingColorRgb = `24,134,255`;
private config: interfaces.IExtensionConfiguration;
private tracker: interfaces.IDocumentMergeConflictTracker;
constructor(private context: vscode.ExtensionContext, trackerService: interfaces.IDocumentMergeConflictTrackerService) {
this.tracker = trackerService.createTracker('decorator');
}
begin(config: interfaces.IExtensionConfiguration) {
this.config = config;
this.registerDecorationTypes(config);
// Check if we already have a set of active windows, attempt to track these.
vscode.window.visibleTextEditors.forEach(e => this.applyDecorations(e));
vscode.workspace.onDidOpenTextDocument(event => {
this.applyDecorationsFromEvent(event);
}, null, this.context.subscriptions);
vscode.workspace.onDidChangeTextDocument(event => {
this.applyDecorationsFromEvent(event.document);
}, null, this.context.subscriptions);
vscode.window.onDidChangeActiveTextEditor((e) => {
// New editor attempt to apply
this.applyDecorations(e);
}, null, this.context.subscriptions);
}
configurationUpdated(config: interfaces.IExtensionConfiguration) {
this.config = config;
this.registerDecorationTypes(config);
// Re-apply the decoration
vscode.window.visibleTextEditors.forEach(e => {
this.removeDecorations(e);
this.applyDecorations(e);
});
}
private registerDecorationTypes(config: interfaces.IExtensionConfiguration) {
// Dispose of existing decorations
Object.keys(this.decorations).forEach(k => this.decorations[k].dispose());
this.decorations = {};
// None of our features are enabled
if (!config.enableDecorations || !config.enableEditorOverview) {
return;
}
// Create decorators
if (config.enableDecorations || config.enableEditorOverview) {
this.decorations['current.content'] = vscode.window.createTextEditorDecorationType(
this.generateBlockRenderOptions(this.currentColorRgb, config)
);
this.decorations['incoming.content'] = vscode.window.createTextEditorDecorationType(
this.generateBlockRenderOptions(this.incomingColorRgb, config)
);
}
if (config.enableDecorations) {
this.decorations['current.header'] = vscode.window.createTextEditorDecorationType({
// backgroundColor: 'rgba(255, 0, 0, 0.01)',
// border: '2px solid red',
isWholeLine: this.decorationUsesWholeLine,
backgroundColor: `rgba(${this.currentColorRgb}, 1.0)`,
color: 'white',
after: {
contentText: ' ' + localize('currentChange', '(Current change)'),
color: 'rgba(0, 0, 0, 0.7)'
}
});
this.decorations['splitter'] = vscode.window.createTextEditorDecorationType({
backgroundColor: 'rgba(0, 0, 0, 0.25)',
color: 'white',
isWholeLine: this.decorationUsesWholeLine,
});
this.decorations['incoming.header'] = vscode.window.createTextEditorDecorationType({
backgroundColor: `rgba(${this.incomingColorRgb}, 1.0)`,
color: 'white',
isWholeLine: this.decorationUsesWholeLine,
after: {
contentText: ' ' + localize('incomingChange', '(Incoming change)'),
color: 'rgba(0, 0, 0, 0.7)'
}
});
}
}
dispose() {
// TODO: Replace with Map<string, T>
Object.keys(this.decorations).forEach(name => {
this.decorations[name].dispose();
});
this.decorations = {};
}
private generateBlockRenderOptions(color: string, config: interfaces.IExtensionConfiguration): vscode.DecorationRenderOptions {
let renderOptions: any = {};
if (config.enableDecorations) {
renderOptions.backgroundColor = `rgba(${color}, 0.2)`;
renderOptions.isWholeLine = this.decorationUsesWholeLine;
}
if (config.enableEditorOverview) {
renderOptions.overviewRulerColor = `rgba(${color}, 0.5)`;
renderOptions.overviewRulerLane = vscode.OverviewRulerLane.Full;
}
return renderOptions;
}
private applyDecorationsFromEvent(eventDocument: vscode.TextDocument) {
for (var i = 0; i < vscode.window.visibleTextEditors.length; i++) {
if (vscode.window.visibleTextEditors[i].document === eventDocument) {
// Attempt to apply
this.applyDecorations(vscode.window.visibleTextEditors[i]);
}
}
}
private async applyDecorations(editor: vscode.TextEditor) {
if (!editor || !editor.document) { return; }
if (!this.config || (!this.config.enableDecorations && !this.config.enableEditorOverview)) {
return;
}
// If we have a pending scan from the same origin, exit early.
if (this.tracker.isPending(editor.document)) {
return;
}
let conflicts = await this.tracker.getConflicts(editor.document);
if (conflicts.length === 0) {
this.removeDecorations(editor);
return;
}
// Store decorations keyed by the type of decoration, set decoration wants a "style"
// to go with it, which will match this key (see constructor);
let matchDecorations: { [key: string]: vscode.DecorationOptions[] } = {};
let pushDecoration = (key: string, d: vscode.DecorationOptions) => {
matchDecorations[key] = matchDecorations[key] || [];
matchDecorations[key].push(d);
};
conflicts.forEach(conflict => {
// TODO, this could be more effective, just call getMatchPositions once with a map of decoration to position
pushDecoration('current.content', { range: conflict.current.decoratorContent });
pushDecoration('incoming.content', { range: conflict.incoming.decoratorContent });
if (this.config.enableDecorations) {
pushDecoration('current.header', { range: conflict.current.header });
pushDecoration('splitter', { range: conflict.splitter });
pushDecoration('incoming.header', { range: conflict.incoming.header });
}
});
// For each match we've generated, apply the generated decoration with the matching decoration type to the
// editor instance. Keys in both matches and decorations should match.
Object.keys(matchDecorations).forEach(decorationKey => {
let decorationType = this.decorations[decorationKey];
if (decorationType) {
editor.setDecorations(decorationType, matchDecorations[decorationKey]);
}
});
}
private removeDecorations(editor: vscode.TextEditor) {
// Remove all decorations, there might be none
Object.keys(this.decorations).forEach(decorationKey => {
// Race condition, while editing the settings, it's possible to
// generate regions before the configuration has been refreshed
let decorationType = this.decorations[decorationKey];
if (decorationType) {
editor.setDecorations(decorationType, []);
}
});
}
}
\ No newline at end of file
此差异已折叠。
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import DocumentTracker from './documentTracker';
import CodeLensProvider from './codelensProvider';
import CommandHandler from './commandHandler';
import ContentProvider from './contentProvider';
import Decorator from './mergeDecorator';
import * as interfaces from './interfaces';
const ConfigurationSectionName = 'merge-conflict';
export default class ServiceWrapper implements vscode.Disposable {
private services: vscode.Disposable[] = [];
constructor(private context: vscode.ExtensionContext) {
}
begin() {
let configuration = this.createExtensionConfiguration();
const documentTracker = new DocumentTracker();
this.services.push(
documentTracker,
new CommandHandler(this.context, documentTracker),
new CodeLensProvider(this.context, documentTracker),
new ContentProvider(this.context),
new Decorator(this.context, documentTracker),
);
this.services.forEach((service: any) => {
if (service.begin && service.begin instanceof Function) {
service.begin(configuration);
}
});
vscode.workspace.onDidChangeConfiguration(() => {
this.services.forEach((service: any) => {
if (service.configurationUpdated && service.configurationUpdated instanceof Function) {
service.configurationUpdated(this.createExtensionConfiguration());
}
});
});
}
createExtensionConfiguration(): interfaces.IExtensionConfiguration {
const workspaceConfiguration = vscode.workspace.getConfiguration(ConfigurationSectionName);
const codeLensEnabled: boolean = workspaceConfiguration.get('codeLens.enabled', true);
const decoratorsEnabled: boolean = workspaceConfiguration.get('decorators.enabled', true);
return {
enableCodeLens: codeLensEnabled,
enableDecorations: decoratorsEnabled,
enableEditorOverview: decoratorsEnabled
};
}
dispose() {
this.services.forEach(disposable => disposable.dispose());
this.services = [];
}
}
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
/// <reference path='../../../../src/vs/vscode.d.ts'/>
/// <reference types='@types/node'/>
/// <reference types='@types/mocha'/>
{
"compilerOptions": {
"target": "es6",
"lib": [
"es2016"
],
"module": "commonjs",
"outDir": "./out",
"strictNullChecks": true,
"experimentalDecorators": true
},
"include": [
"src/**/*"
]
}
......@@ -910,6 +910,13 @@ export default class TypeScriptServiceClient implements ITypescriptServiceClient
return true;
} catch (e) {
// noop
} finally {
const p = this.callbacks[seq];
if (p) {
delete this.callbacks[seq];
this.pendingResponses--;
p.e(new Error(`Cancelled Request ${seq}`));
}
}
}
......
......@@ -8,17 +8,6 @@ import * as Platform from 'vs/base/common/platform';
import Event, { Emitter } from 'vs/base/common/event';
import { IDisposable } from 'vs/base/common/lifecycle';
export const enum AccessibilitySupport {
/**
* This should be the browser case where it is not known if a screen reader is attached or no.
*/
Unknown = 0,
Disabled = 1,
Enabled = 2
}
class WindowManager {
public static INSTANCE = new WindowManager();
......@@ -88,11 +77,11 @@ class WindowManager {
}
// --- Accessibility
private _accessibilitySupport = AccessibilitySupport.Unknown;
private _accessibilitySupport = Platform.AccessibilitySupport.Unknown;
private _onDidChangeAccessibilitySupport: Emitter<void> = new Emitter<void>();
public onDidChangeAccessibilitySupport: Event<void> = this._onDidChangeAccessibilitySupport.event;
public setAccessibilitySupport(accessibilitySupport: AccessibilitySupport): void {
public setAccessibilitySupport(accessibilitySupport: Platform.AccessibilitySupport): void {
if (this._accessibilitySupport === accessibilitySupport) {
return;
}
......@@ -100,7 +89,7 @@ class WindowManager {
this._accessibilitySupport = accessibilitySupport;
this._onDidChangeAccessibilitySupport.fire();
}
public getAccessibilitySupport(): AccessibilitySupport {
public getAccessibilitySupport(): Platform.AccessibilitySupport {
return this._accessibilitySupport;
}
......@@ -144,10 +133,10 @@ export function onDidChangeFullscreen(callback: () => void): IDisposable {
return WindowManager.INSTANCE.onDidChangeFullscreen(callback);
}
export function setAccessibilitySupport(accessibilitySupport: AccessibilitySupport): void {
export function setAccessibilitySupport(accessibilitySupport: Platform.AccessibilitySupport): void {
WindowManager.INSTANCE.setAccessibilitySupport(accessibilitySupport);
}
export function getAccessibilitySupport(): AccessibilitySupport {
export function getAccessibilitySupport(): Platform.AccessibilitySupport {
return WindowManager.INSTANCE.getAccessibilitySupport();
}
export function onDidChangeAccessibilitySupport(callback: () => void): IDisposable {
......
......@@ -107,6 +107,7 @@ export interface IHeaderViewOptions extends IHeaderViewStyles {
}
export interface IHeaderViewStyles {
headerForeground?: Color;
headerBackground?: Color;
headerHighContrastBorder?: Color;
}
......@@ -123,6 +124,7 @@ export abstract class HeaderView extends View {
protected header: HTMLElement;
protected body: HTMLElement;
private headerForeground: Color;
private headerBackground: Color;
private headerHighContrastBorder;
......@@ -132,11 +134,13 @@ export abstract class HeaderView extends View {
this._headerSize = types.isUndefined(opts.headerSize) ? 22 : opts.headerSize;
this._showHeader = this._headerSize > 0;
this.headerForeground = opts.headerForeground;
this.headerBackground = opts.headerBackground || headerDefaultOpts.headerBackground;
this.headerHighContrastBorder = opts.headerHighContrastBorder;
}
style(styles: IHeaderViewStyles): void {
this.headerForeground = styles.headerForeground;
this.headerBackground = styles.headerBackground;
this.headerHighContrastBorder = styles.headerHighContrastBorder;
......@@ -149,9 +153,11 @@ export abstract class HeaderView extends View {
protected applyStyles(): void {
if (this.header) {
const headerForegroundColor = this.headerForeground ? this.headerForeground.toString() : null;
const headerBackgroundColor = this.headerBackground ? this.headerBackground.toString() : null;
const headerHighContrastBorderColor = this.headerHighContrastBorder ? this.headerHighContrastBorder.toString() : null;
this.header.style.color = headerForegroundColor;
this.header.style.backgroundColor = headerBackgroundColor;
this.header.style.borderTop = headerHighContrastBorderColor ? `1px solid ${headerHighContrastBorderColor}` : null;
}
......
......@@ -142,3 +142,14 @@ export const enum OperatingSystem {
Linux = 3
}
export const OS = (_isMacintosh ? OperatingSystem.Macintosh : (_isWindows ? OperatingSystem.Windows : OperatingSystem.Linux));
export const enum AccessibilitySupport {
/**
* This should be the browser case where it is not known if a screen reader is attached or no.
*/
Unknown = 0,
Disabled = 1,
Enabled = 2
}
......@@ -310,6 +310,7 @@ export class Configuration extends CommonEditorConfiguration {
}
this._register(browser.onDidChangeZoomLevel(_ => this._recomputeOptions()));
this._register(browser.onDidChangeAccessibilitySupport(() => this._recomputeOptions()));
this._recomputeOptions();
}
......@@ -353,7 +354,8 @@ export class Configuration extends CommonEditorConfiguration {
canUseTranslate3d: browser.canUseTranslate3d(),
emptySelectionClipboard: browser.isWebKit,
pixelRatio: browser.getPixelRatio(),
zoomLevel: browser.getZoomLevel()
zoomLevel: browser.getZoomLevel(),
accessibilitySupport: browser.getAccessibilitySupport()
};
}
......
......@@ -5,6 +5,7 @@
'use strict';
import 'vs/css!./textAreaHandler';
import * as platform from 'vs/base/common/platform';
import * as browser from 'vs/base/browser/browser';
import { TextAreaInput, ITextAreaInputHost, IPasteData, ICompositionData } from 'vs/editor/browser/controller/textAreaInput';
import { ISimpleModel, ITypeData, TextAreaState, IENarratorStrategy, NVDAPagedStrategy } from 'vs/editor/browser/controller/textAreaState';
......@@ -54,6 +55,7 @@ export class TextAreaHandler extends ViewPart {
private readonly _viewHelper: ITextAreaHandlerHelper;
private _pixelRatio: number;
private _accessibilitySupport: platform.AccessibilitySupport;
private _contentLeft: number;
private _contentWidth: number;
private _contentHeight: number;
......@@ -85,6 +87,7 @@ export class TextAreaHandler extends ViewPart {
const conf = this._context.configuration.editor;
this._pixelRatio = conf.pixelRatio;
this._accessibilitySupport = conf.accessibilitySupport;
this._contentLeft = conf.layoutInfo.contentLeft;
this._contentWidth = conf.layoutInfo.contentWidth;
this._contentHeight = conf.layoutInfo.contentHeight;
......@@ -160,6 +163,11 @@ export class TextAreaHandler extends ViewPart {
return TextAreaState.EMPTY;
}
if (this._accessibilitySupport === platform.AccessibilitySupport.Disabled) {
// We know for a fact that a screen reader is not attached
return TextAreaState.EMPTY;
}
const selection = this._selections[0];
if (this._experimentalScreenReader) {
......@@ -286,6 +294,10 @@ export class TextAreaHandler extends ViewPart {
if (e.pixelRatio) {
this._pixelRatio = conf.pixelRatio;
}
if (e.accessibilitySupport) {
this._accessibilitySupport = conf.accessibilitySupport;
this._textAreaInput.writeScreenReaderContent('strategy changed');
}
if (e.emptySelectionClipboard) {
this._emptySelectionClipboard = conf.emptySelectionClipboard;
}
......@@ -294,6 +306,7 @@ export class TextAreaHandler extends ViewPart {
}
public onCursorStateChanged(e: viewEvents.ViewCursorStateChangedEvent): boolean {
this._selections = e.selections.slice(0);
this._textAreaInput.writeScreenReaderContent('selection changed');
return true;
}
public onDecorationsChanged(e: viewEvents.ViewDecorationsChangedEvent): boolean {
......@@ -333,10 +346,6 @@ export class TextAreaHandler extends ViewPart {
this._textAreaInput.focusTextArea();
}
public writeToTextArea(): void {
this._textAreaInput.writeScreenReaderContent('selection changed');
}
public setAriaActiveDescendant(id: string): void {
if (id) {
this.textArea.setAttribute('role', 'combobox');
......@@ -356,11 +365,18 @@ export class TextAreaHandler extends ViewPart {
private _primaryCursorVisibleRange: HorizontalRange = null;
public prepareRender(ctx: RenderingContext): void {
const primaryCursorPosition = new Position(this._selections[0].positionLineNumber, this._selections[0].positionColumn);
this._primaryCursorVisibleRange = ctx.visibleRangeForPosition(primaryCursorPosition);
if (this._accessibilitySupport === platform.AccessibilitySupport.Enabled) {
// Do not move the textarea with the cursor, as this generates accessibility events that might confuse screen readers
// See https://github.com/Microsoft/vscode/issues/26730
this._primaryCursorVisibleRange = null;
} else {
const primaryCursorPosition = new Position(this._selections[0].positionLineNumber, this._selections[0].positionColumn);
this._primaryCursorVisibleRange = ctx.visibleRangeForPosition(primaryCursorPosition);
}
}
public render(ctx: RestrictedRenderingContext): void {
this._textAreaInput.writeScreenReaderContent('render');
this._render();
}
......
......@@ -403,7 +403,6 @@ export class View extends ViewEventHandler {
if (!this.viewLines.shouldRender() && viewPartsToRender.length === 0) {
// Nothing to render
this._textAreaHandler.writeToTextArea();
return;
}
......@@ -413,15 +412,11 @@ export class View extends ViewEventHandler {
let viewportData = new ViewportData(partialViewportData, this._context.model);
if (this.viewLines.shouldRender()) {
this.viewLines.renderText(viewportData, () => {
this._textAreaHandler.writeToTextArea();
});
this.viewLines.renderText(viewportData);
this.viewLines.onDidRender();
// Rendering of viewLines might cause scroll events to occur, so collect view parts to render again
viewPartsToRender = this._getViewPartsToRender();
} else {
this._textAreaHandler.writeToTextArea();
}
let renderingContext = new RenderingContext(this._context.viewLayout, viewportData, this.viewLines);
......
......@@ -405,17 +405,14 @@ export class ViewLines extends ViewPart implements IVisibleLinesHost<ViewLine>,
throw new Error('Not supported');
}
public renderText(viewportData: ViewportData, onAfterLinesRendered: () => void): void {
public renderText(viewportData: ViewportData): void {
// (1) render lines - ensures lines are in the DOM
this._visibleLines.renderLines(viewportData);
this._lastRenderedData.setCurrentVisibleRange(viewportData.visibleRange);
this.domNode.setWidth(this._context.viewLayout.getScrollWidth());
this.domNode.setHeight(Math.min(this._context.viewLayout.getScrollHeight(), 1000000));
// (2) execute DOM writing that forces sync layout (e.g. textArea manipulation)
onAfterLinesRendered();
// (3) compute horizontal scroll position:
// (2) compute horizontal scroll position:
// - this must happen after the lines are in the DOM since it might need a line that rendered just now
// - it might change `scrollWidth` and `scrollLeft`
if (this._lastCursorRevealRangeHorizontallyEvent) {
......@@ -440,7 +437,7 @@ export class ViewLines extends ViewPart implements IVisibleLinesHost<ViewLine>,
});
}
// (4) handle scrolling
// (3) handle scrolling
const adjustedScrollTop = this._context.viewLayout.getScrollTop() - viewportData.bigNumbersDelta;
if (this._canUseTranslate3d) {
let transform = 'translate3d(' + -this._context.viewLayout.getScrollLeft() + 'px, ' + -adjustedScrollTop + 'px, 0px)';
......
......@@ -59,6 +59,7 @@ export interface IEnvConfiguration {
emptySelectionClipboard: boolean;
pixelRatio: number;
zoomLevel: number;
accessibilitySupport: platform.AccessibilitySupport;
}
export abstract class CommonEditorConfiguration extends Disposable implements editorCommon.IConfiguration {
......@@ -122,7 +123,8 @@ export abstract class CommonEditorConfiguration extends Disposable implements ed
canUseTranslate3d: partialEnv.canUseTranslate3d,
emptySelectionClipboard: partialEnv.emptySelectionClipboard,
pixelRatio: partialEnv.pixelRatio,
tabFocusMode: TabFocus.getTabFocusMode()
tabFocusMode: TabFocus.getTabFocusMode(),
accessibilitySupport: partialEnv.accessibilitySupport
};
return editorOptions.InternalEditorOptionsFactory.createInternalEditorOptions(env, opts);
}
......@@ -257,6 +259,16 @@ const editorConfiguration: IConfigurationNode = {
'default': EDITOR_DEFAULTS.viewInfo.minimap.maxColumn,
'description': nls.localize('minimap.maxColumn', "Limit the width of the minimap to render at most a certain number of columns")
},
'editor.find.seedSearchStringFromSelection': {
'type': 'boolean',
'default': EDITOR_DEFAULTS.contribInfo.find.seedSearchStringFromSelection,
'description': nls.localize('find.seedSearchStringFromSelection', "Controls if we seed the search string in Find Widget from editor selection")
},
'editor.find.autoFindInSelection': {
'type': 'boolean',
'default': EDITOR_DEFAULTS.contribInfo.find.autoFindInSelection,
'description': nls.localize('find.autoFindInSelection', "Controls if Find in Selection flag is turned on when multiple characters or lines of text are selected in the editor")
},
'editor.wordWrap': {
'type': 'string',
'enum': ['off', 'on', 'wordWrapColumn', 'bounded'],
......@@ -373,7 +385,7 @@ const editorConfiguration: IConfigurationNode = {
'type': 'string',
'enum': ['on', 'smart', 'off'],
'default': EDITOR_DEFAULTS.contribInfo.acceptSuggestionOnEnter,
'description': nls.localize('acceptSuggestionOnEnter', "Controls if suggestions should be accepted on 'Enter' - in addition to 'Tab'. Helps to avoid ambiguity between inserting new lines or accepting suggestions.")
'description': nls.localize('acceptSuggestionOnEnter', "Controls if suggestions should be accepted on 'Enter' - in addition to 'Tab'. Helps to avoid ambiguity between inserting new lines or accepting suggestions. The value 'smart' means only accept a suggestion with Enter when it makes a textual change")
},
'editor.acceptSuggestionOnCommitCharacter': {
'type': 'boolean',
......
......@@ -74,6 +74,20 @@ export interface IEditorScrollbarOptions {
horizontalSliderSize?: number;
}
/**
* Configuration options for editor find widget
*/
export interface IEditorFindOptions {
/**
* Controls if we seed search string in the Find Widget with editor selection.
*/
seedSearchStringFromSelection?: boolean;
/**
* Controls if Find in Selection flag is turned on when multiple lines of text are selected in the editor.
*/
autoFindInSelection: boolean;
}
/**
* Configuration options for editor minimap
*/
......@@ -186,6 +200,10 @@ export interface IEditorOptions {
* Control the behavior and rendering of the minimap.
*/
minimap?: IEditorMinimapOptions;
/**
* Control the behavior of the find widget.
*/
find?: IEditorFindOptions;
/**
* Display overflow widgets as `fixed`.
* Defaults to `false`.
......@@ -672,6 +690,11 @@ export interface InternalEditorMinimapOptions {
readonly maxColumn: number;
}
export interface InternalEditorFindOptions {
readonly seedSearchStringFromSelection: boolean;
readonly autoFindInSelection: boolean;
}
export interface EditorWrappingInfo {
readonly inDiffEditor: boolean;
readonly isDominatedByLongLines: boolean;
......@@ -738,6 +761,7 @@ export interface EditorContribOptions {
readonly folding: boolean;
readonly showFoldingControls: 'always' | 'mouseover';
readonly matchBrackets: boolean;
readonly find: InternalEditorFindOptions;
}
/**
......@@ -765,7 +789,6 @@ export interface IValidatedEditorOptions {
readonly dragAndDrop: boolean;
readonly emptySelectionClipboard: boolean;
readonly useTabStops: boolean;
readonly performanceCritical: boolean;
readonly viewInfo: InternalEditorViewOptions;
readonly contribInfo: EditorContribOptions;
......@@ -782,6 +805,10 @@ export class InternalEditorOptions {
readonly editorClassName: string;
readonly lineHeight: number;
readonly readOnly: boolean;
/**
* @internal
*/
readonly accessibilitySupport: platform.AccessibilitySupport;
// ---- cursor options
readonly wordSeparators: string;
......@@ -807,6 +834,7 @@ export class InternalEditorOptions {
editorClassName: string;
lineHeight: number;
readOnly: boolean;
accessibilitySupport: platform.AccessibilitySupport;
wordSeparators: string;
autoClosingBrackets: boolean;
useTabStops: boolean;
......@@ -824,6 +852,7 @@ export class InternalEditorOptions {
this.editorClassName = source.editorClassName;
this.lineHeight = source.lineHeight | 0;
this.readOnly = source.readOnly;
this.accessibilitySupport = source.accessibilitySupport;
this.wordSeparators = source.wordSeparators;
this.autoClosingBrackets = source.autoClosingBrackets;
this.useTabStops = source.useTabStops;
......@@ -847,6 +876,7 @@ export class InternalEditorOptions {
&& this.editorClassName === other.editorClassName
&& this.lineHeight === other.lineHeight
&& this.readOnly === other.readOnly
&& this.accessibilitySupport === other.accessibilitySupport
&& this.wordSeparators === other.wordSeparators
&& this.autoClosingBrackets === other.autoClosingBrackets
&& this.useTabStops === other.useTabStops
......@@ -871,6 +901,7 @@ export class InternalEditorOptions {
editorClassName: (this.editorClassName !== newOpts.editorClassName),
lineHeight: (this.lineHeight !== newOpts.lineHeight),
readOnly: (this.readOnly !== newOpts.readOnly),
accessibilitySupport: (this.accessibilitySupport !== newOpts.accessibilitySupport),
wordSeparators: (this.wordSeparators !== newOpts.wordSeparators),
autoClosingBrackets: (this.autoClosingBrackets !== newOpts.autoClosingBrackets),
useTabStops: (this.useTabStops !== newOpts.useTabStops),
......@@ -1004,6 +1035,17 @@ export class InternalEditorOptions {
return true;
}
/**
* @internal
*/
private static _equalFindOptions(a: InternalEditorFindOptions, b: InternalEditorFindOptions): boolean {
return (
a.seedSearchStringFromSelection === b.seedSearchStringFromSelection
&& a.autoFindInSelection === b.autoFindInSelection
);
}
/**
* @internal
*/
......@@ -1048,6 +1090,7 @@ export class InternalEditorOptions {
&& a.folding === b.folding
&& a.showFoldingControls === b.showFoldingControls
&& a.matchBrackets === b.matchBrackets
&& this._equalFindOptions(a.find, b.find)
);
}
......@@ -1196,6 +1239,7 @@ export interface IConfigurationChangedEvent {
readonly editorClassName: boolean;
readonly lineHeight: boolean;
readonly readOnly: boolean;
readonly accessibilitySupport: boolean;
readonly wordSeparators: boolean;
readonly autoClosingBrackets: boolean;
readonly useTabStops: boolean;
......@@ -1223,6 +1267,7 @@ export interface IEnvironmentalOptions {
readonly emptySelectionClipboard: boolean;
readonly pixelRatio: number;
readonly tabFocusMode: boolean;
readonly accessibilitySupport: platform.AccessibilitySupport;
}
function _boolean<T>(value: any, defaultValue: T): boolean | T {
......@@ -1345,9 +1390,8 @@ export class EditorOptionsValidator {
wordWrap = _stringSet<'off' | 'on' | 'wordWrapColumn' | 'bounded'>(wordWrap, defaults.wordWrap, ['off', 'on', 'wordWrapColumn', 'bounded']);
}
const performanceCritical = false;
const viewInfo = this._sanitizeViewInfo(opts, defaults.viewInfo);
const contribInfo = this._sanitizeContribInfo(opts, defaults.contribInfo, performanceCritical);
const contribInfo = this._sanitizeContribInfo(opts, defaults.contribInfo);
return {
inDiffEditor: _boolean(opts.inDiffEditor, defaults.inDiffEditor),
......@@ -1369,7 +1413,6 @@ export class EditorOptionsValidator {
dragAndDrop: _boolean(opts.dragAndDrop, defaults.dragAndDrop),
emptySelectionClipboard: _boolean(opts.emptySelectionClipboard, defaults.emptySelectionClipboard),
useTabStops: _boolean(opts.useTabStops, defaults.useTabStops),
performanceCritical: performanceCritical,
viewInfo: viewInfo,
contribInfo: contribInfo,
};
......@@ -1413,6 +1456,17 @@ export class EditorOptionsValidator {
};
}
private static _santizeFindOpts(opts: IEditorFindOptions, defaults: InternalEditorFindOptions): InternalEditorFindOptions {
if (typeof opts !== 'object') {
return defaults;
}
return {
seedSearchStringFromSelection: _boolean(opts.seedSearchStringFromSelection, defaults.seedSearchStringFromSelection),
autoFindInSelection: _boolean(opts.autoFindInSelection, defaults.autoFindInSelection)
};
}
private static _sanitizeViewInfo(opts: IEditorOptions, defaults: InternalEditorViewOptions): InternalEditorViewOptions {
let rulers: number[] = [];
......@@ -1517,13 +1571,14 @@ export class EditorOptionsValidator {
};
}
private static _sanitizeContribInfo(opts: IEditorOptions, defaults: EditorContribOptions, performanceCritical: boolean): EditorContribOptions {
private static _sanitizeContribInfo(opts: IEditorOptions, defaults: EditorContribOptions): EditorContribOptions {
let quickSuggestions: boolean | { other: boolean, comments: boolean, strings: boolean };
if (typeof opts.quickSuggestions === 'object') {
quickSuggestions = { other: true, ...opts.quickSuggestions };
} else {
quickSuggestions = _boolean(opts.quickSuggestions, defaults.quickSuggestions);
}
const find = this._santizeFindOpts(opts.find, defaults.find);
return {
selectionClipboard: _boolean(opts.selectionClipboard, defaults.selectionClipboard),
hover: _boolean(opts.hover, defaults.hover),
......@@ -1541,12 +1596,13 @@ export class EditorOptionsValidator {
wordBasedSuggestions: _boolean(opts.wordBasedSuggestions, defaults.wordBasedSuggestions),
suggestFontSize: _clampedInt(opts.suggestFontSize, defaults.suggestFontSize, 0, 1000),
suggestLineHeight: _clampedInt(opts.suggestLineHeight, defaults.suggestLineHeight, 0, 1000),
selectionHighlight: !performanceCritical && _boolean(opts.selectionHighlight, defaults.selectionHighlight),
occurrencesHighlight: !performanceCritical && _boolean(opts.occurrencesHighlight, defaults.occurrencesHighlight),
codeLens: !performanceCritical && _boolean(opts.codeLens, defaults.codeLens) && _boolean(opts.referenceInfos, true),
folding: !performanceCritical && _boolean(opts.folding, defaults.folding),
selectionHighlight: _boolean(opts.selectionHighlight, defaults.selectionHighlight),
occurrencesHighlight: _boolean(opts.occurrencesHighlight, defaults.occurrencesHighlight),
codeLens: _boolean(opts.codeLens, defaults.codeLens) && _boolean(opts.referenceInfos, true),
folding: _boolean(opts.folding, defaults.folding),
showFoldingControls: _stringSet<'always' | 'mouseover'>(opts.showFoldingControls, defaults.showFoldingControls, ['always', 'mouseover']),
matchBrackets: !performanceCritical && _boolean(opts.matchBrackets, defaults.matchBrackets),
matchBrackets: _boolean(opts.matchBrackets, defaults.matchBrackets),
find: find
};
}
}
......@@ -1556,7 +1612,100 @@ export class EditorOptionsValidator {
*/
export class InternalEditorOptionsFactory {
public static createInternalEditorOptions(env: IEnvironmentalOptions, opts: IValidatedEditorOptions) {
private static _handlePerformanceCritical(opts: IValidatedEditorOptions, performanceCritical: boolean): IValidatedEditorOptions {
if (!performanceCritical) {
return opts;
}
return {
inDiffEditor: opts.inDiffEditor,
wordSeparators: opts.wordSeparators,
lineNumbersMinChars: opts.lineNumbersMinChars,
lineDecorationsWidth: opts.lineDecorationsWidth,
readOnly: opts.readOnly,
mouseStyle: opts.mouseStyle,
disableTranslate3d: opts.disableTranslate3d,
automaticLayout: opts.automaticLayout,
wordWrap: opts.wordWrap,
wordWrapColumn: opts.wordWrapColumn,
wordWrapMinified: opts.wordWrapMinified,
wrappingIndent: opts.wrappingIndent,
wordWrapBreakBeforeCharacters: opts.wordWrapBreakBeforeCharacters,
wordWrapBreakAfterCharacters: opts.wordWrapBreakAfterCharacters,
wordWrapBreakObtrusiveCharacters: opts.wordWrapBreakObtrusiveCharacters,
autoClosingBrackets: opts.autoClosingBrackets,
dragAndDrop: opts.dragAndDrop,
emptySelectionClipboard: opts.emptySelectionClipboard,
useTabStops: opts.useTabStops,
viewInfo: {
extraEditorClassName: opts.viewInfo.extraEditorClassName,
disableMonospaceOptimizations: opts.viewInfo.disableMonospaceOptimizations,
experimentalScreenReader: opts.viewInfo.experimentalScreenReader,
rulers: opts.viewInfo.rulers,
ariaLabel: opts.viewInfo.ariaLabel,
renderLineNumbers: opts.viewInfo.renderLineNumbers,
renderCustomLineNumbers: opts.viewInfo.renderCustomLineNumbers,
renderRelativeLineNumbers: opts.viewInfo.renderRelativeLineNumbers,
selectOnLineNumbers: opts.viewInfo.selectOnLineNumbers,
glyphMargin: opts.viewInfo.glyphMargin,
revealHorizontalRightPadding: opts.viewInfo.revealHorizontalRightPadding,
roundedSelection: false, // DISABLED
overviewRulerLanes: opts.viewInfo.overviewRulerLanes,
overviewRulerBorder: opts.viewInfo.overviewRulerBorder,
cursorBlinking: opts.viewInfo.cursorBlinking,
mouseWheelZoom: opts.viewInfo.mouseWheelZoom,
cursorStyle: opts.viewInfo.cursorStyle,
hideCursorInOverviewRuler: opts.viewInfo.hideCursorInOverviewRuler,
scrollBeyondLastLine: opts.viewInfo.scrollBeyondLastLine,
stopRenderingLineAfter: opts.viewInfo.stopRenderingLineAfter,
renderWhitespace: 'none', // DISABLED
renderControlCharacters: false, // DISABLED
fontLigatures: false, // DISABLED
renderIndentGuides: false, // DISABLED
renderLineHighlight: 'none', // DISABLED
scrollbar: opts.viewInfo.scrollbar,
minimap: {
enabled: false, // DISABLED
renderCharacters: opts.viewInfo.minimap.renderCharacters,
maxColumn: opts.viewInfo.minimap.maxColumn
},
fixedOverflowWidgets: opts.viewInfo.fixedOverflowWidgets
},
contribInfo: {
selectionClipboard: opts.contribInfo.selectionClipboard,
hover: opts.contribInfo.hover,
contextmenu: opts.contribInfo.contextmenu,
quickSuggestions: opts.contribInfo.quickSuggestions,
quickSuggestionsDelay: opts.contribInfo.quickSuggestionsDelay,
parameterHints: opts.contribInfo.parameterHints,
iconsInSuggestions: opts.contribInfo.iconsInSuggestions,
formatOnType: opts.contribInfo.formatOnType,
formatOnPaste: opts.contribInfo.formatOnPaste,
suggestOnTriggerCharacters: opts.contribInfo.suggestOnTriggerCharacters,
acceptSuggestionOnEnter: opts.contribInfo.acceptSuggestionOnEnter,
acceptSuggestionOnCommitCharacter: opts.contribInfo.acceptSuggestionOnCommitCharacter,
snippetSuggestions: opts.contribInfo.snippetSuggestions,
wordBasedSuggestions: opts.contribInfo.wordBasedSuggestions,
suggestFontSize: opts.contribInfo.suggestFontSize,
suggestLineHeight: opts.contribInfo.suggestLineHeight,
selectionHighlight: false, // DISABLED
occurrencesHighlight: false, // DISABLED
codeLens: false, // DISABLED
folding: false, // DISABLED
showFoldingControls: opts.contribInfo.showFoldingControls,
matchBrackets: false, // DISABLED
find: opts.contribInfo.find
}
};
}
public static createInternalEditorOptions(env: IEnvironmentalOptions, _opts: IValidatedEditorOptions) {
// Disable some non critical features to get as best performance as possible
// See https://github.com/Microsoft/vscode/issues/26730
const opts = this._handlePerformanceCritical(_opts, (env.accessibilitySupport === platform.AccessibilitySupport.Enabled));
let lineDecorationsWidth: number;
if (typeof opts.lineDecorationsWidth === 'string' && /^\d+(\.\d+)?ch$/.test(opts.lineDecorationsWidth)) {
......@@ -1664,6 +1813,7 @@ export class InternalEditorOptionsFactory {
editorClassName: className,
lineHeight: env.fontInfo.lineHeight,
readOnly: opts.readOnly,
accessibilitySupport: env.accessibilitySupport,
wordSeparators: opts.wordSeparators,
autoClosingBrackets: opts.autoClosingBrackets,
useTabStops: opts.useTabStops,
......@@ -1674,7 +1824,7 @@ export class InternalEditorOptionsFactory {
fontInfo: env.fontInfo,
viewInfo: opts.viewInfo,
wrappingInfo: wrappingInfo,
contribInfo: opts.contribInfo,
contribInfo: opts.contribInfo
});
}
}
......@@ -1882,7 +2032,6 @@ export const EDITOR_DEFAULTS: IValidatedEditorOptions = {
dragAndDrop: false,
emptySelectionClipboard: true,
useTabStops: true,
performanceCritical: false,
viewInfo: {
extraEditorClassName: '',
......@@ -1943,7 +2092,7 @@ export const EDITOR_DEFAULTS: IValidatedEditorOptions = {
formatOnType: false,
formatOnPaste: false,
suggestOnTriggerCharacters: true,
acceptSuggestionOnEnter: 'smart',
acceptSuggestionOnEnter: 'on',
acceptSuggestionOnCommitCharacter: true,
snippetSuggestions: 'inline',
wordBasedSuggestions: true,
......@@ -1955,5 +2104,9 @@ export const EDITOR_DEFAULTS: IValidatedEditorOptions = {
folding: true,
showFoldingControls: 'mouseover',
matchBrackets: true,
find: {
seedSearchStringFromSelection: true,
autoFindInSelection: false
}
},
};
......@@ -38,6 +38,7 @@ export class ViewConfigurationChangedEvent {
public readonly editorClassName: boolean;
public readonly lineHeight: boolean;
public readonly readOnly: boolean;
public readonly accessibilitySupport: boolean;
public readonly emptySelectionClipboard: boolean;
public readonly layoutInfo: boolean;
public readonly fontInfo: boolean;
......@@ -50,6 +51,7 @@ export class ViewConfigurationChangedEvent {
this.editorClassName = source.editorClassName;
this.lineHeight = source.lineHeight;
this.readOnly = source.readOnly;
this.accessibilitySupport = source.accessibilitySupport;
this.emptySelectionClipboard = source.emptySelectionClipboard;
this.layoutInfo = source.layoutInfo;
this.fontInfo = source.fontInfo;
......
......@@ -363,6 +363,13 @@ export class FindWidget extends Widget implements IOverlayWidget {
if (!this._isVisible) {
this._isVisible = true;
let selection = this._codeEditor.getSelection();
let isSelection = selection ? (selection.startLineNumber !== selection.endLineNumber || selection.startColumn !== selection.endColumn) : false;
if (isSelection && this._codeEditor.getConfiguration().contribInfo.find.autoFindInSelection) {
this._toggleSelectionFind.checked = true;
} else {
this._toggleSelectionFind.checked = false;
}
this._updateButtons();
setTimeout(() => {
......
......@@ -235,7 +235,7 @@ export class CommonFindController extends Disposable implements editorCommon.IEd
};
// Consider editor selection and overwrite the state with it
if (opts.seedSearchStringFromSelection) {
if (opts.seedSearchStringFromSelection && this._editor.getConfiguration().contribInfo.find.seedSearchStringFromSelection) {
let selectionSearchString = this.getSelectionSearchString();
if (selectionSearchString) {
if (this._state.isRegex) {
......
......@@ -8,6 +8,7 @@ import * as assert from 'assert';
import { EditorZoom } from 'vs/editor/common/config/editorZoom';
import { TestConfiguration } from 'vs/editor/test/common/mocks/testConfiguration';
import { IEnvConfiguration } from "vs/editor/common/config/commonEditorConfig";
import { AccessibilitySupport } from "vs/base/common/platform";
suite('Common Editor Config', () => {
test('Zoom Level', () => {
......@@ -61,7 +62,8 @@ suite('Common Editor Config', () => {
canUseTranslate3d: true,
emptySelectionClipboard: true,
pixelRatio: 1,
zoomLevel: 0
zoomLevel: 0,
accessibilitySupport: AccessibilitySupport.Unknown
};
}
}
......
......@@ -7,6 +7,7 @@
import { CommonEditorConfiguration, IEnvConfiguration } from 'vs/editor/common/config/commonEditorConfig';
import { IEditorOptions } from 'vs/editor/common/config/editorOptions';
import { FontInfo, BareFontInfo } from 'vs/editor/common/config/fontInfo';
import { AccessibilitySupport } from "vs/base/common/platform";
export class TestConfiguration extends CommonEditorConfiguration {
......@@ -23,7 +24,8 @@ export class TestConfiguration extends CommonEditorConfiguration {
canUseTranslate3d: true,
emptySelectionClipboard: true,
pixelRatio: 1,
zoomLevel: 0
zoomLevel: 0,
accessibilitySupport: AccessibilitySupport.Unknown
};
}
......
......@@ -2627,6 +2627,20 @@ declare module monaco.editor {
horizontalSliderSize?: number;
}
/**
* Configuration options for editor find widget
*/
export interface IEditorFindOptions {
/**
* Controls if we seed search string in the Find Widget with editor selection.
*/
seedSearchStringFromSelection?: boolean;
/**
* Controls if Find in Selection flag is turned on when multiple lines of text are selected in the editor.
*/
autoFindInSelection: boolean;
}
/**
* Configuration options for editor minimap
*/
......@@ -2734,6 +2748,10 @@ declare module monaco.editor {
* Control the behavior and rendering of the minimap.
*/
minimap?: IEditorMinimapOptions;
/**
* Control the behavior of the find widget.
*/
find?: IEditorFindOptions;
/**
* Display overflow widgets as `fixed`.
* Defaults to `false`.
......@@ -3153,6 +3171,11 @@ declare module monaco.editor {
readonly maxColumn: number;
}
export interface InternalEditorFindOptions {
readonly seedSearchStringFromSelection: boolean;
readonly autoFindInSelection: boolean;
}
export interface EditorWrappingInfo {
readonly inDiffEditor: boolean;
readonly isDominatedByLongLines: boolean;
......@@ -3223,6 +3246,7 @@ declare module monaco.editor {
readonly folding: boolean;
readonly showFoldingControls: 'always' | 'mouseover';
readonly matchBrackets: boolean;
readonly find: InternalEditorFindOptions;
}
/**
......@@ -3365,6 +3389,7 @@ declare module monaco.editor {
readonly editorClassName: boolean;
readonly lineHeight: boolean;
readonly readOnly: boolean;
readonly accessibilitySupport: boolean;
readonly wordSeparators: boolean;
readonly autoClosingBrackets: boolean;
readonly useTabStops: boolean;
......
......@@ -82,10 +82,9 @@ export const BetterMergeId = 'pprice.better-merge';
/**
* Globally disabled extensions, taking care of disabling obsolete extensions.
*/
const CHECK = false;
export function getGloballyDisabledExtensions(extensionEnablementService: IExtensionEnablementService, storageService: IStorageService, installedExtensions: { id: string; }[]) {
const globallyDisabled = extensionEnablementService.getGloballyDisabledExtensions();
if (CHECK && !storageService.getBoolean(BetterMergeCheckKey, StorageScope.GLOBAL, false)) {
if (!storageService.getBoolean(BetterMergeCheckKey, StorageScope.GLOBAL, false)) {
storageService.store(BetterMergeCheckKey, true);
if (globallyDisabled.indexOf(BetterMergeId) === -1 && installedExtensions.some(d => d.id === BetterMergeId)) {
globallyDisabled.push(BetterMergeId);
......
......@@ -8,7 +8,7 @@
import { ITheme, IThemeService } from 'vs/platform/theme/common/themeService';
import { inputBackground, inputForeground, ColorIdentifier, selectForeground, selectBackground, selectBorder, inputBorder, foreground, editorBackground, contrastBorder, inputActiveOptionBorder, listFocusBackground, listFocusForeground, listActiveSelectionBackground, listActiveSelectionForeground, listInactiveSelectionForeground, listInactiveSelectionBackground, listHoverBackground, listHoverForeground, listDropBackground, pickerGroupBorder, pickerGroupForeground, widgetShadow, inputValidationInfoBorder, inputValidationInfoBackground, inputValidationWarningBorder, inputValidationWarningBackground, inputValidationErrorBorder, inputValidationErrorBackground, activeContrastBorder, buttonForeground, buttonBackground, buttonHoverBackground, ColorFunction, lighten, badgeBackground, badgeForeground, progressBarBackground } from 'vs/platform/theme/common/colorRegistry';
import { IDisposable } from 'vs/base/common/lifecycle';
import { SIDE_BAR_SECTION_HEADER_BACKGROUND } from 'vs/workbench/common/theme';
import { SIDE_BAR_SECTION_HEADER_BACKGROUND, SIDE_BAR_SECTION_HEADER_FOREGROUND } from 'vs/workbench/common/theme';
export type styleFn = (colors: { [name: string]: ColorIdentifier }) => void;
......@@ -224,6 +224,7 @@ export function attachListStyler(widget: IThemable, themeService: IThemeService,
export function attachHeaderViewStyler(widget: IThemable, themeService: IThemeService, options?: { noContrastBorder?: boolean }): IDisposable {
return doAttachStyler(themeService, {
headerForeground: SIDE_BAR_SECTION_HEADER_FOREGROUND,
headerBackground: SIDE_BAR_SECTION_HEADER_BACKGROUND,
headerHighContrastBorder: (options && options.noContrastBorder) ? null : contrastBorder
}, widget);
......
......@@ -448,9 +448,9 @@ export function createApiFactory(
getConfiguration: (section?: string): vscode.WorkspaceConfiguration => {
return extHostConfiguration.getConfiguration(section);
},
registerTaskProvider: proposedApiFunction(extension, (provider: vscode.TaskProvider) => {
registerTaskProvider: (provider: vscode.TaskProvider) => {
return extHostTask.registerTaskProvider(extension, provider);
})
}
};
class SCM {
......
......@@ -17,7 +17,7 @@ import errors = require('vs/base/common/errors');
import * as browser from 'vs/base/browser/browser';
import { IStatusbarItem } from 'vs/workbench/browser/parts/statusbar/statusbar';
import { Action } from 'vs/base/common/actions';
import { language, LANGUAGE_DEFAULT } from 'vs/base/common/platform';
import { language, LANGUAGE_DEFAULT, AccessibilitySupport } from 'vs/base/common/platform';
import { IMode } from 'vs/editor/common/modes';
import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput';
import { IFileEditorInput, EncodingMode, IEncodingSupport, toResource, SideBySideEditorInput } from 'vs/workbench/common/editor';
......@@ -383,7 +383,7 @@ export class EditorStatus implements IStatusbarItem {
}
if (changed.selectionStatus) {
if (this.state.selectionStatus) {
if (this.state.selectionStatus && !this.state.screenReaderMode) {
this.selectionElement.textContent = this.state.selectionStatus;
show(this.selectionElement);
} else {
......@@ -686,7 +686,7 @@ export class EditorStatus implements IStatusbarItem {
}
private onScreenReaderModeChange(): void {
const info: StateDelta = { screenReaderMode: browser.getAccessibilitySupport() === browser.AccessibilitySupport.Enabled };
const info: StateDelta = { screenReaderMode: browser.getAccessibilitySupport() === AccessibilitySupport.Enabled };
this.updateState(info);
}
......
......@@ -27,7 +27,7 @@ import { getCodeEditor } from 'vs/editor/common/services/codeEditorService';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { Action } from 'vs/base/common/actions';
import { IThemeService, registerThemingParticipant, ITheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService';
import { STATUS_BAR_BACKGROUND, STATUS_BAR_FOREGROUND, STATUS_BAR_NO_FOLDER_BACKGROUND, STATUS_BAR_ITEM_HOVER_BACKGROUND, STATUS_BAR_ITEM_ACTIVE_BACKGROUND, STATUS_BAR_PROMINENT_ITEM_BACKGROUND, STATUS_BAR_PROMINENT_ITEM_HOVER_BACKGROUND, STATUS_BAR_BORDER } from 'vs/workbench/common/theme';
import { STATUS_BAR_BACKGROUND, STATUS_BAR_FOREGROUND, STATUS_BAR_NO_FOLDER_BACKGROUND, STATUS_BAR_ITEM_HOVER_BACKGROUND, STATUS_BAR_ITEM_ACTIVE_BACKGROUND, STATUS_BAR_PROMINENT_ITEM_BACKGROUND, STATUS_BAR_PROMINENT_ITEM_HOVER_BACKGROUND, STATUS_BAR_BORDER, STATUS_BAR_NO_FOLDER_FOREGROUND } from 'vs/workbench/common/theme';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { contrastBorder } from 'vs/platform/theme/common/colorRegistry';
......@@ -136,7 +136,7 @@ export class StatusbarPart extends Part implements IStatusbarService {
const container = this.getContainer();
container.style('color', this.getColor(STATUS_BAR_FOREGROUND));
container.style('color', this.getColor(this.contextService.hasWorkspace() ? STATUS_BAR_FOREGROUND : STATUS_BAR_NO_FOLDER_FOREGROUND));
container.style('background-color', this.getColor(this.contextService.hasWorkspace() ? STATUS_BAR_BACKGROUND : STATUS_BAR_NO_FOLDER_BACKGROUND));
const borderColor = this.getColor(STATUS_BAR_BORDER) || this.getColor(contrastBorder);
......
......@@ -142,6 +142,12 @@ export const STATUS_BAR_NO_FOLDER_BACKGROUND = registerColor('statusBar.noFolder
hc: null
}, nls.localize('statusBarNoFolderBackground', "Status bar background color when no folder is opened. The status bar is shown in the bottom of the window."));
export const STATUS_BAR_NO_FOLDER_FOREGROUND = registerColor('statusBar.noFolderForeground', {
dark: STATUS_BAR_FOREGROUND,
light: STATUS_BAR_FOREGROUND,
hc: STATUS_BAR_FOREGROUND
}, nls.localize('statusBarNoFolderForeground', "Status bar foreground color when no folder is opened. The status bar is shown in the bottom of the window."));
export const STATUS_BAR_ITEM_ACTIVE_BACKGROUND = registerColor('statusBarItem.activeBackground', {
dark: Color.white.transparent(0.18),
light: Color.white.transparent(0.18),
......@@ -240,6 +246,12 @@ export const SIDE_BAR_SECTION_HEADER_BACKGROUND = registerColor('sideBarSectionH
hc: null
}, nls.localize('sideBarSectionHeaderBackground', "Side bar section header background color. The side bar is the container for views like explorer and search."));
export const SIDE_BAR_SECTION_HEADER_FOREGROUND = registerColor('sideBarSectionHeader.foreground', {
dark: SIDE_BAR_FOREGROUND,
light: SIDE_BAR_FOREGROUND,
hc: SIDE_BAR_FOREGROUND
}, nls.localize('sideBarSectionHeaderForeground', "Side bar section header foreground color. The side bar is the container for views like explorer and search."));
// < --- Title Bar --- >
......
......@@ -70,7 +70,7 @@ export function startup(configuration: IWindowConfiguration): TPromise<void> {
KeyboardMapperFactory.INSTANCE._onKeyboardLayoutChanged(configuration.isISOKeyboard);
browser.setAccessibilitySupport(configuration.accessibilitySupportEnabled ? browser.AccessibilitySupport.Enabled : browser.AccessibilitySupport.Disabled);
browser.setAccessibilitySupport(configuration.accessibilitySupportEnabled ? platform.AccessibilitySupport.Enabled : platform.AccessibilitySupport.Disabled);
// Setup Intl
comparer.setFileNameComparer(new Intl.Collator(undefined, { numeric: true, sensitivity: 'base' }));
......
......@@ -298,7 +298,7 @@ export class ElectronWindow extends Themable {
// keyboard layout changed event
ipc.on('vscode:accessibilitySupportChanged', (event, accessibilitySupportEnabled: boolean) => {
browser.setAccessibilitySupport(accessibilitySupportEnabled ? browser.AccessibilitySupport.Enabled : browser.AccessibilitySupport.Disabled);
browser.setAccessibilitySupport(accessibilitySupportEnabled ? platform.AccessibilitySupport.Enabled : platform.AccessibilitySupport.Disabled);
});
// Configuration changes
......
......@@ -3,14 +3,15 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { IThemeService, registerThemingParticipant, ITheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService';
import { localize } from 'vs/nls';
import { registerColor } from 'vs/platform/theme/common/colorRegistry';
import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
import { IPartService, Parts } from 'vs/workbench/services/part/common/partService';
import { IDebugService, State } from 'vs/workbench/parts/debug/common/debug';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { STATUS_BAR_NO_FOLDER_BACKGROUND, STATUS_BAR_BACKGROUND, Themable } from 'vs/workbench/common/theme';
import { STATUS_BAR_NO_FOLDER_BACKGROUND, STATUS_BAR_NO_FOLDER_FOREGROUND, STATUS_BAR_BACKGROUND, Themable, STATUS_BAR_FOREGROUND } from 'vs/workbench/common/theme';
import { addClass, removeClass } from "vs/base/browser/dom";
// colors for theming
......@@ -20,6 +21,12 @@ export const STATUS_BAR_DEBUGGING_BACKGROUND = registerColor('statusBar.debuggin
hc: '#CC6633'
}, localize('statusBarDebuggingBackground', "Status bar background color when a program is being debugged. The status bar is shown in the bottom of the window"));
export const STATUS_BAR_DEBUGGING_FOREGROUND = registerColor('statusBar.debuggingForeground', {
dark: STATUS_BAR_FOREGROUND,
light: STATUS_BAR_FOREGROUND,
hc: STATUS_BAR_FOREGROUND
}, localize('statusBarDebuggingForeground', "Status bar foreground color when a program is being debugged. The status bar is shown in the bottom of the window"));
export class StatusBarColorProvider extends Themable implements IWorkbenchContribution {
private static ID = 'debug.statusbarColorProvider';
......@@ -45,25 +52,42 @@ export class StatusBarColorProvider extends Themable implements IWorkbenchContri
protected updateStyles(): void {
super.updateStyles();
if (this.partService.isVisible(Parts.STATUSBAR_PART)) {
const container = this.partService.getContainer(Parts.STATUSBAR_PART);
container.style.backgroundColor = this.getColor(this.getBackgroundColorKey());
const container = this.partService.getContainer(Parts.STATUSBAR_PART);
if (this.isDebugging()) {
addClass(container, 'debugging');
} else {
removeClass(container, 'debugging');
}
container.style.backgroundColor = this.getColor(this.getColorKey(STATUS_BAR_NO_FOLDER_BACKGROUND, STATUS_BAR_DEBUGGING_BACKGROUND, STATUS_BAR_BACKGROUND));
container.style.color = this.getColor(this.getColorKey(STATUS_BAR_NO_FOLDER_FOREGROUND, STATUS_BAR_DEBUGGING_FOREGROUND, STATUS_BAR_FOREGROUND));
}
private getBackgroundColorKey(): string {
private getColorKey(noFolderColor: string, debuggingColor: string, normalColor: string): string {
// no debugging
if (this.debugService.state === State.Inactive || this.isRunningWithoutDebug()) {
// Not debugging
if (!this.isDebugging()) {
if (this.contextService.hasWorkspace()) {
return STATUS_BAR_BACKGROUND;
return normalColor;
}
return STATUS_BAR_NO_FOLDER_BACKGROUND;
return noFolderColor;
}
// Debugging
return debuggingColor;
}
private isDebugging(): boolean {
if (this.debugService.state === State.Inactive) {
return false;
}
if (this.isRunningWithoutDebug()) {
return false;
}
// debugging
return STATUS_BAR_DEBUGGING_BACKGROUND;
return true;
}
private isRunningWithoutDebug(): boolean {
......@@ -75,4 +99,11 @@ export class StatusBarColorProvider extends Themable implements IWorkbenchContri
public getId(): string {
return StatusBarColorProvider.ID;
}
}
\ No newline at end of file
}
registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => {
const statusBarItemDebuggingForeground = theme.getColor(STATUS_BAR_DEBUGGING_FOREGROUND);
if (statusBarItemDebuggingForeground) {
collector.addRule(`.monaco-workbench > .part.statusbar.debugging > .statusbar-item .mask-icon { background-color: ${statusBarItemDebuggingForeground} !important; }`);
}
});
\ No newline at end of file
......@@ -59,6 +59,11 @@ configurationRegistry.registerConfiguration({
'type': 'string',
'default': null,
'description': nls.localize('emmetExtensionsPath', 'Path to a folder containing emmet profiles, snippets and preferences')
},
'emmet.useModules': {
'type': 'boolean',
'default': false,
'description': nls.localize('emmetUseModules', 'Use the new emmet modules for emmet features than the single emmet library.')
}
}
});
......@@ -22,7 +22,7 @@ import * as pfs from 'vs/base/node/pfs';
import Severity from 'vs/base/common/severity';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
import { ICommandService, CommandsRegistry } from 'vs/platform/commands/common/commands';
import { ICommandService } from 'vs/platform/commands/common/commands';
interface IEmmetConfiguration {
emmet: {
......@@ -30,7 +30,8 @@ interface IEmmetConfiguration {
syntaxProfiles: any;
triggerExpansionOnTab: boolean,
excludeLanguages: string[],
extensionsPath: string
extensionsPath: string,
useModules: boolean
};
}
......@@ -240,7 +241,16 @@ export abstract class EmmetEditorAction extends EditorAction {
'editor.emmet.action.updateTag': 'emmet.updateTag',
'editor.emmet.action.matchingPair': 'emmet.matchTag',
'editor.emmet.action.wrapWithAbbreviation': 'emmet.wrapWithAbbreviation',
'editor.emmet.action.expandAbbreviation': 'emmet.expandAbbreviation'
'editor.emmet.action.expandAbbreviation': 'emmet.expandAbbreviation',
'editor.emmet.action.balanceInward': 'emmet.balanceIn',
'editor.emmet.action.balanceOutward': 'emmet.balanceOut',
'editor.emmet.action.previousEditPoint': 'emmet.prevEditPoint',
'editor.emmet.action.nextEditPoint': 'emmet.nextEditPoint',
'editor.emmet.action.mergeLines': 'emmet.mergeLines',
'editor.emmet.action.selectPreviousItem': 'emmet.selectPrevItem',
'editor.emmet.action.selectNextItem': 'emmet.selectNextItem',
'editor.emmet.action.splitJoinTag': 'emmet.splitJoinTag',
'editor.emmet.action.toggleComment': 'emmet.toggleComment'
};
protected emmetActionName: string;
......@@ -280,7 +290,7 @@ export abstract class EmmetEditorAction extends EditorAction {
const commandService = accessor.get(ICommandService);
let mappedCommand = this.actionMap[this.id];
if (mappedCommand && CommandsRegistry.getCommand(mappedCommand)) {
if (mappedCommand && configurationService.getConfiguration<IEmmetConfiguration>().emmet.useModules) {
return commandService.executeCommand<void>(mappedCommand);
}
......
......@@ -76,7 +76,7 @@ export class FeedbackDropdown extends Dropdown {
super(container, {
contextViewProvider: options.contextViewProvider,
labelRenderer: (container: HTMLElement): IDisposable => {
$(container).addClass('send-feedback');
$(container).addClass('send-feedback', 'mask-icon');
return null;
}
......
......@@ -11,8 +11,9 @@ import { FeedbackDropdown, IFeedback, IFeedbackService } from './feedback';
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import product from 'vs/platform/node/product';
import { Themable, STATUS_BAR_FOREGROUND } from 'vs/workbench/common/theme';
import { Themable, STATUS_BAR_FOREGROUND, STATUS_BAR_NO_FOLDER_FOREGROUND } from 'vs/workbench/common/theme';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { IWorkspaceContextService } from "vs/platform/workspace/common/workspace";
class TwitterFeedbackService implements IFeedbackService {
......@@ -53,6 +54,7 @@ export class FeedbackStatusbarItem extends Themable implements IStatusbarItem {
constructor(
@IInstantiationService private instantiationService: IInstantiationService,
@IContextViewService private contextViewService: IContextViewService,
@IWorkspaceContextService private contextService: IWorkspaceContextService,
@IThemeService themeService: IThemeService
) {
super(themeService);
......@@ -62,7 +64,7 @@ export class FeedbackStatusbarItem extends Themable implements IStatusbarItem {
super.updateStyles();
if (this.dropdown) {
this.dropdown.label.style('background-color', this.getColor(STATUS_BAR_FOREGROUND));
this.dropdown.label.style('background-color', this.getColor(this.contextService.hasWorkspace() ? STATUS_BAR_FOREGROUND : STATUS_BAR_NO_FOLDER_FOREGROUND));
}
}
......
......@@ -207,11 +207,11 @@ export class ExplorerViewlet extends Viewlet {
const views = [];
const viewsState = JSON.parse(this.storageService.get(ExplorerViewlet.EXPLORER_VIEWS_STATE, this.contextService.hasWorkspace() ? StorageScope.WORKSPACE : StorageScope.GLOBAL, '{}'));
for (const viewDescrirptor of viewDescriptors) {
const view = this.instantiationService.createInstance(viewDescrirptor.ctor, viewDescrirptor.id, {
name: viewDescrirptor.name,
for (const viewDescriptor of viewDescriptors) {
const view = this.instantiationService.createInstance(viewDescriptor.ctor, viewDescriptor.id, {
name: viewDescriptor.name,
actionRunner: this.getActionRunner(),
collapsed: viewsState[viewDescrirptor.id] ? (<IViewState>viewsState[viewDescrirptor.id]).collapsed : true
collapsed: viewsState[viewDescriptor.id] ? (<IViewState>viewsState[viewDescriptor.id]).collapsed : true
});
views.push(view);
this.views.push(view);
......
......@@ -80,6 +80,9 @@ import { ProcessRunnerDetector } from 'vs/workbench/parts/tasks/node/processRunn
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { Themable, STATUS_BAR_FOREGROUND, STATUS_BAR_NO_FOLDER_FOREGROUND } from 'vs/workbench/common/theme';
import { IThemeService } from 'vs/platform/theme/common/themeService';
let $ = Builder.$;
let tasksCategory = nls.localize('tasksCategory', "Tasks");
......@@ -248,7 +251,8 @@ class StatusBarItem extends Themable implements IStatusbarItem {
@IOutputService private outputService: IOutputService,
@ITaskService private taskService: ITaskService,
@IPartService private partService: IPartService,
@IThemeService themeService: IThemeService
@IThemeService themeService: IThemeService,
@IWorkspaceContextService private contextService: IWorkspaceContextService
) {
super(themeService);
......@@ -260,7 +264,7 @@ class StatusBarItem extends Themable implements IStatusbarItem {
super.updateStyles();
this.icons.forEach(icon => {
icon.style.backgroundColor = this.getColor(STATUS_BAR_FOREGROUND);
icon.style.backgroundColor = this.getColor(this.contextService.hasWorkspace() ? STATUS_BAR_FOREGROUND : STATUS_BAR_NO_FOLDER_FOREGROUND);
});
}
......@@ -289,6 +293,7 @@ class StatusBarItem extends Themable implements IStatusbarItem {
element.title = nls.localize('problems', "Problems");
Dom.addClass(errorIcon, 'task-statusbar-item-label-error');
Dom.addClass(errorIcon, 'mask-icon');
label.appendChild(errorIcon);
this.icons.push(errorIcon);
......@@ -297,6 +302,7 @@ class StatusBarItem extends Themable implements IStatusbarItem {
label.appendChild(error);
Dom.addClass(warningIcon, 'task-statusbar-item-label-warning');
Dom.addClass(warningIcon, 'mask-icon');
label.appendChild(warningIcon);
this.icons.push(warningIcon);
......@@ -305,6 +311,7 @@ class StatusBarItem extends Themable implements IStatusbarItem {
label.appendChild(warning);
Dom.addClass(infoIcon, 'task-statusbar-item-label-info');
Dom.addClass(infoIcon, 'mask-icon');
label.appendChild(infoIcon);
this.icons.push(infoIcon);
$(infoIcon).hide();
......@@ -1354,8 +1361,6 @@ let schema: IJSONSchema = {
import schemaVersion1 from './jsonSchema_v1';
import schemaVersion2 from './jsonSchema_v2';
import { Themable, STATUS_BAR_FOREGROUND } from 'vs/workbench/common/theme';
import { IThemeService } from 'vs/platform/theme/common/themeService';
schema.definitions = {
...schemaVersion1.definitions,
...schemaVersion2.definitions,
......
......@@ -17,7 +17,7 @@ const optimist = require('optimist')
.describe('build', 'run with build output (out-build)').boolean('build')
.describe('coverage', 'generate coverage report').boolean('coverage')
.describe('debug', 'open dev tools, keep window open, reuse app data').string('debug')
.describe('reporter', 'the mocha reporter').string('reporter').default('reporter', 'spec')
.describe('reporter', 'the mocha reporter').string('reporter').default('reporter', process.platform === 'win32' ? 'dot' : 'spec')
.describe('help', 'show the help').alias('help', 'h');
const argv = optimist.argv;
......@@ -113,7 +113,7 @@ app.on('ready', () => {
Reporter = require(reporterPath);
} catch (err) {
console.warn(`could not load reporter: ${argv.reporter}`);
Reporter = mocha.reporters.Spec;
Reporter = process.platform === 'win32' ? mocha.reporters.Dot : mocha.reporters.Spec;
}
const runner = new IPCRunner();
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册