Merge branch 'master' into joh/ftp

This commit is contained in:
Johannes Rieken 2017-09-20 13:48:02 +02:00
commit 57aab4f84c
40 changed files with 628 additions and 393 deletions

View file

@ -18,5 +18,10 @@
["[", "]"],
["(", ")"],
["\"", "\""]
]
],
"folding": {
"indendationBasedFolding": {
"offSide": true
}
}
}

View file

@ -21,5 +21,10 @@
["(", ")"],
["\"", "\""],
["'", "'"]
]
],
"folding": {
"indendationBasedFolding": {
"offSide": true
}
}
}

View file

@ -20,5 +20,10 @@
["(", ")"],
["\"", "\""],
["'", "'"]
]
],
"folding": {
"indendationBasedFolding": {
"offSide": true
}
}
}

View file

@ -66,7 +66,7 @@ function findSpecificGit(path: string): Promise<IGit> {
const buffers: Buffer[] = [];
const child = cp.spawn(path, ['--version']);
child.stdout.on('data', (b: Buffer) => buffers.push(b));
child.on('error', e);
child.on('error', cpErrorHandler(e));
child.on('exit', code => code ? e(new Error('Not found')) : c({ path, version: parseVersion(Buffer.concat(buffers).toString('utf8').trim()) }));
});
}
@ -159,6 +159,20 @@ export interface IExecutionResult {
stderr: string;
}
function cpErrorHandler(cb: (reason?: any) => void): (reason?: any) => void {
return err => {
if (/ENOENT/.test(err.message)) {
err = new GitError({
error: err,
message: 'Failed to execute git (ENOENT)',
gitErrorCode: GitErrorCodes.NotAGitRepository
});
}
cb(err);
};
}
async function exec(child: cp.ChildProcess, options: any = {}): Promise<IExecutionResult> {
if (!child.stdout || !child.stderr) {
throw new GitError({
@ -183,7 +197,7 @@ async function exec(child: cp.ChildProcess, options: any = {}): Promise<IExecuti
const [exitCode, stdout, stderr] = await Promise.all<any>([
new Promise<number>((c, e) => {
once(child, 'error', e);
once(child, 'error', cpErrorHandler(e));
once(child, 'exit', c);
}),
new Promise<string>(c => {
@ -919,7 +933,7 @@ export class Repository {
child.stderr.setEncoding('utf8');
child.stderr.on('data', raw => stderrData.push(raw as string));
child.on('error', e);
child.on('error', cpErrorHandler(e));
child.on('exit', onExit);
});
}

View file

@ -37,5 +37,10 @@
["(", ")"],
["[", "]"],
["`", "`"]
]
],
"folding": {
"indendationBasedFolding": {
"offSide": true
}
}
}

View file

@ -20,5 +20,10 @@
["(", ")"],
["'", "'"],
["\"", "\""]
]
],
"folding": {
"indendationBasedFolding": {
"offSide": true
}
}
}

View file

@ -21,6 +21,10 @@
["(", ")"],
["\"", "\""],
["'", "'"]
]
// enhancedBrackets: [ { open: /.*:\s*$/, closeComplete: 'else:' } ],
],
"folding": {
"indendationBasedFolding": {
"offSide": true
}
}
}

View file

@ -20,5 +20,10 @@
["(", ")"],
["\"", "\""],
["'", "'"]
]
],
"folding": {
"indendationBasedFolding": {
"offSide": true
}
}
}

View file

@ -1820,7 +1820,7 @@ class WorkspacesManager {
} else {
const resolvedWorkspace = this.workspacesService.resolveWorkspaceSync(workspace.configPath);
if (resolvedWorkspace && resolvedWorkspace.folders.length > 0) {
defaultPath = dirname(resolvedWorkspace.folders[0].path);
defaultPath = dirname(resolvedWorkspace.folders[0].uri.fsPath);
}
}
}

View file

