diff --git a/src/vs/editor/common/editorCommon.ts b/src/vs/editor/common/editorCommon.ts index 3f3ef4a870f..8e1efce6942 100644 --- a/src/vs/editor/common/editorCommon.ts +++ b/src/vs/editor/common/editorCommon.ts @@ -1255,8 +1255,14 @@ export interface ITextModelCreationOptions { defaultEOL: DefaultEndOfLine; } +export interface ITextModelUpdateOptions { + tabSize?: number; + insertSpaces?: boolean; +} + export interface IModelOptionsChangedEvent { - // TODO@Alex TODO@indent + tabSize: boolean; + insertSpaces: boolean; } /** @@ -1710,6 +1716,10 @@ export interface IEditableTextModel extends ITextModelWithMarkers { getOneIndent(): string; + updateOptions(newOpts:ITextModelUpdateOptions): void; + + detectIndentation(defaultInsertSpaces:boolean, defaultTabSize:number): void; + /** * Push a stack element onto the undo stack. This acts as an undo/redo point. * The idea is to use `pushEditOperations` to edit the model and then to diff --git a/src/vs/editor/common/model/textModel.ts b/src/vs/editor/common/model/textModel.ts index e01d7e34c53..9c7f6fd39c4 100644 --- a/src/vs/editor/common/model/textModel.ts +++ b/src/vs/editor/common/model/textModel.ts @@ -38,7 +38,7 @@ export class TextModel extends OrderGuaranteeEventEmitter implements editorCommo private _BOM:string; constructor(allowedEventTypes:string[], rawText:editorCommon.IRawText) { - allowedEventTypes.push(editorCommon.EventType.ModelContentChanged); + allowedEventTypes.push(editorCommon.EventType.ModelContentChanged, editorCommon.EventType.ModelOptionsChanged); super(allowedEventTypes); this._options = rawText.options; @@ -56,6 +56,42 @@ export class TextModel extends OrderGuaranteeEventEmitter implements editorCommo return this._options; } + public updateOptions(newOpts:editorCommon.ITextModelUpdateOptions): void { + let somethingChanged = false; + let changed:editorCommon.IModelOptionsChangedEvent = { + tabSize: false, + insertSpaces: false + }; + + if (typeof newOpts.insertSpaces !== 'undefined') { + if (this._options.insertSpaces !== newOpts.insertSpaces) { + somethingChanged = true; + changed.insertSpaces = true; + this._options.insertSpaces = newOpts.insertSpaces; + } + } + if (typeof newOpts.tabSize !== 'undefined') { + if (this._options.tabSize !== newOpts.tabSize) { + somethingChanged = true; + changed.tabSize = true; + this._options.tabSize = newOpts.tabSize; + } + } + + if (somethingChanged) { + this.emit(editorCommon.EventType.ModelOptionsChanged, changed); + } + } + + public detectIndentation(defaultInsertSpaces:boolean, defaultTabSize:number): void { + let lines = this._lines.map(line => line.text); + let guessedIndentation = guessIndentation(lines, defaultTabSize, defaultInsertSpaces); + this.updateOptions({ + insertSpaces: guessedIndentation.insertSpaces, + tabSize: guessedIndentation.tabSize + }); + } + private _normalizeIndentationFromWhitespace(str:string): string { let tabSize = this._options.tabSize; let insertSpaces = this._options.insertSpaces; diff --git a/src/vs/editor/common/services/modelService.ts b/src/vs/editor/common/services/modelService.ts index 596cc0c5c8e..4d60cdee677 100644 --- a/src/vs/editor/common/services/modelService.ts +++ b/src/vs/editor/common/services/modelService.ts @@ -8,7 +8,7 @@ import Event from 'vs/base/common/event'; import URI from 'vs/base/common/uri'; import {TPromise} from 'vs/base/common/winjs.base'; import {ServiceIdentifier, createDecorator} from 'vs/platform/instantiation/common/instantiation'; -import {IModel} from 'vs/editor/common/editorCommon'; +import {IModel, ITextModelCreationOptions} from 'vs/editor/common/editorCommon'; import {IMode} from 'vs/editor/common/modes'; export var IModelService = createDecorator('modelService'); @@ -22,6 +22,8 @@ export interface IModelService { getModels(): IModel[]; + getCreationOptions(): ITextModelCreationOptions; + getModel(resource: URI): IModel; onModelAdded: Event; diff --git a/src/vs/editor/common/services/modelServiceImpl.ts b/src/vs/editor/common/services/modelServiceImpl.ts index 8f451362dd4..b6d3f745f5a 100644 --- a/src/vs/editor/common/services/modelServiceImpl.ts +++ b/src/vs/editor/common/services/modelServiceImpl.ts @@ -170,6 +170,17 @@ class ModelMarkerHandler { } } +interface IRawConfig { + files?: { + eol?: any; + }; + editor?: { + tabSize?: any; + insertSpaces?: any; + detectIndentation?: any; + }; +} + export class ModelServiceImpl implements IModelService { public serviceId = IModelService; @@ -210,20 +221,20 @@ export class ModelServiceImpl implements IModelService { this._workerHelper = this._threadService.getRemotable(ModelServiceWorkerHelper); this._configurationService = configurationService; - let readDefaultEOL = (config:any) => { + let readConfig = (config:IRawConfig) => { const eol = config.files && config.files.eol; - let newTabSize = DEFAULT_INDENTATION.tabSize; + let tabSize = DEFAULT_INDENTATION.tabSize; if (config.editor && typeof config.editor.tabSize !== 'undefined') { let parsedTabSize = parseInt(config.editor.tabSize, 10); if (!isNaN(parsedTabSize)) { - newTabSize = parsedTabSize; + tabSize = parsedTabSize; } } - let newInsertSpaces = DEFAULT_INDENTATION.insertSpaces; + let insertSpaces = DEFAULT_INDENTATION.insertSpaces; if (config.editor && typeof config.editor.insertSpaces !== 'undefined') { - newInsertSpaces = (config.editor.insertSpaces === 'false' ? false : Boolean(config.editor.insertSpaces)); + insertSpaces = (config.editor.insertSpaces === 'false' ? false : Boolean(config.editor.insertSpaces)); } let newDefaultEOL = this._modelCreationOptions.defaultEOL; @@ -234,19 +245,23 @@ export class ModelServiceImpl implements IModelService { } let detectIndentation = DEFAULT_INDENTATION.detectIndentation; + if (config.editor && typeof config.editor.detectIndentation !== 'undefined') { + detectIndentation = (config.editor.detectIndentation === 'false' ? false : Boolean(config.editor.detectIndentation)); + } - this._modelCreationOptions = { - tabSize: newTabSize, - insertSpaces: newInsertSpaces, + this._setModelOptions({ + tabSize: tabSize, + insertSpaces: insertSpaces, detectIndentation: detectIndentation, defaultEOL: newDefaultEOL - }; + }); + }; this._configurationServiceSubscription = this._configurationService.addListener2(ConfigurationServiceEventTypes.UPDATED, (e: IConfigurationServiceEvent) => { - readDefaultEOL(e.config); + readConfig(e.config); }); this._configurationService.loadConfiguration().then((config) => { - readDefaultEOL(config); + readConfig(config); }); this._models = {}; @@ -260,6 +275,26 @@ export class ModelServiceImpl implements IModelService { } } + public getCreationOptions(): editorCommon.ITextModelCreationOptions { + return this._modelCreationOptions; + } + + private _setModelOptions(newOpts: editorCommon.ITextModelCreationOptions): void { + if ( + (this._modelCreationOptions.detectIndentation === newOpts.detectIndentation) + && (this._modelCreationOptions.insertSpaces === newOpts.insertSpaces) + && (this._modelCreationOptions.tabSize === newOpts.tabSize) + ) { + // Same indent opts, no need to touch created models + this._modelCreationOptions = newOpts; + return; + } + this._modelCreationOptions = newOpts; + + // TODO@Alex TODO@indent + // console.log('I NEED TO UPDATE EXISTING MODELS!!!'); + } + public dispose(): void { if(this._markerServiceSubscription) { this._markerServiceSubscription.dispose(); diff --git a/src/vs/editor/contrib/indentation/common/indentation.ts b/src/vs/editor/contrib/indentation/common/indentation.ts index 65342b71f12..fcd1d7affa0 100644 --- a/src/vs/editor/contrib/indentation/common/indentation.ts +++ b/src/vs/editor/contrib/indentation/common/indentation.ts @@ -10,6 +10,7 @@ import {ICommonCodeEditor, IEditorActionDescriptorData} from 'vs/editor/common/e import {CommonEditorRegistry, EditorActionDescriptor} from 'vs/editor/common/editorCommonExtensions'; import {IndentationToSpacesCommand, IndentationToTabsCommand} from 'vs/editor/contrib/indentation/common/indentationCommands'; import {IQuickOpenService} from 'vs/workbench/services/quickopen/common/quickOpenService'; +import {IModelService} from 'vs/editor/common/services/modelService'; export class IndentationToSpacesAction extends EditorAction { static ID = 'editor.action.indentationToSpaces'; @@ -26,10 +27,9 @@ export class IndentationToSpacesAction extends EditorAction { let modelOpts = model.getOptions(); const command = new IndentationToSpacesCommand(this.editor.getSelection(), modelOpts.tabSize); this.editor.executeCommands(this.id, [command]); - // TODO@Alex TODO@indent - // model.updateOptions({ - // insertSpaces: true - // }); + model.updateOptions({ + insertSpaces: true + }); return TPromise.as(true); } @@ -50,10 +50,9 @@ export class IndentationToTabsAction extends EditorAction { let modelOpts = model.getOptions(); const command = new IndentationToTabsCommand(this.editor.getSelection(), modelOpts.tabSize); this.editor.executeCommands(this.id, [command]); - // TODO@Alex TODO@indent - // model.updateOptions({ - // insertSpaces: false - // }); + model.updateOptions({ + insertSpaces: false + }); return TPromise.as(true); } @@ -63,7 +62,8 @@ export class ChangeIndentationSizeAction extends EditorAction { constructor(descriptor: IEditorActionDescriptorData, editor: ICommonCodeEditor, private insertSpaces: boolean, - private quickOpenService: IQuickOpenService + private quickOpenService: IQuickOpenService, + private modelService:IModelService ) { super(descriptor, editor); } @@ -73,22 +73,22 @@ export class ChangeIndentationSizeAction extends EditorAction { if (!model) { return; } - let modelOpts = model.getOptions(); + + let creationOpts = this.modelService.getCreationOptions(); const picks = [1, 2, 3, 4, 5, 6, 7, 8].map(n => ({ id: n.toString(), label: n.toString(), - description: n === modelOpts.tabSize ? nls.localize('configuredTabSize', "Configured Tab Size") : null + description: n === creationOpts.tabSize ? nls.localize('configuredTabSize', "Configured Tab Size") : null })); - const autoFocusIndex = Math.min(modelOpts.tabSize - 1, 7); + const autoFocusIndex = Math.min(creationOpts.tabSize - 1, 7); return TPromise.timeout(50 /* quick open is sensitive to being opened so soon after another */).then(() => this.quickOpenService.pick(picks, { placeHolder: nls.localize('selectTabWidth', "Select Tab Size for Current File"), autoFocus: { autoFocusIndex } }).then(pick => { if (pick) { - // TODO@Alex TODO@indent - // model.updateOptions({ - // tabSize: parseInt(pick.label) - // insertSpaces: this.insertSpaces - // }); + model.updateOptions({ + tabSize: parseInt(pick.label, 10), + insertSpaces: this.insertSpaces + }); } return true; @@ -101,10 +101,13 @@ export class IndentUsingTabs extends ChangeIndentationSizeAction { static ID = 'editor.action.indentUsingTabs'; - constructor(descriptor: IEditorActionDescriptorData, editor: ICommonCodeEditor, - @IQuickOpenService quickOpenService: IQuickOpenService + constructor( + descriptor: IEditorActionDescriptorData, + editor: ICommonCodeEditor, + @IQuickOpenService quickOpenService: IQuickOpenService, + @IModelService modelService:IModelService ) { - super(descriptor, editor, false, quickOpenService); + super(descriptor, editor, false, quickOpenService, modelService); } } @@ -112,10 +115,36 @@ export class IndentUsingSpaces extends ChangeIndentationSizeAction { static ID = 'editor.action.indentUsingSpaces'; - constructor(descriptor: IEditorActionDescriptorData, editor: ICommonCodeEditor, - @IQuickOpenService quickOpenService: IQuickOpenService + constructor( + descriptor: IEditorActionDescriptorData, + editor: ICommonCodeEditor, + @IQuickOpenService quickOpenService: IQuickOpenService, + @IModelService modelService:IModelService ) { - super(descriptor, editor, true, quickOpenService); + super(descriptor, editor, true, quickOpenService, modelService); + } +} + +export class DetectIndentation extends EditorAction { + + static ID = 'editor.action.detectIndentation'; + + constructor( + descriptor: IEditorActionDescriptorData, + editor: ICommonCodeEditor, + @IModelService private modelService:IModelService + ) { + super(descriptor, editor); + } + + public run(): TPromise { + let model = this.editor.getModel(); + if (!model) { + return; + } + + let creationOpts = this.modelService.getCreationOptions(); + model.detectIndentation(creationOpts.insertSpaces, creationOpts.tabSize); } } @@ -140,4 +169,5 @@ CommonEditorRegistry.registerEditorAction(new EditorActionDescriptor(Indentation CommonEditorRegistry.registerEditorAction(new EditorActionDescriptor(IndentationToTabsAction, IndentationToTabsAction.ID, nls.localize('indentationToTabs', "Convert Indentation to Tabs"))); CommonEditorRegistry.registerEditorAction(new EditorActionDescriptor(IndentUsingSpaces, IndentUsingSpaces.ID, nls.localize('indentUsingSpaces', "Indent Using Spaces"))); CommonEditorRegistry.registerEditorAction(new EditorActionDescriptor(IndentUsingTabs, IndentUsingTabs.ID, nls.localize('indentUsingTabs', "Indent Using Tabs"))); +CommonEditorRegistry.registerEditorAction(new EditorActionDescriptor(DetectIndentation, DetectIndentation.ID, nls.localize('detectIndentation', "Detect Indentation from Content"))); CommonEditorRegistry.registerEditorAction(new EditorActionDescriptor(ToggleRenderWhitespaceAction, ToggleRenderWhitespaceAction.ID, nls.localize('toggleRenderWhitespace', "Toggle Render Whitespace"))); diff --git a/src/vs/workbench/api/node/mainThreadEditors.ts b/src/vs/workbench/api/node/mainThreadEditors.ts index 8bad4a55cce..bb21b41595b 100644 --- a/src/vs/workbench/api/node/mainThreadEditors.ts +++ b/src/vs/workbench/api/node/mainThreadEditors.ts @@ -19,7 +19,6 @@ export interface ITextEditorConfigurationUpdate { insertSpaces?: boolean | string; } export interface IResolvedTextEditorConfiguration { - IResolvedTextEditorConfiguration: any; // TODO@Alex TODO@indent tabSize: number; insertSpaces: boolean; } @@ -199,7 +198,6 @@ export class MainThreadTextEditor { private static _readConfiguration(model:EditorCommon.IModel): IResolvedTextEditorConfiguration { let indent = model.getOptions(); return { - IResolvedTextEditorConfiguration: null, // TODO@Alex TODO@indent insertSpaces: indent.insertSpaces, tabSize: indent.tabSize }; diff --git a/src/vs/workbench/browser/parts/editor/editorStatus.ts b/src/vs/workbench/browser/parts/editor/editorStatus.ts index 541e296e888..1fc75e06b26 100644 --- a/src/vs/workbench/browser/parts/editor/editorStatus.ts +++ b/src/vs/workbench/browser/parts/editor/editorStatus.ts @@ -24,7 +24,7 @@ import {ICommonCodeEditor} from 'vs/editor/common/editorCommon'; import {OpenGlobalSettingsAction} from 'vs/workbench/browser/actions/openSettings'; import {ICodeEditor, IDiffEditor} from 'vs/editor/browser/editorBrowser'; import {EndOfLineSequence, ITokenizedModel, EditorType, IEditorSelection, ITextModel, IDiffEditorModel, IEditor} from 'vs/editor/common/editorCommon'; -import {IndentUsingSpaces, IndentUsingTabs, IndentationToSpacesAction, IndentationToTabsAction} from 'vs/editor/contrib/indentation/common/indentation'; +import {IndentUsingSpaces, IndentUsingTabs, DetectIndentation, IndentationToSpacesAction, IndentationToTabsAction} from 'vs/editor/contrib/indentation/common/indentation'; import {EventType, ResourceEvent, EditorEvent, TextEditorSelectionEvent} from 'vs/workbench/common/events'; import {BaseTextEditor} from 'vs/workbench/browser/parts/editor/textEditor'; import {IEditor as IBaseEditor} from 'vs/platform/editor/common/editor'; @@ -732,9 +732,9 @@ class ChangeIndentationAction extends Action { } const control = activeEditor.getControl(); - const picks = [control.getAction(IndentUsingSpaces.ID), control.getAction(IndentUsingTabs.ID), control.getAction(IndentationToSpacesAction.ID), control.getAction(IndentationToTabsAction.ID)]; + const picks = [control.getAction(IndentUsingSpaces.ID), control.getAction(IndentUsingTabs.ID), control.getAction(DetectIndentation.ID), control.getAction(IndentationToSpacesAction.ID), control.getAction(IndentationToTabsAction.ID)]; (picks[0]).separator = { label: nls.localize('indentView', "change view") }; - (picks[2]).separator = { label: nls.localize('indentConvert', "convert file"), border: true }; + (picks[3]).separator = { label: nls.localize('indentConvert', "convert file"), border: true }; return this.quickOpenService.pick(picks, { placeHolder: nls.localize('pickAction', "Select Action") }).then(action => action && action.run()); }