@ -62,7 +62,7 @@ function findWindowOnFilePath<W extends ISimpleWindow>(windows: W[], filePath: s
for (let i = 0; i < workspaceWindows.length; i++) {
const window = workspaceWindows[i];
const resolvedWorkspace = workspaceResolver(window.openedWorkspace);
if (resolvedWorkspace && resolvedWorkspace.folders.some(folder => paths.isEqualOrParent(filePath, folder.path, !platform.isLinux /* ignorecase */))) {
if (resolvedWorkspace && resolvedWorkspace.folders.some(folder => paths.isEqualOrParent(filePath, folder.uri.fsPath, !platform.isLinux /* ignorecase */))) {
return window;
}
}

View file

@ -9,6 +9,7 @@ import path = require('path');
import { findBestWindowOrFolderForFile, ISimpleWindow, IBestWindowOrFolderOptions } from 'vs/code/node/windowsFinder';
import { OpenContext } from 'vs/platform/windows/common/windows';
import { IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
import { toWorkspaceFolders } from 'vs/platform/workspace/common/workspace';
const fixturesFolder = require.toUrl('./fixtures');
@ -24,7 +25,7 @@ function options(custom?: Partial<IBestWindowOrFolderOptions<ISimpleWindow>>): I
reuseWindow: false,
context: OpenContext.CLI,
codeSettingsFolder: '_vscode',
workspaceResolver: workspace => { return workspace === testWorkspace ? { id: testWorkspace.id, configPath: workspace.configPath, folders: [{ path: path.join(fixturesFolder, 'vscode_workspace_1_folder') }, { path: path.join(fixturesFolder, 'vscode_workspace_2_folder') }] } : null; },
workspaceResolver: workspace => { return workspace === testWorkspace ? { id: testWorkspace.id, configPath: workspace.configPath, folders: toWorkspaceFolders([{ path: path.join(fixturesFolder, 'vscode_workspace_1_folder') }, { path: path.join(fixturesFolder, 'vscode_workspace_2_folder') }]) } : null; },
...custom
};
}

View file

@ -77,6 +77,9 @@ export class IndentGuidesOverlay extends DynamicViewOverlay {
public onZonesChanged(e: viewEvents.ViewZonesChangedEvent): boolean {
return true;
}
public onLanguageConfigurationChanged(e: viewEvents.ViewLanguageConfigurationEvent): boolean {
return true;
}
// --- end event handlers

View file

@ -23,7 +23,7 @@ import { hash } from 'vs/base/common/hash';
import { EditorModeContext } from 'vs/editor/common/modes/editorModeContext';
import {
IModelContentChangedEvent, IModelDecorationsChangedEvent,
IModelLanguageChangedEvent, IModelOptionsChangedEvent, TextModelEventType
IModelLanguageChangedEvent, IModelOptionsChangedEvent, TextModelEventType, IModelLanguageConfigurationChangedEvent
} from 'vs/editor/common/model/textModelEvents';
import * as editorOptions from 'vs/editor/common/config/editorOptions';
import { ICursorPositionChangedEvent, ICursorSelectionChangedEvent } from 'vs/editor/common/controller/cursorEvents';
@ -44,6 +44,9 @@ export abstract class CommonCodeEditor extends Disposable implements editorCommo
private readonly _onDidChangeModelLanguage: Emitter<IModelLanguageChangedEvent> = this._register(new Emitter<IModelLanguageChangedEvent>());
public readonly onDidChangeModelLanguage: Event<IModelLanguageChangedEvent> = this._onDidChangeModelLanguage.event;
private readonly _onDidChangeModelLanguageConfiguration: Emitter<IModelLanguageConfigurationChangedEvent> = this._register(new Emitter<IModelLanguageConfigurationChangedEvent>());
public readonly onDidChangeModelLanguageConfiguration: Event<IModelLanguageConfigurationChangedEvent> = this._onDidChangeModelLanguage.event;
private readonly _onDidChangeModelOptions: Emitter<IModelOptionsChangedEvent> = this._register(new Emitter<IModelOptionsChangedEvent>());
public readonly onDidChangeModelOptions: Event<IModelOptionsChangedEvent> = this._onDidChangeModelOptions.event;
@ -170,7 +173,7 @@ export abstract class CommonCodeEditor extends Disposable implements editorCommo
// editor actions don't need to be disposed
this._actions = {};
this._removeDecorationTypes();
this._postDetachModelCleanup(this._detachModel());
this._onDidDispose.fire();
@ -232,10 +235,24 @@ export abstract class CommonCodeEditor extends Disposable implements editorCommo
newModelUrl: model ? model.uri : null
};
this._removeDecorationTypes();
this._onDidChangeModel.fire(e);
this._postDetachModelCleanup(detachedModel);
}
private _removeDecorationTypes(): void {
this._decorationTypeKeysToIds = {};
if (this._decorationTypeSubtypes) {
for (let decorationType in this._decorationTypeSubtypes) {
let subTypes = this._decorationTypeSubtypes[decorationType];
for (let subType in subTypes) {
this._removeDecorationType(decorationType + '-' + subType);
}
}
this._decorationTypeSubtypes = {};
}
}
public getCenteredRangeInViewport(): Range {
if (!this.hasView) {
return null;
@ -887,6 +904,10 @@ export abstract class CommonCodeEditor extends Disposable implements editorCommo
this._onDidChangeModelLanguage.fire(e);
break;
case TextModelEventType.ModelLanguageConfigurationChanged:
this._onDidChangeModelLanguageConfiguration.fire(e);
break;
case TextModelEventType.ModelContentChanged:
this._onDidChangeModelContent.fire(e);
break;
@ -948,16 +969,6 @@ export abstract class CommonCodeEditor extends Disposable implements editorCommo
protected _postDetachModelCleanup(detachedModel: editorCommon.IModel): void {
if (detachedModel) {
this._decorationTypeKeysToIds = {};
if (this._decorationTypeSubtypes) {
for (let decorationType in this._decorationTypeSubtypes) {
let subTypes = this._decorationTypeSubtypes[decorationType];
for (let subType in subTypes) {
this._removeDecorationType(decorationType + '-' + subType);
}
}
this._decorationTypeSubtypes = {};
}
detachedModel.removeAllDecorationsWithOwnerId(this.id);
}
}

View file

@ -13,7 +13,6 @@ import { Range } from 'vs/editor/common/core/range';
import { Selection, SelectionDirection, ISelection } from 'vs/editor/common/core/selection';
import * as editorCommon from 'vs/editor/common/editorCommon';
import { CursorColumns, CursorConfiguration, EditOperationResult, CursorContext, CursorState, RevealTarget, IColumnSelectData, ICursors } from 'vs/editor/common/controller/cursorCommon';
import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry';
import { DeleteOperations } from 'vs/editor/common/controller/cursorDeleteOperations';
import { TypeOperations } from 'vs/editor/common/controller/cursorTypeOperations';
import { TextModelEventType, ModelRawContentChangedEvent, RawContentChangedType } from 'vs/editor/common/model/textModelEvents';
@ -150,11 +149,10 @@ export class Cursor extends viewEvents.ViewEventEmitter implements ICursors {
this.context = new CursorContext(this._configuration, this._model, this._viewModel);
this._cursors.updateContext(this.context);
};
this._register(this._model.onDidChangeLanguage((e) => {
this._register(model.onDidChangeLanguage((e) => {
updateCursorContext();
}));
this._register(LanguageConfigurationRegistry.onDidChange(() => {
// TODO@Alex: react only if certain supports changed? (and if my model's mode changed)
this._register(model.onDidChangeLanguageConfiguration(() => {
updateCursorContext();
}));
this._register(model.onDidChangeOptions(() => {

View file

@ -19,7 +19,7 @@ import { IndentRange } from 'vs/editor/common/model/indentRanges';
import { ITextSource } from 'vs/editor/common/model/textSource';
import {
ModelRawContentChangedEvent, IModelContentChangedEvent, IModelDecorationsChangedEvent,
IModelLanguageChangedEvent, IModelOptionsChangedEvent
IModelLanguageChangedEvent, IModelOptionsChangedEvent, IModelLanguageConfigurationChangedEvent
} from 'vs/editor/common/model/textModelEvents';
import * as editorOptions from 'vs/editor/common/config/editorOptions';
import { ICursorPositionChangedEvent, ICursorSelectionChangedEvent } from 'vs/editor/common/controller/cursorEvents';
@ -585,26 +585,17 @@ export interface ITextModel {
*/
getLineContent(lineNumber: number): string;
/**
* @internal
*/
getIndentLevel(lineNumber: number): number;
/**
* @internal
*/
getIndentRanges(): IndentRange[];
/**
* @internal
*/
getLineIndentGuide(lineNumber: number): number;
/**
* Get the text for all lines.
*/
getLinesContent(): string[];
/**
* @internal
*/
getIndentLevel(lineNumber: number): number;
/**
* Get the end of line sequence predominantly used in the text buffer.
* @return EOL char sequence (e.g.: '\n' or '\r\n').
@ -912,6 +903,16 @@ export interface ITokenizedModel extends ITextModel {
* @internal
*/
matchBracket(position: IPosition): [Range, Range];
/**
* @internal
*/
getIndentRanges(): IndentRange[];
/**
* @internal
*/
getLineIndentGuide(lineNumber: number): number;
}
/**
@ -1149,6 +1150,11 @@ export interface IModel extends IReadOnlyModel, IEditableTextModel, ITextModelWi
* @event
*/
onDidChangeLanguage(listener: (e: IModelLanguageChangedEvent) => void): IDisposable;
/**
* An event emitted when the language configuration associated with the model has changed.
* @event
*/
onDidChangeLanguageConfiguration(listener: (e: IModelLanguageConfigurationChangedEvent) => void): IDisposable;
/**
* An event emitted right before disposing the model.
* @event
@ -1748,6 +1754,11 @@ export interface ICommonCodeEditor extends IEditor {
* @event
*/
onDidChangeModelLanguage(listener: (e: IModelLanguageChangedEvent) => void): IDisposable;
/**
* An event emitted when the language configuration of the current model has changed.
* @event
*/
onDidChangeModelLanguageConfiguration(listener: (e: IModelLanguageConfigurationChangedEvent) => void): IDisposable;
/**
* An event emitted when the options of the current model has changed.
* @event

View file

@ -29,7 +29,7 @@ export class IndentRange {
}
}
export function computeRanges(model: ITextModel, minimumRangeSize: number = 1): IndentRange[] {
export function computeRanges(model: ITextModel, offSide: boolean, minimumRangeSize: number = 1): IndentRange[] {
let result: IndentRange[] = [];
@ -38,11 +38,15 @@ export function computeRanges(model: ITextModel, minimumRangeSize: number = 1):
for (let line = model.getLineCount(); line > 0; line--) {
let indent = model.getIndentLevel(line);
let previous = previousRegions[previousRegions.length - 1];
if (indent === -1) {
if (offSide) {
// for offSide languages, empty lines are associated to the next block
previous.line = line;
}
continue; // only whitespace
}
let previous = previousRegions[previousRegions.length - 1];
if (previous.indent > indent) {
// discard all regions with larger indent

View file

@ -34,7 +34,9 @@ export class Model extends EditableTextModel implements IModel {
public onDidChangeLanguage(listener: (e: textModelEvents.IModelLanguageChangedEvent) => void): IDisposable {
return this._eventEmitter.addListener(textModelEvents.TextModelEventType.ModelLanguageChanged, listener);
}
public onDidChangeLanguageConfiguration(listener: (e: textModelEvents.IModelLanguageConfigurationChangedEvent) => void): IDisposable {
return this._eventEmitter.addListener(textModelEvents.TextModelEventType.ModelLanguageConfigurationChanged, listener);
}
public static createFromString(text: string, options: ITextModelCreationOptions = TextModel.DEFAULT_CREATION_OPTIONS, languageIdentifier: LanguageIdentifier = null, uri: URI = null): Model {
return new Model(RawTextSource.fromString(text), options, languageIdentifier, uri);
}

View file

@ -13,7 +13,6 @@ import { ModelLine, IModelLine, MinimalModelLine } from 'vs/editor/common/model/
import { guessIndentation } from 'vs/editor/common/model/indentationGuesser';
import { EDITOR_MODEL_DEFAULTS } from 'vs/editor/common/config/editorOptions';
import { PrefixSumComputer } from 'vs/editor/common/viewModel/prefixSumComputer';
import { IndentRange, computeRanges } from 'vs/editor/common/model/indentRanges';
import { TextModelSearch, SearchParams } from 'vs/editor/common/model/textModelSearch';
import { TextSource, ITextSource, IRawTextSource, RawTextSource } from 'vs/editor/common/model/textSource';
import { IDisposable } from 'vs/base/common/lifecycle';
@ -85,7 +84,6 @@ export class TextModel implements editorCommon.ITextModel {
protected _isDisposing: boolean;
protected _options: editorCommon.TextModelResolvedOptions;
protected _lineStarts: PrefixSumComputer;
private _indentRanges: IndentRange[];
private _versionId: number;
/**
@ -513,65 +511,6 @@ export class TextModel implements editorCommon.ITextModel {
return this._lines[lineNumber - 1].getIndentLevel();
}
protected _resetIndentRanges(): void {
this._indentRanges = null;
}
private _getIndentRanges(): IndentRange[] {
if (!this._indentRanges) {
this._indentRanges = computeRanges(this);
}
return this._indentRanges;
}
public getIndentRanges(): IndentRange[] {
this._assertNotDisposed();
let indentRanges = this._getIndentRanges();
return IndentRange.deepCloneArr(indentRanges);
}
private _toValidLineIndentGuide(lineNumber: number, indentGuide: number): number {
let lineIndentLevel = this._lines[lineNumber - 1].getIndentLevel();
if (lineIndentLevel === -1) {
return indentGuide;
}
let maxIndentGuide = Math.ceil(lineIndentLevel / this._options.tabSize);
return Math.min(maxIndentGuide, indentGuide);
}
public getLineIndentGuide(lineNumber: number): number {
this._assertNotDisposed();
if (lineNumber < 1 || lineNumber > this.getLineCount()) {
throw new Error('Illegal value ' + lineNumber + ' for `lineNumber`');
}
let indentRanges = this._getIndentRanges();
for (let i = indentRanges.length - 1; i >= 0; i--) {
let rng = indentRanges[i];
if (rng.startLineNumber === lineNumber) {
return this._toValidLineIndentGuide(lineNumber, Math.ceil(rng.indent / this._options.tabSize));
}
if (rng.startLineNumber < lineNumber && lineNumber <= rng.endLineNumber) {
return this._toValidLineIndentGuide(lineNumber, 1 + Math.floor(rng.indent / this._options.tabSize));
}
if (rng.endLineNumber + 1 === lineNumber) {
let bestIndent = rng.indent;
while (i > 0) {
i--;
rng = indentRanges[i];
if (rng.endLineNumber + 1 === lineNumber) {
bestIndent = rng.indent;
}
}
return this._toValidLineIndentGuide(lineNumber, Math.ceil(bestIndent / this._options.tabSize));
}
}
return 0;
}
public getLinesContent(): string[] {
this._assertNotDisposed();
var r: string[] = [];
@ -785,7 +724,6 @@ export class TextModel implements editorCommon.ITextModel {
this._EOL = textSource.EOL;
this._lines = modelLines;
this._lineStarts = null;
this._resetIndentRanges();
}
private _getEndOfLine(eol: editorCommon.EndOfLinePreference): string {

View file

@ -18,6 +18,7 @@ export const TextModelEventType = {
ModelContentChanged: 'contentChanged',
ModelRawContentChanged2: 'rawContentChanged2',
ModelDecorationsChanged: 'decorationsChanged',
ModelLanguageConfigurationChanged: 'modelLanguageConfigurationChanged'
};
/**
@ -34,6 +35,12 @@ export interface IModelLanguageChangedEvent {
readonly newLanguage: string;
}
/**
* An event describing that the language configuration associated with a model has changed.
*/
export interface IModelLanguageConfigurationChangedEvent {
}
export interface IModelContentChange {
/**
* The range that got replaced.

View file

@ -22,6 +22,7 @@ import { getWordAtText } from 'vs/editor/common/model/wordHelper';
import { TokenizationResult2 } from 'vs/editor/common/core/token';
import { ITextSource, IRawTextSource } from 'vs/editor/common/model/textSource';
import * as textModelEvents from 'vs/editor/common/model/textModelEvents';
import { IndentRange, computeRanges } from 'vs/editor/common/model/indentRanges';
class ModelTokensChangedEventBuilder {
@ -69,6 +70,9 @@ export class TextModelWithTokens extends TextModel implements editorCommon.IToke
private _invalidLineStartIndex: number;
private _lastState: IState;
private _indentRanges: IndentRange[];
private _languageRegistryListener: IDisposable;
private _revalidateTokensTimeout: number;
constructor(rawTextSource: IRawTextSource, creationOptions: editorCommon.ITextModelCreationOptions, languageIdentifier: LanguageIdentifier) {
@ -95,11 +99,20 @@ export class TextModelWithTokens extends TextModel implements editorCommon.IToke
this._revalidateTokensTimeout = -1;
this._languageRegistryListener = LanguageConfigurationRegistry.onDidChange((e) => {
if (e.languageIdentifier.id === this._languageIdentifier.id) {
this._resetIndentRanges();
this._emitModelLanguageConfigurationEvent({});
}
});
this._resetTokenizationState();
this._resetIndentRanges();
}
public dispose(): void {
this._tokenizationListener.dispose();
this._languageRegistryListener.dispose();
this._clearTimers();
this._lastState = null;
@ -114,6 +127,7 @@ export class TextModelWithTokens extends TextModel implements editorCommon.IToke
super._resetValue(newValue);
// Cancel tokenization, clear all tokens and begin tokenizing
this._resetTokenizationState();
this._resetIndentRanges();
}
protected _resetTokenizationState(): void {
@ -225,6 +239,7 @@ export class TextModelWithTokens extends TextModel implements editorCommon.IToke
// Cancel tokenization, clear all tokens and begin tokenizing
this._resetTokenizationState();
this._resetIndentRanges();
this.emitModelTokensChangedEvent({
ranges: [{
@ -233,6 +248,7 @@ export class TextModelWithTokens extends TextModel implements editorCommon.IToke
}]
});
this._emitModelModeChangedEvent(e);
this._emitModelLanguageConfigurationEvent({});
}
public getLanguageIdAtPosition(_lineNumber: number, _column: number): LanguageId {
@ -398,6 +414,12 @@ export class TextModelWithTokens extends TextModel implements editorCommon.IToke
}
}
private _emitModelLanguageConfigurationEvent(e: textModelEvents.IModelLanguageConfigurationChangedEvent): void {
if (!this._isDisposing) {
this._eventEmitter.emit(textModelEvents.TextModelEventType.ModelLanguageConfigurationChanged, e);
}
}
private _emitModelModeChangedEvent(e: textModelEvents.IModelLanguageChangedEvent): void {
if (!this._isDisposing) {
this._eventEmitter.emit(textModelEvents.TextModelEventType.ModelLanguageChanged, e);
@ -814,4 +836,65 @@ export class TextModelWithTokens extends TextModel implements editorCommon.IToke
isOpen: modeBrackets.textIsOpenBracket[text]
};
}
protected _resetIndentRanges(): void {
this._indentRanges = null;
}
private _getIndentRanges(): IndentRange[] {
if (!this._indentRanges) {
let foldingRules = LanguageConfigurationRegistry.getFoldingRules(this._languageIdentifier.id);
let offSide = foldingRules && foldingRules.indendationBasedFolding && foldingRules.indendationBasedFolding.offSide;
this._indentRanges = computeRanges(this, offSide);
}
return this._indentRanges;
}
public getIndentRanges(): IndentRange[] {
this._assertNotDisposed();
let indentRanges = this._getIndentRanges();
return IndentRange.deepCloneArr(indentRanges);
}
public getLineIndentGuide(lineNumber: number): number {
this._assertNotDisposed();
if (lineNumber < 1 || lineNumber > this.getLineCount()) {
throw new Error('Illegal value ' + lineNumber + ' for `lineNumber`');
}
let indentRanges = this._getIndentRanges();
for (let i = indentRanges.length - 1; i >= 0; i--) {
let rng = indentRanges[i];
if (rng.startLineNumber === lineNumber) {
return this._toValidLineIndentGuide(lineNumber, Math.ceil(rng.indent / this._options.tabSize));
}
if (rng.startLineNumber < lineNumber && lineNumber <= rng.endLineNumber) {
return this._toValidLineIndentGuide(lineNumber, 1 + Math.floor(rng.indent / this._options.tabSize));
}
if (rng.endLineNumber + 1 === lineNumber) {
let bestIndent = rng.indent;
while (i > 0) {
i--;
rng = indentRanges[i];
if (rng.endLineNumber + 1 === lineNumber) {
bestIndent = rng.indent;
}
}
return this._toValidLineIndentGuide(lineNumber, Math.ceil(bestIndent / this._options.tabSize));
}
}
return 0;
}
private _toValidLineIndentGuide(lineNumber: number, indentGuide: number): number {
let lineIndentLevel = this._lines[lineNumber - 1].getIndentLevel();
if (lineIndentLevel === -1) {
return indentGuide;
}
let maxIndentGuide = Math.ceil(lineIndentLevel / this._options.tabSize);
return Math.min(maxIndentGuide, indentGuide);
}
}

View file

@ -61,6 +61,12 @@ export interface LanguageConfiguration {
* settings will be used.
*/
surroundingPairs?: IAutoClosingPair[];
/**
* The language's folding rules.
*/
folding?: FoldingRules;
/**
* **Deprecated** Do not use.
*
@ -89,8 +95,25 @@ export interface IndentationRule {
* If a line matches this pattern, then its indentation should not be changed and it should not be evaluated against the other rules.
*/
unIndentedLinePattern?: RegExp;
}
/**
* Describes folding rules for a language.
*/
export interface FoldingRules {
indendationBasedFolding?: {
/**
* Used by the indentation based strategy to decide wheter empty lines belong to the previous or the next block.
* A language adheres to the off-side rule if blocks in that language are expressed by their indentation.
* See [wikipedia](https://en.wikipedia.org/wiki/Off-side_rule) for more information.
* If not set, `false` is used and empty lines belong to the previous block.
*/
offSide: boolean;
};
}
/**
* Describes a rule to be evaluated when pressing Enter.
*/

View file

@ -18,7 +18,7 @@ import { DEFAULT_WORD_REGEXP, ensureValidWordDefinition } from 'vs/editor/common
import { createScopedLineTokens } from 'vs/editor/common/modes/supports';
import { LineTokens } from 'vs/editor/common/core/lineTokens';
import { Range } from 'vs/editor/common/core/range';
import { IndentAction, EnterAction, IAutoClosingPair, LanguageConfiguration, IndentationRule } from 'vs/editor/common/modes/languageConfiguration';
import { IndentAction, EnterAction, IAutoClosingPair, LanguageConfiguration, IndentationRule, FoldingRules } from 'vs/editor/common/modes/languageConfiguration';
import { LanguageIdentifier, LanguageId } from 'vs/editor/common/modes';
/**
@ -55,6 +55,7 @@ export class RichEditSupport {
public readonly indentRulesSupport: IndentRulesSupport;
public readonly brackets: RichEditBrackets;
public readonly indentationRules: IndentationRule;
public readonly foldingRules: FoldingRules;
constructor(languageIdentifier: LanguageIdentifier, previous: RichEditSupport, rawConf: LanguageConfiguration) {
@ -82,6 +83,8 @@ export class RichEditSupport {
if (this._conf.indentationRules) {
this.indentRulesSupport = new IndentRulesSupport(this._conf.indentationRules);
}
this.foldingRules = this._conf.folding || {};
}
private static _mergeConf(prev: LanguageConfiguration, current: LanguageConfiguration): LanguageConfiguration {
@ -93,6 +96,7 @@ export class RichEditSupport {
onEnterRules: (prev ? current.onEnterRules || prev.onEnterRules : current.onEnterRules),
autoClosingPairs: (prev ? current.autoClosingPairs || prev.autoClosingPairs : current.autoClosingPairs),
surroundingPairs: (prev ? current.surroundingPairs || prev.surroundingPairs : current.surroundingPairs),
folding: (prev ? current.folding || prev.folding : current.folding),
__electricCharacterSupport: (prev ? current.__electricCharacterSupport || prev.__electricCharacterSupport : current.__electricCharacterSupport),
};
}
@ -143,12 +147,16 @@ export class RichEditSupport {
}
}
export class LanguageConfigurationChangeEvent {
languageIdentifier: LanguageIdentifier;
}
export class LanguageConfigurationRegistryImpl {
private _entries: RichEditSupport[];
private _onDidChange: Emitter<void> = new Emitter<void>();
public onDidChange: Event<void> = this._onDidChange.event;
private _onDidChange: Emitter<LanguageConfigurationChangeEvent> = new Emitter<LanguageConfigurationChangeEvent>();
public onDidChange: Event<LanguageConfigurationChangeEvent> = this._onDidChange.event;
constructor() {
this._entries = [];
@ -158,12 +166,12 @@ export class LanguageConfigurationRegistryImpl {
let previous = this._getRichEditSupport(languageIdentifier.id);
let current = new RichEditSupport(languageIdentifier, previous, configuration);
this._entries[languageIdentifier.id] = current;
this._onDidChange.fire(void 0);
this._onDidChange.fire({ languageIdentifier });
return {
dispose: () => {
if (this._entries[languageIdentifier.id] === current) {
this._entries[languageIdentifier.id] = previous;
this._onDidChange.fire(void 0);
this._onDidChange.fire({ languageIdentifier });
}
}
};
@ -268,9 +276,15 @@ export class LanguageConfigurationRegistryImpl {
return ensureValidWordDefinition(value.wordDefinition || null);
}
public getFoldingRules(languageId: LanguageId): FoldingRules {
let value = this._getRichEditSupport(languageId);
if (!value) {
return {};
}
return value.foldingRules;
}
// beigin Indent Rules
// begin Indent Rules
public getIndentRulesSupport(languageId: LanguageId): IndentRulesSupport {
let value = this._getRichEditSupport(languageId);

View file

@ -27,7 +27,8 @@ export const enum ViewEventType {
ViewTokensChanged = 12,
ViewTokensColorsChanged = 13,
ViewZonesChanged = 14,
ViewThemeChanged = 15
ViewThemeChanged = 15,
ViewLanguageConfigurationChanged = 16
}
export class ViewConfigurationChangedEvent {
@ -282,6 +283,14 @@ export class ViewZonesChangedEvent {
}
}
export class ViewLanguageConfigurationEvent {
public readonly type = ViewEventType.ViewLanguageConfigurationChanged;
constructor() {
}
}
export type ViewEvent = (
ViewConfigurationChangedEvent
| ViewCursorStateChangedEvent
@ -298,6 +307,7 @@ export type ViewEvent = (
| ViewTokensColorsChangedEvent
| ViewZonesChangedEvent
| ViewThemeChangedEvent
| ViewLanguageConfigurationEvent
);
export interface IViewEventListener {

View file

@ -49,6 +49,9 @@ export class ViewEventHandler extends Disposable {
public onFocusChanged(e: viewEvents.ViewFocusChangedEvent): boolean {
return false;
}
public onLanguageConfigurationChanged(e: viewEvents.ViewLanguageConfigurationEvent): boolean {
return false;
}
public onLineMappingChanged(e: viewEvents.ViewLineMappingChangedEvent): boolean {
return false;
}
@ -121,6 +124,12 @@ export class ViewEventHandler extends Disposable {
}
break;
case viewEvents.ViewEventType.ViewLanguageConfigurationChanged:
if (this.onLanguageConfigurationChanged(e)) {
shouldRender = true;
}
break;
case viewEvents.ViewEventType.ViewLineMappingChanged:
if (this.onLineMappingChanged(e)) {
shouldRender = true;
@ -175,7 +184,6 @@ export class ViewEventHandler extends Disposable {
}
break;
case viewEvents.ViewEventType.ViewThemeChanged:
if (this.onThemeChanged(e)) {
shouldRender = true;

View file

@ -261,6 +261,10 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel
// That's ok, a model tokens changed event will follow shortly
break;
}
case textModelEvents.TextModelEventType.ModelLanguageConfigurationChanged: {
eventsCollector.emit(new viewEvents.ViewLanguageConfigurationEvent());
break;
}
case textModelEvents.TextModelEventType.ModelContentChanged: {
// Ignore
break;

View file

@ -237,6 +237,9 @@ export class FoldingController implements IFoldingController {
this.localToDispose.push(this.contentChangedScheduler);
this.localToDispose.push(this.cursorChangedScheduler);
this.localToDispose.push(model.onDidChangeLanguageConfiguration(e => {
this.contentChangedScheduler.schedule();
}));
this.localToDispose.push(this.editor.onDidChangeModelContent(e => this.contentChangedScheduler.schedule()));
this.localToDispose.push(this.editor.onDidChangeCursorPosition((e) => {

View file

@ -16,9 +16,9 @@ export interface IndentRange {
}
suite('Indentation Folding', () => {
function assertRanges(lines: string[], expected: IndentRange[]): void {
function assertRanges(lines: string[], expected: IndentRange[], offside): void {
let model = Model.createFromString(lines.join('\n'));
let actual = computeRanges(model);
let actual = computeRanges(model, offside);
actual.sort((r1, r2) => r1.startLineNumber - r2.startLineNumber);
assert.deepEqual(actual, expected);
model.dispose();
@ -29,40 +29,48 @@ suite('Indentation Folding', () => {
}
test('Fold one level', () => {
assertRanges([
let range = [
'A',
' A',
' A',
' A'
], [r(1, 4, 0)]);
];
assertRanges(range, [r(1, 4, 0)], true);
assertRanges(range, [r(1, 4, 0)], false);
});
test('Fold two levels', () => {
assertRanges([
let range = [
'A',
' A',
' A',
' A',
' A'
], [r(1, 5, 0), r(3, 5, 2)]);
];
assertRanges(range, [r(1, 5, 0), r(3, 5, 2)], true);
assertRanges(range, [r(1, 5, 0), r(3, 5, 2)], false);
});
test('Fold three levels', () => {
assertRanges([
let range = [
'A',
' A',
' A',
' A',
'A'
], [r(1, 4, 0), r(2, 4, 2), r(3, 4, 4)]);
];
assertRanges(range, [r(1, 4, 0), r(2, 4, 2), r(3, 4, 4)], true);
assertRanges(range, [r(1, 4, 0), r(2, 4, 2), r(3, 4, 4)], false);
});
test('Fold decreasing indent', () => {
assertRanges([
let range = [
' A',
' A',
'A'
], []);
];
assertRanges(range, [], true);
assertRanges(range, [], false);
});
test('Fold Java', () => {
@ -80,7 +88,7 @@ suite('Indentation Folding', () => {
/*11*/ 'interface B {',
/*12*/ ' void bar();',
/*13*/ '}',
], [r(1, 9, 0), r(2, 4, 2), r(7, 8, 2), r(11, 12, 0)]);
], [r(1, 9, 0), r(2, 4, 2), r(7, 8, 2), r(11, 12, 0)], false);
});
test('Fold Javadoc', () => {
@ -92,9 +100,9 @@ suite('Indentation Folding', () => {
/* 5*/ ' void foo() {',
/* 6*/ ' }',
/* 7*/ '}',
], [r(1, 3, 0), r(4, 6, 0)]);
], [r(1, 3, 0), r(4, 6, 0)], false);
});
test('Fold Whitespace', () => {
test('Fold Whitespace Java', () => {
assertRanges([
/* 1*/ 'class A {',
/* 2*/ '',
@ -104,7 +112,20 @@ suite('Indentation Folding', () => {
/* 6*/ ' }',
/* 7*/ ' ',
/* 8*/ '}',
], [r(1, 7, 0), r(3, 5, 2)]);
], [r(1, 7, 0), r(3, 5, 2)], false);
});
test('Fold Whitespace Python', () => {
assertRanges([
/* 1*/ 'def a:',
/* 2*/ ' pass',
/* 3*/ ' ',
/* 4*/ ' def b:',
/* 5*/ ' pass',
/* 6*/ ' ',
/* 7*/ ' ',
/* 8*/ 'def c: # since there was a deintent here'
], [r(1, 5, 0), r(4, 5, 2)], true);
});
test('Fold Tabs', () => {
@ -117,6 +138,6 @@ suite('Indentation Folding', () => {
/* 6*/ ' \t}',
/* 7*/ ' ',
/* 8*/ '}',
], [r(1, 7, 0), r(3, 5, 4)]);
], [r(1, 7, 0), r(3, 5, 4)], false);
});
});

View file

@ -820,178 +820,4 @@ suite('TextModel.mightContainRTL', () => {
assert.equal(model.mightContainRTL(), false);
});
});
suite('TextModel.getLineIndentGuide', () => {
function assertIndentGuides(lines: [number, string][]): void {
let text = lines.map(l => l[1]).join('\n');
let model = TextModel.createFromString(text);
let actual: [number, string][] = [];
for (let line = 1; line <= model.getLineCount(); line++) {
actual[line - 1] = [model.getLineIndentGuide(line), model.getLineContent(line)];
}
// let expected = lines.map(l => l[0]);
assert.deepEqual(actual, lines);
model.dispose();
}
test('getLineIndentGuide one level', () => {
assertIndentGuides([
[0, 'A'],
[1, ' A'],
[1, ' A'],
[1, ' A'],
]);
});
test('getLineIndentGuide two levels', () => {
assertIndentGuides([
[0, 'A'],
[1, ' A'],
[1, ' A'],
[1, ' A'],
[1, ' A'],
]);
});
test('getLineIndentGuide three levels', () => {
assertIndentGuides([
[0, 'A'],
[1, ' A'],
[1, ' A'],
[2, ' A'],
[0, 'A'],
]);
});
test('getLineIndentGuide decreasing indent', () => {
assertIndentGuides([
[0, ' A'],
[0, ' A'],
[0, 'A'],
]);
});
test('getLineIndentGuide Java', () => {
assertIndentGuides([
/* 1*/[0, 'class A {'],
/* 2*/[1, ' void foo() {'],
/* 3*/[1, ' console.log(1);'],
/* 4*/[1, ' console.log(2);'],
/* 5*/[1, ' }'],
/* 6*/[1, ''],
/* 7*/[1, ' void bar() {'],
/* 8*/[1, ' console.log(3);'],
/* 9*/[1, ' }'],
/*10*/[0, '}'],
/*11*/[0, 'interface B {'],
/*12*/[1, ' void bar();'],
/*13*/[0, '}'],
]);
});
test('getLineIndentGuide Javadoc', () => {
assertIndentGuides([
[0, '/**'],
[1, ' * Comment'],
[1, ' */'],
[0, 'class A {'],
[1, ' void foo() {'],
[1, ' }'],
[0, '}'],
]);
});
test('getLineIndentGuide Whitespace', () => {
assertIndentGuides([
[0, 'class A {'],
[1, ''],
[1, ' void foo() {'],
[1, ' '],
[1, ' return 1;'],
[1, ' }'],
[1, ' '],
[0, '}'],
]);
});
test('getLineIndentGuide Tabs', () => {
assertIndentGuides([
[0, 'class A {'],
[1, '\t\t'],
[1, '\tvoid foo() {'],
[2, '\t \t//hello'],
[2, '\t return 2;'],
[1, ' \t}'],
[1, ' '],
[0, '}'],
]);
});
test('getLineIndentGuide checker.ts', () => {
assertIndentGuides([
/* 1*/[0, '/// <reference path="binder.ts"/>'],
/* 2*/[0, ''],
/* 3*/[0, '/* @internal */'],
/* 4*/[0, 'namespace ts {'],
/* 5*/[1, ' let nextSymbolId = 1;'],
/* 6*/[1, ' let nextNodeId = 1;'],
/* 7*/[1, ' let nextMergeId = 1;'],
/* 8*/[1, ' let nextFlowId = 1;'],
/* 9*/[1, ''],
/*10*/[1, ' export function getNodeId(node: Node): number {'],
/*11*/[2, ' if (!node.id) {'],
/*12*/[3, ' node.id = nextNodeId;'],
/*13*/[3, ' nextNodeId++;'],
/*14*/[2, ' }'],
/*15*/[2, ' return node.id;'],
/*16*/[1, ' }'],
/*17*/[0, '}'],
]);
});
test('issue #8425 - Missing indentation lines for first level indentation', () => {
assertIndentGuides([
[1, '\tindent1'],
[2, '\t\tindent2'],
[2, '\t\tindent2'],
[1, '\tindent1'],
]);
});
test('issue #8952 - Indentation guide lines going through text on .yml file', () => {
assertIndentGuides([
[0, 'properties:'],
[1, ' emailAddress:'],
[2, ' - bla'],
[2, ' - length:'],
[3, ' max: 255'],
[0, 'getters:'],
]);
});
test('issue #11892 - Indent guides look funny', () => {
assertIndentGuides([
[0, 'function test(base) {'],
[1, '\tswitch (base) {'],
[2, '\t\tcase 1:'],
[3, '\t\t\treturn 1;'],
[2, '\t\tcase 2:'],
[3, '\t\t\treturn 2;'],
[1, '\t}'],
[0, '}'],
]);
});
test('issue #12398 - Problem in indent guidelines', () => {
assertIndentGuides([
[2, '\t\t.bla'],
[3, '\t\t\tlabel(for)'],
[0, 'include script'],
]);
});
});
});

View file

@ -359,3 +359,177 @@ suite('TextModelWithTokens regression tests', () => {
registration.dispose();
});
});
suite('TextModel.getLineIndentGuide', () => {
function assertIndentGuides(lines: [number, string][]): void {
let text = lines.map(l => l[1]).join('\n');
let model = Model.createFromString(text);
let actual: [number, string][] = [];
for (let line = 1; line <= model.getLineCount(); line++) {
actual[line - 1] = [model.getLineIndentGuide(line), model.getLineContent(line)];
}
// let expected = lines.map(l => l[0]);
assert.deepEqual(actual, lines);
model.dispose();
}
test('getLineIndentGuide one level', () => {
assertIndentGuides([
[0, 'A'],
[1, ' A'],
[1, ' A'],
[1, ' A'],
]);
});
test('getLineIndentGuide two levels', () => {
assertIndentGuides([
[0, 'A'],
[1, ' A'],
[1, ' A'],
[1, ' A'],
[1, ' A'],
]);
});
test('getLineIndentGuide three levels', () => {
assertIndentGuides([
[0, 'A'],
[1, ' A'],
[1, ' A'],
[2, ' A'],
[0, 'A'],
]);
});
test('getLineIndentGuide decreasing indent', () => {
assertIndentGuides([
[0, ' A'],
[0, ' A'],
[0, 'A'],
]);
});
test('getLineIndentGuide Java', () => {
assertIndentGuides([
/* 1*/[0, 'class A {'],
/* 2*/[1, ' void foo() {'],
/* 3*/[1, ' console.log(1);'],
/* 4*/[1, ' console.log(2);'],
/* 5*/[1, ' }'],
/* 6*/[1, ''],
/* 7*/[1, ' void bar() {'],
/* 8*/[1, ' console.log(3);'],
/* 9*/[1, ' }'],
/*10*/[0, '}'],
/*11*/[0, 'interface B {'],
/*12*/[1, ' void bar();'],
/*13*/[0, '}'],
]);
});
test('getLineIndentGuide Javadoc', () => {
assertIndentGuides([
[0, '/**'],
[1, ' * Comment'],
[1, ' */'],
[0, 'class A {'],
[1, ' void foo() {'],
[1, ' }'],
[0, '}'],
]);
});
test('getLineIndentGuide Whitespace', () => {
assertIndentGuides([
[0, 'class A {'],
[1, ''],
[1, ' void foo() {'],
[1, ' '],
[1, ' return 1;'],
[1, ' }'],
[1, ' '],
[0, '}'],
]);
});
test('getLineIndentGuide Tabs', () => {
assertIndentGuides([
[0, 'class A {'],
[1, '\t\t'],
[1, '\tvoid foo() {'],
[2, '\t \t//hello'],
[2, '\t return 2;'],
[1, ' \t}'],
[1, ' '],
[0, '}'],
]);
});
test('getLineIndentGuide checker.ts', () => {
assertIndentGuides([
/* 1*/[0, '/// <reference path="binder.ts"/>'],
/* 2*/[0, ''],
/* 3*/[0, '/* @internal */'],
/* 4*/[0, 'namespace ts {'],
/* 5*/[1, ' let nextSymbolId = 1;'],
/* 6*/[1, ' let nextNodeId = 1;'],
/* 7*/[1, ' let nextMergeId = 1;'],
/* 8*/[1, ' let nextFlowId = 1;'],
/* 9*/[1, ''],
/*10*/[1, ' export function getNodeId(node: Node): number {'],
/*11*/[2, ' if (!node.id) {'],
/*12*/[3, ' node.id = nextNodeId;'],
/*13*/[3, ' nextNodeId++;'],
/*14*/[2, ' }'],
/*15*/[2, ' return node.id;'],
/*16*/[1, ' }'],
/*17*/[0, '}'],
]);
});
test('issue #8425 - Missing indentation lines for first level indentation', () => {
assertIndentGuides([
[1, '\tindent1'],
[2, '\t\tindent2'],
[2, '\t\tindent2'],
[1, '\tindent1'],
]);
});
test('issue #8952 - Indentation guide lines going through text on .yml file', () => {
assertIndentGuides([
[0, 'properties:'],
[1, ' emailAddress:'],
[2, ' - bla'],
[2, ' - length:'],
[3, ' max: 255'],
[0, 'getters:'],
]);
});
test('issue #11892 - Indent guides look funny', () => {
assertIndentGuides([
[0, 'function test(base) {'],
[1, '\tswitch (base) {'],
[2, '\t\tcase 1:'],
[3, '\t\t\treturn 1;'],
[2, '\t\tcase 2:'],
[3, '\t\t\treturn 2;'],
[1, '\t}'],
[0, '}'],
]);
});
test('issue #12398 - Problem in indent guidelines', () => {
assertIndentGuides([
[2, '\t\t.bla'],
[3, '\t\t\tlabel(for)'],
[0, 'include script'],
]);
});
});

35
src/vs/monaco.d.ts vendored
View file

@ -1787,6 +1787,11 @@ declare module monaco.editor {
* @event
*/
onDidChangeLanguage(listener: (e: IModelLanguageChangedEvent) => void): IDisposable;
/**
* An event emitted when the language configuration associated with the model has changed.
* @event
*/
onDidChangeLanguageConfiguration(listener: (e: IModelLanguageConfigurationChangedEvent) => void): IDisposable;
/**
* An event emitted right before disposing the model.
* @event
@ -2185,6 +2190,11 @@ declare module monaco.editor {
* @event
*/
onDidChangeModelLanguage(listener: (e: IModelLanguageChangedEvent) => void): IDisposable;
/**
* An event emitted when the language configuration of the current model has changed.
* @event
*/
onDidChangeModelLanguageConfiguration(listener: (e: IModelLanguageConfigurationChangedEvent) => void): IDisposable;
/**
* An event emitted when the options of the current model has changed.
* @event
@ -2419,6 +2429,12 @@ declare module monaco.editor {
readonly newLanguage: string;
}
/**
* An event describing that the language configuration associated with a model has changed.
*/
export interface IModelLanguageConfigurationChangedEvent {
}
export interface IModelContentChange {
/**
* The range that got replaced.
@ -4294,6 +4310,10 @@ declare module monaco.languages {
* settings will be used.
*/
surroundingPairs?: IAutoClosingPair[];
/**
* The language's folding rules.
*/
folding?: FoldingRules;
/**
* **Deprecated** Do not use.
*
@ -4324,6 +4344,21 @@ declare module monaco.languages {
unIndentedLinePattern?: RegExp;
}
/**
* Describes folding rules for a language.
*/
export interface FoldingRules {
indendationBasedFolding?: {
/**
* Used by the indentation based strategy to decide wheter empty lines belong to the previous or the next block.
* A language adheres to the off-side rule if blocks in that language are expressed by their indentation.
* See [wikipedia](https://en.wikipedia.org/wiki/Off-side_rule) for more information.
* If not set, `false` is used and empty lines belong to the previous block.
*/
offSide: boolean;
};
}
/**
* Describes a rule to be evaluated when pressing Enter.
*/

View file

@ -14,6 +14,7 @@ import { isLinux } from 'vs/base/common/platform';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import Event from 'vs/base/common/event';
import { tildify, getPathLabel } from 'vs/base/common/labels';
import { WorkspaceFolder } from 'vs/platform/workspace/common/workspace';
export const IWorkspacesMainService = createDecorator<IWorkspacesMainService>('workspacesMainService');
export const IWorkspacesService = createDecorator<IWorkspacesService>('workspacesService');
@ -37,12 +38,10 @@ export interface IStoredWorkspaceFolder {
name?: string;
}
export interface IStoredWorkspace {
folders: IStoredWorkspaceFolder[];
export interface IResolvedWorkspace extends IWorkspaceIdentifier {
folders: WorkspaceFolder[];
}
export interface IResolvedWorkspace extends IWorkspaceIdentifier, IStoredWorkspace { }
export interface IWorkspaceSavedEvent {
workspace: IWorkspaceIdentifier;
oldConfigPath: string;

View file

@ -5,7 +5,7 @@
'use strict';
import { IWorkspacesMainService, IWorkspaceIdentifier, IStoredWorkspace, WORKSPACE_EXTENSION, IWorkspaceSavedEvent, UNTITLED_WORKSPACE_NAME, IResolvedWorkspace } from 'vs/platform/workspaces/common/workspaces';
import { IWorkspacesMainService, IWorkspaceIdentifier, WORKSPACE_EXTENSION, IWorkspaceSavedEvent, UNTITLED_WORKSPACE_NAME, IResolvedWorkspace, IStoredWorkspaceFolder } from 'vs/platform/workspaces/common/workspaces';
import { TPromise } from 'vs/base/common/winjs.base';
import { isParent } from 'vs/platform/files/common/files';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
@ -23,6 +23,11 @@ import * as json from 'vs/base/common/json';
import * as jsonEdit from 'vs/base/common/jsonEdit';
import { applyEdit } from 'vs/base/common/jsonFormatter';
import { massageFolderPathForWorkspace } from 'vs/platform/workspaces/node/workspaces';
import { toWorkspaceFolders } from 'vs/platform/workspace/common/workspace';
export interface IStoredWorkspace {
folders: IStoredWorkspaceFolder[];
}
export class WorkspacesMainService implements IWorkspacesMainService {
@ -85,7 +90,7 @@ export class WorkspacesMainService implements IWorkspacesMainService {
return {
id: this.getWorkspaceId(path),
configPath: path,
folders: workspace.folders
folders: toWorkspaceFolders(workspace.folders)
};
} catch (error) {
this.logService.log(error.toString());

View file

@ -13,8 +13,8 @@ import extfs = require('vs/base/node/extfs');
import pfs = require('vs/base/node/pfs');
import { EnvironmentService } from 'vs/platform/environment/node/environmentService';
import { parseArgs } from 'vs/platform/environment/node/argv';
import { WorkspacesMainService } from 'vs/platform/workspaces/electron-main/workspacesMainService';
import { IStoredWorkspace, WORKSPACE_EXTENSION, IWorkspaceSavedEvent, IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
import { WorkspacesMainService, IStoredWorkspace } from 'vs/platform/workspaces/electron-main/workspacesMainService';
import { WORKSPACE_EXTENSION, IWorkspaceSavedEvent, IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
import { LogMainService } from 'vs/platform/log/common/log';
import URI from 'vs/base/common/uri';
@ -138,7 +138,7 @@ suite('WorkspacesMainService', () => {
fs.writeFileSync(workspace.configPath, JSON.stringify({ folders: [{ path: './ticino-playground/lib' }] }));
const resolved = service.resolveWorkspaceSync(workspace.configPath);
assert.equal(URI.file(resolved.folders[0].path).fsPath, URI.file(path.join(path.dirname(workspace.configPath), 'ticino-playground', 'lib')).fsPath);
assert.equal(resolved.folders[0].uri.fsPath, URI.file(path.join(path.dirname(workspace.configPath), 'ticino-playground', 'lib')).fsPath);
done();
});
@ -149,7 +149,7 @@ suite('WorkspacesMainService', () => {
fs.writeFileSync(workspace.configPath, JSON.stringify({ folders: [{ path: './ticino-playground/lib/../other' }] }));
const resolved = service.resolveWorkspaceSync(workspace.configPath);
assert.equal(URI.file(resolved.folders[0].path).fsPath, URI.file(path.join(path.dirname(workspace.configPath), 'ticino-playground', 'other')).fsPath);
assert.equal(resolved.folders[0].uri.fsPath, URI.file(path.join(path.dirname(workspace.configPath), 'ticino-playground', 'other')).fsPath);
done();
});
@ -160,7 +160,7 @@ suite('WorkspacesMainService', () => {
fs.writeFileSync(workspace.configPath, JSON.stringify({ folders: [{ path: 'ticino-playground/lib' }] }));
const resolved = service.resolveWorkspaceSync(workspace.configPath);
assert.equal(URI.file(resolved.folders[0].path).fsPath, URI.file(path.join(path.dirname(workspace.configPath), 'ticino-playground', 'lib')).fsPath);
assert.equal(resolved.folders[0].uri.fsPath, URI.file(path.join(path.dirname(workspace.configPath), 'ticino-playground', 'lib')).fsPath);
done();
});
@ -171,7 +171,7 @@ suite('WorkspacesMainService', () => {
fs.writeFileSync(workspace.configPath, '{ "folders": [ { "path": "./ticino-playground/lib" } , ] }'); // trailing comma
const resolved = service.resolveWorkspaceSync(workspace.configPath);
assert.equal(URI.file(resolved.folders[0].path).fsPath, URI.file(path.join(path.dirname(workspace.configPath), 'ticino-playground', 'lib')).fsPath);
assert.equal(resolved.folders[0].uri.fsPath, URI.file(path.join(path.dirname(workspace.configPath), 'ticino-playground', 'lib')).fsPath);
done();
});

View file

@ -129,11 +129,6 @@ export class PanelViewlet extends Viewlet {
private panelItems: IViewletPanelItem[] = [];
private panelview: PanelView;
// TODO@Joao make this into method so people can override it
protected get isSingleView(): boolean {
return this.options.showHeaderInTitleWhenSingleView && this.panelItems.length === 1;
}
protected get length(): number {
return this.panelItems.length;
}
@ -157,7 +152,7 @@ export class PanelViewlet extends Viewlet {
getTitle(): string {
let title = Registry.as<ViewletRegistry>(Extensions.Viewlets).getViewlet(this.getId()).name;
if (this.isSingleView) {
if (this.isSingleView()) {
title += ': ' + this.panelItems[0].panel.title;
}
@ -165,7 +160,7 @@ export class PanelViewlet extends Viewlet {
}
getActions(): IAction[] {
if (this.isSingleView) {
if (this.isSingleView()) {
return this.panelItems[0].panel.getActions();
}
@ -173,7 +168,7 @@ export class PanelViewlet extends Viewlet {
}
getSecondaryActions(): IAction[] {
if (this.isSingleView) {
if (this.isSingleView()) {
return this.panelItems[0].panel.getSecondaryActions();
}
@ -257,13 +252,17 @@ export class PanelViewlet extends Viewlet {
}
private updateViewHeaders(): void {
if (this.isSingleView) {
if (this.isSingleView()) {
this.panelItems[0].panel.headerVisible = false;
} else {
this.panelItems.forEach(i => i.panel.headerVisible = true);
}
}
protected isSingleView(): boolean {
return this.options.showHeaderInTitleWhenSingleView && this.panelItems.length === 1;
}
dispose(): void {
super.dispose();
this.panelItems.forEach(i => i.disposable.dispose());

View file

@ -7,7 +7,7 @@
import * as nls from 'vs/nls';
import { parse, ParseError } from 'vs/base/common/json';
import { readFile } from 'vs/base/node/pfs';
import { CharacterPair, LanguageConfiguration, IAutoClosingPair, IAutoClosingPairConditional, IndentationRule, CommentRule } from 'vs/editor/common/modes/languageConfiguration';
import { CharacterPair, LanguageConfiguration, IAutoClosingPair, IAutoClosingPairConditional, IndentationRule, CommentRule, FoldingRules } from 'vs/editor/common/modes/languageConfiguration';
import { IModeService } from 'vs/editor/common/services/modeService';
import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry';
import { Extensions, IJSONContributionRegistry } from 'vs/platform/jsonschemas/common/jsonContributionRegistry';
@ -35,6 +35,7 @@ interface ILanguageConfiguration {
surroundingPairs?: (CharacterPair | IAutoClosingPair)[];
wordPattern?: string | IRegExp;
indentationRules?: IIndentationRules;
folding?: FoldingRules;
}
export class LanguageConfigurationFileHandler {
@ -117,6 +118,10 @@ export class LanguageConfigurationFileHandler {
}
}
if (configuration.folding) {
richEditConfig.folding = configuration.folding;
}
LanguageConfigurationRegistry.register(languageIdentifier, richEditConfig);
}
@ -377,7 +382,24 @@ const schema: IJSONSchema = {
}
}
}
},
folding: {
type: 'object',
description: nls.localize('schema.folding', 'The language\'s folding settings.'),
properties: {
indendationBasedFolding: {
type: 'object',
description: nls.localize('schema.folding.indendationBasedFolding', 'Settings for indentation based folding.'),
properties: {
offSide: {
type: 'boolean',
description: nls.localize('schema.folding.indendationBasedFolding.offSide', 'A language adheres to the off-side rule if blocks in that language are expressed by their indentation. If set, empty lines belong to the subsequent block.'),
}
}
}
}
}
}
};
let schemaRegistry = <IJSONContributionRegistry>Registry.as(Extensions.JSONContribution);

View file

@ -26,19 +26,19 @@ import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
import { Query } from 'vs/workbench/parts/extensions/common/extensionQuery';
import { IFileService, IContent } from 'vs/platform/files/common/files';
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
import { IWorkspaceContextService, WorkbenchState, WorkspaceFolder } from 'vs/platform/workspace/common/workspace';
import { IWindowService } from 'vs/platform/windows/common/windows';
import { IExtensionService, IExtensionDescription } from 'vs/platform/extensions/common/extensions';
import URI from 'vs/base/common/uri';
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
import { CommandsRegistry, ICommandService } from 'vs/platform/commands/common/commands';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { registerThemingParticipant, ITheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService';
import { buttonBackground, buttonForeground, buttonHoverBackground, contrastBorder, registerColor, foreground } from 'vs/platform/theme/common/colorRegistry';
import { Color } from 'vs/base/common/color';
import { IPickOpenEntry, IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen';
import { IJSONEditingService } from 'vs/workbench/services/configuration/common/jsonEditing';
import { ITextEditorSelection } from 'vs/platform/editor/common/editor';
import { ITextModelService } from 'vs/editor/common/services/resolverService';
import { PICK_WORKSPACE_FOLDER_COMMAND } from 'vs/workbench/browser/actions/workspaceActions';
export class InstallAction extends Action {
@ -1393,12 +1393,12 @@ export class ConfigureWorkspaceFolderRecommendedExtensionsAction extends Abstrac
constructor(
id: string,
label: string,
@IQuickOpenService private quickOpenService: IQuickOpenService,
@IFileService fileService: IFileService,
@IWorkspaceContextService contextService: IWorkspaceContextService,
@IWorkbenchEditorService editorService: IWorkbenchEditorService,
@IJSONEditingService jsonEditingService: IJSONEditingService,
@ITextModelService textModelResolverService: ITextModelService
@ITextModelService textModelResolverService: ITextModelService,
@ICommandService private commandService: ICommandService
) {
super(id, label, contextService, fileService, editorService, jsonEditingService, textModelResolverService);
this.contextService.onDidChangeWorkspaceFolders(() => this.update(), this, this.disposables);
@ -1410,19 +1410,12 @@ export class ConfigureWorkspaceFolderRecommendedExtensionsAction extends Abstrac
}
public run(): TPromise<any> {
const picks: IPickOpenEntry[] = this.contextService.getWorkspace().folders.map((folder, index) => {
return <IPickOpenEntry>{
label: folder.name,
id: `${index}`
};
});
return this.quickOpenService.pick(picks, { placeHolder: localize('pickFolder', "Select Workspace Folder") })
.then(pick => {
if (pick) {
return this.openExtensionsFile(this.contextService.toResource(paths.join('.vscode', 'extensions.json'), this.contextService.getWorkspace().folders[parseInt(pick.id)]));
return this.commandService.executeCommand<WorkspaceFolder>(PICK_WORKSPACE_FOLDER_COMMAND)
.then(workspaceFolder => {
if (workspaceFolder) {
return this.openExtensionsFile(this.contextService.toResource(paths.join('.vscode', 'extensions.json'), workspaceFolder));
}
return undefined;
return null;
});
}

View file

@ -32,7 +32,7 @@ import { FileStat, Model } from 'vs/workbench/parts/files/common/explorerModel';
import { IListService } from 'vs/platform/list/browser/listService';
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IPartService } from 'vs/workbench/services/part/common/partService';
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
import { IWorkspaceContextService, WorkbenchState, WorkspaceFolder } from 'vs/platform/workspace/common/workspace';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
@ -166,7 +166,7 @@ export class ExplorerView extends CollapsibleView {
};
this.toDispose.push(this.themeService.onDidFileIconThemeChange(onFileIconThemeChange));
this.toDispose.push(this.contextService.onDidChangeWorkspaceFolders(() => this.refreshFromEvent()));
this.toDispose.push(this.contextService.onDidChangeWorkspaceFolders((e) => this.refreshFromEvent(e.added)));
onFileIconThemeChange(this.themeService.getFileIconTheme());
}
@ -194,7 +194,11 @@ export class ExplorerView extends CollapsibleView {
this.onConfigurationUpdated(configuration);
// Load and Fill Viewer
return this.doRefresh().then(() => {
let targetsToExpand = [];
if (this.settings[ExplorerView.MEMENTO_EXPANDED_FOLDER_RESOURCES]) {
targetsToExpand = this.settings[ExplorerView.MEMENTO_EXPANDED_FOLDER_RESOURCES].map((e: string) => URI.parse(e));
}
return this.doRefresh(targetsToExpand).then(() => {
// When the explorer viewer is loaded, listen to changes to the editor input
this.toDispose.push(this.editorGroupService.onEditorsChanged(() => this.onEditorsChanged()));
@ -676,11 +680,11 @@ export class ExplorerView extends CollapsibleView {
}));
}
private refreshFromEvent(): void {
private refreshFromEvent(newRoots: WorkspaceFolder[] = []): void {
if (this.isVisible()) {
this.explorerRefreshDelayer.trigger(() => {
if (!this.explorerViewer.getHighlight()) {
return this.doRefresh();
return this.doRefresh(newRoots.map(r => r.uri));
}
return TPromise.as(null);
@ -722,20 +726,13 @@ export class ExplorerView extends CollapsibleView {
});
}
private doRefresh(): TPromise<void> {
private doRefresh(targetsToExpand: URI[] = []): TPromise<void> {
const targetsToResolve: { root: FileStat, resource: URI, options: { resolveTo: URI[] } }[] = [];
this.model.roots.forEach(root => {
const rootAndTargets = { root, resource: root.resource, options: { resolveTo: [] } };
targetsToResolve.push(rootAndTargets);
});
let targetsToExpand: URI[] = [];
if (this.settings[ExplorerView.MEMENTO_EXPANDED_FOLDER_RESOURCES]) {
targetsToExpand = this.settings[ExplorerView.MEMENTO_EXPANDED_FOLDER_RESOURCES].map((e: string) => URI.parse(e));
} else if (this.model.roots.length === 1) {
targetsToExpand = this.model.roots.map(root => root.resource); // always expand if there is just one root
}
// First time refresh: Receive target through active editor input or selection and also include settings from previous session
if (!this.isCreated) {
const activeFile = this.getActiveFile();
@ -783,17 +780,12 @@ export class ExplorerView extends CollapsibleView {
// Subsequent refresh: Merge stat into our local model and refresh tree
modelStats.forEach((modelStat, index) => FileStat.mergeLocalWithDisk(modelStat, this.model.roots[index]));
let input = this.contextService.getWorkbenchState() === WorkbenchState.FOLDER ? this.model.roots[0] : this.model;
const input = this.contextService.getWorkbenchState() === WorkbenchState.FOLDER ? this.model.roots[0] : this.model;
const statsToExpand = this.explorerViewer.getExpandedElements().concat(targetsToExpand.map(target => this.model.findClosest(target)));
if (input === this.explorerViewer.getInput()) {
return this.explorerViewer.refresh();
return this.explorerViewer.refresh().then(() => this.explorerViewer.expandAll(statsToExpand));
}
// Preserve expanded elements if tree input changed.
// If it is a brand new tree just expand elements from memento
const expanded = this.explorerViewer.getExpandedElements();
const statsToExpand = expanded.length ? [this.model.roots[0]].concat(expanded) :
targetsToExpand.map(expand => this.model.findClosest(expand));
// Display roots only when multi folder workspace
// Make sure to expand all folders that where expanded in the previous session
return this.explorerViewer.setInput(input).then(() => this.explorerViewer.expandAll(statsToExpand));

View file

@ -11,9 +11,10 @@ import { Action } from 'vs/base/common/actions';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { IModeService } from 'vs/editor/common/services/modeService';
import { IQuickOpenService, IPickOpenEntry, IFilePickOpenEntry } from 'vs/platform/quickOpen/common/quickOpen';
import { IPreferencesService, getSettingsTargetName } from 'vs/workbench/parts/preferences/common/preferences';
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
import { ConfigurationTarget } from 'vs/workbench/services/configuration/common/configurationEditing';
import { IPreferencesService } from 'vs/workbench/parts/preferences/common/preferences';
import { IWorkspaceContextService, WorkbenchState, WorkspaceFolder } from 'vs/platform/workspace/common/workspace';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { PICK_WORKSPACE_FOLDER_COMMAND } from 'vs/workbench/browser/actions/workspaceActions';
export class OpenGlobalSettingsAction extends Action {
@ -114,7 +115,7 @@ export class OpenFolderSettingsAction extends Action {
label: string,
@IPreferencesService private preferencesService: IPreferencesService,
@IWorkspaceContextService private workspaceContextService: IWorkspaceContextService,
@IQuickOpenService private quickOpenService: IQuickOpenService
@ICommandService private commandService: ICommandService
) {
super(id, label);
this.update();
@ -127,21 +128,13 @@ export class OpenFolderSettingsAction extends Action {
}
public run(): TPromise<any> {
const picks: IPickOpenEntry[] = this.workspaceContextService.getWorkspace().folders.map((folder, index) => {
return <IPickOpenEntry>{
label: getSettingsTargetName(ConfigurationTarget.FOLDER, folder.uri, this.workspaceContextService),
id: `${index}`
};
});
return this.quickOpenService.pick(picks, { placeHolder: nls.localize('pickFolder', "Select Folder") })
.then(pick => {
if (pick) {
return this.preferencesService.openFolderSettings(this.workspaceContextService.getWorkspace().folders[parseInt(pick.id)].uri);
return this.commandService.executeCommand<WorkspaceFolder>(PICK_WORKSPACE_FOLDER_COMMAND)
.then(workspaceFolder => {
if (workspaceFolder) {
return this.preferencesService.openFolderSettings(workspaceFolder.uri);
}
return undefined;
return null;
});
}
public dispose(): void {

View file

@ -13,7 +13,7 @@ import { basename } from 'vs/base/common/paths';
import { onUnexpectedError } from 'vs/base/common/errors';
import { IDisposable, dispose, combinedDisposable, empty as EmptyDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { Builder, Dimension } from 'vs/base/browser/builder';
import { PanelViewlet, ViewletPanel } from 'vs/workbench/browser/parts/views/views2';
import { PanelViewlet, ViewletPanel } from 'vs/workbench/browser/parts/views/panelViewlet';
import { append, $, addClass, toggleClass, trackFocus } from 'vs/base/browser/dom';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { List } from 'vs/base/browser/ui/list/listWidget';
@ -789,7 +789,7 @@ export class SCMViewlet extends PanelViewlet implements IViewModel {
}
getActions(): IAction[] {
if (this.isSingleView && this.repositories.length === 1) {
if (this.isSingleView()) {
const [panel] = this.repositoryPanels;
return panel.getActions();
}
@ -800,7 +800,7 @@ export class SCMViewlet extends PanelViewlet implements IViewModel {
getSecondaryActions(): IAction[] {
let result: IAction[];
if (this.isSingleView && this.repositories.length === 1) {
if (this.isSingleView()) {
const [panel] = this.repositoryPanels;
result = [
@ -865,6 +865,10 @@ export class SCMViewlet extends PanelViewlet implements IViewModel {
}
}
protected isSingleView(): boolean {
return super.isSingleView() && this.repositories.length === 1;
}
dispose(): void {
this.disposables = dispose(this.disposables);
this.mainPanelDisposable.dispose();

View file

@ -720,7 +720,10 @@ export class HistoryService extends BaseHistoryService implements IHistoryServic
if (input instanceof EditorInput) {
const factory = registry.getEditorInputFactory(input.getTypeId());
if (factory) {
return { editorInputJSON: { typeId: input.getTypeId(), deserialized: factory.serialize(input) } } as ISerializedEditorHistoryEntry;
const deserialized = factory.serialize(input);
if (deserialized) {
return { editorInputJSON: { typeId: input.getTypeId(), deserialized } } as ISerializedEditorHistoryEntry;
}
}
}
@ -754,10 +757,11 @@ export class HistoryService extends BaseHistoryService implements IHistoryServic
}
// Editor input: via factory
if (serializedEditorHistoryEntry.editorInputJSON) {
const factory = registry.getEditorInputFactory(serializedEditorHistoryEntry.editorInputJSON.typeId);
const { editorInputJSON } = serializedEditorHistoryEntry;
if (editorInputJSON && editorInputJSON.deserialized) {
const factory = registry.getEditorInputFactory(editorInputJSON.typeId);
if (factory) {
return factory.deserialize(this.instantiationService, serializedEditorHistoryEntry.editorInputJSON.deserialized);
return factory.deserialize(this.instantiationService, editorInputJSON.deserialized);
}
}