From d5887eeb03dc2aa9ed1d7226dcb58dd2acd88dba Mon Sep 17 00:00:00 2001 From: Daniel Podder Date: Mon, 28 Dec 2015 08:38:48 -0600 Subject: [PATCH 01/36] Fix parsing of 'rem' directive - remove 'rem' from list of keywords so it's not misparsed as a keyword - change \s to \b so that 'rem' is detected in case of @rem or &rem, etc. --- extensions/bat/syntaxes/Batch File.tmLanguage | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/extensions/bat/syntaxes/Batch File.tmLanguage b/extensions/bat/syntaxes/Batch File.tmLanguage index 2a7752e5cfc..bc52ff5486d 100644 --- a/extensions/bat/syntaxes/Batch File.tmLanguage +++ b/extensions/bat/syntaxes/Batch File.tmLanguage @@ -10,7 +10,7 @@ name keyword.command.dosbatch match - \b(?i)(?:append|assoc|at|attrib|break|cacls|cd|chcp|chdir|chkdsk|chkntfs|cls|cmd|color|comp|compact|convert|copy|date|del|dir|diskcomp|diskcopy|doskey|echo|endlocal|erase|fc|find|findstr|format|ftype|graftabl|help|keyb|label|md|mkdir|mode|more|move|path|pause|popd|print|prompt|pushd|rd|recover|rem|ren|rename|replace|restore|rmdir|set|setlocal|shift|sort|start|subst|time|title|tree|type|ver|verify|vol|xcopy)\b + \b(?i)(?:append|assoc|at|attrib|break|cacls|cd|chcp|chdir|chkdsk|chkntfs|cls|cmd|color|comp|compact|convert|copy|date|del|dir|diskcomp|diskcopy|doskey|echo|endlocal|erase|fc|find|findstr|format|ftype|graftabl|help|keyb|label|md|mkdir|mode|more|move|path|pause|popd|print|prompt|pushd|rd|recover|ren|rename|replace|restore|rmdir|set|setlocal|shift|sort|start|subst|time|title|tree|type|ver|verify|vol|xcopy)\b name @@ -54,7 +54,7 @@ name comment.line.rem.dosbatch match - (?:^|\s)((?i)rem)(?:$|\s.*$) + \b((?i)rem)(?:$|\s.*$) name From 3edcbfbd911eb5b443da5066d1837778b7d02be0 Mon Sep 17 00:00:00 2001 From: Daniel Podder Date: Mon, 28 Dec 2015 10:53:08 -0600 Subject: [PATCH 02/36] Add tokens to grammar for parameter/loop variables --- extensions/bat/syntaxes/Batch File.tmLanguage | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/extensions/bat/syntaxes/Batch File.tmLanguage b/extensions/bat/syntaxes/Batch File.tmLanguage index bc52ff5486d..dc106c723c8 100644 --- a/extensions/bat/syntaxes/Batch File.tmLanguage +++ b/extensions/bat/syntaxes/Batch File.tmLanguage @@ -62,6 +62,34 @@ match \s*:\s*:.*$ + + captures + + 1 + + name + variable.parameter.function.begin.shell + + + name + variable.parameter.function.dosbatch + match + (?i)(%)(~(?:f|d|p|n|x|s|a|t|z|\$[^:]*:)*)?\d + + + captures + + 1 + + name + variable.parameter.loop.begin.shell + + + name + variable.parameter.loop.dosbatch + match + (?i)(%%)(~(?:f|d|p|n|x|s|a|t|z|\$[^:]*:)*)?[a-z] + begin " From 867a87f0702a48dfd9ab9e85a3021451fe1d3a61 Mon Sep 17 00:00:00 2001 From: Daniel Podder Date: Mon, 28 Dec 2015 12:30:15 -0600 Subject: [PATCH 03/36] Add tokens to grammar for environment variables --- extensions/bat/syntaxes/Batch File.tmLanguage | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/extensions/bat/syntaxes/Batch File.tmLanguage b/extensions/bat/syntaxes/Batch File.tmLanguage index dc106c723c8..3c97a70b4c2 100644 --- a/extensions/bat/syntaxes/Batch File.tmLanguage +++ b/extensions/bat/syntaxes/Batch File.tmLanguage @@ -90,6 +90,44 @@ match (?i)(%%)(~(?:f|d|p|n|x|s|a|t|z|\$[^:]*:)*)?[a-z] + + captures + + 1 + + name + variable.other.parsetime.begin.shell + + 2 + + name + variable.other.parsetime.end.shell + + + name + variable.other.parsetime.dosbatch + match + (%)[^%]+(%) + + + captures + + 1 + + name + variable.other.delayed.begin.shell + + 2 + + name + variable.other.delayed.end.shell + + + name + variable.other.delayed.dosbatch + match + (!)[^!]+(!) + begin " From 2e273e710f5ef069e800c749cf8a4779687d17d6 Mon Sep 17 00:00:00 2001 From: Daniel Podder Date: Mon, 28 Dec 2015 12:48:21 -0600 Subject: [PATCH 04/36] Stop strings from spilling onto next line --- extensions/bat/syntaxes/Batch File.tmLanguage | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/bat/syntaxes/Batch File.tmLanguage b/extensions/bat/syntaxes/Batch File.tmLanguage index 3c97a70b4c2..0554fa176d3 100644 --- a/extensions/bat/syntaxes/Batch File.tmLanguage +++ b/extensions/bat/syntaxes/Batch File.tmLanguage @@ -150,7 +150,7 @@ name string.quoted.double.dosbatch end - " + "|$ name From 2dd02eba0be677540ccc2d3198503cfa5a740cd2 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Thu, 7 Jan 2016 09:49:04 +0100 Subject: [PATCH 05/36] [theme] rename extension to 'colorful-defaults' --- .../theme-colorful-defaults/package.json | 25 +++++++++++++++++++ .../themes/dark_plus.tmTheme | 0 .../themes/light_plus.tmTheme | 0 extensions/theme-dark-plus/package.json | 22 ---------------- 4 files changed, 25 insertions(+), 22 deletions(-) create mode 100644 extensions/theme-colorful-defaults/package.json rename extensions/{theme-dark-plus => theme-colorful-defaults}/themes/dark_plus.tmTheme (100%) rename extensions/{theme-dark-plus => theme-colorful-defaults}/themes/light_plus.tmTheme (100%) delete mode 100644 extensions/theme-dark-plus/package.json diff --git a/extensions/theme-colorful-defaults/package.json b/extensions/theme-colorful-defaults/package.json new file mode 100644 index 00000000000..373f92d6975 --- /dev/null +++ b/extensions/theme-colorful-defaults/package.json @@ -0,0 +1,25 @@ +{ + "name": "theme-colorful-defaults", + "displayName": "Colorful Default Themes - Please provide feedback in issue 1849", + "description": "The default VS Code Light and Dark themes with a touch of color. We are considering adding these to the default themes in the January release. Please provide feedback in issue 1849.", + "categories": [ "Themes" ], + "version": "0.1.5", + "publisher": "aeschli", + "engines": { "vscode": "*" }, + "contributes": { + "themes": [ + { + "label": "Dark+ (Experimental)", + "description": "Default dark theme with a touch of color - Please provide feedback in issue 1849", + "uiTheme": "vs-dark", + "path": "./themes/dark_plus.tmTheme" + }, + { + "label": "Light+ (Experimental)", + "description": "Default light theme with a touch of color - Please provide feedback in issue 1849", + "uiTheme": "vs", + "path": "./themes/light_plus.tmTheme" + } + ] + } +} \ No newline at end of file diff --git a/extensions/theme-dark-plus/themes/dark_plus.tmTheme b/extensions/theme-colorful-defaults/themes/dark_plus.tmTheme similarity index 100% rename from extensions/theme-dark-plus/themes/dark_plus.tmTheme rename to extensions/theme-colorful-defaults/themes/dark_plus.tmTheme diff --git a/extensions/theme-dark-plus/themes/light_plus.tmTheme b/extensions/theme-colorful-defaults/themes/light_plus.tmTheme similarity index 100% rename from extensions/theme-dark-plus/themes/light_plus.tmTheme rename to extensions/theme-colorful-defaults/themes/light_plus.tmTheme diff --git a/extensions/theme-dark-plus/package.json b/extensions/theme-dark-plus/package.json deleted file mode 100644 index 83d805bda92..00000000000 --- a/extensions/theme-dark-plus/package.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "name": "theme-dark-plus", - "version": "0.1.0", - "publisher": "vscode", - "engines": { "vscode": "*" }, - "contributes": { - "themes": [ - { - "label": "Dark+", - "description": "Dark theme with a touch of color", - "uiTheme": "vs-dark", - "path": "./themes/dark_plus.tmTheme" - }, - { - "label": "Light+", - "description": "Light theme with a touch of color", - "uiTheme": "vs", - "path": "./themes/light_plus.tmTheme" - } - ] - } -} \ No newline at end of file From bdae5cdc6d5033a3b48ea838622c2c5915240b26 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Thu, 7 Jan 2016 10:49:22 +0100 Subject: [PATCH 06/36] remove experimental from label --- extensions/theme-colorful-defaults/package.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/extensions/theme-colorful-defaults/package.json b/extensions/theme-colorful-defaults/package.json index 373f92d6975..41318692e88 100644 --- a/extensions/theme-colorful-defaults/package.json +++ b/extensions/theme-colorful-defaults/package.json @@ -9,14 +9,14 @@ "contributes": { "themes": [ { - "label": "Dark+ (Experimental)", - "description": "Default dark theme with a touch of color - Please provide feedback in issue 1849", + "label": "Dark+", + "description": "Default dark theme with a touch of color", "uiTheme": "vs-dark", "path": "./themes/dark_plus.tmTheme" }, { - "label": "Light+ (Experimental)", - "description": "Default light theme with a touch of color - Please provide feedback in issue 1849", + "label": "Light+", + "description": "Default light theme with a touch of color", "uiTheme": "vs", "path": "./themes/light_plus.tmTheme" } From 4732e95b1dace6d74a80a41cf4658592b113dce8 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Thu, 7 Jan 2016 16:00:01 +0100 Subject: [PATCH 07/36] [php] prefilter completion proposals based on prefix --- .../src/features/completionItemProvider.ts | 30 ++++++++++++------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/extensions/php/src/features/completionItemProvider.ts b/extensions/php/src/features/completionItemProvider.ts index 6eb3b0953db..426649fb314 100644 --- a/extensions/php/src/features/completionItemProvider.ts +++ b/extensions/php/src/features/completionItemProvider.ts @@ -14,6 +14,8 @@ export default class PHPCompletionItemProvider implements CompletionItemProvider public provideCompletionItems(document: TextDocument, position: Position, token: CancellationToken): Promise { let result: CompletionItem[] = []; + var range = document.getWordRangeAtPosition(position); + var prefix = range ? document.getText(range) : ''; var added : any = {}; var createNewProposal = function(kind: CompletionItemKind, name: string, entry: phpGlobals.IEntry) : CompletionItem { @@ -30,39 +32,45 @@ export default class PHPCompletionItemProvider implements CompletionItemProvider return proposal; }; + var matches = (name:string) => { + return prefix.length === 0 || name.length > prefix.length && name.substr(0, prefix.length) === prefix; + } + for (var name in phpGlobals.globalvariables) { - if (phpGlobals.globalvariables.hasOwnProperty(name)) { + if (phpGlobals.globalvariables.hasOwnProperty(name) && matches(name)) { added[name] = true; result.push(createNewProposal(CompletionItemKind.Variable, name, phpGlobals.globalvariables[name])); } } for (var name in phpGlobals.globalfunctions) { - if (phpGlobals.globalfunctions.hasOwnProperty(name)) { + if (phpGlobals.globalfunctions.hasOwnProperty(name) && matches(name)) { added[name] = true; result.push(createNewProposal(CompletionItemKind.Function, name, phpGlobals.globalfunctions[name])); } } for (var name in phpGlobals.compiletimeconstants) { - if (phpGlobals.compiletimeconstants.hasOwnProperty(name)) { + if (phpGlobals.compiletimeconstants.hasOwnProperty(name) && matches(name)) { added[name] = true; result.push(createNewProposal(CompletionItemKind.Field, name, phpGlobals.compiletimeconstants[name])); } } for (var name in phpGlobals.keywords) { - if (phpGlobals.keywords.hasOwnProperty(name)) { + if (phpGlobals.keywords.hasOwnProperty(name) && matches(name)) { added[name] = true; result.push(createNewProposal(CompletionItemKind.Keyword, name, phpGlobals.keywords[name])); } } var text = document.getText(); - var variableMatch = /\$([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)/g; - var match : RegExpExecArray = null; - while (match = variableMatch.exec(text)) { - var word = match[0]; - if (!added[word]) { - added[word] = true; - result.push(createNewProposal(CompletionItemKind.Variable, word, null)); + if (matches('$')) { + var variableMatch = /\$([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)/g; + var match : RegExpExecArray = null; + while (match = variableMatch.exec(text)) { + var word = match[0]; + if (!added[word]) { + added[word] = true; + result.push(createNewProposal(CompletionItemKind.Variable, word, null)); + } } } var functionMatch = /function\s+([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)\s*\(/g; From 75eaa861620cf0b169459caecf4db6189e30cfd0 Mon Sep 17 00:00:00 2001 From: isidor Date: Thu, 7 Jan 2016 15:43:42 +0100 Subject: [PATCH 08/36] debug: there can be only one breakpoint widget --- .../parts/debug/browser/breakpointWidget.ts | 18 +++++++++++++----- .../debug/electron-browser/debugService.ts | 9 +++++++-- 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/src/vs/workbench/parts/debug/browser/breakpointWidget.ts b/src/vs/workbench/parts/debug/browser/breakpointWidget.ts index 0d3169785b1..799e56c0e24 100644 --- a/src/vs/workbench/parts/debug/browser/breakpointWidget.ts +++ b/src/vs/workbench/parts/debug/browser/breakpointWidget.ts @@ -20,12 +20,14 @@ const $ = dom.emmet; export class BreakpointWidget extends ZoneWidget { private inputBox: InputBox; + private toDispose: lifecycle.IDisposable[]; constructor(editor: editorbrowser.ICodeEditor, private lineNumber: number, @IContextViewService private contextViewService: IContextViewService, @debug.IDebugService private debugService: debug.IDebugService ) { super(editor, { showFrame: true, showArrow: false }); + this.toDispose = []; this.create(); } @@ -36,15 +38,16 @@ export class BreakpointWidget extends ZoneWidget { const inputBoxContainer = dom.append(container, $('.inputBoxContainer')); this.inputBox = new InputBox(inputBoxContainer, this.contextViewService, { - placeholder: `The breakpoint on line ${ this.lineNumber } will only stop if this condition is true` + placeholder: `Breakpoint on line ${ this.lineNumber } will only stop if this condition is true. 'Enter' to accept, 'esc' to cancel.` }); + this.toDispose.push(this.inputBox); + dom.addClass(this.inputBox.inputElement, platform.isWindows ? 'windows' : platform.isMacintosh ? 'mac' : 'linux'); this.inputBox.value = (breakpoint && breakpoint.condition) ? breakpoint.condition : ''; // Due to an electron bug we have to do the timeout, otherwise we do not get focus setTimeout(() => this.inputBox.focus(), 0); let disposed = false; - const toDispose: [lifecycle.IDisposable] = [this.inputBox, this]; const wrapUp = async.once((success: boolean) => { if (!disposed) { disposed = true; @@ -64,11 +67,11 @@ export class BreakpointWidget extends ZoneWidget { this.debugService.toggleBreakpoint(raw).done(null, errors.onUnexpectedError); } - lifecycle.disposeAll(toDispose); + this.dispose(); } }); - toDispose.push(dom.addStandardDisposableListener(this.inputBox.inputElement, 'keydown', (e: dom.IKeyboardEvent) => { + this.toDispose.push(dom.addStandardDisposableListener(this.inputBox.inputElement, 'keydown', (e: dom.IKeyboardEvent) => { const isEscape = e.equals(CommonKeybindings.ESCAPE); const isEnter = e.equals(CommonKeybindings.ENTER); if (isEscape || isEnter) { @@ -76,8 +79,13 @@ export class BreakpointWidget extends ZoneWidget { } })); - toDispose.push(dom.addDisposableListener(this.inputBox.inputElement, 'blur', () => { + this.toDispose.push(dom.addDisposableListener(this.inputBox.inputElement, 'blur', () => { wrapUp(true); })); } + + public dispose(): void { + super.dispose(); + lifecycle.disposeAll(this.toDispose); + } } diff --git a/src/vs/workbench/parts/debug/electron-browser/debugService.ts b/src/vs/workbench/parts/debug/electron-browser/debugService.ts index cc30aa2921f..e103b775474 100644 --- a/src/vs/workbench/parts/debug/electron-browser/debugService.ts +++ b/src/vs/workbench/parts/debug/electron-browser/debugService.ts @@ -66,6 +66,7 @@ export class DebugService extends ee.EventEmitter implements debug.IDebugService private lastTaskEvent: TaskEvent; private toDispose: lifecycle.IDisposable[]; private inDebugMode: IKeybindingContextKey; + private breakpointWidget: BreakpointWidget; constructor( @IStorageService private storageService: IStorageService, @@ -424,8 +425,12 @@ export class DebugService extends ee.EventEmitter implements debug.IDebugService } public editBreakpoint(editor: editorbrowser.ICodeEditor, lineNumber: number): Promise { - const breakpointWidget = this.instantiationService.createInstance(BreakpointWidget, editor, lineNumber); - breakpointWidget.show({ lineNumber, column: 1 }, 2); + if (this.breakpointWidget) { + this.breakpointWidget.dispose(); + } + + this.breakpointWidget = this.instantiationService.createInstance(BreakpointWidget, editor, lineNumber); + this.breakpointWidget.show({ lineNumber, column: 1 }, 2); return Promise.as(true); } From dc7344aba0a7a0ad6eb1dc0803346abe5be92a67 Mon Sep 17 00:00:00 2001 From: isidor Date: Thu, 7 Jan 2016 15:44:47 +0100 Subject: [PATCH 09/36] debug: do not hide breakpoint widget on 'blur' --- src/vs/workbench/parts/debug/browser/breakpointWidget.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/vs/workbench/parts/debug/browser/breakpointWidget.ts b/src/vs/workbench/parts/debug/browser/breakpointWidget.ts index 799e56c0e24..ba1a11a3291 100644 --- a/src/vs/workbench/parts/debug/browser/breakpointWidget.ts +++ b/src/vs/workbench/parts/debug/browser/breakpointWidget.ts @@ -78,10 +78,6 @@ export class BreakpointWidget extends ZoneWidget { wrapUp(isEnter); } })); - - this.toDispose.push(dom.addDisposableListener(this.inputBox.inputElement, 'blur', () => { - wrapUp(true); - })); } public dispose(): void { From 997f4a19c4f88cc8a96db1a13779af4f153af2bd Mon Sep 17 00:00:00 2001 From: isidor Date: Thu, 7 Jan 2016 16:24:50 +0100 Subject: [PATCH 10/36] debug: breakpoint widget should get dismissed with 'esc' --- .../parts/debug/browser/breakpointWidget.ts | 25 +++++++++++++++++-- .../debug/electron-browser/debugService.ts | 11 ++++---- 2 files changed, 28 insertions(+), 8 deletions(-) diff --git a/src/vs/workbench/parts/debug/browser/breakpointWidget.ts b/src/vs/workbench/parts/debug/browser/breakpointWidget.ts index ba1a11a3291..f05ceab53af 100644 --- a/src/vs/workbench/parts/debug/browser/breakpointWidget.ts +++ b/src/vs/workbench/parts/debug/browser/breakpointWidget.ts @@ -6,29 +6,42 @@ import 'vs/css!../browser/media/breakpointWidget'; import async = require('vs/base/common/async'); import errors = require('vs/base/common/errors'); -import { CommonKeybindings } from 'vs/base/common/keyCodes'; +import { CommonKeybindings, KeyCode } from 'vs/base/common/keyCodes'; import platform = require('vs/base/common/platform'); import lifecycle = require('vs/base/common/lifecycle'); import dom = require('vs/base/browser/dom'); import { InputBox } from 'vs/base/browser/ui/inputbox/inputBox'; +import { CommonEditorRegistry } from 'vs/editor/common/editorCommonExtensions'; import editorbrowser = require('vs/editor/browser/editorBrowser'); import { ZoneWidget } from 'vs/editor/contrib/zoneWidget/browser/zoneWidget'; import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; +import { IKeybindingService, IKeybindingContextKey } from 'vs/platform/keybinding/common/keybindingService'; import debug = require('vs/workbench/parts/debug/common/debug'); const $ = dom.emmet; +const CONTEXT_BREAKPOINT_WIDGET_VISIBLE = 'breakpointWidgetVisible'; +const CLOSE_BREAKPOINT_WIDGET_COMMAND_ID = 'closeBreakpointWidget'; export class BreakpointWidget extends ZoneWidget { + + public static INSTANCE: BreakpointWidget; + private inputBox: InputBox; private toDispose: lifecycle.IDisposable[]; + private breakpointWidgetVisible: IKeybindingContextKey; constructor(editor: editorbrowser.ICodeEditor, private lineNumber: number, @IContextViewService private contextViewService: IContextViewService, - @debug.IDebugService private debugService: debug.IDebugService + @debug.IDebugService private debugService: debug.IDebugService, + @IKeybindingService keybindingService: IKeybindingService ) { super(editor, { showFrame: true, showArrow: false }); + this.toDispose = []; this.create(); + this.breakpointWidgetVisible = keybindingService.createKey(CONTEXT_BREAKPOINT_WIDGET_VISIBLE, false); + this.breakpointWidgetVisible.set(true); + BreakpointWidget.INSTANCE = this; } public fillContainer(container: HTMLElement): void { @@ -82,6 +95,14 @@ export class BreakpointWidget extends ZoneWidget { public dispose(): void { super.dispose(); + this.breakpointWidgetVisible.reset(); + BreakpointWidget.INSTANCE = undefined; lifecycle.disposeAll(this.toDispose); } } + +CommonEditorRegistry.registerEditorCommand(CLOSE_BREAKPOINT_WIDGET_COMMAND_ID, CommonEditorRegistry.commandWeight(8), { primary: KeyCode.Escape, }, false, CONTEXT_BREAKPOINT_WIDGET_VISIBLE, (ctx, editor, args) => { + if (BreakpointWidget.INSTANCE) { + BreakpointWidget.INSTANCE.dispose(); + } +}); diff --git a/src/vs/workbench/parts/debug/electron-browser/debugService.ts b/src/vs/workbench/parts/debug/electron-browser/debugService.ts index e103b775474..a5570c248e0 100644 --- a/src/vs/workbench/parts/debug/electron-browser/debugService.ts +++ b/src/vs/workbench/parts/debug/electron-browser/debugService.ts @@ -66,7 +66,6 @@ export class DebugService extends ee.EventEmitter implements debug.IDebugService private lastTaskEvent: TaskEvent; private toDispose: lifecycle.IDisposable[]; private inDebugMode: IKeybindingContextKey; - private breakpointWidget: BreakpointWidget; constructor( @IStorageService private storageService: IStorageService, @@ -425,12 +424,12 @@ export class DebugService extends ee.EventEmitter implements debug.IDebugService } public editBreakpoint(editor: editorbrowser.ICodeEditor, lineNumber: number): Promise { - if (this.breakpointWidget) { - this.breakpointWidget.dispose(); + if (BreakpointWidget.INSTANCE) { + BreakpointWidget.INSTANCE.dispose(); } - - this.breakpointWidget = this.instantiationService.createInstance(BreakpointWidget, editor, lineNumber); - this.breakpointWidget.show({ lineNumber, column: 1 }, 2); + + this.instantiationService.createInstance(BreakpointWidget, editor, lineNumber); + BreakpointWidget.INSTANCE.show({ lineNumber, column: 1 }, 2); return Promise.as(true); } From 907e6adb615a9651c48f150378597b09eb9360b3 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Thu, 7 Jan 2016 16:37:18 +0100 Subject: [PATCH 11/36] clean up cssrules for comment tokens --- src/vs/editor/browser/widget/media/tokens.css | 42 ++----------------- 1 file changed, 3 insertions(+), 39 deletions(-) diff --git a/src/vs/editor/browser/widget/media/tokens.css b/src/vs/editor/browser/widget/media/tokens.css index aa83928a7f0..6798f1cdcac 100644 --- a/src/vs/editor/browser/widget/media/tokens.css +++ b/src/vs/editor/browser/widget/media/tokens.css @@ -14,8 +14,6 @@ .monaco-editor.vs .token.constant { color: #dd0000; } .monaco-editor.vs .token.string { color: #A31515; } .monaco-editor.vs .token.string.escape { color: #A31515; } -.monaco-editor.vs .token.comment { color: #008000; } -.monaco-editor.vs .token.comment.shebang { color: #929292; } .monaco-editor.vs .token.literal { color: #000000; } .monaco-editor.vs .token.literal.string { color: #A31515; } .monaco-editor.vs .token.literal.hex { color: #e07000; } @@ -83,7 +81,6 @@ /* VSXML */ .monaco-editor.vs .token.vs { color: #006400; } -.monaco-editor.vs .token.comment.vs { color: #aeb9ae; } .monaco-editor.vs .token.tag.vs { color: #aeb9ae; } .monaco-editor.vs .token.attribute.name.vs { color: #aeb9ae; } .monaco-editor.vs .token.attribute.value.vs { color: #2c51cc; } @@ -109,9 +106,6 @@ .monaco-editor.vs-dark .token.constant { color: #dd0000; } .monaco-editor.vs-dark .token.string { color: #CE9178; } .monaco-editor.vs-dark .token.string.escape { color: #CE9178; } -.monaco-editor.vs-dark .token.comment { color: #929292; } -.monaco-editor.vs-dark .token.comment.shebang { color: #929292; } -.monaco-editor.vs-dark .token.comment.doc { color: #608B4E; } .monaco-editor.vs-dark .token.literal { color: #e00000; } .monaco-editor.vs-dark .token.literal.hex { color: #e07000; } .monaco-editor.vs-dark .token.number { color: #B5CEA8; } @@ -204,9 +198,6 @@ .monaco-editor.hc-black .token.constant { color: #dd0000; } .monaco-editor.hc-black .token.string { color: #CE9178; } .monaco-editor.hc-black .token.string.escape { color: #CE9178; } -.monaco-editor.hc-black .token.comment { color: #008000; } -.monaco-editor.hc-black .token.comment.shebang { color: #929292; } -.monaco-editor.hc-black .token.comment.doc { color: #608B4E; } .monaco-editor.hc-black .token.literal { color: #FFFFFF; } .monaco-editor.hc-black .token.literal.hex { color: #FFFFFF; } @@ -301,18 +292,7 @@ .monaco-editor.vs .token.builtin.function { color: #0000FF; } -.monaco-editor.vs .token.comment, -.monaco-editor.vs .token.comment.block, -.monaco-editor.vs .token.comment.block.documentation, -.monaco-editor.vs .token.comment.line { color: #008000; } -/* -.monaco-editor.vs .token.comment.line.documentation -.monaco-editor.vs .token.comment.line.double-slash -.monaco-editor.vs .token.comment.line.double-dash -.monaco-editor.vs .token.comment.line.number-sign -.monaco-editor.vs .token.comment.line.percentage -.monaco-editor.vs .token.comment.line.character -*/ +.monaco-editor.vs .token.comment { color: #008000; } .monaco-editor.vs .token.constant { color: #dd0000; } .monaco-editor.vs .token.constant.language { color: #0000FF; } @@ -441,15 +421,7 @@ .monaco-editor.vs-dark .token.builtin.function { color: #569CD6; } -.monaco-editor.vs-dark .token.comment, -.monaco-editor.vs-dark .token.comment.block, -.monaco-editor.vs-dark .token.comment.block.documentation, -.monaco-editor.vs-dark .token.comment.line { color: #608B4E; } -/*.monaco-editor.vs-dark .token.comment.line.double-slash -.monaco-editor.vs-dark .token.comment.line.double-dash -.monaco-editor.vs-dark .token.comment.line.number-sign -.monaco-editor.vs-dark .token.comment.line.percentage -.monaco-editor.vs-dark .token.comment.line.character*/ +.monaco-editor.vs-dark .token.comment { color: #608B4E; } .monaco-editor.vs-dark .token.constant { color: #569CD6; } .monaco-editor.vs-dark .token.constant.language { color: #569CD6; } @@ -578,15 +550,7 @@ .monaco-editor.hc-black .token.builtin.function { color: #569CD6; } -.monaco-editor.hc-black .token.comment, -.monaco-editor.hc-black .token.comment.block, -.monaco-editor.hc-black .token.comment.block.documentation, -.monaco-editor.hc-black .token.comment.line { color: #608B4E; } -/*.monaco-editor.hc-black .token.comment.line.double-slash -.monaco-editor.hc-black .token.comment.line.double-dash -.monaco-editor.hc-black .token.comment.line.number-sign -.monaco-editor.hc-black .token.comment.line.percentage -.monaco-editor.hc-black .token.comment.line.character*/ +.monaco-editor.hc-black .token.comment { color: #608B4E; } .monaco-editor.hc-black .token.constant { color: #569CD6; } .monaco-editor.hc-black .token.constant.language { color: #569CD6; } From 920e4024a048e77ecedcaa1c6f74f3506dab1532 Mon Sep 17 00:00:00 2001 From: isidor Date: Thu, 7 Jan 2016 16:47:17 +0100 Subject: [PATCH 12/36] debug: show breakpoint condition in hover --- src/vs/workbench/parts/debug/browser/debugViewer.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/vs/workbench/parts/debug/browser/debugViewer.ts b/src/vs/workbench/parts/debug/browser/debugViewer.ts index 7453ab451b5..0d080b51cfe 100644 --- a/src/vs/workbench/parts/debug/browser/debugViewer.ts +++ b/src/vs/workbench/parts/debug/browser/debugViewer.ts @@ -846,6 +846,9 @@ export class BreakpointsRenderer implements tree.IRenderer { data.filePath.textContent = labels.getPathLabel(paths.dirname(breakpoint.source.uri.fsPath), this.contextService); data.checkbox.checked = breakpoint.enabled; data.actionBar.context = breakpoint; + if (breakpoint.condition) { + data.breakpoint.title = breakpoint.condition; + } } public disposeTemplate(tree: tree.ITree, templateId: string, templateData: any): void { From 1f59ee0612f91806f9abbc4ba57b3241abb5bf0a Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Thu, 7 Jan 2016 17:59:30 +0100 Subject: [PATCH 13/36] fixes #1508 --- .../parts/git/electron-browser/electronGitService.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/parts/git/electron-browser/electronGitService.ts b/src/vs/workbench/parts/git/electron-browser/electronGitService.ts index 7d5c036dd75..180c3631f71 100644 --- a/src/vs/workbench/parts/git/electron-browser/electronGitService.ts +++ b/src/vs/workbench/parts/git/electron-browser/electronGitService.ts @@ -52,7 +52,14 @@ function findGitDarwin(): Promise { return e('git not found'); } - return c(gitPath); + // make sure git executes + exec('git --version', err => { + if (err) { + return e('git not found'); + } + + return c(gitPath); + }); }); }); }); From b20ab37db959c328630aeb880a8573247d26f8aa Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Thu, 7 Jan 2016 18:11:34 +0100 Subject: [PATCH 14/36] fix typo fixes #155 --- src/vs/workbench/parts/extensions/node/extensionsService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/parts/extensions/node/extensionsService.ts b/src/vs/workbench/parts/extensions/node/extensionsService.ts index 5eb0d748d91..84b299148d9 100644 --- a/src/vs/workbench/parts/extensions/node/extensionsService.ts +++ b/src/vs/workbench/parts/extensions/node/extensionsService.ts @@ -127,7 +127,7 @@ export class ExtensionsService implements IExtensionsService { const settings = TPromise.join([ UserSettings.getValue(this.contextService, 'http.proxy'), - UserSettings.getValue(this.contextService, 'http.proxy.strictSSL') + UserSettings.getValue(this.contextService, 'http.proxyStrictSSL') ]); return settings From ac55a05ba699a8b3e67998a117b5a8667ec29b1c Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Thu, 7 Jan 2016 18:40:58 +0100 Subject: [PATCH 15/36] mark beginning of javascript changes to vscode.d.ts --- src/vs/vscode.d.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/vscode.d.ts b/src/vs/vscode.d.ts index 1156a04d295..af7ad16ce97 100644 --- a/src/vs/vscode.d.ts +++ b/src/vs/vscode.d.ts @@ -3367,8 +3367,8 @@ declare namespace vscode { } } -// TS 1.6 & node_module -// export = vscode; +// [javascript start marker] +// DO NOT MODIFY ABOVE COMMENT // when used for JS* declare module 'vscode' { From 7c00d7ac334951fce85a8b017d6e9cfd661b7d78 Mon Sep 17 00:00:00 2001 From: isidor Date: Thu, 7 Jan 2016 19:02:17 +0100 Subject: [PATCH 16/36] joao: tune the git icons with regards to the new tree size --- .../git/browser/views/changes/changesView.css | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/src/vs/workbench/parts/git/browser/views/changes/changesView.css b/src/vs/workbench/parts/git/browser/views/changes/changesView.css index 2a4ca960597..a55218bab2e 100644 --- a/src/vs/workbench/parts/git/browser/views/changes/changesView.css +++ b/src/vs/workbench/parts/git/browser/views/changes/changesView.css @@ -65,9 +65,9 @@ cursor: default; } -.git-viewlet > .changes-view > .status-view > .monaco-tree .monaco-tree-row .monaco-count-badge { +.git-viewlet > .changes-view > .status-view > .monaco-tree .monaco-tree-row .count-badge-wrapper { float: right; - margin-right: 12px; + padding-right: 12px; } .git-viewlet > .changes-view > .status-view > .monaco-tree .monaco-tree-row:hover .monaco-count-badge { @@ -89,21 +89,17 @@ } .git-viewlet > .changes-view > .status-view > .monaco-tree .monaco-tree-row .file-status .status { - position: absolute; - top: 4px; - height: 12px; - width: 6px; - line-height: 12px; padding: 2px 4px; font-family: Menlo, Monaco, Consolas, "Droid Sans Mono", "Inconsolata", "Courier New", monospace, "Droid Sans Fallback"; - font-size: 9px; + font-size: 70%; color: white; text-align: center; - border-radius: 4px; + border-radius: 0.5em; + vertical-align: bottom; } .git-viewlet > .changes-view > .status-view > .monaco-tree .monaco-tree-row .file-status .name { - margin-left: 20px; + margin-left: 0.4em; } .git-viewlet > .changes-view > .status-view > .monaco-tree .monaco-tree-row .file-status.modified .status { background-color: #007ACC; } From 27db7e99c6bf9832b77448636c75cd8fbbf62599 Mon Sep 17 00:00:00 2001 From: isidor Date: Thu, 7 Jan 2016 19:02:38 +0100 Subject: [PATCH 17/36] joao: tune the count badge size with regards to the new tree size --- src/vs/base/browser/ui/countBadge/countBadge.css | 10 ++++------ .../referenceSearch/browser/referenceSearchWidget.css | 4 ++++ .../parts/git/browser/views/changes/changesViewer.ts | 5 ++++- .../parts/search/browser/media/searchviewlet.css | 4 ++++ 4 files changed, 16 insertions(+), 7 deletions(-) diff --git a/src/vs/base/browser/ui/countBadge/countBadge.css b/src/vs/base/browser/ui/countBadge/countBadge.css index c44ae4e8f00..232c87013e3 100644 --- a/src/vs/base/browser/ui/countBadge/countBadge.css +++ b/src/vs/base/browser/ui/countBadge/countBadge.css @@ -4,16 +4,14 @@ *--------------------------------------------------------------------------------------------*/ .monaco-count-badge { - min-width: 18px; - padding: 0 3px; - margin: 4px 13px 0 6px; - border-radius: 8px; - font-size: 11px; - line-height: 16px; + padding: 0.2em 0.4em; + border-radius: 1em; + font-size: 80%; font-weight: normal; text-align: center; background: #BEBEBE; color: #FFF; + display: inline; } .vs-dark .monaco-count-badge { diff --git a/src/vs/editor/contrib/referenceSearch/browser/referenceSearchWidget.css b/src/vs/editor/contrib/referenceSearch/browser/referenceSearchWidget.css index be287288ce2..9b79d1302d8 100644 --- a/src/vs/editor/contrib/referenceSearch/browser/referenceSearchWidget.css +++ b/src/vs/editor/contrib/referenceSearch/browser/referenceSearchWidget.css @@ -112,6 +112,10 @@ background-color: rgba(234, 92, 0, 0.3); } +.monaco-editor .reference-zone-widget .monaco-count-badge { + margin-right: 12px; +} + /* dark room */ .monaco-editor.vs-dark .reference-zone-widget .tree .block, diff --git a/src/vs/workbench/parts/git/browser/views/changes/changesViewer.ts b/src/vs/workbench/parts/git/browser/views/changes/changesViewer.ts index d5e83f77474..8ad7d53e44d 100644 --- a/src/vs/workbench/parts/git/browser/views/changes/changesViewer.ts +++ b/src/vs/workbench/parts/git/browser/views/changes/changesViewer.ts @@ -269,7 +269,10 @@ export class Renderer implements tree.IRenderer { data.actionBar = new actionbar.ActionBar(container, { actionRunner: this.actionRunner }); data.actionBar.push(this.actionProvider.getActionsForGroupStatusType(statusType), { icon: true, label: false }); data.actionBar.addListener2('run', e => e.error && this.onError(e.error)); - data.count = new countbadge.CountBadge(container); + + const wrapper = dom.append(container, $('.count-badge-wrapper')); + data.count = new countbadge.CountBadge(wrapper); + data.root = dom.append(container, $('.status-group')); switch (statusType) { diff --git a/src/vs/workbench/parts/search/browser/media/searchviewlet.css b/src/vs/workbench/parts/search/browser/media/searchviewlet.css index e8f7cbfcd4c..e83b7bc0b75 100644 --- a/src/vs/workbench/parts/search/browser/media/searchviewlet.css +++ b/src/vs/workbench/parts/search/browser/media/searchviewlet.css @@ -158,6 +158,10 @@ font-style: italic; } +.search-viewlet .monaco-count-badge { + margin-right: 12px; +} + .vs .search-viewlet input.disabled, .vs .search-viewlet .file-types.disabled .monaco-inputbox { background-color: #E1E1E1; From ec5bd6464b87cbc56c5afd87adccc9699d702f5b Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Thu, 7 Jan 2016 10:55:33 +0100 Subject: [PATCH 18/36] Minor cleanup in the find controller --- src/vs/editor/contrib/find/browser/find.ts | 144 +++++++++++---------- 1 file changed, 79 insertions(+), 65 deletions(-) diff --git a/src/vs/editor/contrib/find/browser/find.ts b/src/vs/editor/contrib/find/browser/find.ts index e1f248e76ec..2065cf48786 100644 --- a/src/vs/editor/contrib/find/browser/find.ts +++ b/src/vs/editor/contrib/find/browser/find.ts @@ -24,10 +24,26 @@ import {KeyMod, KeyCode} from 'vs/base/common/keyCodes'; import {Range} from 'vs/editor/common/core/range'; import {OccurrencesRegistry} from 'vs/editor/contrib/wordHighlighter/common/wordHighlighter'; +enum FindStartFocusAction { + NoFocusChange, + FocusFindInput, + FocusReplaceInput +} + +interface IFindStartOptions { + forceRevealReplace:boolean; + seedSearchStringFromSelection:boolean; + seedSearchScopeFromSelection:boolean; + shouldFocus:FindStartFocusAction; + shouldAnimate:boolean; +} + +const CONTEXT_FIND_WIDGET_VISIBLE = 'findWidgetVisible'; + /** * The Find controller will survive an editor.setModel(..) call */ -export class FindController implements EditorCommon.IEditorContribution, FindWidget.IFindController { +class FindController implements EditorCommon.IEditorContribution, FindWidget.IFindController { static ID = 'editor.contrib.findController'; @@ -65,9 +81,18 @@ export class FindController implements EditorCommon.IEditorContribution, FindWid this.editorListeners = []; this.editorListeners.push(this.editor.addListener(EditorCommon.EventType.ModelChanged, () => { + let shouldRestartFind = (this.editor.getModel() && this.lastState && this.widgetIsVisible); + this.disposeBindingAndModel(); - if (this.editor.getModel() && this.lastState && this.widgetIsVisible) { - this._start(false, false, false, false); + + if (shouldRestartFind) { + this._start({ + forceRevealReplace: false, + seedSearchStringFromSelection: false, + seedSearchScopeFromSelection: false, + shouldFocus: FindStartFocusAction.NoFocusChange, + shouldAnimate: false + }); } })); this.editorListeners.push(this.editor.addListener(EditorCommon.EventType.Disposed, () => { @@ -143,7 +168,12 @@ export class FindController implements EditorCommon.IEditorContribution, FindWid this._eventEmitter.emit(FindController._STATE_CHANGED_EVENT); } - private _start(forceRevealReplace:boolean, seedSearchStringFromSelection:boolean, seedSearchScopeFromSelection:boolean, shouldFocus:boolean): void { + private _start(opts:IFindStartOptions): void { + if (!this.editor.getModel()) { + // cannot do anything with an editor that doesn't have a model... + return; + } + if (!this.model) { this.model = new FindModel.FindModelBoundToEditorModel(this.editor); this.widget.setModel(this.model); @@ -155,14 +185,9 @@ export class FindController implements EditorCommon.IEditorContribution, FindWid this.lastState = this.lastState || this.widget.getState(); // Consider editor selection and overwrite the state with it - var selection = this.editor.getSelection(); + let selection = this.editor.getSelection(); - if (!selection) { - // Someone started the find controller with an editor that doesn't have a model... - return; - } - - if (seedSearchStringFromSelection) { + if (opts.seedSearchStringFromSelection) { if (selection.startLineNumber === selection.endLineNumber) { if (selection.isEmpty()) { let wordAtPosition = this.editor.getModel().getWordAtPosition(selection.getStartPosition()); @@ -175,32 +200,36 @@ export class FindController implements EditorCommon.IEditorContribution, FindWid } } - var searchScope:EditorCommon.IEditorRange = null; - if (seedSearchScopeFromSelection && selection.startLineNumber < selection.endLineNumber) { + let searchScope:EditorCommon.IEditorRange = null; + if (opts.seedSearchScopeFromSelection && selection.startLineNumber < selection.endLineNumber) { // Take search scope into account only if it is more than one line. searchScope = selection; } // Overwrite isReplaceRevealed - if (forceRevealReplace) { - this.lastState.isReplaceRevealed = forceRevealReplace; + if (opts.forceRevealReplace) { + this.lastState.isReplaceRevealed = true; } // Start searching - this.model.start(this.lastState, searchScope, shouldFocus); + this.model.start(this.lastState, searchScope, opts.shouldAnimate); this.widgetIsVisible = true; - if (shouldFocus) { - if (forceRevealReplace) { - this.widget.focusReplaceInput(); - } else { - this.widget.focusFindInput(); - } + if (opts.shouldFocus === FindStartFocusAction.FocusReplaceInput) { + this.widget.focusReplaceInput(); + } else if (opts.shouldFocus === FindStartFocusAction.FocusFindInput) { + this.widget.focusFindInput(); } } public startFromAction(withReplace:boolean): void { - this._start(withReplace, true, true, true); + this._start({ + forceRevealReplace: withReplace, + seedSearchStringFromSelection: true, + seedSearchScopeFromSelection: true, + shouldFocus: withReplace ? FindStartFocusAction.FocusReplaceInput : FindStartFocusAction.FocusFindInput, + shouldAnimate: true + }); } public next(): boolean { @@ -248,30 +277,19 @@ export class FindController implements EditorCommon.IEditorContribution, FindWid } } -export class BaseStartFindAction extends EditorAction { +export class StartFindAction extends EditorAction { - constructor(descriptor: EditorCommon.IEditorActionDescriptorData, editor: EditorCommon.ICommonCodeEditor, condition: Behaviour) { - super(descriptor, editor, condition || Behaviour.WidgetFocus); - } - - _startController(controller:FindController): void { - controller.startFromAction(false); + constructor(descriptor: EditorCommon.IEditorActionDescriptorData, editor: EditorCommon.ICommonCodeEditor, @INullService ns) { + super(descriptor, editor, Behaviour.WidgetFocus); } public run(): TPromise { - - var controller = FindController.getFindController(this.editor); - this._startController(controller); + let controller = FindController.getFindController(this.editor); + controller.startFromAction(false); return TPromise.as(true); } } -export class StartFindAction extends BaseStartFindAction { - constructor(descriptor: EditorCommon.IEditorActionDescriptorData, editor: EditorCommon.ICommonCodeEditor, @INullService ns) { - super(descriptor, editor, Behaviour.WidgetFocus); - } -} - export class NextMatchFindAction extends EditorAction { constructor(descriptor:EditorCommon.IEditorActionDescriptorData, editor:EditorCommon.ICommonCodeEditor, @INullService ns) { @@ -279,7 +297,7 @@ export class NextMatchFindAction extends EditorAction { } public run(): TPromise { - var controller = FindController.getFindController(this.editor); + let controller = FindController.getFindController(this.editor); if (!controller.next()) { controller.startFromAction(false); controller.next(); @@ -295,7 +313,7 @@ export class PreviousMatchFindAction extends EditorAction { } public run(): TPromise { - var controller = FindController.getFindController(this.editor); + let controller = FindController.getFindController(this.editor); if (!controller.prev()) { controller.startFromAction(false); controller.prev(); @@ -304,18 +322,16 @@ export class PreviousMatchFindAction extends EditorAction { } } -export class StartFindReplaceAction extends BaseStartFindAction { +export class StartFindReplaceAction extends EditorAction { constructor(descriptor:EditorCommon.IEditorActionDescriptorData, editor:EditorCommon.ICommonCodeEditor, @INullService ns) { super(descriptor, editor, Behaviour.WidgetFocus | Behaviour.Writeable); } - public getId(): string { - return FindModel.START_FIND_REPLACE_ACTION_ID; - } - - _startController(controller:FindController): void { + public run(): TPromise { + let controller = FindController.getFindController(this.editor); controller.startFromAction(true); + return TPromise.as(true); } } @@ -329,9 +345,9 @@ export interface IMultiCursorFindResult { } export function multiCursorFind(editor:EditorCommon.ICommonCodeEditor, changeFindSearchString:boolean): IMultiCursorFindResult { - var controller = FindController.getFindController(editor); - var state = controller.getFindState(); - var searchText: string, + let controller = FindController.getFindController(editor); + let state = controller.getFindState(); + let searchText: string, isRegex = false, wholeWord = false, matchCase = false, @@ -353,7 +369,7 @@ export function multiCursorFind(editor:EditorCommon.ICommonCodeEditor, changeFin searchText = state.searchString; } else { // Selection owns what is searched for - var s = editor.getSelection(); + let s = editor.getSelection(); if (s.startLineNumber !== s.endLineNumber) { // Cannot search for multiline string... yet... @@ -362,7 +378,7 @@ export function multiCursorFind(editor:EditorCommon.ICommonCodeEditor, changeFin if (s.isEmpty()) { // selection is empty => expand to current word - var word = editor.getModel().getWordAtPosition(s.getStartPosition()); + let word = editor.getModel().getWordAtPosition(s.getStartPosition()); if (!word) { return null; } @@ -391,7 +407,7 @@ class SelectNextFindMatchAction extends EditorAction { } protected _getNextMatch(): EditorCommon.IEditorSelection { - var r = multiCursorFind(this.editor, true); + let r = multiCursorFind(this.editor, true); if (!r) { return null; } @@ -399,10 +415,10 @@ class SelectNextFindMatchAction extends EditorAction { return r.nextMatch; } - var allSelections = this.editor.getSelections(); - var lastAddedSelection = allSelections[allSelections.length - 1]; + let allSelections = this.editor.getSelections(); + let lastAddedSelection = allSelections[allSelections.length - 1]; - var nextMatch = this.editor.getModel().findNextMatch(r.searchText, lastAddedSelection.getEndPosition(), r.isRegex, r.matchCase, r.wholeWord); + let nextMatch = this.editor.getModel().findNextMatch(r.searchText, lastAddedSelection.getEndPosition(), r.isRegex, r.matchCase, r.wholeWord); if (!nextMatch) { return null; @@ -420,13 +436,13 @@ class AddSelectionToNextFindMatchAction extends SelectNextFindMatchAction { } public run(): TPromise { - var nextMatch = this._getNextMatch(); + let nextMatch = this._getNextMatch(); if (!nextMatch) { return TPromise.as(false); } - var allSelections = this.editor.getSelections(); + let allSelections = this.editor.getSelections(); this.editor.setSelections(allSelections.concat(nextMatch)); this.editor.revealRangeInCenterIfOutsideViewport(nextMatch); @@ -442,14 +458,14 @@ class MoveSelectionToNextFindMatchAction extends SelectNextFindMatchAction { } public run(): TPromise { - var nextMatch = this._getNextMatch(); + let nextMatch = this._getNextMatch(); if (!nextMatch) { return TPromise.as(false); } - var allSelections = this.editor.getSelections(); - var lastAddedSelection = allSelections[allSelections.length - 1]; + let allSelections = this.editor.getSelections(); + let lastAddedSelection = allSelections[allSelections.length - 1]; this.editor.setSelections(allSelections.slice(0, allSelections.length - 1).concat(nextMatch)); this.editor.revealRangeInCenterIfOutsideViewport(nextMatch); @@ -475,12 +491,12 @@ class SelectHighlightsAction extends EditorAction { } public run(): TPromise { - var r = multiCursorFind(this.editor, true); + let r = multiCursorFind(this.editor, true); if (!r) { return TPromise.as(false); } - var matches = this.editor.getModel().findMatches(r.searchText, true, r.isRegex, r.matchCase, r.wholeWord); + let matches = this.editor.getModel().findMatches(r.searchText, true, r.isRegex, r.matchCase, r.wholeWord); if (matches.length > 0) { this.editor.setSelections(matches.map(m => Selection.createSelection(m.startLineNumber, m.startColumn, m.endLineNumber, m.endColumn))); @@ -612,8 +628,6 @@ CommonEditorRegistry.registerEditorAction(new EditorActionDescriptor(SelectHighl primary: KeyMod.CtrlCmd | KeyCode.F2 })); -var CONTEXT_FIND_WIDGET_VISIBLE = 'findWidgetVisible'; - // register actions CommonEditorRegistry.registerEditorAction(new EditorActionDescriptor(StartFindAction, FindModel.START_FIND_ACTION_ID, nls.localize('startFindAction',"Find"), { context: ContextKey.None, From bb9c1d70574bb9bfdb6bdb0b01dfd68caa2c3075 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Fri, 8 Jan 2016 09:54:42 +0100 Subject: [PATCH 19/36] Clean up find widget logic --- src/vs/base/browser/ui/findinput/findInput.ts | 4 +- src/vs/editor/contrib/find/browser/find.ts | 258 ++++----- .../editor/contrib/find/browser/findWidget.ts | 513 ++++++++---------- .../contrib/find/common/findDecorations.ts | 221 ++++++++ .../editor/contrib/find/common/findModel.ts | 472 ++++------------ .../editor/contrib/find/common/findState.ts | 167 ++++++ 6 files changed, 817 insertions(+), 818 deletions(-) create mode 100644 src/vs/editor/contrib/find/common/findDecorations.ts create mode 100644 src/vs/editor/contrib/find/common/findState.ts diff --git a/src/vs/base/browser/ui/findinput/findInput.ts b/src/vs/base/browser/ui/findinput/findInput.ts index e05585ac8df..380e3e65702 100644 --- a/src/vs/base/browser/ui/findinput/findInput.ts +++ b/src/vs/base/browser/ui/findinput/findInput.ts @@ -48,8 +48,7 @@ export class FindInput { private caseSensitive:Checkbox.Checkbox; public domNode: HTMLElement; public validationNode: Builder.Builder; - private inputNode:HTMLInputElement; - private inputBox:InputBox.InputBox; + public inputBox:InputBox.InputBox; constructor(parent:HTMLElement, contextViewProvider: ContextView.IContextViewProvider, options?:IOptions) { this.contextViewProvider = contextViewProvider; @@ -64,7 +63,6 @@ export class FindInput { this.wholeWords = null; this.caseSensitive = null; this.domNode = null; - this.inputNode = null; this.inputBox = null; this.validationNode = null; diff --git a/src/vs/editor/contrib/find/browser/find.ts b/src/vs/editor/contrib/find/browser/find.ts index 2065cf48786..3512c181514 100644 --- a/src/vs/editor/contrib/find/browser/find.ts +++ b/src/vs/editor/contrib/find/browser/find.ts @@ -4,18 +4,16 @@ *--------------------------------------------------------------------------------------------*/ 'use strict'; +import * as nls from 'vs/nls'; import {TPromise} from 'vs/base/common/winjs.base'; import {EditorBrowserRegistry} from 'vs/editor/browser/editorBrowserExtensions'; import {CommonEditorRegistry, ContextKey, EditorActionDescriptor} from 'vs/editor/common/editorCommonExtensions'; import {EditorAction, Behaviour} from 'vs/editor/common/editorAction'; -import FindWidget = require('./findWidget'); -import FindModel = require('vs/editor/contrib/find/common/findModel'); -import nls = require('vs/nls'); -import EventEmitter = require('vs/base/common/eventEmitter'); -import EditorBrowser = require('vs/editor/browser/editorBrowser'); -import Lifecycle = require('vs/base/common/lifecycle'); -import config = require('vs/editor/common/config/config'); -import EditorCommon = require('vs/editor/common/editorCommon'); +import {IFindController, FindWidget} from 'vs/editor/contrib/find/browser/findWidget'; +import {FindModelBoundToEditorModel, FindIds} from 'vs/editor/contrib/find/common/findModel'; +import * as EditorBrowser from 'vs/editor/browser/editorBrowser'; +import {IDisposable, disposeAll} from 'vs/base/common/lifecycle'; +import * as EditorCommon from 'vs/editor/common/editorCommon'; import {Selection} from 'vs/editor/common/core/selection'; import {IKeybindingService, IKeybindingContextKey, IKeybindings} from 'vs/platform/keybinding/common/keybindingService'; import {IContextViewService} from 'vs/platform/contextview/browser/contextView'; @@ -23,6 +21,7 @@ import {INullService} from 'vs/platform/instantiation/common/instantiation'; import {KeyMod, KeyCode} from 'vs/base/common/keyCodes'; import {Range} from 'vs/editor/common/core/range'; import {OccurrencesRegistry} from 'vs/editor/contrib/wordHighlighter/common/wordHighlighter'; +import {INewFindReplaceState, FindReplaceStateChangedEvent, FindReplaceState} from 'vs/editor/contrib/find/common/findState'; enum FindStartFocusAction { NoFocusChange, @@ -43,47 +42,40 @@ const CONTEXT_FIND_WIDGET_VISIBLE = 'findWidgetVisible'; /** * The Find controller will survive an editor.setModel(..) call */ -class FindController implements EditorCommon.IEditorContribution, FindWidget.IFindController { +class FindController implements EditorCommon.IEditorContribution, IFindController { static ID = 'editor.contrib.findController'; - private static _STATE_CHANGED_EVENT = 'stateChanged'; - - private editor:EditorBrowser.ICodeEditor; + private _editor: EditorBrowser.ICodeEditor; + private _toDispose: IDisposable[]; private _findWidgetVisible: IKeybindingContextKey; - private model:FindModel.FindModelBoundToEditorModel; - private widget:FindWidget.FindWidget; - private widgetIsVisible:boolean; - private widgetListeners:Lifecycle.IDisposable[]; - - private editorListeners:EventEmitter.ListenerUnbind[]; - private lastState:FindModel.IFindState; - private _eventEmitter: EventEmitter.EventEmitter; + private _state: FindReplaceState; + private _widget: FindWidget; + private _model: FindModelBoundToEditorModel; static getFindController(editor:EditorCommon.ICommonCodeEditor): FindController { return editor.getContribution(FindController.ID); } constructor(editor:EditorBrowser.ICodeEditor, @IContextViewService contextViewService: IContextViewService, @IKeybindingService keybindingService: IKeybindingService) { + this._editor = editor; + this._toDispose = []; this._findWidgetVisible = keybindingService.createKey(CONTEXT_FIND_WIDGET_VISIBLE, false); - this.editor = editor; - this.model = null; - this.widgetIsVisible = false; - this.lastState = null; + this._state = new FindReplaceState(); + this._toDispose.push(this._state); + this._toDispose.push(this._state.addChangeListener((e) => this._onStateChanged(e))) - this.widget = new FindWidget.FindWidget(this.editor, this, contextViewService, keybindingService); + this._widget = new FindWidget(this._editor, this, this._state, contextViewService, keybindingService); + this._toDispose.push(this._widget); - this.widgetListeners = []; - this.widgetListeners.push(this.widget.addUserInputEventListener((e) => this.onWidgetUserInput(e))); - this.widgetListeners.push(this.widget.addClosedEventListener(() => this.onWidgetClosed())); + this._model = null; - this.editorListeners = []; - this.editorListeners.push(this.editor.addListener(EditorCommon.EventType.ModelChanged, () => { - let shouldRestartFind = (this.editor.getModel() && this.lastState && this.widgetIsVisible); + this._toDispose.push(this._editor.addListener2(EditorCommon.EventType.ModelChanged, () => { + let shouldRestartFind = (this._editor.getModel() && this._state.isRevealed); - this.disposeBindingAndModel(); + this.disposeModel(); if (shouldRestartFind) { this._start({ @@ -95,130 +87,109 @@ class FindController implements EditorCommon.IEditorContribution, FindWidget.IFi }); } })); - this.editorListeners.push(this.editor.addListener(EditorCommon.EventType.Disposed, () => { - this.editorListeners.forEach((element:EventEmitter.ListenerUnbind) => { - element(); - }); - this.editorListeners = []; - })); + } - this._eventEmitter = new EventEmitter.EventEmitter([FindController._STATE_CHANGED_EVENT]); + public dispose(): void { + this.disposeModel(); + this._toDispose = disposeAll(this._toDispose); + } + + private disposeModel(): void { + if (this._model) { + this._model.dispose(); + this._model = null; + } } public getId(): string { return FindController.ID; } - public dispose(): void { - this.widgetListeners = Lifecycle.disposeAll(this.widgetListeners); - this.widget.dispose(); - this.disposeBindingAndModel(); - this._eventEmitter.dispose(); - } - - public onStateChanged(listener:()=>void): Lifecycle.IDisposable { - return this._eventEmitter.addListener2(FindController._STATE_CHANGED_EVENT, listener); - } - - private disposeBindingAndModel(): void { - this._findWidgetVisible.reset(); - this.widget.setModel(null); - if (this.model) { - this.model.dispose(); - this.model = null; + private _onStateChanged(e:FindReplaceStateChangedEvent): void { + if (e.isRevealed) { + if (this._state.isRevealed) { + this._findWidgetVisible.set(true); + } else { + this._findWidgetVisible.reset(); + this.disposeModel(); + } } } + public getState(): FindReplaceState { + return this._state; + } + public closeFindWidget(): void { - this.widgetIsVisible = false; - this.disposeBindingAndModel(); - this.editor.focus(); + this._state.change({ isRevealed: false }, false); + this._editor.focus(); } public toggleCaseSensitive(): void { - this.widget.toggleCaseSensitive(); + this._state.change({ matchCase: !this._state.matchCase }, false); } public toggleWholeWords(): void { - this.widget.toggleWholeWords(); + this._state.change({ wholeWord: !this._state.wholeWord }, false); } public toggleRegex(): void { - this.widget.toggleRegex(); - } - - private onWidgetClosed(): void { - this.widgetIsVisible = false; - this.disposeBindingAndModel(); - } - - public getFindState(): FindModel.IFindState { - return this.lastState; + this._state.change({ isRegex: !this._state.isRegex }, false); } public setSearchString(searchString:string): void { - this.widget.setSearchString(searchString); - } - - private onWidgetUserInput(e:FindWidget.IUserInputEvent): void { - this.lastState = this.widget.getState(); - if (this.model) { - this.model.recomputeMatches(this.lastState, e.jumpToNextMatch); - } - this._eventEmitter.emit(FindController._STATE_CHANGED_EVENT); + this._state.change({ searchString: searchString }, false); } private _start(opts:IFindStartOptions): void { - if (!this.editor.getModel()) { + this.disposeModel(); + + if (!this._editor.getModel()) { // cannot do anything with an editor that doesn't have a model... return; } - if (!this.model) { - this.model = new FindModel.FindModelBoundToEditorModel(this.editor); - this.widget.setModel(this.model); - } - - this._findWidgetVisible.set(true); - - // Get a default state if none existed before - this.lastState = this.lastState || this.widget.getState(); + let stateChanges: INewFindReplaceState = { + isRevealed: true + }; // Consider editor selection and overwrite the state with it - let selection = this.editor.getSelection(); + let selection = this._editor.getSelection(); if (opts.seedSearchStringFromSelection) { if (selection.startLineNumber === selection.endLineNumber) { if (selection.isEmpty()) { - let wordAtPosition = this.editor.getModel().getWordAtPosition(selection.getStartPosition()); + let wordAtPosition = this._editor.getModel().getWordAtPosition(selection.getStartPosition()); if (wordAtPosition) { - this.lastState.searchString = wordAtPosition.word; + stateChanges.searchString = wordAtPosition.word; } } else { - this.lastState.searchString = this.editor.getModel().getValueInRange(selection); + stateChanges.searchString = this._editor.getModel().getValueInRange(selection); } } } - let searchScope:EditorCommon.IEditorRange = null; + stateChanges.searchScope = null; if (opts.seedSearchScopeFromSelection && selection.startLineNumber < selection.endLineNumber) { // Take search scope into account only if it is more than one line. - searchScope = selection; + stateChanges.searchScope = selection; } // Overwrite isReplaceRevealed if (opts.forceRevealReplace) { - this.lastState.isReplaceRevealed = true; + stateChanges.isReplaceRevealed = true; } - // Start searching - this.model.start(this.lastState, searchScope, opts.shouldAnimate); - this.widgetIsVisible = true; + this._state.change(stateChanges, false); + + if (!this._model) { + this._model = new FindModelBoundToEditorModel(this._editor, this._state); + } if (opts.shouldFocus === FindStartFocusAction.FocusReplaceInput) { - this.widget.focusReplaceInput(); + this._widget.focusReplaceInput(); } else if (opts.shouldFocus === FindStartFocusAction.FocusFindInput) { - this.widget.focusFindInput(); + this._widget.focusFindInput(); } } @@ -232,45 +203,33 @@ class FindController implements EditorCommon.IEditorContribution, FindWidget.IFi }); } - public next(): boolean { - if (this.model) { - this.model.next(); + public moveToNextMatch(): boolean { + if (this._model) { + this._model.moveToNextMatch(); return true; } return false; } - public prev(): boolean { - if (this.model) { - this.model.prev(); + public moveToPrevMatch(): boolean { + if (this._model) { + this._model.moveToPrevMatch(); return true; } return false; } - public enableSelectionFind(): void { - if (this.model) { - this.model.setFindScope(this.editor.getSelection()); - } - } - - public disableSelectionFind(): void { - if (this.model) { - this.model.setFindScope(null); - } - } - public replace(): boolean { - if (this.model) { - this.model.replace(); + if (this._model) { + this._model.replace(); return true; } return false; } public replaceAll(): boolean { - if (this.model) { - this.model.replaceAll(); + if (this._model) { + this._model.replaceAll(); return true; } return false; @@ -298,9 +257,9 @@ export class NextMatchFindAction extends EditorAction { public run(): TPromise { let controller = FindController.getFindController(this.editor); - if (!controller.next()) { + if (!controller.moveToNextMatch()) { controller.startFromAction(false); - controller.next(); + controller.moveToNextMatch(); } return TPromise.as(true); } @@ -314,9 +273,9 @@ export class PreviousMatchFindAction extends EditorAction { public run(): TPromise { let controller = FindController.getFindController(this.editor); - if (!controller.prev()) { + if (!controller.moveToPrevMatch()) { controller.startFromAction(false); - controller.prev(); + controller.moveToPrevMatch(); } return TPromise.as(true); } @@ -346,25 +305,20 @@ export interface IMultiCursorFindResult { export function multiCursorFind(editor:EditorCommon.ICommonCodeEditor, changeFindSearchString:boolean): IMultiCursorFindResult { let controller = FindController.getFindController(editor); - let state = controller.getFindState(); + let state = controller.getState(); let searchText: string, - isRegex = false, - wholeWord = false, - matchCase = false, nextMatch: EditorCommon.IEditorSelection; // In any case, if the find widget was ever opened, the options are taken from it - if (state) { - isRegex = state.properties.isRegex; - wholeWord = state.properties.wholeWord; - matchCase = state.properties.matchCase; - } + let isRegex = state.isRegex; + let wholeWord = state.wholeWord; + let matchCase = state.matchCase; // Find widget owns what we search for if: // - focus is not in the editor (i.e. it is in the find widget) // - and the search widget is visible // - and the search string is non-empty - if (!editor.isFocused() && state && state.searchString.length > 0) { + if (!editor.isFocused() && state.isRevealed && state.searchString.length > 0) { // Find widget owns what is searched for searchText = state.searchString; } else { @@ -474,7 +428,6 @@ class MoveSelectionToNextFindMatchAction extends SelectNextFindMatchAction { } class SelectHighlightsAction extends EditorAction { - static ID = 'editor.action.selectHighlights'; static COMPAT_ID = 'editor.action.changeAll'; @@ -506,12 +459,11 @@ class SelectHighlightsAction extends EditorAction { } export class SelectionHighlighter implements EditorCommon.IEditorContribution { - static ID = 'editor.contrib.selectionHighlighter'; - private editor:EditorCommon.ICommonCodeEditor; - private decorations:string[]; - private toDispose:Lifecycle.IDisposable[]; + private editor: EditorCommon.ICommonCodeEditor; + private decorations: string[]; + private toDispose: IDisposable[]; constructor(editor:EditorCommon.ICommonCodeEditor, @INullService ns) { this.editor = editor; @@ -522,7 +474,7 @@ export class SelectionHighlighter implements EditorCommon.IEditorContribution { this.toDispose.push(editor.addListener2(EditorCommon.EventType.ModelChanged, (e) => { this.removeDecorations(); })); - this.toDispose.push(FindController.getFindController(editor).onStateChanged(() => this._update())); + this.toDispose.push(FindController.getFindController(editor).getState().addChangeListener((e) => this._update())); } public getId(): string { @@ -613,7 +565,7 @@ export class SelectionHighlighter implements EditorCommon.IEditorContribution { public dispose(): void { this.removeDecorations(); - this.toDispose = Lifecycle.disposeAll(this.toDispose); + this.toDispose = disposeAll(this.toDispose); } } @@ -629,22 +581,22 @@ CommonEditorRegistry.registerEditorAction(new EditorActionDescriptor(SelectHighl })); // register actions -CommonEditorRegistry.registerEditorAction(new EditorActionDescriptor(StartFindAction, FindModel.START_FIND_ACTION_ID, nls.localize('startFindAction',"Find"), { +CommonEditorRegistry.registerEditorAction(new EditorActionDescriptor(StartFindAction, FindIds.START_FIND_ACTION_ID, nls.localize('startFindAction',"Find"), { context: ContextKey.None, primary: KeyMod.CtrlCmd | KeyCode.KEY_F, secondary: [KeyMod.CtrlCmd | KeyCode.F3] })); -CommonEditorRegistry.registerEditorAction(new EditorActionDescriptor(NextMatchFindAction, FindModel.NEXT_MATCH_FIND_ACTION_ID, nls.localize('findNextMatchAction', "Find Next"), { +CommonEditorRegistry.registerEditorAction(new EditorActionDescriptor(NextMatchFindAction, FindIds.NEXT_MATCH_FIND_ACTION_ID, nls.localize('findNextMatchAction', "Find Next"), { context: ContextKey.EditorFocus, primary: KeyCode.F3, mac: { primary: KeyMod.CtrlCmd | KeyCode.KEY_G, secondary: [KeyCode.F3] } })); -CommonEditorRegistry.registerEditorAction(new EditorActionDescriptor(PreviousMatchFindAction, FindModel.PREVIOUS_MATCH_FIND_ACTION_ID, nls.localize('findPreviousMatchAction', "Find Previous"), { +CommonEditorRegistry.registerEditorAction(new EditorActionDescriptor(PreviousMatchFindAction, FindIds.PREVIOUS_MATCH_FIND_ACTION_ID, nls.localize('findPreviousMatchAction', "Find Previous"), { context: ContextKey.EditorFocus, primary: KeyMod.Shift | KeyCode.F3, mac: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_G, secondary: [KeyMod.Shift | KeyCode.F3] } })); -CommonEditorRegistry.registerEditorAction(new EditorActionDescriptor(StartFindReplaceAction, FindModel.START_FIND_REPLACE_ACTION_ID, nls.localize('startReplace', "Replace"), { +CommonEditorRegistry.registerEditorAction(new EditorActionDescriptor(StartFindReplaceAction, FindIds.START_FIND_REPLACE_ACTION_ID, nls.localize('startReplace', "Replace"), { context: ContextKey.None, primary: KeyMod.CtrlCmd | KeyCode.KEY_H, mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_F } @@ -666,18 +618,18 @@ function registerFindCommand(id:string, callback:(controller:FindController)=>vo }); } -registerFindCommand(FindModel.CLOSE_FIND_WIDGET_COMMAND_ID, x => x.closeFindWidget(), { +registerFindCommand(FindIds.CLOSE_FIND_WIDGET_COMMAND_ID, x => x.closeFindWidget(), { primary: KeyCode.Escape }, CONTEXT_FIND_WIDGET_VISIBLE); -registerFindCommand(FindModel.TOGGLE_CASE_SENSITIVE_COMMAND_ID, x => x.toggleCaseSensitive(), { +registerFindCommand(FindIds.TOGGLE_CASE_SENSITIVE_COMMAND_ID, x => x.toggleCaseSensitive(), { primary: KeyMod.Alt | KeyCode.KEY_C, mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_C } }); -registerFindCommand(FindModel.TOGGLE_WHOLE_WORD_COMMAND_ID, x => x.toggleWholeWords(), { +registerFindCommand(FindIds.TOGGLE_WHOLE_WORD_COMMAND_ID, x => x.toggleWholeWords(), { primary: KeyMod.Alt | KeyCode.KEY_W, mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_W } }); -registerFindCommand(FindModel.TOGGLE_REGEX_COMMAND_ID, x => x.toggleRegex(), { +registerFindCommand(FindIds.TOGGLE_REGEX_COMMAND_ID, x => x.toggleRegex(), { primary: KeyMod.Alt | KeyCode.KEY_R, mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_R } }); diff --git a/src/vs/editor/contrib/find/browser/findWidget.ts b/src/vs/editor/contrib/find/browser/findWidget.ts index e8b6422971c..584d5bb3563 100644 --- a/src/vs/editor/contrib/find/browser/findWidget.ts +++ b/src/vs/editor/contrib/find/browser/findWidget.ts @@ -6,29 +6,23 @@ 'use strict'; import 'vs/css!./findWidget'; -import nls = require('vs/nls'); -import Errors = require('vs/base/common/errors'); -import EventEmitter = require('vs/base/common/eventEmitter'); -import DomUtils = require('vs/base/browser/dom'); -import ContextView = require('vs/base/browser/ui/contextview/contextview'); -import Keyboard = require('vs/base/browser/keyboardEvent'); -import InputBox = require('vs/base/browser/ui/inputbox/inputBox'); -import Findinput = require('vs/base/browser/ui/findinput/findInput'); -import EditorBrowser = require('vs/editor/browser/editorBrowser'); -import EditorCommon = require('vs/editor/common/editorCommon'); -import FindModel = require('vs/editor/contrib/find/common/findModel'); -import Lifecycle = require('vs/base/common/lifecycle'); + +import * as nls from 'vs/nls'; +import * as Errors from 'vs/base/common/errors'; +import * as DomUtils from 'vs/base/browser/dom'; +import {IContextViewProvider} from 'vs/base/browser/ui/contextview/contextview'; +import {StandardKeyboardEvent} from 'vs/base/browser/keyboardEvent'; +import {InputBox, IMessage as InputBoxMessage} from 'vs/base/browser/ui/inputbox/inputBox'; +import {FindInput} from 'vs/base/browser/ui/findinput/findInput'; +import * as EditorBrowser from 'vs/editor/browser/editorBrowser'; +import * as EditorCommon from 'vs/editor/common/editorCommon'; +import {FindIds} from 'vs/editor/contrib/find/common/findModel'; +import {disposeAll, IDisposable} from 'vs/base/common/lifecycle'; import {CommonKeybindings} from 'vs/base/common/keyCodes'; import {IKeybindingService} from 'vs/platform/keybinding/common/keybindingService'; -import {Keybinding} from 'vs/base/common/keyCodes'; - -export interface IUserInputEvent { - jumpToNextMatch: boolean; -} +import {INewFindReplaceState, FindReplaceStateChangedEvent, FindReplaceState} from 'vs/editor/contrib/find/common/findState'; export interface IFindController { - enableSelectionFind(): void; - disableSelectionFind(): void; replace(): void; replaceAll(): void; } @@ -45,56 +39,49 @@ const NLS_REPLACE_BTN_LABEL = nls.localize('label.replaceButton', "Replace"); const NLS_REPLACE_ALL_BTN_LABEL = nls.localize('label.replaceAllButton', "Replace All"); const NLS_TOGGLE_REPLACE_MODE_BTN_LABEL = nls.localize('label.toggleReplaceButton', "Toggle Replace mode"); -export class FindWidget extends EventEmitter.EventEmitter implements EditorBrowser.IOverlayWidget { - - private static _USER_CLOSED_EVENT = 'close'; - private static _USER_INPUT_EVENT = 'userInputEvent'; +export class FindWidget implements EditorBrowser.IOverlayWidget { private static ID = 'editor.contrib.findWidget'; private static PART_WIDTH = 275; private static FIND_INPUT_AREA_WIDTH = FindWidget.PART_WIDTH - 54; private static REPLACE_INPUT_AREA_WIDTH = FindWidget.FIND_INPUT_AREA_WIDTH; - private _codeEditor:EditorBrowser.ICodeEditor; + private _codeEditor: EditorBrowser.ICodeEditor; + private _state: FindReplaceState; private _controller: IFindController; - private _contextViewProvider:ContextView.IContextViewProvider; + private _contextViewProvider: IContextViewProvider; private _keybindingService: IKeybindingService; - private _domNode:HTMLElement; - private _findInput:Findinput.FindInput; - private _replaceInputBox:InputBox.InputBox; + private _domNode: HTMLElement; + private _findInput: FindInput; + private _replaceInputBox: InputBox; - private _toggleReplaceBtn:SimpleButton; - private _prevBtn:SimpleButton; - private _nextBtn:SimpleButton; - private _toggleSelectionFind:Checkbox; - private _closeBtn:SimpleButton; - private _replaceBtn:SimpleButton; - private _replaceAllBtn:SimpleButton; + private _toggleReplaceBtn: SimpleButton; + private _prevBtn: SimpleButton; + private _nextBtn: SimpleButton; + private _toggleSelectionFind: Checkbox; + private _closeBtn: SimpleButton; + private _replaceBtn: SimpleButton; + private _replaceAllBtn: SimpleButton; - private _isReplaceEnabled:boolean; - private _isVisible:boolean; - private _isReplaceVisible:boolean; + private _isReplaceEnabled: boolean; + private _isVisible: boolean; + private _isReplaceVisible: boolean; - private _toDispose:Lifecycle.IDisposable[]; + private _toDispose: IDisposable[]; - private _model:FindModel.FindModelBoundToEditorModel; - private _modelListenersToDispose:Lifecycle.IDisposable[]; - - private focusTracker:DomUtils.IFocusTracker; + private focusTracker: DomUtils.IFocusTracker; constructor( - codeEditor:EditorBrowser.ICodeEditor, - controller:IFindController, - contextViewProvider:ContextView.IContextViewProvider, - keybindingService:IKeybindingService + codeEditor: EditorBrowser.ICodeEditor, + controller: IFindController, + state: FindReplaceState, + contextViewProvider: IContextViewProvider, + keybindingService: IKeybindingService ) { - super([ - FindWidget._USER_INPUT_EVENT, - FindWidget._USER_CLOSED_EVENT, - ]); this._codeEditor = codeEditor; this._controller = controller; + this._state = state; this._contextViewProvider = contextViewProvider; this._keybindingService = keybindingService; @@ -103,30 +90,34 @@ export class FindWidget extends EventEmitter.EventEmitter implements EditorBrows this._isReplaceEnabled = false; this._toDispose = []; - this._model = null; - this._modelListenersToDispose = []; + this._toDispose.push(this._state.addChangeListener((e) => this._onStateChanged(e))); this._buildDomNode(); - this.focusTracker = DomUtils.trackFocus(this._domNode); + this.focusTracker = DomUtils.trackFocus(this._findInput.inputBox.inputElement); + this.focusTracker.addFocusListener(() => this._reseedFindScope()); + this._toDispose.push(this.focusTracker); - this._codeEditor.addOverlayWidget(this); - - this.focusTracker.addFocusListener(() => { - var selection = this._codeEditor.getSelection(); - if (selection.startLineNumber !== selection.endLineNumber) { - // Search in selection - this._controller.enableSelectionFind(); + this._toDispose.push({ + dispose: () => { + this._findInput.destroy(); } }); + this._toDispose.push(this._replaceInputBox); + + this._codeEditor.addOverlayWidget(this); } public dispose(): void { - this.focusTracker.dispose(); - this._removeModel(); - this._findInput.destroy(); - this._replaceInputBox.dispose(); - this._toDispose = Lifecycle.disposeAll(this._toDispose); + this._toDispose = disposeAll(this._toDispose); + } + + private _reseedFindScope(): void { + let selection = this._codeEditor.getSelection(); + if (selection.startLineNumber !== selection.endLineNumber) { + // Reseed find scope + this._state.change({ searchScope: selection }, true); + } } // ----- IOverlayWidget API @@ -148,62 +139,60 @@ export class FindWidget extends EventEmitter.EventEmitter implements EditorBrows return null; } - public setSearchString(searchString:string): void { - this._findInput.setValue(searchString); - this._emitUserInputEvent(false); - } + // ----- React to state changes - private _setState(state:FindModel.IFindState, selectionFindEnabled:boolean): void { - this._findInput.setValue(state.searchString); - this._findInput.setCaseSensitive(state.properties.matchCase); - this._findInput.setWholeWords(state.properties.wholeWord); - this._findInput.setRegex(state.properties.isRegex); - this._toggleSelectionFind.checkbox.disabled = !selectionFindEnabled; - this._toggleSelectionFind.checkbox.checked = selectionFindEnabled; + private _onStateChanged(e:FindReplaceStateChangedEvent): void { + if (e.searchString) { + this._findInput.setValue(this._state.searchString); - this._replaceInputBox.value = state.replaceString; - if (state.isReplaceRevealed) { - this._enableReplace(false); - } else { - this._disableReplace(false); + let findInputIsNonEmpty = (this._state.searchString.length > 0); + this._prevBtn.setEnabled(findInputIsNonEmpty); + this._nextBtn.setEnabled(findInputIsNonEmpty); + this._replaceBtn.setEnabled(findInputIsNonEmpty); + this._replaceAllBtn.setEnabled(findInputIsNonEmpty); + } + if (e.replaceString) { + this._replaceInputBox.value = this._state.replaceString; + } + if (e.isRevealed) { + if (this._state.isRevealed) { + this._reveal(true); + } else { + this._hide(true); + } + } + if (e.isReplaceRevealed) { + if (this._state.isReplaceRevealed) { + this._enableReplace(); + } else { + this._disableReplace(); + } + } + if (e.isRegex) { + this._findInput.setRegex(this._state.isRegex); + } + if (e.wholeWord) { + this._findInput.setWholeWords(this._state.wholeWord); + } + if (e.matchCase) { + this._findInput.setCaseSensitive(this._state.matchCase); + } + if (e.searchScope) { + if (this._state.searchScope) { + this._toggleSelectionFind.checkbox.checked = true; + } else { + this._toggleSelectionFind.checkbox.checked = false; + } + this._updateToggleSelectionFindButton(); + } + if (e.searchString || e.matchesCount) { + let showRedOutline = (this._state.searchString.length > 0 && this._state.matchesCount === 0); + DomUtils.toggleClass(this._domNode, 'no-results', showRedOutline); } - this._onFindValueChange(); } // ----- Public - public getState(): FindModel.IFindState { - var result:FindModel.IFindState = { - searchString: this._findInput.getValue(), - replaceString: this._replaceInputBox.value, - properties: { - isRegex: this._findInput.getRegex(), - wholeWord: this._findInput.getWholeWords(), - matchCase: this._findInput.getCaseSensitive() - }, - isReplaceRevealed: this._isReplaceEnabled - }; - return result; - } - - public setModel(newFindModel:FindModel.FindModelBoundToEditorModel): void { - this._removeModel(); - if (newFindModel) { - // We have a new model! :) - this._model = newFindModel; - this._modelListenersToDispose.push(this._model.addStartEventListener((e:FindModel.IFindStartEvent) => { - this._reveal(e.shouldAnimate); - this._setState(e.state, e.selectionFindEnabled); - })); - this._modelListenersToDispose.push(this._model.addMatchesUpdatedEventListener((e:FindModel.IFindMatchesEvent) => { - DomUtils.toggleClass(this._domNode, 'no-results', this._findInput.getValue() !== '' && e.count === 0); - })); - } else { - // No model :( - this._hide(false); - } - } - public focusFindInput(): void { this._findInput.select(); // Edge browser requires focus() in addition to select() @@ -216,14 +205,7 @@ export class FindWidget extends EventEmitter.EventEmitter implements EditorBrows this._replaceInputBox.focus(); } - private _removeModel(): void { - if (this._model !== null) { - this._modelListenersToDispose = Lifecycle.disposeAll(this._modelListenersToDispose); - this._model = null; - } - } - - private _enableReplace(sendEvent:boolean): void { + private _enableReplace(): void { this._isReplaceEnabled = true; if (!this._codeEditor.getConfiguration().readOnly && !this._isReplaceVisible) { this._replaceInputBox.enable(); @@ -233,12 +215,9 @@ export class FindWidget extends EventEmitter.EventEmitter implements EditorBrows this._toggleReplaceBtn.toggleClass('expand', true); this._toggleReplaceBtn.setExpanded(true); } - if (sendEvent) { - this._emitUserInputEvent(false); - } } - private _disableReplace(sendEvent:boolean): void { + private _disableReplace(): void { this._isReplaceEnabled = false; if (this._isReplaceVisible) { this._replaceInputBox.disable(); @@ -248,22 +227,17 @@ export class FindWidget extends EventEmitter.EventEmitter implements EditorBrows this._toggleReplaceBtn.setExpanded(false); this._isReplaceVisible = false; } - if (sendEvent) { - this._emitUserInputEvent(false); - } } - // ----- initialization - private _onFindInputKeyDown(e:DomUtils.IKeyboardEvent): void { - var handled = false; + let handled = false; if (e.equals(CommonKeybindings.ENTER)) { - this._codeEditor.getAction(FindModel.NEXT_MATCH_FIND_ACTION_ID).run().done(null, Errors.onUnexpectedError); + this._codeEditor.getAction(FindIds.NEXT_MATCH_FIND_ACTION_ID).run().done(null, Errors.onUnexpectedError); handled = true; } else if (e.equals(CommonKeybindings.SHIFT_ENTER)) { - this._codeEditor.getAction(FindModel.PREVIOUS_MATCH_FIND_ACTION_ID).run().done(null, Errors.onUnexpectedError); + this._codeEditor.getAction(FindIds.PREVIOUS_MATCH_FIND_ACTION_ID).run().done(null, Errors.onUnexpectedError); handled = true; } else if (e.equals(CommonKeybindings.TAB)) { if (this._isReplaceVisible) { @@ -280,16 +254,16 @@ export class FindWidget extends EventEmitter.EventEmitter implements EditorBrows if (handled) { e.preventDefault(); } else { + // getValue() is not updated right away setTimeout(() => { - this._onFindValueChange(); - this._emitUserInputEvent(true); + this._state.change({ searchString: this._findInput.getValue() }, true); }, 10); } } private _onReplaceInputKeyDown(e:DomUtils.IKeyboardEvent): void { - var handled = false; + let handled = false; if (e.equals(CommonKeybindings.ENTER)) { this._controller.replace(); @@ -309,19 +283,12 @@ export class FindWidget extends EventEmitter.EventEmitter implements EditorBrows e.preventDefault(); } else { setTimeout(() => { - this._emitUserInputEvent(true); + this._state.change({ replaceString: this._replaceInputBox.value }, false); }, 10); } } - private _onFindValueChange(): void { - var findInputIsNonEmpty = (this._findInput.getValue().length > 0); - - this._prevBtn.setEnabled(findInputIsNonEmpty); - this._nextBtn.setEnabled(findInputIsNonEmpty); - this._replaceBtn.setEnabled(findInputIsNonEmpty); - this._replaceAllBtn.setEnabled(findInputIsNonEmpty); - } + // ----- initialization private _keybindingLabelFor(actionId:string): string { let keybindings = this._keybindingService.lookupKeybindings(actionId); @@ -333,14 +300,14 @@ export class FindWidget extends EventEmitter.EventEmitter implements EditorBrows private _buildFindPart(): HTMLElement { // Find input - this._findInput = new Findinput.FindInput(null, this._contextViewProvider, { + this._findInput = new FindInput(null, this._contextViewProvider, { width: FindWidget.FIND_INPUT_AREA_WIDTH, label: NLS_FIND_INPUT_LABEL, placeholder: NLS_FIND_INPUT_PLACEHOLDER, - appendCaseSensitiveLabel: this._keybindingLabelFor(FindModel.TOGGLE_CASE_SENSITIVE_COMMAND_ID), - appendWholeWordsLabel: this._keybindingLabelFor(FindModel.TOGGLE_WHOLE_WORD_COMMAND_ID), - appendRegexLabel: this._keybindingLabelFor(FindModel.TOGGLE_REGEX_COMMAND_ID), - validation: (value:string): InputBox.IMessage => { + appendCaseSensitiveLabel: this._keybindingLabelFor(FindIds.TOGGLE_CASE_SENSITIVE_COMMAND_ID), + appendWholeWordsLabel: this._keybindingLabelFor(FindIds.TOGGLE_WHOLE_WORD_COMMAND_ID), + appendRegexLabel: this._keybindingLabelFor(FindIds.TOGGLE_REGEX_COMMAND_ID), + validation: (value:string): InputBoxMessage => { if (value.length === 0) { return null; } @@ -355,32 +322,40 @@ export class FindWidget extends EventEmitter.EventEmitter implements EditorBrows } } }).on('keydown', (browserEvent:KeyboardEvent) => { - this._onFindInputKeyDown(new Keyboard.StandardKeyboardEvent(browserEvent)); - }).on(Findinput.FindInput.OPTION_CHANGE, () => { - this._emitUserInputEvent(true); + this._onFindInputKeyDown(new StandardKeyboardEvent(browserEvent)); + }).on(FindInput.OPTION_CHANGE, () => { + this._state.change({ + isRegex: this._findInput.getRegex(), + wholeWord: this._findInput.getWholeWords(), + matchCase: this._findInput.getCaseSensitive() + }, true); }); this._findInput.disable(); // Previous button - this._prevBtn = new SimpleButton( - NLS_PREVIOUS_MATCH_BTN_LABEL + this._keybindingLabelFor(FindModel.PREVIOUS_MATCH_FIND_ACTION_ID), - 'previous' - ).onTrigger(() => { - this._codeEditor.getAction(FindModel.PREVIOUS_MATCH_FIND_ACTION_ID).run().done(null, Errors.onUnexpectedError); + this._prevBtn = new SimpleButton({ + label: NLS_PREVIOUS_MATCH_BTN_LABEL + this._keybindingLabelFor(FindIds.PREVIOUS_MATCH_FIND_ACTION_ID), + className: 'previous', + onTrigger: () => { + this._codeEditor.getAction(FindIds.PREVIOUS_MATCH_FIND_ACTION_ID).run().done(null, Errors.onUnexpectedError); + }, + onKeyDown: (e) => {} }); this._toDispose.push(this._prevBtn); // Next button - this._nextBtn = new SimpleButton( - NLS_NEXT_MATCH_BTN_LABEL + this._keybindingLabelFor(FindModel.NEXT_MATCH_FIND_ACTION_ID), - 'next' - ).onTrigger(() => { - this._codeEditor.getAction(FindModel.NEXT_MATCH_FIND_ACTION_ID).run().done(null, Errors.onUnexpectedError); + this._nextBtn = new SimpleButton({ + label: NLS_NEXT_MATCH_BTN_LABEL + this._keybindingLabelFor(FindIds.NEXT_MATCH_FIND_ACTION_ID), + className: 'next', + onTrigger: () => { + this._codeEditor.getAction(FindIds.NEXT_MATCH_FIND_ACTION_ID).run().done(null, Errors.onUnexpectedError); + }, + onKeyDown: (e) => {} }); this._toDispose.push(this._nextBtn); - var findPart = document.createElement('div'); + let findPart = document.createElement('div'); findPart.className = 'find-part'; findPart.appendChild(this._findInput.domNode); findPart.appendChild(this._prevBtn.domNode); @@ -391,29 +366,28 @@ export class FindWidget extends EventEmitter.EventEmitter implements EditorBrows this._toggleSelectionFind.disable(); this._toDispose.push(DomUtils.addStandardDisposableListener(this._toggleSelectionFind.checkbox, 'change', (e) => { if (this._toggleSelectionFind.checkbox.checked) { - this._controller.enableSelectionFind(); + this._reseedFindScope(); } else { - this._controller.disableSelectionFind(); - this._updateToggleSelectionFindButton(); + this._state.change({ searchScope: null }, true); } })); - this._toDispose.push(this._toggleSelectionFind); this._codeEditor.addListener(EditorCommon.EventType.CursorSelectionChanged, () => { this._updateToggleSelectionFindButton(); }); // Close button - this._closeBtn = new SimpleButton( - NLS_CLOSE_BTN_LABEL + this._keybindingLabelFor(FindModel.CLOSE_FIND_WIDGET_COMMAND_ID), - 'close-fw' - ).onTrigger(() => { - this._hide(true); - this._emitClosedEvent(); - }).onKeyDown((e) => { - if (this._isReplaceVisible) { - this._replaceBtn.focus(); - e.preventDefault(); + this._closeBtn = new SimpleButton({ + label: NLS_CLOSE_BTN_LABEL + this._keybindingLabelFor(FindIds.CLOSE_FIND_WIDGET_COMMAND_ID), + className: 'close-fw', + onTrigger: () => { + this._state.change({ isRevealed: false }, false); + }, + onKeyDown: (e) => { + if (this._isReplaceVisible) { + this._replaceBtn.focus(); + e.preventDefault(); + } } }); this._toDispose.push(this._closeBtn); @@ -433,7 +407,7 @@ export class FindWidget extends EventEmitter.EventEmitter implements EditorBrows } if (!this._toggleSelectionFind.checkbox.checked) { - var selection = this._codeEditor.getSelection(); + let selection = this._codeEditor.getSelection(); if (selection.startLineNumber === selection.endLineNumber) { this._toggleSelectionFind.disable(); @@ -445,10 +419,10 @@ export class FindWidget extends EventEmitter.EventEmitter implements EditorBrows private _buildReplacePart(): HTMLElement { // Replace input - var replaceInput = document.createElement('div'); + let replaceInput = document.createElement('div'); replaceInput.className = 'replace-input'; replaceInput.style.width = FindWidget.REPLACE_INPUT_AREA_WIDTH + 'px'; - this._replaceInputBox = new InputBox.InputBox(replaceInput, null, { + this._replaceInputBox = new InputBox(replaceInput, null, { ariaLabel: NLS_REPLACE_INPUT_LABEL, placeholder: NLS_REPLACE_INPUT_PLACEHOLDER }); @@ -457,24 +431,28 @@ export class FindWidget extends EventEmitter.EventEmitter implements EditorBrows this._replaceInputBox.disable(); // Replace one button - this._replaceBtn = new SimpleButton( - NLS_REPLACE_BTN_LABEL, - 'replace' - ).onTrigger(() => { - this._controller.replace(); + this._replaceBtn = new SimpleButton({ + label: NLS_REPLACE_BTN_LABEL, + className: 'replace', + onTrigger: () => { + this._controller.replace(); + }, + onKeyDown: (e) => {} }); this._toDispose.push(this._replaceBtn); // Replace all button - this._replaceAllBtn = new SimpleButton( - NLS_REPLACE_ALL_BTN_LABEL, - 'replace-all' - ).onTrigger(() => { - this._controller.replaceAll(); + this._replaceAllBtn = new SimpleButton({ + label: NLS_REPLACE_ALL_BTN_LABEL, + className: 'replace-all', + onTrigger: () => { + this._controller.replaceAll(); + }, + onKeyDown: (e) => {} }); this._toDispose.push(this._replaceAllBtn); - var replacePart = document.createElement('div'); + let replacePart = document.createElement('div'); replacePart.className = 'replace-part'; replacePart.appendChild(replaceInput); replacePart.appendChild(this._replaceBtn.domNode); @@ -485,21 +463,19 @@ export class FindWidget extends EventEmitter.EventEmitter implements EditorBrows private _buildDomNode(): void { // Find part - var findPart = this._buildFindPart(); + let findPart = this._buildFindPart(); // Replace part - var replacePart = this._buildReplacePart(); + let replacePart = this._buildReplacePart(); // Toggle replace button - this._toggleReplaceBtn = new SimpleButton( - NLS_TOGGLE_REPLACE_MODE_BTN_LABEL, - 'toggle left' - ).onTrigger(() => { - if (this._isReplaceVisible) { - this._disableReplace(true); - } else { - this._enableReplace(true); - } + this._toggleReplaceBtn = new SimpleButton({ + label: NLS_TOGGLE_REPLACE_MODE_BTN_LABEL, + className: 'toggle left', + onTrigger: () => { + this._state.change({ isReplaceRevealed: !this._isReplaceVisible }, true); + }, + onKeyDown: (e) => {} }); this._toggleReplaceBtn.toggleClass('expand', this._isReplaceVisible); this._toggleReplaceBtn.toggleClass('collapse', !this._isReplaceVisible); @@ -532,14 +508,18 @@ export class FindWidget extends EventEmitter.EventEmitter implements EditorBrows this._toggleSelectionFind.enable(); this._closeBtn.setEnabled(true); - this._onFindValueChange(); + let findInputIsNonEmpty = (this._state.searchString.length > 0); + this._prevBtn.setEnabled(findInputIsNonEmpty); + this._nextBtn.setEnabled(findInputIsNonEmpty); + this._replaceBtn.setEnabled(findInputIsNonEmpty); + this._replaceAllBtn.setEnabled(findInputIsNonEmpty); this._isVisible = true; - window.setTimeout(() => { + setTimeout(() => { DomUtils.addClass(this._domNode, 'visible'); if (!animate) { DomUtils.addClass(this._domNode, 'noanimation'); - window.setTimeout(() => { + setTimeout(() => { DomUtils.removeClass(this._domNode, 'noanimation'); }, 200); } @@ -568,48 +548,15 @@ export class FindWidget extends EventEmitter.EventEmitter implements EditorBrows this._codeEditor.layoutOverlayWidget(this); } } - - public addUserInputEventListener(callback:(e:IUserInputEvent)=>void): Lifecycle.IDisposable { - return this.addListener2(FindWidget._USER_INPUT_EVENT, callback); - } - - private _emitUserInputEvent(jumpToNextMatch:boolean): void { - var e:IUserInputEvent = { - jumpToNextMatch: jumpToNextMatch - }; - this.emit(FindWidget._USER_INPUT_EVENT, e); - } - - public addClosedEventListener(callback:()=>void): Lifecycle.IDisposable { - return this.addListener2(FindWidget._USER_CLOSED_EVENT, callback); - } - - private _emitClosedEvent(): void { - this.emit(FindWidget._USER_CLOSED_EVENT); - } - - public toggleCaseSensitive(): void { - this._findInput.setCaseSensitive(!this._findInput.getCaseSensitive()); - this._emitUserInputEvent(false); - } - - public toggleWholeWords(): void { - this._findInput.setWholeWords(!this._findInput.getWholeWords()); - this._emitUserInputEvent(false); - } - - public toggleRegex(): void { - this._findInput.setRegex(!this._findInput.getRegex()); - this._emitUserInputEvent(false); - } } -export class Checkbox implements Lifecycle.IDisposable { +export class Checkbox { - private static COUNTER = 0; - private _domNode:HTMLElement; - private _checkbox:HTMLInputElement; - private label:HTMLLabelElement; + private static _COUNTER = 0; + + private _domNode: HTMLElement; + private _checkbox: HTMLInputElement; + private _label: HTMLLabelElement; constructor(parent: HTMLElement, title: string) { this._domNode = document.createElement('div'); @@ -619,15 +566,15 @@ export class Checkbox implements Lifecycle.IDisposable { this._checkbox = document.createElement('input'); this._checkbox.type = 'checkbox'; this._checkbox.className = 'checkbox'; - this._checkbox.id = 'checkbox-' + Checkbox.COUNTER++; + this._checkbox.id = 'checkbox-' + Checkbox._COUNTER++; - this.label = document.createElement('label'); - this.label.className = 'label'; + this._label = document.createElement('label'); + this._label.className = 'label'; // Connect the label and the checkbox. Checkbox will get checked when the label recieves a click. - this.label.htmlFor = this._checkbox.id; + this._label.htmlFor = this._checkbox.id; this._domNode.appendChild(this._checkbox); - this._domNode.appendChild(this.label); + this._domNode.appendChild(this._label); parent.appendChild(this._domNode); } @@ -651,50 +598,48 @@ export class Checkbox implements Lifecycle.IDisposable { public disable(): void { this._checkbox.disabled = true; } - - public dispose(): void { - this._domNode = null; - this._checkbox = null; - this.label = null; - } } -class SimpleButton implements Lifecycle.IDisposable { +interface ISimpleButtonOpts { + label: string; + className: string; + onTrigger: ()=>void; + onKeyDown: (e:DomUtils.IKeyboardEvent)=>void; +} - private _onTrigger:()=>void; - private _onKeyDown:(e:DomUtils.IKeyboardEvent)=>void; - private _domNode:HTMLElement; - private _toDispose:Lifecycle.IDisposable[]; +class SimpleButton implements IDisposable { - constructor(label:string, className:string) { + private _opts: ISimpleButtonOpts; + private _domNode: HTMLElement; + private _toDispose: IDisposable[]; - this._onTrigger = null; - this._onKeyDown = null; + constructor(opts:ISimpleButtonOpts) { + this._opts = opts; this._domNode = document.createElement('div'); - this._domNode.title = label; + this._domNode.title = this._opts.label; this._domNode.tabIndex = -1; - this._domNode.className = 'button ' + className; + this._domNode.className = 'button ' + this._opts.className; this._domNode.setAttribute('role', 'button'); - this._domNode.setAttribute('aria-label', label); + this._domNode.setAttribute('aria-label', this._opts.label); this._toDispose = []; this._toDispose.push(DomUtils.addStandardDisposableListener(this._domNode, 'click', (e) => { - this._invokeOnTrigger(); + this._opts.onTrigger(); e.preventDefault(); })); this._toDispose.push(DomUtils.addStandardDisposableListener(this._domNode, 'keydown', (e) => { if (e.equals(CommonKeybindings.SPACE) || e.equals(CommonKeybindings.ENTER)) { - this._invokeOnTrigger(); + this._opts.onTrigger(); e.preventDefault(); return; } - this._invokeOnKeyDown(e); + this._opts.onKeyDown(e); })); } public dispose(): void { - this._toDispose = Lifecycle.disposeAll(this._toDispose); + this._toDispose = disposeAll(this._toDispose); } public get domNode(): HTMLElement { @@ -718,26 +663,4 @@ class SimpleButton implements Lifecycle.IDisposable { public toggleClass(className:string, shouldHaveIt:boolean): void { DomUtils.toggleClass(this._domNode, className, shouldHaveIt); } - - public onTrigger(onTrigger:()=>void): SimpleButton { - this._onTrigger = onTrigger; - return this; - } - - public onKeyDown(onKeyDown:(e:DomUtils.IKeyboardEvent)=>void): SimpleButton { - this._onKeyDown = onKeyDown; - return this; - } - - private _invokeOnTrigger(): void { - if (this._onTrigger) { - this._onTrigger(); - } - } - - private _invokeOnKeyDown(e:DomUtils.IKeyboardEvent): void { - if (this._onKeyDown) { - this._onKeyDown(e); - } - } } diff --git a/src/vs/editor/contrib/find/common/findDecorations.ts b/src/vs/editor/contrib/find/common/findDecorations.ts new file mode 100644 index 00000000000..7dd035d73c0 --- /dev/null +++ b/src/vs/editor/contrib/find/common/findDecorations.ts @@ -0,0 +1,221 @@ +/*--------------------------------------------------------------------------------------------- + * 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 EditorCommon = require('vs/editor/common/editorCommon'); +import Strings = require('vs/base/common/strings'); +import Events = require('vs/base/common/eventEmitter'); +import ReplaceAllCommand = require('./replaceAllCommand'); +import Lifecycle = require('vs/base/common/lifecycle'); +import Schedulers = require('vs/base/common/async'); +import {Range} from 'vs/editor/common/core/range'; +import {Position} from 'vs/editor/common/core/position'; +import {ReplaceCommand} from 'vs/editor/common/commands/replaceCommand'; + +export class FindDecorations implements Lifecycle.IDisposable { + private editor:EditorCommon.ICommonCodeEditor; + + private decorations:string[]; + private decorationIndex:number; + private findScopeDecorationId:string; + private highlightedDecorationId:string; + private startPosition:EditorCommon.IEditorPosition; + + constructor(editor:EditorCommon.ICommonCodeEditor) { + this.editor = editor; + this.decorations = []; + this.decorationIndex = 0; + this.findScopeDecorationId = null; + this.highlightedDecorationId = null; + this.startPosition = this.editor.getPosition(); + } + + public dispose(): void { + this.editor.deltaDecorations(this._allDecorations(), []); + + this.editor = null; + this.decorations = []; + this.decorationIndex = 0; + this.findScopeDecorationId = null; + this.highlightedDecorationId = null; + this.startPosition = null; + } + + public reset(): void { + this.decorations = []; + this.decorationIndex = -1; + this.findScopeDecorationId = null; + this.highlightedDecorationId = null; + } + + public getFindScope(): EditorCommon.IEditorRange { + if (this.findScopeDecorationId) { + return this.editor.getModel().getDecorationRange(this.findScopeDecorationId); + } + return null; + } + + public setStartPosition(newStartPosition:EditorCommon.IEditorPosition): void { + this.startPosition = newStartPosition; + this._setDecorationIndex(-1, false); + } + + public hasMatches(): boolean { + return (this.decorations.length > 0); + } + + public getCurrentIndexRange(): EditorCommon.IEditorRange { + if (this.decorationIndex >= 0 && this.decorationIndex < this.decorations.length) { + return this.editor.getModel().getDecorationRange(this.decorations[this.decorationIndex]); + } + return null; + } + + public setIndexToFirstAfterStartPosition(): void { + this._setDecorationIndex(this.indexAfterPosition(this.startPosition), false); + } + + public moveToFirstAfterStartPosition(): void { + this._setDecorationIndex(this.indexAfterPosition(this.startPosition), true); + } + + public movePrev(): void { + if (!this.hasMatches()) { + this._revealFindScope(); + return; + } + if (this.decorationIndex === -1) { + this._setDecorationIndex(this.previousIndex(this.indexAfterPosition(this.startPosition)), true); + } else { + this._setDecorationIndex(this.previousIndex(this.decorationIndex), true); + } + } + + public moveNext(): void { + if (!this.hasMatches()) { + this._revealFindScope(); + return; + } + if (this.decorationIndex === -1) { + this._setDecorationIndex(this.indexAfterPosition(this.startPosition), true); + } else { + this._setDecorationIndex(this.nextIndex(this.decorationIndex), true); + } + } + + private _revealFindScope(): void { + let findScope = this.getFindScope(); + if (findScope) { + // Reveal the selection so user is reminded that 'selection find' is on. + this.editor.revealRangeInCenterIfOutsideViewport(findScope); + } + } + + private _setDecorationIndex(newIndex:number, moveCursor:boolean): void { + this.decorationIndex = newIndex; + this.editor.changeDecorations((changeAccessor: EditorCommon.IModelDecorationsChangeAccessor) => { + if (this.highlightedDecorationId !== null) { + changeAccessor.changeDecorationOptions(this.highlightedDecorationId, FindDecorations.createFindMatchDecorationOptions(false)); + this.highlightedDecorationId = null; + } + if (moveCursor && this.decorationIndex >= 0 && this.decorationIndex < this.decorations.length) { + this.highlightedDecorationId = this.decorations[this.decorationIndex]; + changeAccessor.changeDecorationOptions(this.highlightedDecorationId, FindDecorations.createFindMatchDecorationOptions(true)); + } + }); + if (moveCursor && this.decorationIndex >= 0 && this.decorationIndex < this.decorations.length) { + let range = this.editor.getModel().getDecorationRange(this.decorations[this.decorationIndex]); + this.editor.setSelection(range); + } + } + + public set(matches:EditorCommon.IEditorRange[], findScope:EditorCommon.IEditorRange): void { + let newDecorations: EditorCommon.IModelDeltaDecoration[] = matches.map((match) => { + return { + range: match, + options: FindDecorations.createFindMatchDecorationOptions(false) + }; + }); + if (findScope) { + newDecorations.unshift({ + range: findScope, + options: FindDecorations.createFindScopeDecorationOptions() + }); + } + let tmpDecorations = this.editor.deltaDecorations(this._allDecorations(), newDecorations); + + if (findScope) { + this.findScopeDecorationId = tmpDecorations.shift(); + } else { + this.findScopeDecorationId = null; + } + this.decorations = tmpDecorations; + this.decorationIndex = -1; + this.highlightedDecorationId = null; + } + + private _allDecorations(): string[] { + let result:string[] = []; + result = result.concat(this.decorations); + if (this.findScopeDecorationId) { + result.push(this.findScopeDecorationId); + } + return result; + } + + private indexAfterPosition(position:EditorCommon.IEditorPosition): number { + if (this.decorations.length === 0) { + return 0; + } + for (let i = 0, len = this.decorations.length; i < len; i++) { + let decorationId = this.decorations[i]; + let r = this.editor.getModel().getDecorationRange(decorationId); + if (!r || r.startLineNumber < position.lineNumber) { + continue; + } + if (r.startLineNumber > position.lineNumber) { + return i; + } + if (r.startColumn < position.column) { + continue; + } + return i; + } + return 0; + } + + private previousIndex(index:number): number { + if (this.decorations.length > 0) { + return (index - 1 + this.decorations.length) % this.decorations.length; + } + return 0; + } + + private nextIndex(index:number): number { + if (this.decorations.length > 0) { + return (index + 1) % this.decorations.length; + } + return 0; + } + + private static createFindMatchDecorationOptions(isCurrent:boolean): EditorCommon.IModelDecorationOptions { + return { + stickiness: EditorCommon.TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges, + className: isCurrent ? 'currentFindMatch' : 'findMatch', + overviewRuler: { + color: 'rgba(246, 185, 77, 0.7)', + darkColor: 'rgba(246, 185, 77, 0.7)', + position: EditorCommon.OverviewRulerLane.Center + } + }; + } + + private static createFindScopeDecorationOptions(): EditorCommon.IModelDecorationOptions { + return { + className: 'findScope', + isWholeLine: true + }; + } +} diff --git a/src/vs/editor/contrib/find/common/findModel.ts b/src/vs/editor/contrib/find/common/findModel.ts index 27d105759b2..73d7f156000 100644 --- a/src/vs/editor/contrib/find/common/findModel.ts +++ b/src/vs/editor/contrib/find/common/findModel.ts @@ -12,340 +12,140 @@ import Schedulers = require('vs/base/common/async'); import {Range} from 'vs/editor/common/core/range'; import {Position} from 'vs/editor/common/core/position'; import {ReplaceCommand} from 'vs/editor/common/commands/replaceCommand'; +import {FindDecorations} from './findDecorations'; +import {FindReplaceStateChangedEvent, FindReplaceState} from './findState'; -export const START_FIND_ACTION_ID = 'actions.find'; -export const NEXT_MATCH_FIND_ACTION_ID = 'editor.action.nextMatchFindAction'; -export const PREVIOUS_MATCH_FIND_ACTION_ID = 'editor.action.previousMatchFindAction'; -export const START_FIND_REPLACE_ACTION_ID = 'editor.action.startFindReplaceAction'; -export const CLOSE_FIND_WIDGET_COMMAND_ID = 'closeFindWidget'; -export const TOGGLE_CASE_SENSITIVE_COMMAND_ID = 'toggleFindCaseSensitive'; -export const TOGGLE_WHOLE_WORD_COMMAND_ID = 'toggleFindWholeWord'; -export const TOGGLE_REGEX_COMMAND_ID = 'toggleFindRegex'; - -export interface IFindMatchesEvent { - position: number; - count: number; +export const FindIds = { + START_FIND_ACTION_ID: 'actions.find', + NEXT_MATCH_FIND_ACTION_ID: 'editor.action.nextMatchFindAction', + PREVIOUS_MATCH_FIND_ACTION_ID: 'editor.action.previousMatchFindAction', + START_FIND_REPLACE_ACTION_ID: 'editor.action.startFindReplaceAction', + CLOSE_FIND_WIDGET_COMMAND_ID: 'closeFindWidget', + TOGGLE_CASE_SENSITIVE_COMMAND_ID: 'toggleFindCaseSensitive', + TOGGLE_WHOLE_WORD_COMMAND_ID: 'toggleFindWholeWord', + TOGGLE_REGEX_COMMAND_ID: 'toggleFindRegex' } -export interface IFindProperties { - isRegex: boolean; - wholeWord: boolean; - matchCase: boolean; -} - -export interface IFindState { - searchString: string; - replaceString: string; - properties: IFindProperties; - isReplaceRevealed: boolean; -} - -export interface IFindStartEvent { - state: IFindState; - selectionFindEnabled: boolean; - shouldAnimate: boolean; -} - -export class FindModelBoundToEditorModel extends Events.EventEmitter { - - private static _START_EVENT = 'start'; - private static _MATCHES_UPDATED_EVENT = 'matches'; +export class FindModelBoundToEditorModel { private editor:EditorCommon.ICommonCodeEditor; - private startPosition:EditorCommon.IEditorPosition; - private searchString:string; - private replaceString:string; - private searchOnlyEditableRange:boolean; - private decorations:string[]; - private decorationIndex:number; - private findScopeDecorationId:string; - private highlightedDecorationId:string; - private listenersToRemove:Events.ListenerUnbind[]; + private _state:FindReplaceState; + private _toDispose:Lifecycle.IDisposable[]; + private _decorations: FindDecorations; + private _ignoreModelContentChanged:boolean; + private updateDecorationsScheduler:Schedulers.RunOnceScheduler; - private didReplace:boolean; - private isRegex:boolean; - private matchCase:boolean; - private wholeWord:boolean; - - constructor(editor:EditorCommon.ICommonCodeEditor) { - super([ - FindModelBoundToEditorModel._MATCHES_UPDATED_EVENT, - FindModelBoundToEditorModel._START_EVENT - ]); + constructor(editor:EditorCommon.ICommonCodeEditor, state:FindReplaceState) { this.editor = editor; - this.startPosition = null; - this.searchString = ''; - this.replaceString = ''; - this.searchOnlyEditableRange = false; - this.decorations = []; - this.decorationIndex = 0; - this.findScopeDecorationId = null; - this.highlightedDecorationId = null; - this.listenersToRemove = []; - this.didReplace = false; + this._state = state; + this._toDispose = []; - this.isRegex = false; - this.matchCase = false; - this.wholeWord = false; + this._decorations = new FindDecorations(editor); + this._toDispose.push(this._decorations); - this.updateDecorationsScheduler = new Schedulers.RunOnceScheduler(() => { - this.updateDecorations(false, false, null); - }, 100); + this.updateDecorationsScheduler = new Schedulers.RunOnceScheduler(() => this.research(false), 100); + this._toDispose.push(this.updateDecorationsScheduler); - this.listenersToRemove.push(this.editor.addListener(EditorCommon.EventType.CursorPositionChanged, (e:EditorCommon.ICursorPositionChangedEvent) => { + this._toDispose.push(this.editor.addListener2(EditorCommon.EventType.CursorPositionChanged, (e:EditorCommon.ICursorPositionChangedEvent) => { if (e.reason === 'explicit' || e.reason === 'undo' || e.reason === 'redo') { - if (this.highlightedDecorationId !== null) { - this.editor.changeDecorations((changeAccessor: EditorCommon.IModelDecorationsChangeAccessor) => { - changeAccessor.changeDecorationOptions(this.highlightedDecorationId, this.createFindMatchDecorationOptions(false)); - this.highlightedDecorationId = null; - }); - } - this.startPosition = this.editor.getPosition(); - this.decorationIndex = -1; + this._decorations.setStartPosition(this.editor.getPosition()); } })); - this.listenersToRemove.push(this.editor.addListener(EditorCommon.EventType.ModelContentChanged, (e:EditorCommon.IModelContentChangedEvent) => { + this._ignoreModelContentChanged = false; + this._toDispose.push(this.editor.addListener2(EditorCommon.EventType.ModelContentChanged, (e:EditorCommon.IModelContentChangedEvent) => { + if (this._ignoreModelContentChanged) { + return; + } if (e.changeType === EditorCommon.EventType.ModelContentChangedFlush) { // a model.setValue() was called - this.decorations = []; - this.decorationIndex = -1; - this.findScopeDecorationId = null; - this.highlightedDecorationId = null; + this._decorations.reset(); } - this.startPosition = this.editor.getPosition(); + this._decorations.setStartPosition(this.editor.getPosition()); this.updateDecorationsScheduler.schedule(); })); + + this._toDispose.push(this._state.addChangeListener((e) => this._onStateChanged(e))); + + this.research(false, this._state.searchScope); } - private removeOldDecorations(changeAccessor:EditorCommon.IModelDecorationsChangeAccessor, removeFindScopeDecoration:boolean): void { - let toRemove: string[] = []; - var i:number, len:number; - for (i = 0, len = this.decorations.length; i < len; i++) { - toRemove.push(this.decorations[i]); - } - this.decorations = []; - - if (removeFindScopeDecoration && this.hasFindScope()) { - toRemove.push(this.findScopeDecorationId); - this.findScopeDecorationId = null; - } - - changeAccessor.deltaDecorations(toRemove, []); + public dispose(): void { + this._toDispose = Lifecycle.disposeAll(this._toDispose); } - private createFindMatchDecorationOptions(isCurrent:boolean): EditorCommon.IModelDecorationOptions { - return { - stickiness: EditorCommon.TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges, - className: isCurrent ? 'currentFindMatch' : 'findMatch', - overviewRuler: { - color: 'rgba(246, 185, 77, 0.7)', - darkColor: 'rgba(246, 185, 77, 0.7)', - position: EditorCommon.OverviewRulerLane.Center + private _onStateChanged(e:FindReplaceStateChangedEvent): void { + if (e.searchString || e.isReplaceRevealed || e.isRegex || e.wholeWord || e.matchCase || e.searchScope) { + if (e.searchScope) { + this.research(e.moveCursor, this._state.searchScope); + } else { + this.research(e.moveCursor); } - }; - } - - private createFindScopeDecorationOptions(): EditorCommon.IModelDecorationOptions { - return { - className: 'findScope', - isWholeLine: true - }; - } - - private addMatchesDecorations(changeAccessor:EditorCommon.IModelDecorationsChangeAccessor, matches:EditorCommon.IEditorRange[]): void { - var newDecorations: EditorCommon.IModelDeltaDecoration[] = []; - - var i:number, len:number; - for (i = 0, len = matches.length; i < len; i++) { - newDecorations[i] = { - range: matches[i], - options: this.createFindMatchDecorationOptions(false) - }; } - - this.decorations = changeAccessor.deltaDecorations([], newDecorations); } - private _getSearchRange(): EditorCommon.IEditorRange { - var searchRange:EditorCommon.IEditorRange; + private static _getSearchRange(model:EditorCommon.IModel, searchOnlyEditableRange:boolean, findScope:EditorCommon.IEditorRange): EditorCommon.IEditorRange { + let searchRange:EditorCommon.IEditorRange; - if (this.searchOnlyEditableRange) { - searchRange = this.editor.getModel().getEditableRange(); + if (searchOnlyEditableRange) { + searchRange = model.getEditableRange(); } else { - searchRange = this.editor.getModel().getFullModelRange(); + searchRange = model.getFullModelRange(); } - if (this.hasFindScope()) { - // If we have set now or before a find scope, use it for computing the search range - searchRange = searchRange.intersectRanges(this.editor.getModel().getDecorationRange(this.findScopeDecorationId)); + // If we have set now or before a find scope, use it for computing the search range + if (findScope) { + searchRange = searchRange.intersectRanges(findScope); } + return searchRange; } - private updateDecorations(jumpToNextMatch:boolean, resetFindScopeDecoration:boolean, newFindScope:EditorCommon.IEditorRange): void { - if (this.didReplace) { - this.next(); - } - - this.editor.changeDecorations((changeAccessor:EditorCommon.IModelDecorationsChangeAccessor) => { - this.removeOldDecorations(changeAccessor, resetFindScopeDecoration); - - if (resetFindScopeDecoration && newFindScope) { - // Add a decoration to track the find scope - let decorations = changeAccessor.deltaDecorations([], [{ - range: newFindScope, - options: this.createFindScopeDecorationOptions() - }]); - this.findScopeDecorationId = decorations[0]; - } - - this.addMatchesDecorations(changeAccessor, this._findMatches()); - }); - this.highlightedDecorationId = null; - - this.decorationIndex = this.indexAfterPosition(this.startPosition); - - if (!this.didReplace && !jumpToNextMatch) { - this.decorationIndex = this.previousIndex(this.decorationIndex); - } else if (this.decorations.length > 0) { - this.setSelectionToDecoration(this.decorations[this.decorationIndex]); - } - - var e:IFindMatchesEvent = { - position: this.decorations.length > 0 ? (this.decorationIndex+1) : 0, - count: this.decorations.length - }; - - this._emitMatchesUpdatedEvent(e); - - this.didReplace = false; - } - - - /** - * Updates selection find scope. - * Selection find scope just gets removed if passed findScope is null. - * Selection find scope does not take columns into account. - */ - public setFindScope(findScope:EditorCommon.IEditorRange): void { - if (findScope === null) { - this.updateDecorations(false, true, findScope); + private research(moveCursor:boolean, newFindScope?:EditorCommon.IEditorRange): void { + let findScope: EditorCommon.IEditorRange = null; + if (typeof newFindScope !== 'undefined') { + findScope = newFindScope; } else { - this.updateDecorations(false, true, new Range(findScope.startLineNumber, 1, findScope.endLineNumber, this.editor.getModel().getLineMaxColumn(findScope.endLineNumber))); + findScope = this._decorations.getFindScope(); + } + + let findMatches = this._findMatches(findScope); + this._decorations.set(findMatches, findScope); + + this._state.change({ matchesCount: findMatches.length }, false); + + if (moveCursor) { + this._decorations.moveToFirstAfterStartPosition(); } } - public recomputeMatches(newFindData:IFindState, jumpToNextMatch:boolean): void { - var somethingChanged = false; - if (this.isRegex !== newFindData.properties.isRegex) { - this.isRegex = newFindData.properties.isRegex; - somethingChanged = true; - } - if (this.matchCase !== newFindData.properties.matchCase) { - this.matchCase = newFindData.properties.matchCase; - somethingChanged = true; - } - if (this.wholeWord !== newFindData.properties.wholeWord) { - this.wholeWord = newFindData.properties.wholeWord; - somethingChanged = true; - } - if (newFindData.searchString !== this.searchString) { - this.searchString = newFindData.searchString; - somethingChanged = true; - } - this.replaceString = newFindData.replaceString; - if (newFindData.isReplaceRevealed !== this.searchOnlyEditableRange) { - this.searchOnlyEditableRange = newFindData.isReplaceRevealed; - somethingChanged = true; - } - - if (somethingChanged) { - this.updateDecorations(jumpToNextMatch, false, null); - } + public moveToPrevMatch(): void { + this._decorations.movePrev(); } - public start(newFindData:IFindState, findScope:EditorCommon.IEditorRange, shouldAnimate:boolean): void { - this.startPosition = this.editor.getPosition(); - - this.isRegex = newFindData.properties.isRegex; - this.matchCase = newFindData.properties.matchCase; - this.wholeWord = newFindData.properties.wholeWord; - this.searchString = newFindData.searchString; - this.replaceString = newFindData.replaceString; - this.searchOnlyEditableRange = newFindData.isReplaceRevealed; - - this.setFindScope(findScope); - this.decorationIndex = this.previousIndex(this.indexAfterPosition(this.startPosition)); - var e:IFindStartEvent = { - state: newFindData, - selectionFindEnabled: this.hasFindScope(), - shouldAnimate: shouldAnimate - }; - this._emitStartEvent(e); - } - - public prev(): void { - if (this.decorations.length > 0) { - if (this.decorationIndex === -1) { - this.decorationIndex = this.indexAfterPosition(this.startPosition); - } - this.decorationIndex = this.previousIndex(this.decorationIndex); - this.setSelectionToDecoration(this.decorations[this.decorationIndex]); - } else if (this.hasFindScope()) { - // Reveal the selection so user is reminded that 'selection find' is on. - this.editor.revealRangeInCenterIfOutsideViewport(this.editor.getModel().getDecorationRange(this.findScopeDecorationId)); - } - } - - public next(): void { - if (this.decorations.length > 0) { - if (this.decorationIndex === -1) { - this.decorationIndex = this.indexAfterPosition(this.startPosition); - } else { - this.decorationIndex = this.nextIndex(this.decorationIndex); - } - this.setSelectionToDecoration(this.decorations[this.decorationIndex]); - } else if (this.hasFindScope()) { - // Reveal the selection so user is reminded that 'selection find' is on. - this.editor.revealRangeInCenterIfOutsideViewport(this.editor.getModel().getDecorationRange(this.findScopeDecorationId)); - } - } - - private setSelectionToDecoration(decorationId:string): void { - this.editor.changeDecorations((changeAccessor: EditorCommon.IModelDecorationsChangeAccessor) => { - if (this.highlightedDecorationId !== null) { - changeAccessor.changeDecorationOptions(this.highlightedDecorationId, this.createFindMatchDecorationOptions(false)); - } - changeAccessor.changeDecorationOptions(decorationId, this.createFindMatchDecorationOptions(true)); - this.highlightedDecorationId = decorationId; - }); - var decorationRange = this.editor.getModel().getDecorationRange(decorationId); - if (Range.isIRange(decorationRange)) { - this.editor.setSelection(decorationRange); - this.editor.revealRangeInCenterIfOutsideViewport(decorationRange); - } + public moveToNextMatch(): void { + this._decorations.moveNext(); } private getReplaceString(matchedString:string): string { - if (!this.isRegex) { - return this.replaceString; + if (!this._state.isRegex) { + return this._state.replaceString; } - let regexp = Strings.createRegExp(this.searchString, this.isRegex, this.matchCase, this.wholeWord); + let regexp = Strings.createRegExp(this._state.searchString, this._state.isRegex, this._state.matchCase, this._state.wholeWord); // Parse the replace string to support that \t or \n mean the right thing - let parsedReplaceString = parseReplaceString(this.replaceString); + let parsedReplaceString = parseReplaceString(this._state.replaceString); return matchedString.replace(regexp, parsedReplaceString); } public replace(): void { - if (this.decorations.length === 0) { + if (!this._decorations.hasMatches()) { return; } - var model = this.editor.getModel(); - var currentDecorationRange = model.getDecorationRange(this.decorations[this.decorationIndex]); - var selection = this.editor.getSelection(); + let model = this.editor.getModel(); + let currentDecorationRange = this._decorations.getCurrentIndexRange(); + let selection = this.editor.getSelection(); if (currentDecorationRange !== null && selection.startColumn === currentDecorationRange.startColumn && @@ -353,117 +153,55 @@ export class FindModelBoundToEditorModel extends Events.EventEmitter { selection.startLineNumber === currentDecorationRange.startLineNumber && selection.endLineNumber === currentDecorationRange.endLineNumber) { - var matchedString = model.getValueInRange(selection); - var replaceString = this.getReplaceString(matchedString); + let matchedString = model.getValueInRange(selection); + let replaceString = this.getReplaceString(matchedString); - var command = new ReplaceCommand(selection, replaceString); - this.editor.executeCommand('replace', command); + let command = new ReplaceCommand(selection, replaceString); - this.startPosition = new Position(selection.startLineNumber, selection.startColumn + replaceString.length); - this.decorationIndex = -1; - this.didReplace = true; + this._executeEditorCommand('replace', command); + + this._decorations.setStartPosition(new Position(selection.startLineNumber, selection.startColumn + replaceString.length)); + this.research(true); } else { - this.next(); + this.moveToNextMatch(); } } - private _findMatches(limitResultCount?:number): EditorCommon.IEditorRange[] { - return this.editor.getModel().findMatches(this.searchString, this._getSearchRange(), this.isRegex, this.matchCase, this.wholeWord, limitResultCount); + private _findMatches(findScope: EditorCommon.IEditorRange, limitResultCount?:number): EditorCommon.IEditorRange[] { + let searchRange = FindModelBoundToEditorModel._getSearchRange(this.editor.getModel(), this._state.isReplaceRevealed, findScope); + return this.editor.getModel().findMatches(this._state.searchString, searchRange, this._state.isRegex, this._state.matchCase, this._state.wholeWord, limitResultCount); } public replaceAll(): void { - if (this.decorations.length === 0) { + if (!this._decorations.hasMatches()) { return; } let model = this.editor.getModel(); + let findScope = this._decorations.getFindScope(); // Get all the ranges (even more than the highlighted ones) - let ranges = this._findMatches(Number.MAX_VALUE); + let ranges = this._findMatches(findScope, Number.MAX_VALUE); - // Remove all decorations - this.editor.changeDecorations((changeAccessor:EditorCommon.IModelDecorationsChangeAccessor) => { - this.removeOldDecorations(changeAccessor, false); - }); + this._decorations.set([], findScope); - var replaceStrings:string[] = []; - for (var i = 0, len = ranges.length; i < len; i++) { + let replaceStrings:string[] = []; + for (let i = 0, len = ranges.length; i < len; i++) { replaceStrings.push(this.getReplaceString(model.getValueInRange(ranges[i]))); } - var command = new ReplaceAllCommand.ReplaceAllCommand(ranges, replaceStrings); - this.editor.executeCommand('replaceAll', command); + let command = new ReplaceAllCommand.ReplaceAllCommand(ranges, replaceStrings); + this._executeEditorCommand('replaceAll', command); } - public dispose(): void { - super.dispose(); - this.updateDecorationsScheduler.dispose(); - this.listenersToRemove.forEach((element) => { - element(); - }); - this.listenersToRemove = []; - if (this.editor.getModel()) { - this.editor.changeDecorations((changeAccessor:EditorCommon.IModelDecorationsChangeAccessor) => { - this.removeOldDecorations(changeAccessor, true); - }); + private _executeEditorCommand(source:string, command:EditorCommon.ICommand): void { + try { + this._ignoreModelContentChanged = true; + this.editor.executeCommand(source, command); + } finally { + this._ignoreModelContentChanged = false; } } - - public hasFindScope(): boolean { - return !!this.findScopeDecorationId; - } - - private previousIndex(index:number): number { - if (this.decorations.length > 0) { - return (index - 1 + this.decorations.length) % this.decorations.length; - } - return 0; - } - - private nextIndex(index:number): number { - if (this.decorations.length > 0) { - return (index + 1) % this.decorations.length; - } - return 0; - } - - private indexAfterPosition(position:EditorCommon.IEditorPosition): number { - if (this.decorations.length === 0) { - return 0; - } - for (var i = 0, len = this.decorations.length; i < len; i++) { - var decorationId = this.decorations[i]; - var r = this.editor.getModel().getDecorationRange(decorationId); - if (!r || r.startLineNumber < position.lineNumber) { - continue; - } - if (r.startLineNumber > position.lineNumber) { - return i; - } - if (r.startColumn < position.column) { - continue; - } - return i; - } - return 0; - } - - public addStartEventListener(callback:(e:IFindStartEvent)=>void): Lifecycle.IDisposable { - return this.addListener2(FindModelBoundToEditorModel._START_EVENT, callback); - } - - private _emitStartEvent(e:IFindStartEvent): void { - this.emit(FindModelBoundToEditorModel._START_EVENT, e); - } - - public addMatchesUpdatedEventListener(callback:(e:IFindMatchesEvent)=>void): Lifecycle.IDisposable { - return this.addListener2(FindModelBoundToEditorModel._MATCHES_UPDATED_EVENT, callback); - } - - private _emitMatchesUpdatedEvent(e:IFindMatchesEvent): void { - this.emit(FindModelBoundToEditorModel._MATCHES_UPDATED_EVENT, e); - } - } const BACKSLASH_CHAR_CODE = '\\'.charCodeAt(0); diff --git a/src/vs/editor/contrib/find/common/findState.ts b/src/vs/editor/contrib/find/common/findState.ts new file mode 100644 index 00000000000..11452b825ec --- /dev/null +++ b/src/vs/editor/contrib/find/common/findState.ts @@ -0,0 +1,167 @@ +/*--------------------------------------------------------------------------------------------- + * 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 EditorCommon from 'vs/editor/common/editorCommon'; +import {EventEmitter} from 'vs/base/common/eventEmitter'; +import {IDisposable} from 'vs/base/common/lifecycle'; +import {Range} from 'vs/editor/common/core/range'; + +export interface FindReplaceStateChangedEvent { + moveCursor: boolean; + + searchString: boolean; + replaceString: boolean; + isRevealed: boolean; + isReplaceRevealed: boolean; + isRegex: boolean; + wholeWord: boolean; + matchCase: boolean; + searchScope: boolean; + matchesCount: boolean; +} + +export interface INewFindReplaceState { + searchString?: string; + replaceString?: string; + isRevealed?: boolean; + isReplaceRevealed?: boolean; + isRegex?: boolean; + wholeWord?: boolean; + matchCase?: boolean; + searchScope?: EditorCommon.IEditorRange; + matchesCount?: number; +} + +export class FindReplaceState implements IDisposable { + + private static _CHANGED_EVENT = 'changed'; + + private _searchString: string; + private _replaceString: string; + private _isRevealed: boolean; + private _isReplaceRevealed: boolean; + private _isRegex: boolean; + private _wholeWord: boolean; + private _matchCase: boolean; + private _searchScope: EditorCommon.IEditorRange; + private _matchesCount: number; + private _eventEmitter: EventEmitter; + + public get searchString(): string { return this._searchString; } + public get replaceString(): string { return this._replaceString; } + public get isRevealed(): boolean { return this._isRevealed; } + public get isReplaceRevealed(): boolean { return this._isReplaceRevealed; } + public get isRegex(): boolean { return this._isRegex; } + public get wholeWord(): boolean { return this._wholeWord; } + public get matchCase(): boolean { return this._matchCase; } + public get searchScope(): EditorCommon.IEditorRange { return this._searchScope; } + public get matchesCount(): number { return this._matchesCount; } + + constructor() { + this._searchString = ''; + this._replaceString = ''; + this._isRevealed = false; + this._isReplaceRevealed = false; + this._isRegex = false; + this._wholeWord = false; + this._matchCase = false; + this._searchScope = null; + this._matchesCount = 0; + this._eventEmitter = new EventEmitter(); + } + + public dispose(): void { + this._eventEmitter.dispose(); + } + + public addChangeListener(listener:(e:FindReplaceStateChangedEvent)=>void): IDisposable { + return this._eventEmitter.addListener2(FindReplaceState._CHANGED_EVENT, listener); + } + + public change(newState:INewFindReplaceState, moveCursor:boolean): void { + let changeEvent:FindReplaceStateChangedEvent = { + moveCursor: moveCursor, + searchString: false, + replaceString: false, + isRevealed: false, + isReplaceRevealed: false, + isRegex: false, + wholeWord: false, + matchCase: false, + searchScope: false, + matchesCount: false + }; + let somethingChanged = false; + + if (typeof newState.searchString !== 'undefined') { + if (this._searchString !== newState.searchString) { + this._searchString = newState.searchString; + changeEvent.searchString = true; + somethingChanged = true; + } + } + if (typeof newState.replaceString !== 'undefined') { + if (this._replaceString !== newState.replaceString) { + this._replaceString = newState.replaceString; + changeEvent.replaceString = true; + somethingChanged = true; + } + } + if (typeof newState.isRevealed !== 'undefined') { + if (this._isRevealed !== newState.isRevealed) { + this._isRevealed = newState.isRevealed; + changeEvent.isRevealed = true; + somethingChanged = true; + } + } + if (typeof newState.isReplaceRevealed !== 'undefined') { + if (this._isReplaceRevealed !== newState.isReplaceRevealed) { + this._isReplaceRevealed = newState.isReplaceRevealed; + changeEvent.isReplaceRevealed = true; + somethingChanged = true; + } + } + if (typeof newState.isRegex !== 'undefined') { + if (this._isRegex !== newState.isRegex) { + this._isRegex = newState.isRegex; + changeEvent.isRegex = true; + somethingChanged = true; + } + } + if (typeof newState.wholeWord !== 'undefined') { + if (this._wholeWord !== newState.wholeWord) { + this._wholeWord = newState.wholeWord; + changeEvent.wholeWord = true; + somethingChanged = true; + } + } + if (typeof newState.matchCase !== 'undefined') { + if (this._matchCase !== newState.matchCase) { + this._matchCase = newState.matchCase; + changeEvent.matchCase = true; + somethingChanged = true; + } + } + if (typeof newState.searchScope !== 'undefined') { + if (!Range.equalsRange(this._searchScope, newState.searchScope)) { + this._searchScope = newState.searchScope; + changeEvent.searchScope = true; + somethingChanged = true; + } + } + if (typeof newState.matchesCount !== 'undefined') { + if (this._matchesCount !== newState.matchesCount) { + this._matchesCount = newState.matchesCount; + changeEvent.matchesCount = true; + somethingChanged = true; + } + } + + if (somethingChanged) { + this._eventEmitter.emit(FindReplaceState._CHANGED_EVENT, changeEvent); + } + } +} From 42c3b942f50b61580db1ff430663334b2b49df0f Mon Sep 17 00:00:00 2001 From: isidor Date: Fri, 8 Jan 2016 10:04:39 +0100 Subject: [PATCH 20/36] tree size tuning: move monaco count badge in working file view fixes --- src/vs/base/browser/ui/countBadge/countBadge.css | 2 +- .../workbench/parts/files/browser/media/explorerviewlet.css | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/vs/base/browser/ui/countBadge/countBadge.css b/src/vs/base/browser/ui/countBadge/countBadge.css index 232c87013e3..c08164258ec 100644 --- a/src/vs/base/browser/ui/countBadge/countBadge.css +++ b/src/vs/base/browser/ui/countBadge/countBadge.css @@ -6,7 +6,7 @@ .monaco-count-badge { padding: 0.2em 0.4em; border-radius: 1em; - font-size: 80%; + font-size: 85%; font-weight: normal; text-align: center; background: #BEBEBE; diff --git a/src/vs/workbench/parts/files/browser/media/explorerviewlet.css b/src/vs/workbench/parts/files/browser/media/explorerviewlet.css index 3ae54e985f4..01eadb701c5 100644 --- a/src/vs/workbench/parts/files/browser/media/explorerviewlet.css +++ b/src/vs/workbench/parts/files/browser/media/explorerviewlet.css @@ -34,8 +34,8 @@ } .explorer-viewlet .monaco-count-badge { - padding: 0 6px; - display: inline-block; + padding: 1px 6px; + margin-left: 6px; } .explorer-viewlet .explorer-empty-view { From e021c0549505815e245de872c943435c4dda634f Mon Sep 17 00:00:00 2001 From: isidor Date: Fri, 8 Jan 2016 10:25:51 +0100 Subject: [PATCH 21/36] tree size tuning: center header action icons in split panes --- src/vs/workbench/browser/parts/sidebar/media/sidebarpart.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/browser/parts/sidebar/media/sidebarpart.css b/src/vs/workbench/browser/parts/sidebar/media/sidebarpart.css index 6a441aaeaa6..ccf818257f1 100644 --- a/src/vs/workbench/browser/parts/sidebar/media/sidebarpart.css +++ b/src/vs/workbench/browser/parts/sidebar/media/sidebarpart.css @@ -74,7 +74,7 @@ .monaco-workbench .viewlet .collapsible.header .action-label { margin-right: 0.2em; - margin-top: 4px; + margin-top: 3px; background-repeat: no-repeat; width: 16px; height: 16px; From 8168eb25bf99aaf7a6cac767e5228e6170b3e4d9 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Fri, 8 Jan 2016 10:00:49 +0100 Subject: [PATCH 22/36] Minor renames --- src/vs/editor/contrib/find/browser/find.ts | 18 +++++++-------- .../editor/contrib/find/browser/findWidget.ts | 22 +++++++++---------- .../editor/contrib/find/common/findModel.ts | 18 +++++++-------- 3 files changed, 29 insertions(+), 29 deletions(-) diff --git a/src/vs/editor/contrib/find/browser/find.ts b/src/vs/editor/contrib/find/browser/find.ts index 3512c181514..25871b5470f 100644 --- a/src/vs/editor/contrib/find/browser/find.ts +++ b/src/vs/editor/contrib/find/browser/find.ts @@ -10,7 +10,7 @@ import {EditorBrowserRegistry} from 'vs/editor/browser/editorBrowserExtensions'; import {CommonEditorRegistry, ContextKey, EditorActionDescriptor} from 'vs/editor/common/editorCommonExtensions'; import {EditorAction, Behaviour} from 'vs/editor/common/editorAction'; import {IFindController, FindWidget} from 'vs/editor/contrib/find/browser/findWidget'; -import {FindModelBoundToEditorModel, FindIds} from 'vs/editor/contrib/find/common/findModel'; +import {FindModelBoundToEditorModel, FIND_IDS} from 'vs/editor/contrib/find/common/findModel'; import * as EditorBrowser from 'vs/editor/browser/editorBrowser'; import {IDisposable, disposeAll} from 'vs/base/common/lifecycle'; import * as EditorCommon from 'vs/editor/common/editorCommon'; @@ -581,22 +581,22 @@ CommonEditorRegistry.registerEditorAction(new EditorActionDescriptor(SelectHighl })); // register actions -CommonEditorRegistry.registerEditorAction(new EditorActionDescriptor(StartFindAction, FindIds.START_FIND_ACTION_ID, nls.localize('startFindAction',"Find"), { +CommonEditorRegistry.registerEditorAction(new EditorActionDescriptor(StartFindAction, FIND_IDS.StartFindAction, nls.localize('startFindAction',"Find"), { context: ContextKey.None, primary: KeyMod.CtrlCmd | KeyCode.KEY_F, secondary: [KeyMod.CtrlCmd | KeyCode.F3] })); -CommonEditorRegistry.registerEditorAction(new EditorActionDescriptor(NextMatchFindAction, FindIds.NEXT_MATCH_FIND_ACTION_ID, nls.localize('findNextMatchAction', "Find Next"), { +CommonEditorRegistry.registerEditorAction(new EditorActionDescriptor(NextMatchFindAction, FIND_IDS.NextMatchFindAction, nls.localize('findNextMatchAction', "Find Next"), { context: ContextKey.EditorFocus, primary: KeyCode.F3, mac: { primary: KeyMod.CtrlCmd | KeyCode.KEY_G, secondary: [KeyCode.F3] } })); -CommonEditorRegistry.registerEditorAction(new EditorActionDescriptor(PreviousMatchFindAction, FindIds.PREVIOUS_MATCH_FIND_ACTION_ID, nls.localize('findPreviousMatchAction', "Find Previous"), { +CommonEditorRegistry.registerEditorAction(new EditorActionDescriptor(PreviousMatchFindAction, FIND_IDS.PreviousMatchFindAction, nls.localize('findPreviousMatchAction', "Find Previous"), { context: ContextKey.EditorFocus, primary: KeyMod.Shift | KeyCode.F3, mac: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_G, secondary: [KeyMod.Shift | KeyCode.F3] } })); -CommonEditorRegistry.registerEditorAction(new EditorActionDescriptor(StartFindReplaceAction, FindIds.START_FIND_REPLACE_ACTION_ID, nls.localize('startReplace', "Replace"), { +CommonEditorRegistry.registerEditorAction(new EditorActionDescriptor(StartFindReplaceAction, FIND_IDS.StartFindReplaceAction, nls.localize('startReplace', "Replace"), { context: ContextKey.None, primary: KeyMod.CtrlCmd | KeyCode.KEY_H, mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_F } @@ -618,18 +618,18 @@ function registerFindCommand(id:string, callback:(controller:FindController)=>vo }); } -registerFindCommand(FindIds.CLOSE_FIND_WIDGET_COMMAND_ID, x => x.closeFindWidget(), { +registerFindCommand(FIND_IDS.CloseFindWidgetCommand, x => x.closeFindWidget(), { primary: KeyCode.Escape }, CONTEXT_FIND_WIDGET_VISIBLE); -registerFindCommand(FindIds.TOGGLE_CASE_SENSITIVE_COMMAND_ID, x => x.toggleCaseSensitive(), { +registerFindCommand(FIND_IDS.ToggleCaseSensitiveCommand, x => x.toggleCaseSensitive(), { primary: KeyMod.Alt | KeyCode.KEY_C, mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_C } }); -registerFindCommand(FindIds.TOGGLE_WHOLE_WORD_COMMAND_ID, x => x.toggleWholeWords(), { +registerFindCommand(FIND_IDS.ToggleWholeWordCommand, x => x.toggleWholeWords(), { primary: KeyMod.Alt | KeyCode.KEY_W, mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_W } }); -registerFindCommand(FindIds.TOGGLE_REGEX_COMMAND_ID, x => x.toggleRegex(), { +registerFindCommand(FIND_IDS.ToggleRegexCommand, x => x.toggleRegex(), { primary: KeyMod.Alt | KeyCode.KEY_R, mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_R } }); diff --git a/src/vs/editor/contrib/find/browser/findWidget.ts b/src/vs/editor/contrib/find/browser/findWidget.ts index 584d5bb3563..fb95a0d2448 100644 --- a/src/vs/editor/contrib/find/browser/findWidget.ts +++ b/src/vs/editor/contrib/find/browser/findWidget.ts @@ -16,7 +16,7 @@ import {InputBox, IMessage as InputBoxMessage} from 'vs/base/browser/ui/inputbox import {FindInput} from 'vs/base/browser/ui/findinput/findInput'; import * as EditorBrowser from 'vs/editor/browser/editorBrowser'; import * as EditorCommon from 'vs/editor/common/editorCommon'; -import {FindIds} from 'vs/editor/contrib/find/common/findModel'; +import {FIND_IDS} from 'vs/editor/contrib/find/common/findModel'; import {disposeAll, IDisposable} from 'vs/base/common/lifecycle'; import {CommonKeybindings} from 'vs/base/common/keyCodes'; import {IKeybindingService} from 'vs/platform/keybinding/common/keybindingService'; @@ -234,10 +234,10 @@ export class FindWidget implements EditorBrowser.IOverlayWidget { let handled = false; if (e.equals(CommonKeybindings.ENTER)) { - this._codeEditor.getAction(FindIds.NEXT_MATCH_FIND_ACTION_ID).run().done(null, Errors.onUnexpectedError); + this._codeEditor.getAction(FIND_IDS.NextMatchFindAction).run().done(null, Errors.onUnexpectedError); handled = true; } else if (e.equals(CommonKeybindings.SHIFT_ENTER)) { - this._codeEditor.getAction(FindIds.PREVIOUS_MATCH_FIND_ACTION_ID).run().done(null, Errors.onUnexpectedError); + this._codeEditor.getAction(FIND_IDS.PreviousMatchFindAction).run().done(null, Errors.onUnexpectedError); handled = true; } else if (e.equals(CommonKeybindings.TAB)) { if (this._isReplaceVisible) { @@ -304,9 +304,9 @@ export class FindWidget implements EditorBrowser.IOverlayWidget { width: FindWidget.FIND_INPUT_AREA_WIDTH, label: NLS_FIND_INPUT_LABEL, placeholder: NLS_FIND_INPUT_PLACEHOLDER, - appendCaseSensitiveLabel: this._keybindingLabelFor(FindIds.TOGGLE_CASE_SENSITIVE_COMMAND_ID), - appendWholeWordsLabel: this._keybindingLabelFor(FindIds.TOGGLE_WHOLE_WORD_COMMAND_ID), - appendRegexLabel: this._keybindingLabelFor(FindIds.TOGGLE_REGEX_COMMAND_ID), + appendCaseSensitiveLabel: this._keybindingLabelFor(FIND_IDS.ToggleCaseSensitiveCommand), + appendWholeWordsLabel: this._keybindingLabelFor(FIND_IDS.ToggleWholeWordCommand), + appendRegexLabel: this._keybindingLabelFor(FIND_IDS.ToggleRegexCommand), validation: (value:string): InputBoxMessage => { if (value.length === 0) { return null; @@ -335,10 +335,10 @@ export class FindWidget implements EditorBrowser.IOverlayWidget { // Previous button this._prevBtn = new SimpleButton({ - label: NLS_PREVIOUS_MATCH_BTN_LABEL + this._keybindingLabelFor(FindIds.PREVIOUS_MATCH_FIND_ACTION_ID), + label: NLS_PREVIOUS_MATCH_BTN_LABEL + this._keybindingLabelFor(FIND_IDS.PreviousMatchFindAction), className: 'previous', onTrigger: () => { - this._codeEditor.getAction(FindIds.PREVIOUS_MATCH_FIND_ACTION_ID).run().done(null, Errors.onUnexpectedError); + this._codeEditor.getAction(FIND_IDS.PreviousMatchFindAction).run().done(null, Errors.onUnexpectedError); }, onKeyDown: (e) => {} }); @@ -346,10 +346,10 @@ export class FindWidget implements EditorBrowser.IOverlayWidget { // Next button this._nextBtn = new SimpleButton({ - label: NLS_NEXT_MATCH_BTN_LABEL + this._keybindingLabelFor(FindIds.NEXT_MATCH_FIND_ACTION_ID), + label: NLS_NEXT_MATCH_BTN_LABEL + this._keybindingLabelFor(FIND_IDS.NextMatchFindAction), className: 'next', onTrigger: () => { - this._codeEditor.getAction(FindIds.NEXT_MATCH_FIND_ACTION_ID).run().done(null, Errors.onUnexpectedError); + this._codeEditor.getAction(FIND_IDS.NextMatchFindAction).run().done(null, Errors.onUnexpectedError); }, onKeyDown: (e) => {} }); @@ -378,7 +378,7 @@ export class FindWidget implements EditorBrowser.IOverlayWidget { // Close button this._closeBtn = new SimpleButton({ - label: NLS_CLOSE_BTN_LABEL + this._keybindingLabelFor(FindIds.CLOSE_FIND_WIDGET_COMMAND_ID), + label: NLS_CLOSE_BTN_LABEL + this._keybindingLabelFor(FIND_IDS.CloseFindWidgetCommand), className: 'close-fw', onTrigger: () => { this._state.change({ isRevealed: false }, false); diff --git a/src/vs/editor/contrib/find/common/findModel.ts b/src/vs/editor/contrib/find/common/findModel.ts index 73d7f156000..c52158da047 100644 --- a/src/vs/editor/contrib/find/common/findModel.ts +++ b/src/vs/editor/contrib/find/common/findModel.ts @@ -15,15 +15,15 @@ import {ReplaceCommand} from 'vs/editor/common/commands/replaceCommand'; import {FindDecorations} from './findDecorations'; import {FindReplaceStateChangedEvent, FindReplaceState} from './findState'; -export const FindIds = { - START_FIND_ACTION_ID: 'actions.find', - NEXT_MATCH_FIND_ACTION_ID: 'editor.action.nextMatchFindAction', - PREVIOUS_MATCH_FIND_ACTION_ID: 'editor.action.previousMatchFindAction', - START_FIND_REPLACE_ACTION_ID: 'editor.action.startFindReplaceAction', - CLOSE_FIND_WIDGET_COMMAND_ID: 'closeFindWidget', - TOGGLE_CASE_SENSITIVE_COMMAND_ID: 'toggleFindCaseSensitive', - TOGGLE_WHOLE_WORD_COMMAND_ID: 'toggleFindWholeWord', - TOGGLE_REGEX_COMMAND_ID: 'toggleFindRegex' +export const FIND_IDS = { + StartFindAction: 'actions.find', + NextMatchFindAction: 'editor.action.nextMatchFindAction', + PreviousMatchFindAction: 'editor.action.previousMatchFindAction', + StartFindReplaceAction: 'editor.action.startFindReplaceAction', + CloseFindWidgetCommand: 'closeFindWidget', + ToggleCaseSensitiveCommand: 'toggleFindCaseSensitive', + ToggleWholeWordCommand: 'toggleFindWholeWord', + ToggleRegexCommand: 'toggleFindRegex' } export class FindModelBoundToEditorModel { From df43d6a7eee10186210ddcb32771b95dd5e0d758 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Fri, 8 Jan 2016 10:30:45 +0100 Subject: [PATCH 23/36] Make replace not use `decorationIndex` --- src/vs/base/common/strings.ts | 28 ++++++++++++++++++ src/vs/editor/common/editorCommon.ts | 6 ++-- src/vs/editor/common/model/textModel.ts | 29 ++----------------- .../contrib/find/common/findDecorations.ts | 7 ----- .../editor/contrib/find/common/findModel.ts | 18 +++++------- 5 files changed, 40 insertions(+), 48 deletions(-) diff --git a/src/vs/base/common/strings.ts b/src/vs/base/common/strings.ts index b914a83c85f..def07124650 100644 --- a/src/vs/base/common/strings.ts +++ b/src/vs/base/common/strings.ts @@ -202,6 +202,34 @@ export function createRegExp(searchString: string, isRegex: boolean, matchCase: return new RegExp(searchString, modifiers); } +/** + * Create a regular expression only if it is valid and it doesn't lead to endless loop. + */ +export function createSafeRegExp(searchString:string, isRegex:boolean, matchCase:boolean, wholeWord:boolean): RegExp { + if (searchString === '') { + return null; + } + + // Try to create a RegExp out of the params + var regex:RegExp = null; + try { + regex = createRegExp(searchString, isRegex, matchCase, wholeWord); + } catch (err) { + return null; + } + + // Guard against endless loop RegExps & wrap around try-catch as very long regexes produce an exception when executed the first time + try { + if (regExpLeadsToEndlessLoop(regex)) { + return null; + } + } catch (err) { + return null; + } + + return regex; + } + export function regExpLeadsToEndlessLoop(regexp: RegExp): boolean { // Exit early if it's one of these special cases which are meant to match // against an empty string diff --git a/src/vs/editor/common/editorCommon.ts b/src/vs/editor/common/editorCommon.ts index c0301683a91..84a78528980 100644 --- a/src/vs/editor/common/editorCommon.ts +++ b/src/vs/editor/common/editorCommon.ts @@ -1745,17 +1745,17 @@ export interface IModel extends IEditableTextModel, ITextModelWithMarkers, IToke * @param matchCase Force the matching to match lower/upper case exactly. * @param wholeWord Force the matching to match entire words only. * @param limitResultCount Limit the number of results - * @return The ranges where the matches are. It is empty if not matches have been found. + * @return The ranges where the matches are. It is empty if no matches have been found. */ findMatches(searchString:string, searchScope:IRange, isRegex:boolean, matchCase:boolean, wholeWord:boolean, limitResultCount?:number): IEditorRange[]; /** - * Search the model. + * Search the model for the next match. Loops to the beginning of the model if needed. * @param searchString The string used to search. If it is a regular expression, set `isRegex` to true. * @param searchStart Start the searching at the specified position. * @param isRegex Used to indicate that `searchString` is a regular expression. * @param matchCase Force the matching to match lower/upper case exactly. * @param wholeWord Force the matching to match entire words only. - * @return The ranges where the matches are. It is empty if not matches have been found. + * @return The range where the next match is. It is null if no next match has been found. */ findNextMatch(searchString:string, searchStart:IPosition, isRegex:boolean, matchCase:boolean, wholeWord:boolean): IEditorRange; diff --git a/src/vs/editor/common/model/textModel.ts b/src/vs/editor/common/model/textModel.ts index c630f94b648..06b5fb0bda1 100644 --- a/src/vs/editor/common/model/textModel.ts +++ b/src/vs/editor/common/model/textModel.ts @@ -849,37 +849,12 @@ export class TextModel extends OrderGuaranteeEventEmitter implements EditorCommo throw new Error('Unknown EOL preference'); } - private _toRegExp(searchString:string, isRegex:boolean, matchCase:boolean, wholeWord:boolean): RegExp { - if (searchString === '') { - return null; - } - - // Try to create a RegExp out of the params - var regex:RegExp = null; - try { - regex = Strings.createRegExp(searchString, isRegex, matchCase, wholeWord); - } catch (err) { - return null; - } - - // Guard against endless loop RegExps & wrap around try-catch as very long regexes produce an exception when executed the first time - try { - if (Strings.regExpLeadsToEndlessLoop(regex)) { - return null; - } - } catch (err) { - return null; - } - - return regex; - } - public findMatches(searchString:string, rawSearchScope:any, isRegex:boolean, matchCase:boolean, wholeWord:boolean, limitResultCount:number = LIMIT_FIND_COUNT): EditorCommon.IEditorRange[] { if (this._isDisposed) { throw new Error('Model.findMatches: Model is disposed'); } - var regex = this._toRegExp(searchString, isRegex, matchCase, wholeWord); + var regex = Strings.createSafeRegExp(searchString, isRegex, matchCase, wholeWord); if (!regex) { return []; } @@ -899,7 +874,7 @@ export class TextModel extends OrderGuaranteeEventEmitter implements EditorCommo throw new Error('Model.findNextMatch: Model is disposed'); } - var regex = this._toRegExp(searchString, isRegex, matchCase, wholeWord); + var regex = Strings.createSafeRegExp(searchString, isRegex, matchCase, wholeWord); if (!regex) { return null; } diff --git a/src/vs/editor/contrib/find/common/findDecorations.ts b/src/vs/editor/contrib/find/common/findDecorations.ts index 7dd035d73c0..746c8b6e020 100644 --- a/src/vs/editor/contrib/find/common/findDecorations.ts +++ b/src/vs/editor/contrib/find/common/findDecorations.ts @@ -66,13 +66,6 @@ export class FindDecorations implements Lifecycle.IDisposable { return (this.decorations.length > 0); } - public getCurrentIndexRange(): EditorCommon.IEditorRange { - if (this.decorationIndex >= 0 && this.decorationIndex < this.decorations.length) { - return this.editor.getModel().getDecorationRange(this.decorations[this.decorationIndex]); - } - return null; - } - public setIndexToFirstAfterStartPosition(): void { this._setDecorationIndex(this.indexAfterPosition(this.startPosition), false); } diff --git a/src/vs/editor/contrib/find/common/findModel.ts b/src/vs/editor/contrib/find/common/findModel.ts index c52158da047..e24e5f80dd7 100644 --- a/src/vs/editor/contrib/find/common/findModel.ts +++ b/src/vs/editor/contrib/find/common/findModel.ts @@ -143,18 +143,13 @@ export class FindModelBoundToEditorModel { return; } - let model = this.editor.getModel(); - let currentDecorationRange = this._decorations.getCurrentIndexRange(); let selection = this.editor.getSelection(); - - if (currentDecorationRange !== null && - selection.startColumn === currentDecorationRange.startColumn && - selection.endColumn === currentDecorationRange.endColumn && - selection.startLineNumber === currentDecorationRange.startLineNumber && - selection.endLineNumber === currentDecorationRange.endLineNumber) { - - let matchedString = model.getValueInRange(selection); - let replaceString = this.getReplaceString(matchedString); + let selectionText = this.editor.getModel().getValueInRange(selection); + let regexp = Strings.createSafeRegExp(this._state.searchString, this._state.isRegex, this._state.matchCase, this._state.wholeWord); + let m = selectionText.match(regexp); + if (m && m[0].length === selectionText.length) { + // selection sits on a find match => replace it! + let replaceString = this.getReplaceString(selectionText); let command = new ReplaceCommand(selection, replaceString); @@ -163,6 +158,7 @@ export class FindModelBoundToEditorModel { this._decorations.setStartPosition(new Position(selection.startLineNumber, selection.startColumn + replaceString.length)); this.research(true); } else { + this._decorations.setStartPosition(this.editor.getPosition()); this.moveToNextMatch(); } } From 4a7ddb07886e5ffd93b972dcb4fc9b4fc60589dc Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Fri, 8 Jan 2016 11:11:03 +0100 Subject: [PATCH 24/36] use base proxy util for request service too related to #155 --- .../services/request/node/rawHttpService.ts | 45 ++++--------------- .../services/request/node/requestService.ts | 6 +-- 2 files changed, 12 insertions(+), 39 deletions(-) diff --git a/src/vs/workbench/services/request/node/rawHttpService.ts b/src/vs/workbench/services/request/node/rawHttpService.ts index 04495ce01cb..bb854412d4e 100644 --- a/src/vs/workbench/services/request/node/rawHttpService.ts +++ b/src/vs/workbench/services/request/node/rawHttpService.ts @@ -9,9 +9,7 @@ import { TPromise } from 'vs/base/common/winjs.base'; import { assign } from 'vs/base/common/objects'; import { Url, parse as parseUrl } from 'url'; import { request, IRequestOptions } from 'vs/base/node/request'; - -import HttpProxyAgent = require('http-proxy-agent'); -import HttpsProxyAgent = require('https-proxy-agent'); +import { getProxyAgent } from 'vs/workbench/node/proxy'; export interface IXHROptions extends IRequestOptions { responseType?: string; @@ -23,41 +21,18 @@ export interface IXHRResponse { status: number; } -let proxyConfiguration: string = null; +let proxyUrl: string = null; +let strictSSL: boolean = true; -export function configure(proxyURI: string): void { - proxyConfiguration = proxyURI; -} - -function getProxyURI(uri: Url): string { - let proxyURI = proxyConfiguration; - if (!proxyURI) { - if (uri.protocol === 'http:') { - proxyURI = process.env.HTTP_PROXY || process.env.http_proxy || null; - } else if (uri.protocol === 'https:') { - proxyURI = process.env.HTTPS_PROXY || process.env.https_proxy || process.env.HTTP_PROXY || process.env.http_proxy || null; - } - } - return proxyURI; -} - -function getProxyAgent(uri: Url): any { - let proxyURI = getProxyURI(uri); - if (proxyURI) { - let proxyEndpoint = parseUrl(proxyURI); - switch (proxyEndpoint.protocol) { - case 'http:': - case 'https:': - return uri.protocol === 'http:' ? new HttpProxyAgent(proxyURI) : new HttpsProxyAgent(proxyURI); - } - } - return void 0; +export function configure(_proxyUrl: string, _strictSSL: boolean): void { + proxyUrl = _proxyUrl; + strictSSL = _strictSSL; } export function xhr(options: IXHROptions): TPromise { - let endpoint = parseUrl(options.url); + const agent = getProxyAgent(options.url, { proxyUrl, strictSSL }); options = assign({}, options); - options = assign(options, { agent: getProxyAgent(endpoint) }); + options = assign(options, { agent }); return request(options).then(result => new TPromise((c, e, p) => { let res = result.res; @@ -88,12 +63,10 @@ export function xhr(options: IXHROptions): TPromise { } }); }, err => { - let endpoint = parseUrl(options.url); - let agent = getProxyAgent(endpoint); let message: string; if (agent) { - message = 'Unable to to connect to ' + options.url + ' through proxy ' + getProxyURI(endpoint) + '. Error: ' + err.message; + message = 'Unable to to connect to ' + options.url + ' through a proxy . Error: ' + err.message; } else { message = 'Unable to to connect to ' + options.url + '. Error: ' + err.message; } diff --git a/src/vs/workbench/services/request/node/requestService.ts b/src/vs/workbench/services/request/node/requestService.ts index 6a6343bbaa5..8288bf2cbdd 100644 --- a/src/vs/workbench/services/request/node/requestService.ts +++ b/src/vs/workbench/services/request/node/requestService.ts @@ -23,7 +23,7 @@ import {IWorkspaceContextService} from 'vs/platform/workspace/common/workspace'; interface IRawHttpService { xhr(options: http.IXHROptions): TPromise; - configure(proxy: string): void; + configure(proxy: string, strictSSL: boolean): void; } interface IXHRFunction { @@ -44,7 +44,7 @@ export class RequestService extends BaseRequestService implements IThreadSynchro // proxy setting updating this.callOnDispose.push(configurationService.addListener(ConfigurationServiceEventTypes.UPDATED, (e: IConfigurationServiceEvent) => { this.rawHttpServicePromise.then((rawHttpService) => { - rawHttpService.configure(e.config.http && e.config.http.proxy); + rawHttpService.configure(e.config.http && e.config.http.proxy, e.config.http.proxyStrictSSL); }); })); } @@ -53,7 +53,7 @@ export class RequestService extends BaseRequestService implements IThreadSynchro private get rawHttpServicePromise(): TPromise { if (!this._rawHttpServicePromise) { this._rawHttpServicePromise = this.configurationService.loadConfiguration().then((configuration: any) => { - rawHttpService.configure(configuration.http && configuration.http.proxy); + rawHttpService.configure(configuration.http && configuration.http.proxy, configuration.http.proxyStrictSSL); return rawHttpService; }); } From fbb797311c27ce118f6705532b859c5f8f4acec9 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 8 Jan 2016 11:17:05 +0100 Subject: [PATCH 25/36] Revert "mark beginning of javascript changes to vscode.d.ts" This reverts commit ac55a05ba699a8b3e67998a117b5a8667ec29b1c. --- src/vs/vscode.d.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/vscode.d.ts b/src/vs/vscode.d.ts index af7ad16ce97..1156a04d295 100644 --- a/src/vs/vscode.d.ts +++ b/src/vs/vscode.d.ts @@ -3367,8 +3367,8 @@ declare namespace vscode { } } -// [javascript start marker] -// DO NOT MODIFY ABOVE COMMENT +// TS 1.6 & node_module +// export = vscode; // when used for JS* declare module 'vscode' { From f88ab736b3382efa2cfa409f80935457c4c6179e Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 8 Jan 2016 11:18:13 +0100 Subject: [PATCH 26/36] better marker --- src/vs/vscode.d.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/vs/vscode.d.ts b/src/vs/vscode.d.ts index 1156a04d295..231bdcbff14 100644 --- a/src/vs/vscode.d.ts +++ b/src/vs/vscode.d.ts @@ -3371,6 +3371,7 @@ declare namespace vscode { // export = vscode; // when used for JS* +// !!! DO NOT MODIFY ABOVE COMMENT ("when used for JS*") IT IS BEING USED TO DETECT JS* ONLY CHANGES !!! declare module 'vscode' { export = vscode; } From cc3ca4668311989caffb9fbc3e988d2dc1cc81dd Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Fri, 8 Jan 2016 11:37:07 +0100 Subject: [PATCH 27/36] Comments are not colored #1864 --- src/vs/editor/browser/widget/media/tokens.css | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/vs/editor/browser/widget/media/tokens.css b/src/vs/editor/browser/widget/media/tokens.css index 6798f1cdcac..ff6a96f2023 100644 --- a/src/vs/editor/browser/widget/media/tokens.css +++ b/src/vs/editor/browser/widget/media/tokens.css @@ -14,7 +14,6 @@ .monaco-editor.vs .token.constant { color: #dd0000; } .monaco-editor.vs .token.string { color: #A31515; } .monaco-editor.vs .token.string.escape { color: #A31515; } -.monaco-editor.vs .token.literal { color: #000000; } .monaco-editor.vs .token.literal.string { color: #A31515; } .monaco-editor.vs .token.literal.hex { color: #e07000; } .monaco-editor.vs .token.number { color: #09885A; } @@ -106,7 +105,6 @@ .monaco-editor.vs-dark .token.constant { color: #dd0000; } .monaco-editor.vs-dark .token.string { color: #CE9178; } .monaco-editor.vs-dark .token.string.escape { color: #CE9178; } -.monaco-editor.vs-dark .token.literal { color: #e00000; } .monaco-editor.vs-dark .token.literal.hex { color: #e07000; } .monaco-editor.vs-dark .token.number { color: #B5CEA8; } .monaco-editor.vs-dark .token.number.hex { color: #5BB498; } @@ -199,7 +197,6 @@ .monaco-editor.hc-black .token.string { color: #CE9178; } .monaco-editor.hc-black .token.string.escape { color: #CE9178; } -.monaco-editor.hc-black .token.literal { color: #FFFFFF; } .monaco-editor.hc-black .token.literal.hex { color: #FFFFFF; } .monaco-editor.hc-black .token.number { color: #FFFFFF; } .monaco-editor.hc-black .token.number.hex { color: #FFFFFF; } @@ -350,7 +347,6 @@ .monaco-editor.vs .token.markup.raw .monaco-editor.vs .token.markup.other*/ -.monaco-editor.vs .token.meta { color: #000000; } .monaco-editor.vs .token.meta.selector { color: #800000; } .monaco-editor.vs .token.meta.tag { color: #800000; } .monaco-editor.vs .token.meta.preprocessor { color: #0000FF; } @@ -478,7 +474,6 @@ .monaco-editor.vs-dark .token.markup.raw .monaco-editor.vs-dark .token.markup.other*/ -.monaco-editor.vs-dark .token.meta { color: #D4D4D4; } .monaco-editor.vs-dark .token.meta.selector { color: #D7BA7D; } .monaco-editor.vs-dark .token.meta.tag { color: #808080; } /* gray for html/xml-tag brackets */ .monaco-editor.vs-dark .token.meta.preprocessor { color: #569CD6; } @@ -606,7 +601,6 @@ .monaco-editor.hc-black .token.markup.raw .monaco-editor.hc-black .token.markup.other*/ -.monaco-editor.hc-black .token.meta { color: #D4D4D4; } .monaco-editor.hc-black .token.meta.selector { color: #D7BA7D; } .monaco-editor.hc-black .token.meta.tag { color: #808080; } /* gray for html/xml-tag brackets */ .monaco-editor.hc-black .token.meta.preprocessor { color: #569CD6; } From b51fc2d589f3fb3f83bcb38f85aeb8921feaab59 Mon Sep 17 00:00:00 2001 From: isidor Date: Fri, 8 Jan 2016 12:05:22 +0100 Subject: [PATCH 28/36] debug hover: enable selection in hover --- src/vs/workbench/parts/debug/browser/media/debugHover.css | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/parts/debug/browser/media/debugHover.css b/src/vs/workbench/parts/debug/browser/media/debugHover.css index a61dd7d0c91..838e248c483 100644 --- a/src/vs/workbench/parts/debug/browser/media/debugHover.css +++ b/src/vs/workbench/parts/debug/browser/media/debugHover.css @@ -37,7 +37,11 @@ height: 288px; } -/* Disable tree hover highlight in debug hover tree. */ +.monaco-editor .debug-hover-widget .debug-hover-tree .monaco-tree .monaco-tree-row > .content { + -webkit-user-select: initial; +} + +/* Disable tree highlight in debug hover tree. */ .monaco-editor .debug-hover-widget .debug-hover-tree .monaco-tree .monaco-tree-rows > .monaco-tree-row:hover:not(.highlighted):not(.selected):not(.focused) { background-color: inherit; } From cf922e64900e81964281edbf2bd72b24c16d92ab Mon Sep 17 00:00:00 2001 From: isidor Date: Fri, 8 Jan 2016 12:12:09 +0100 Subject: [PATCH 29/36] debug: align twistie in complex hover --- .../workbench/parts/debug/browser/media/debugHover.css | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/vs/workbench/parts/debug/browser/media/debugHover.css b/src/vs/workbench/parts/debug/browser/media/debugHover.css index 838e248c483..9f30d9d3dd0 100644 --- a/src/vs/workbench/parts/debug/browser/media/debugHover.css +++ b/src/vs/workbench/parts/debug/browser/media/debugHover.css @@ -46,6 +46,16 @@ background-color: inherit; } +/* Allign twistie since hover tree is smaller */ + +.monaco-editor .debug-hover-widget .debug-hover-tree .monaco-tree .monaco-tree-rows > .monaco-tree-row.has-children > .content:before { + top: 6px; +} + +.monaco-editor .debug-hover-widget .debug-hover-tree .monaco-tree .monaco-tree-rows > .monaco-tree-row.has-children > .content:after { + top: 8px; +} + .monaco-editor .debug-hover-widget .debug-hover-tree .monaco-tree .monaco-tree-rows > .monaco-tree-row { cursor: default; } From 757968eb874161a01bcc1b37480c25b093d4da9e Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 8 Jan 2016 12:58:37 +0100 Subject: [PATCH 30/36] resource input does not dispose model anymore --- .../browser/parts/editor/textEditorModel.ts | 5 ++++- .../parts/files/browser/saveErrorHandler.ts | 12 ++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/browser/parts/editor/textEditorModel.ts b/src/vs/workbench/browser/parts/editor/textEditorModel.ts index 954203fda12..fa9019a9fc6 100644 --- a/src/vs/workbench/browser/parts/editor/textEditorModel.ts +++ b/src/vs/workbench/browser/parts/editor/textEditorModel.ts @@ -22,6 +22,7 @@ import {IModelService} from 'vs/editor/common/services/modelService'; */ export abstract class BaseTextEditorModel extends EditorModel implements ITextEditorModel { private textEditorModelHandle: URI; + private createdEditorModel: boolean; constructor( @IModelService private modelService: IModelService, @@ -46,6 +47,7 @@ export abstract class BaseTextEditorModel extends EditorModel implements ITextEd // To avoid flickering, give the mode at most 50ms to load. If the mode doesn't load in 50ms, proceed creating the model with a mode promise return Promise.any([Promise.timeout(50), this.getOrCreateMode(this.modeService, mime, firstLineText)]).then(() => { let model = this.modelService.createModel(value, this.getOrCreateMode(this.modeService, mime, firstLineText), resource); + this.createdEditorModel = true; this.textEditorModelHandle = model.getAssociatedResource(); @@ -124,11 +126,12 @@ export abstract class BaseTextEditorModel extends EditorModel implements ITextEd } public dispose(): void { - if (this.textEditorModelHandle) { + if (this.textEditorModelHandle && this.createdEditorModel) { this.modelService.destroyModel(this.textEditorModelHandle); } this.textEditorModelHandle = null; + this.createdEditorModel = false; super.dispose(); } diff --git a/src/vs/workbench/parts/files/browser/saveErrorHandler.ts b/src/vs/workbench/parts/files/browser/saveErrorHandler.ts index 570759eea63..3a563e0af5e 100644 --- a/src/vs/workbench/parts/files/browser/saveErrorHandler.ts +++ b/src/vs/workbench/parts/files/browser/saveErrorHandler.ts @@ -13,6 +13,7 @@ import URI from 'vs/base/common/uri'; import {EditorModel} from 'vs/workbench/common/editor'; import {guessMimeTypes} from 'vs/base/common/mime'; import {EditorInputAction} from 'vs/workbench/browser/parts/editor/baseEditor'; +import {IModel} from 'vs/editor/common/editorCommon'; import {ResourceEditorInput} from 'vs/workbench/browser/parts/editor/resourceEditorInput'; import {DiffEditorInput} from 'vs/workbench/browser/parts/editor/diffEditorInput'; import {DiffEditorModel} from 'vs/workbench/browser/parts/editor/diffEditorModel'; @@ -142,6 +143,7 @@ export class FileOnDiskEditorInput extends ResourceEditorInput { private fileResource: URI; private lastModified: number; private mime: string; + private createdEditorModel: boolean; constructor( fileResource: URI, @@ -175,6 +177,7 @@ export class FileOnDiskEditorInput extends ResourceEditorInput { let codeEditorModel = this.modelService.getModel(this.resource); if (!codeEditorModel) { this.modelService.createModel(content.value, this.modeService.getOrCreateMode(this.mime), this.resource); + this.createdEditorModel = true; } else { codeEditorModel.setValue(content.value); } @@ -182,6 +185,15 @@ export class FileOnDiskEditorInput extends ResourceEditorInput { return super.resolve(refresh); }); } + + public dispose(): void { + if (this.createdEditorModel) { + this.modelService.destroyModel(this.resource); + this.createdEditorModel = false; + } + + super.dispose(); + } } // A message with action to resolve a 412 save conflict From 818640bff683dc7e2d4fc393ebaa680e4c981e10 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Fri, 8 Jan 2016 11:43:40 +0100 Subject: [PATCH 31/36] Clean up Checkbox widget --- src/vs/base/browser/ui/checkbox/checkbox.ts | 107 +++++++++--------- src/vs/base/browser/ui/findinput/findInput.ts | 63 +++++++---- .../parts/search/browser/searchViewlet.ts | 27 +++-- 3 files changed, 108 insertions(+), 89 deletions(-) diff --git a/src/vs/base/browser/ui/checkbox/checkbox.ts b/src/vs/base/browser/ui/checkbox/checkbox.ts index c017267126b..f719c755c31 100644 --- a/src/vs/base/browser/ui/checkbox/checkbox.ts +++ b/src/vs/base/browser/ui/checkbox/checkbox.ts @@ -6,77 +6,81 @@ 'use strict'; import 'vs/css!./checkbox'; -import nls = require('vs/nls'); -import Builder = require('vs/base/browser/builder'); -import mouse = require('vs/base/browser/mouseEvent'); -import keyboard = require('vs/base/browser/keyboardEvent'); + +import * as nls from 'vs/nls'; +import {StandardMouseEvent} from 'vs/base/browser/mouseEvent'; +import {StandardKeyboardEvent} from 'vs/base/browser/keyboardEvent'; import {KeyCode} from 'vs/base/common/keyCodes'; +import {IDisposable, disposeAll} from 'vs/base/common/lifecycle'; +import * as DomUtils from 'vs/base/browser/dom'; -var $ = Builder.$; +export interface ICheckboxOpts { + actionClassName: string; + title: string; + isChecked: boolean; + onChange: () => void; +} -export class Checkbox { +export class Checkbox implements IDisposable { - private actionClassName: string; - private title: string; - public isChecked: boolean; - private onChange: () => void; - private listenersToRemove: { (): void; }[]; + private _opts: ICheckboxOpts; + private _toDispose: IDisposable[]; public domNode: HTMLElement; - constructor(actionClassName: string, title: string, isChecked: boolean, onChange: () => void) { - this.actionClassName = actionClassName; - this.title = title; - this.isChecked = isChecked; - this.onChange = onChange; + private _checked: boolean; - this.listenersToRemove = []; + constructor(opts:ICheckboxOpts) { + this._opts = opts; + this._checked = this._opts.isChecked; + this._toDispose = []; this.domNode = document.createElement('div'); - this.domNode.title = title; - this.render(); + this.domNode.title = this._opts.title; + this.domNode.className = this._className(); + this.domNode.tabIndex = 0; + this.domNode.setAttribute('role', 'checkbox'); + this.domNode.setAttribute('aria-checked', String(this._checked)); + this.domNode.setAttribute('aria-label', this._opts.title); - $(this.domNode).attr({ - 'aria-checked': 'false', - 'aria-label': this.title, - 'tabindex': 0, - 'role': 'checkbox' - }); - - $(this.domNode).on('click', (e: MouseEvent) => { - var ev = new mouse.StandardMouseEvent(e); - this.isChecked = !this.isChecked; - this.render(); - this.onChange(); + this._toDispose.push(DomUtils.addDisposableListener(this.domNode, 'click', (e:MouseEvent) => { + var ev = new StandardMouseEvent(e); + this._checked = !this._checked; + this.domNode.className = this._className(); + this._opts.onChange(); ev.preventDefault(); - }, this.listenersToRemove); + })); - $(this.domNode).on('keydown', (browserEvent: KeyboardEvent) => { - var keyboardEvent = new keyboard.StandardKeyboardEvent(browserEvent); + this._toDispose.push(DomUtils.addDisposableListener(this.domNode, 'keydown', (browserEvent: KeyboardEvent) => { + var keyboardEvent = new StandardKeyboardEvent(browserEvent); if (keyboardEvent.keyCode === KeyCode.Space || keyboardEvent.keyCode === KeyCode.Enter) { - this.isChecked = !this.isChecked; - this.render(); - this.onChange(); + this._checked = !this._checked; + this.domNode.className = this._className(); + this._opts.onChange(); keyboardEvent.preventDefault(); } - }, this.listenersToRemove); + })); + } + + public dispose(): void { + this._toDispose = disposeAll(this._toDispose); } public focus(): void { this.domNode.focus(); } - private render(): void { - this.domNode.className = this.className(); + public get checked(): boolean { + return this._checked; } - public setChecked(newIsChecked: boolean): void { - this.isChecked = newIsChecked; - $(this.domNode).attr('aria-checked', this.isChecked); - this.render(); + public set checked(newIsChecked:boolean) { + this._checked = newIsChecked; + this.domNode.setAttribute('aria-checked', String(this._checked)); + this.domNode.className = this._className(); } - private className(): string { - return 'custom-checkbox ' + this.actionClassName + ' ' + (this.isChecked ? 'checked' : 'unchecked'); + private _className(): string { + return 'custom-checkbox ' + this._opts.actionClassName + ' ' + (this._checked ? 'checked' : 'unchecked'); } public width(): number { @@ -85,16 +89,11 @@ export class Checkbox { public enable(): void { this.domNode.tabIndex = 0; + this.domNode.setAttribute('aria-disabled', String(false)); } public disable(): void { this.domNode.tabIndex = -1; + this.domNode.setAttribute('aria-disabled', String(true)); } - - public destroy(): void { - this.listenersToRemove.forEach((element) => { - element(); - }); - this.listenersToRemove = []; - } -} \ No newline at end of file +} diff --git a/src/vs/base/browser/ui/findinput/findInput.ts b/src/vs/base/browser/ui/findinput/findInput.ts index 380e3e65702..27e6914eaaa 100644 --- a/src/vs/base/browser/ui/findinput/findInput.ts +++ b/src/vs/base/browser/ui/findinput/findInput.ts @@ -74,9 +74,9 @@ export class FindInput { } public destroy(): void { - this.regex.destroy(); - this.wholeWords.destroy(); - this.caseSensitive.destroy(); + this.regex.dispose(); + this.wholeWords.dispose(); + this.caseSensitive.dispose(); this.listenersToRemove.forEach((element) => { element(); }); @@ -144,29 +144,29 @@ export class FindInput { } public getCaseSensitive():boolean { - return this.caseSensitive.isChecked; + return this.caseSensitive.checked; } public setCaseSensitive(value:boolean): void { - this.caseSensitive.setChecked(value); + this.caseSensitive.checked = value; this.setInputWidth(); } public getWholeWords():boolean { - return this.wholeWords.isChecked; + return this.wholeWords.checked; } public setWholeWords(value:boolean): void { - this.wholeWords.setChecked(value); + this.wholeWords.checked = value; this.setInputWidth(); } public getRegex():boolean { - return this.regex.isChecked; + return this.regex.checked; } public setRegex(value:boolean): void { - this.regex.setChecked(value); + this.regex.checked = value; this.setInputWidth(); } @@ -193,23 +193,38 @@ export class FindInput { } }); - this.regex = new Checkbox.Checkbox('regex', NLS_REGEX_CHECKBOX_LABEL + appendRegexLabel, false, () => { - this.onOptionChange(null); - this.inputBox.focus(); - this.setInputWidth(); - this.validate(); + this.regex = new Checkbox.Checkbox({ + actionClassName: 'regex', + title: NLS_REGEX_CHECKBOX_LABEL + appendRegexLabel, + isChecked: false, + onChange: () => { + this.onOptionChange(null); + this.inputBox.focus(); + this.setInputWidth(); + this.validate(); + } }); - this.wholeWords = new Checkbox.Checkbox('whole-word', NLS_WHOLE_WORD_CHECKBOX_LABEL + appendWholeWordsLabel, false, () => { - this.onOptionChange(null); - this.inputBox.focus(); - this.setInputWidth(); - this.validate(); + this.wholeWords = new Checkbox.Checkbox({ + actionClassName: 'whole-word', + title: NLS_WHOLE_WORD_CHECKBOX_LABEL + appendWholeWordsLabel, + isChecked: false, + onChange: () => { + this.onOptionChange(null); + this.inputBox.focus(); + this.setInputWidth(); + this.validate(); + } }); - this.caseSensitive = new Checkbox.Checkbox('case-sensitive', NLS_CASE_SENSITIVE_CHECKBOX_LABEL + appendCaseSensitiveLabel, false, () => { - this.onOptionChange(null); - this.inputBox.focus(); - this.setInputWidth(); - this.validate(); + this.caseSensitive = new Checkbox.Checkbox({ + actionClassName: 'case-sensitive', + title: NLS_CASE_SENSITIVE_CHECKBOX_LABEL + appendCaseSensitiveLabel, + isChecked: false, + onChange: () => { + this.onOptionChange(null); + this.inputBox.focus(); + this.setInputWidth(); + this.validate(); + } }); this.setInputWidth(); diff --git a/src/vs/workbench/parts/search/browser/searchViewlet.ts b/src/vs/workbench/parts/search/browser/searchViewlet.ts index 9d01f5296b4..8d1afe63813 100644 --- a/src/vs/workbench/parts/search/browser/searchViewlet.ts +++ b/src/vs/workbench/parts/search/browser/searchViewlet.ts @@ -451,7 +451,7 @@ class PatternInput { } public destroy(): void { - this.pattern.destroy(); + this.pattern.dispose(); this.listenersToRemove.forEach((element) => { element(); }); @@ -523,11 +523,11 @@ class PatternInput { } public isGlobPattern(): boolean { - return this.pattern.isChecked; + return this.pattern.checked; } public setIsGlobPattern(value: boolean): void { - this.pattern.setChecked(value); + this.pattern.checked = value; this.setInputWidth(); } @@ -550,15 +550,20 @@ class PatternInput { } }); - this.pattern = new Checkbox('pattern', nls.localize('patternDescription', "Use Glob Patterns"), false, () => { - this.onOptionChange(null); - this.inputBox.focus(); - this.setInputWidth(); + this.pattern = new Checkbox({ + actionClassName: 'pattern', + title: nls.localize('patternDescription', "Use Glob Patterns"), + isChecked: false, + onChange: () => { + this.onOptionChange(null); + this.inputBox.focus(); + this.setInputWidth(); - if (this.isGlobPattern()) { - this.showGlobHelp(); - } else { - this.inputBox.hideMessage(); + if (this.isGlobPattern()) { + this.showGlobHelp(); + } else { + this.inputBox.hideMessage(); + } } }); From b7f6251b50fd4b3bdd7c750cc3663308ad17a7e3 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Fri, 8 Jan 2016 13:01:37 +0100 Subject: [PATCH 32/36] Clean up widgets used in find widget --- src/vs/base/browser/dom.ts | 9 +- src/vs/base/browser/ui/checkbox/checkbox.ts | 24 ++-- src/vs/base/browser/ui/findinput/findInput.ts | 110 ++++++++---------- src/vs/base/browser/ui/inputbox/inputBox.ts | 93 +++++++-------- src/vs/base/browser/ui/widget.ts | 38 ++++++ src/vs/base/common/keyCodes.ts | 1 + src/vs/base/common/lifecycle.ts | 18 +++ .../editor/contrib/find/browser/findWidget.ts | 110 ++++++++---------- .../git/browser/views/changes/changesView.ts | 5 +- .../parts/search/browser/searchViewlet.ts | 36 +++--- 10 files changed, 232 insertions(+), 212 deletions(-) create mode 100644 src/vs/base/browser/ui/widget.ts diff --git a/src/vs/base/browser/dom.ts b/src/vs/base/browser/dom.ts index aa4b9205c54..f19e824f514 100644 --- a/src/vs/base/browser/dom.ts +++ b/src/vs/base/browser/dom.ts @@ -919,8 +919,13 @@ export var EventType = { ANIMATION_ITERATION: Browser.isWebKit ? 'webkitAnimationIteration' : 'animationiteration' }; +export interface EventLike { + preventDefault(): void; + stopPropagation(): void; +} + export var EventHelper = { - stop: function (e:Event, cancelBubble?:boolean) { + stop: function (e:EventLike, cancelBubble?:boolean) { if (e.preventDefault) { e.preventDefault(); } else { @@ -933,7 +938,7 @@ export var EventHelper = { e.stopPropagation(); } else { // IE8 - e.cancelBubble = true; + (e).cancelBubble = true; } } } diff --git a/src/vs/base/browser/ui/checkbox/checkbox.ts b/src/vs/base/browser/ui/checkbox/checkbox.ts index f719c755c31..c64c60b58af 100644 --- a/src/vs/base/browser/ui/checkbox/checkbox.ts +++ b/src/vs/base/browser/ui/checkbox/checkbox.ts @@ -8,11 +8,8 @@ import 'vs/css!./checkbox'; import * as nls from 'vs/nls'; -import {StandardMouseEvent} from 'vs/base/browser/mouseEvent'; -import {StandardKeyboardEvent} from 'vs/base/browser/keyboardEvent'; import {KeyCode} from 'vs/base/common/keyCodes'; -import {IDisposable, disposeAll} from 'vs/base/common/lifecycle'; -import * as DomUtils from 'vs/base/browser/dom'; +import {Widget} from 'vs/base/browser/ui/widget'; export interface ICheckboxOpts { actionClassName: string; @@ -21,18 +18,17 @@ export interface ICheckboxOpts { onChange: () => void; } -export class Checkbox implements IDisposable { +export class Checkbox extends Widget { private _opts: ICheckboxOpts; - private _toDispose: IDisposable[]; public domNode: HTMLElement; private _checked: boolean; constructor(opts:ICheckboxOpts) { + super(); this._opts = opts; this._checked = this._opts.isChecked; - this._toDispose = []; this.domNode = document.createElement('div'); this.domNode.title = this._opts.title; @@ -42,27 +38,21 @@ export class Checkbox implements IDisposable { this.domNode.setAttribute('aria-checked', String(this._checked)); this.domNode.setAttribute('aria-label', this._opts.title); - this._toDispose.push(DomUtils.addDisposableListener(this.domNode, 'click', (e:MouseEvent) => { - var ev = new StandardMouseEvent(e); + this.onclick(this.domNode, (ev) => { this._checked = !this._checked; this.domNode.className = this._className(); this._opts.onChange(); ev.preventDefault(); - })); + }); - this._toDispose.push(DomUtils.addDisposableListener(this.domNode, 'keydown', (browserEvent: KeyboardEvent) => { - var keyboardEvent = new StandardKeyboardEvent(browserEvent); + this.onkeydown(this.domNode, (keyboardEvent) => { if (keyboardEvent.keyCode === KeyCode.Space || keyboardEvent.keyCode === KeyCode.Enter) { this._checked = !this._checked; this.domNode.className = this._className(); this._opts.onChange(); keyboardEvent.preventDefault(); } - })); - } - - public dispose(): void { - this._toDispose = disposeAll(this._toDispose); + }); } public focus(): void { diff --git a/src/vs/base/browser/ui/findinput/findInput.ts b/src/vs/base/browser/ui/findinput/findInput.ts index 27e6914eaaa..20a9d008ced 100644 --- a/src/vs/base/browser/ui/findinput/findInput.ts +++ b/src/vs/base/browser/ui/findinput/findInput.ts @@ -5,20 +5,20 @@ 'use strict'; import 'vs/css!./findInput'; -import nls = require('vs/nls'); -import Builder = require('vs/base/browser/builder'); -import mouse = require('vs/base/browser/mouseEvent'); -import keyboard = require('vs/base/browser/keyboardEvent'); -import InputBox = require('vs/base/browser/ui/inputbox/inputBox'); -import Checkbox = require('vs/base/browser/ui/checkbox/checkbox'); -import ContextView = require('vs/base/browser/ui/contextview/contextview'); -var $ = Builder.$; +import * as nls from 'vs/nls'; +import * as dom from 'vs/base/browser/dom'; +import {IMessage as InputBoxMessage, IInputValidator, InputBox} from 'vs/base/browser/ui/inputbox/inputBox'; +import {Checkbox} from 'vs/base/browser/ui/checkbox/checkbox'; +import {IContextViewProvider} from 'vs/base/browser/ui/contextview/contextview'; +import {Widget} from 'vs/base/browser/ui/widget'; +import Event, {Emitter} from 'vs/base/common/event'; +import {StandardKeyboardEvent} from 'vs/base/browser/keyboardEvent'; -export interface IOptions { +export interface IFindInputOptions { placeholder?:string; width?:number; - validation?:InputBox.IInputValidator; + validation?:IInputValidator; label:string; appendCaseSensitiveLabel?: string; @@ -31,73 +31,57 @@ const NLS_WHOLE_WORD_CHECKBOX_LABEL = nls.localize('wordsDescription', "Match Wh const NLS_CASE_SENSITIVE_CHECKBOX_LABEL = nls.localize('caseDescription', "Match Case"); const NLS_DEFAULT_LABEL = nls.localize('defaultLabel', "input"); -export class FindInput { +export class FindInput extends Widget { static OPTION_CHANGE:string = 'optionChange'; - private contextViewProvider: ContextView.IContextViewProvider; - private onOptionChange:(event:Event)=>void; + private contextViewProvider: IContextViewProvider; private width:number; private placeholder:string; - private validation:InputBox.IInputValidator; + private validation:IInputValidator; private label:string; - private listenersToRemove:{():void;}[]; - private regex:Checkbox.Checkbox; - private wholeWords:Checkbox.Checkbox; - private caseSensitive:Checkbox.Checkbox; + private regex:Checkbox; + private wholeWords:Checkbox; + private caseSensitive:Checkbox; public domNode: HTMLElement; - public validationNode: Builder.Builder; - public inputBox:InputBox.InputBox; + public inputBox:InputBox; - constructor(parent:HTMLElement, contextViewProvider: ContextView.IContextViewProvider, options?:IOptions) { + private _onDidOptionChange = this._register(new Emitter()); + public onDidOptionChange: Event = this._onDidOptionChange.event; + + private _onKeyDown = this._register(new Emitter()); + public onKeyDown: Event = this._onKeyDown.event; + + private _onKeyUp = this._register(new Emitter()); + public onKeyUp: Event = this._onKeyUp.event; + + constructor(parent:HTMLElement, contextViewProvider: IContextViewProvider, options?:IFindInputOptions) { + super(); this.contextViewProvider = contextViewProvider; - this.onOptionChange = null; this.width = options.width || 100; this.placeholder = options.placeholder || ''; this.validation = options.validation; this.label = options.label || NLS_DEFAULT_LABEL; - this.listenersToRemove = []; this.regex = null; this.wholeWords = null; this.caseSensitive = null; this.domNode = null; this.inputBox = null; - this.validationNode = null; this.buildDomNode(options.appendCaseSensitiveLabel || '', options.appendWholeWordsLabel || '', options.appendRegexLabel || ''); if(Boolean(parent)) { parent.appendChild(this.domNode); } - } - public destroy(): void { - this.regex.dispose(); - this.wholeWords.dispose(); - this.caseSensitive.dispose(); - this.listenersToRemove.forEach((element) => { - element(); - }); - this.listenersToRemove = []; - } - - public on(eventType:string, handler:(event:Event)=>void): FindInput { - switch(eventType) { - case 'keydown': - case 'keyup': - $(this.inputBox.inputElement).on(eventType, handler); - break; - case FindInput.OPTION_CHANGE: - this.onOptionChange = handler; - break; - } - return this; + this.onkeydown(this.inputBox.inputElement, (e) => this._onKeyDown.fire(e)); + this.onkeyup(this.inputBox.inputElement, (e) => this._onKeyUp.fire(e)); } public enable(): void { - $(this.domNode).removeClass('disabled'); + dom.removeClass(this.domNode, 'disabled'); this.inputBox.enable(); this.regex.enable(); this.wholeWords.enable(); @@ -105,7 +89,7 @@ export class FindInput { } public disable(): void { - $(this.domNode).addClass('disabled'); + dom.addClass(this.domNode, 'disabled'); this.inputBox.disable(); this.regex.disable(); this.wholeWords.disable(); @@ -175,60 +159,60 @@ export class FindInput { } private setInputWidth(): void { - var w = this.width - this.caseSensitive.width() - this.wholeWords.width() - this.regex.width(); + let w = this.width - this.caseSensitive.width() - this.wholeWords.width() - this.regex.width(); this.inputBox.width = w; } private buildDomNode(appendCaseSensitiveLabel:string, appendWholeWordsLabel: string, appendRegexLabel: string): void { this.domNode = document.createElement('div'); this.domNode.style.width = this.width + 'px'; - $(this.domNode).addClass('monaco-findInput'); + dom.addClass(this.domNode, 'monaco-findInput'); - this.inputBox = new InputBox.InputBox(this.domNode, this.contextViewProvider, { + this.inputBox = this._register(new InputBox(this.domNode, this.contextViewProvider, { placeholder: this.placeholder || '', ariaLabel: this.label || '', validationOptions: { validation: this.validation || null, showMessage: true } - }); + })); - this.regex = new Checkbox.Checkbox({ + this.regex = this._register(new Checkbox({ actionClassName: 'regex', title: NLS_REGEX_CHECKBOX_LABEL + appendRegexLabel, isChecked: false, onChange: () => { - this.onOptionChange(null); + this._onDidOptionChange.fire(); this.inputBox.focus(); this.setInputWidth(); this.validate(); } - }); - this.wholeWords = new Checkbox.Checkbox({ + })); + this.wholeWords = this._register(new Checkbox({ actionClassName: 'whole-word', title: NLS_WHOLE_WORD_CHECKBOX_LABEL + appendWholeWordsLabel, isChecked: false, onChange: () => { - this.onOptionChange(null); + this._onDidOptionChange.fire(); this.inputBox.focus(); this.setInputWidth(); this.validate(); } - }); - this.caseSensitive = new Checkbox.Checkbox({ + })); + this.caseSensitive = this._register(new Checkbox({ actionClassName: 'case-sensitive', title: NLS_CASE_SENSITIVE_CHECKBOX_LABEL + appendCaseSensitiveLabel, isChecked: false, onChange: () => { - this.onOptionChange(null); + this._onDidOptionChange.fire(); this.inputBox.focus(); this.setInputWidth(); this.validate(); } - }); + })); this.setInputWidth(); - var controls = document.createElement('div'); + let controls = document.createElement('div'); controls.className = 'controls'; controls.appendChild(this.caseSensitive.domNode); controls.appendChild(this.wholeWords.domNode); @@ -241,7 +225,7 @@ export class FindInput { this.inputBox.validate(); } - public showMessage(message: InputBox.IMessage): void { + public showMessage(message: InputBoxMessage): void { this.inputBox.showMessage(message); } diff --git a/src/vs/base/browser/ui/inputbox/inputBox.ts b/src/vs/base/browser/ui/inputbox/inputBox.ts index 70051e5d296..2caafdaa65c 100644 --- a/src/vs/base/browser/ui/inputbox/inputBox.ts +++ b/src/vs/base/browser/ui/inputbox/inputBox.ts @@ -5,18 +5,19 @@ 'use strict'; import 'vs/css!./inputBox'; -import Bal = require('vs/base/browser/browser'); -import dom = require('vs/base/browser/dom'); -import browser = require('vs/base/browser/browserService'); -import htmlcontent = require('vs/base/common/htmlContent'); -import renderer = require('vs/base/browser/htmlContentRenderer'); -import ee = require('vs/base/common/eventEmitter'); -import actions = require('vs/base/common/actions'); -import actionBar = require('vs/base/browser/ui/actionbar/actionbar'); -import lifecycle = require('vs/base/common/lifecycle'); -import contextview = require('vs/base/browser/ui/contextview/contextview'); -var $ = dom.emmet; +import * as Bal from 'vs/base/browser/browser'; +import * as dom from 'vs/base/browser/dom'; +import * as browser from 'vs/base/browser/browserService'; +import {IHTMLContentElement} from 'vs/base/common/htmlContent'; +import {renderHtml} from 'vs/base/browser/htmlContentRenderer'; +import {IAction} from 'vs/base/common/actions'; +import {ActionBar} from 'vs/base/browser/ui/actionbar/actionbar'; +import {IContextViewProvider, AnchorAlignment} from 'vs/base/browser/ui/contextview/contextview'; +import Event, {Emitter} from 'vs/base/common/event'; +import {Widget} from 'vs/base/browser/ui/widget'; + +let $ = dom.emmet; export interface IInputOptions { placeholder?:string; @@ -24,7 +25,7 @@ export interface IInputOptions { type?:string; validationOptions?:IInputValidationOptions; flexibleHeight?: boolean; - actions?:actions.IAction[]; + actions?:IAction[]; } export interface IInputValidator { @@ -53,14 +54,14 @@ export interface IRange { end: number; } -export class InputBox extends ee.EventEmitter { +export class InputBox extends Widget { - private contextViewProvider: contextview.IContextViewProvider; + private contextViewProvider: IContextViewProvider; private element: HTMLElement; private input: HTMLInputElement; private mirror: HTMLElement; - private actionbar: actionBar.ActionBar; + private actionbar: ActionBar; private options: IInputOptions; private message: IMessage; private placeholder: string; @@ -69,14 +70,19 @@ export class InputBox extends ee.EventEmitter { private showValidationMessage: boolean; private state = 'idle'; private cachedHeight: number; - private toDispose: lifecycle.IDisposable[]; - constructor(container:HTMLElement, contextViewProvider: contextview.IContextViewProvider, options?: IInputOptions) { + private _onDidChange = this._register(new Emitter()); + public onDidChange: Event = this._onDidChange.event; + + private _onDidHeightChange = this._register(new Emitter()); + public onDidHeightChange: Event = this._onDidHeightChange.event; + + constructor(container:HTMLElement, contextViewProvider: IContextViewProvider, options?: IInputOptions) { super(); this.contextViewProvider = contextViewProvider; this.options = options || Object.create(null); - this.toDispose = []; + // this.toDispose = []; this.message = null; this.cachedHeight = null; this.placeholder = this.options.placeholder || ''; @@ -89,9 +95,9 @@ export class InputBox extends ee.EventEmitter { this.element = dom.append(container, $('.monaco-inputbox.idle')); - var tagName = this.options.flexibleHeight ? 'textarea' : 'input'; + let tagName = this.options.flexibleHeight ? 'textarea' : 'input'; - var wrapper = dom.append(this.element, $('.wrapper')); + let wrapper = dom.append(this.element, $('.wrapper')); this.input = dom.append(wrapper, $(tagName + '.input')); this.input.setAttribute('autocorrect', 'off'); this.input.setAttribute('autocapitalize', 'off'); @@ -112,22 +118,20 @@ export class InputBox extends ee.EventEmitter { this.input.setAttribute('placeholder', this.placeholder); } - this.toDispose.push( - dom.addDisposableListener(this.input, dom.EventType.INPUT, () => this.onValueChange()), - dom.addDisposableListener(this.input, dom.EventType.BLUR, () => this.onBlur()), - dom.addDisposableListener(this.input, dom.EventType.FOCUS, () => this.onFocus()) - ); + this.oninput(this.input, () => this.onValueChange()); + this.onblur(this.input, () => this.onBlur()); + this.onfocus(this.input, () => this.onFocus()); // Add placeholder shim for IE because IE decides to hide the placeholder on focus (we dont want that!) if (this.placeholder && Bal.isIE11orEarlier) { - this.toDispose.push(dom.addDisposableListener(this.input, dom.EventType.CLICK, (e) => { + this.onclick(this.input, (e) => { dom.EventHelper.stop(e, true); this.input.focus(); - })); + }); if (Bal.isIE9) { - this.toDispose.push(dom.addDisposableListener(this.input, 'keyup', () => this.onValueChange())); + this.onkeyup(this.input, () => this.onValueChange()); } } @@ -135,7 +139,7 @@ export class InputBox extends ee.EventEmitter { // Support actions if (this.options.actions) { - this.actionbar = new actionBar.ActionBar(this.element); + this.actionbar = this._register(new ActionBar(this.element)); this.actionbar.push(this.options.actions, { icon: true, label: false }); } } @@ -154,7 +158,7 @@ export class InputBox extends ee.EventEmitter { } } - public setContextViewProvider(contextViewProvider: contextview.IContextViewProvider): void { + public setContextViewProvider(contextViewProvider: IContextViewProvider): void { this.contextViewProvider = contextViewProvider; } @@ -244,7 +248,7 @@ export class InputBox extends ee.EventEmitter { } public validate(): boolean { - var result: IMessage = null; + let result: IMessage = null; if (this.validation) { result = this.validation(this.value); @@ -273,19 +277,19 @@ export class InputBox extends ee.EventEmitter { return; } - var div: HTMLElement; - var layout = () => div.style.width = dom.getTotalWidth(this.element) + 'px'; + let div: HTMLElement; + let layout = () => div.style.width = dom.getTotalWidth(this.element) + 'px'; this.state = 'open'; this.contextViewProvider.showContextView({ getAnchor: () => this.element, - anchorAlignment: contextview.AnchorAlignment.RIGHT, + anchorAlignment: AnchorAlignment.RIGHT, render: (container: HTMLElement) => { div = dom.append(container, $('.monaco-inputbox-container')); layout(); - var renderOptions: htmlcontent.IHTMLContentElement = { + let renderOptions: IHTMLContentElement = { tagName: 'span', className: 'monaco-inputbox-message', }; @@ -296,7 +300,7 @@ export class InputBox extends ee.EventEmitter { renderOptions.text = this.message.content; } - var spanElement:HTMLElement = renderer.renderHtml(renderOptions); + let spanElement:HTMLElement = renderHtml(renderOptions); dom.addClass(spanElement, this.classForType(this.message.type)); dom.append(div, spanElement); return null; @@ -316,13 +320,13 @@ export class InputBox extends ee.EventEmitter { } private onValueChange(): void { - this.emit('change', this.value); + this._onDidChange.fire(this.value); this.validate(); if (this.mirror) { - var lastCharCode = this.value.charCodeAt(this.value.length - 1); - var suffix = lastCharCode === 10 ? ' ' : ''; + let lastCharCode = this.value.charCodeAt(this.value.length - 1); + let suffix = lastCharCode === 10 ? ' ' : ''; this.mirror.textContent = this.value + suffix; this.layout(); } @@ -338,14 +342,11 @@ export class InputBox extends ee.EventEmitter { if (previousHeight !== this.cachedHeight) { this.input.style.height = this.cachedHeight + 'px'; - this.emit('heightchange', this.cachedHeight); + this._onDidHeightChange.fire(this.cachedHeight); } } public dispose(): void { - - this.toDispose = lifecycle.disposeAll(this.toDispose); - this._hideMessage(); this.element = null; @@ -357,11 +358,7 @@ export class InputBox extends ee.EventEmitter { this.validation = null; this.showValidationMessage = null; this.state = null; - - if (this.actionbar) { - this.actionbar.dispose(); - this.actionbar = null; - } + this.actionbar = null; super.dispose(); } diff --git a/src/vs/base/browser/ui/widget.ts b/src/vs/base/browser/ui/widget.ts new file mode 100644 index 00000000000..5fa39bcfd37 --- /dev/null +++ b/src/vs/base/browser/ui/widget.ts @@ -0,0 +1,38 @@ +/*--------------------------------------------------------------------------------------------- + * 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 {Disposable} from 'vs/base/common/lifecycle'; +import {StandardMouseEvent} from 'vs/base/browser/mouseEvent'; +import {StandardKeyboardEvent} from 'vs/base/browser/keyboardEvent'; +import * as DomUtils from 'vs/base/browser/dom'; + +export abstract class Widget extends Disposable { + + protected onclick(domNode:HTMLElement, listener:(e:StandardMouseEvent)=>void): void { + this._register(DomUtils.addDisposableListener(domNode, DomUtils.EventType.CLICK, (e:MouseEvent) => listener(new StandardMouseEvent(e)))); + } + + protected onkeydown(domNode:HTMLElement, listener:(e:StandardKeyboardEvent)=>void): void { + this._register(DomUtils.addDisposableListener(domNode, DomUtils.EventType.KEY_DOWN, (e:KeyboardEvent) => listener(new StandardKeyboardEvent(e)))); + } + + protected onkeyup(domNode:HTMLElement, listener:(e:StandardKeyboardEvent)=>void): void { + this._register(DomUtils.addDisposableListener(domNode, DomUtils.EventType.KEY_UP, (e:KeyboardEvent) => listener(new StandardKeyboardEvent(e)))); + } + + protected oninput(domNode:HTMLElement, listener:(e:Event)=>void): void { + this._register(DomUtils.addDisposableListener(domNode, DomUtils.EventType.INPUT, listener)); + } + + protected onblur(domNode:HTMLElement, listener:(e:Event)=>void): void { + this._register(DomUtils.addDisposableListener(domNode, DomUtils.EventType.BLUR, listener)); + } + + protected onfocus(domNode:HTMLElement, listener:(e:Event)=>void): void { + this._register(DomUtils.addDisposableListener(domNode, DomUtils.EventType.FOCUS, listener)); + } +} diff --git a/src/vs/base/common/keyCodes.ts b/src/vs/base/common/keyCodes.ts index f3c75f9aa8a..6db3507409c 100644 --- a/src/vs/base/common/keyCodes.ts +++ b/src/vs/base/common/keyCodes.ts @@ -484,6 +484,7 @@ export class CommonKeybindings { public static WINCTRL_ENTER: number = KeyMod.WinCtrl | KeyCode.Enter; public static TAB: number = KeyCode.Tab; + public static SHIFT_TAB: number = KeyMod.Shift | KeyCode.Tab; public static ESCAPE: number = KeyCode.Escape; public static SPACE: number = KeyCode.Space; public static DELETE: number = KeyCode.Delete; diff --git a/src/vs/base/common/lifecycle.ts b/src/vs/base/common/lifecycle.ts index 255646cf962..bc83b7bf657 100644 --- a/src/vs/base/common/lifecycle.ts +++ b/src/vs/base/common/lifecycle.ts @@ -69,3 +69,21 @@ export interface CallAll { * Calls all functions that are being passed to it. */ export const cAll: CallAll = callAll; + +export abstract class Disposable implements IDisposable { + + private _toDispose: IDisposable[]; + + constructor() { + this._toDispose = []; + } + + public dispose(): void { + this._toDispose = disposeAll(this._toDispose); + } + + protected _register(t:T): T { + this._toDispose.push(t); + return t; + } +} diff --git a/src/vs/editor/contrib/find/browser/findWidget.ts b/src/vs/editor/contrib/find/browser/findWidget.ts index fb95a0d2448..34340233c46 100644 --- a/src/vs/editor/contrib/find/browser/findWidget.ts +++ b/src/vs/editor/contrib/find/browser/findWidget.ts @@ -21,6 +21,7 @@ import {disposeAll, IDisposable} from 'vs/base/common/lifecycle'; import {CommonKeybindings} from 'vs/base/common/keyCodes'; import {IKeybindingService} from 'vs/platform/keybinding/common/keybindingService'; import {INewFindReplaceState, FindReplaceStateChangedEvent, FindReplaceState} from 'vs/editor/contrib/find/common/findState'; +import {Widget} from 'vs/base/browser/ui/widget'; export interface IFindController { replace(): void; @@ -39,7 +40,7 @@ const NLS_REPLACE_BTN_LABEL = nls.localize('label.replaceButton', "Replace"); const NLS_REPLACE_ALL_BTN_LABEL = nls.localize('label.replaceAllButton', "Replace All"); const NLS_TOGGLE_REPLACE_MODE_BTN_LABEL = nls.localize('label.toggleReplaceButton', "Toggle Replace mode"); -export class FindWidget implements EditorBrowser.IOverlayWidget { +export class FindWidget extends Widget implements EditorBrowser.IOverlayWidget { private static ID = 'editor.contrib.findWidget'; private static PART_WIDTH = 275; @@ -68,8 +69,6 @@ export class FindWidget implements EditorBrowser.IOverlayWidget { private _isVisible: boolean; private _isReplaceVisible: boolean; - private _toDispose: IDisposable[]; - private focusTracker: DomUtils.IFocusTracker; constructor( @@ -79,6 +78,7 @@ export class FindWidget implements EditorBrowser.IOverlayWidget { contextViewProvider: IContextViewProvider, keybindingService: IKeybindingService ) { + super(); this._codeEditor = codeEditor; this._controller = controller; this._state = state; @@ -88,30 +88,17 @@ export class FindWidget implements EditorBrowser.IOverlayWidget { this._isVisible = false; this._isReplaceVisible = false; this._isReplaceEnabled = false; - this._toDispose = []; - this._toDispose.push(this._state.addChangeListener((e) => this._onStateChanged(e))); + this._register(this._state.addChangeListener((e) => this._onStateChanged(e))); this._buildDomNode(); - this.focusTracker = DomUtils.trackFocus(this._findInput.inputBox.inputElement); + this.focusTracker = this._register(DomUtils.trackFocus(this._findInput.inputBox.inputElement)); this.focusTracker.addFocusListener(() => this._reseedFindScope()); - this._toDispose.push(this.focusTracker); - - this._toDispose.push({ - dispose: () => { - this._findInput.destroy(); - } - }); - this._toDispose.push(this._replaceInputBox); this._codeEditor.addOverlayWidget(this); } - public dispose(): void { - this._toDispose = disposeAll(this._toDispose); - } - private _reseedFindScope(): void { let selection = this._codeEditor.getSelection(); if (selection.startLineNumber !== selection.endLineNumber) { @@ -300,7 +287,7 @@ export class FindWidget implements EditorBrowser.IOverlayWidget { private _buildFindPart(): HTMLElement { // Find input - this._findInput = new FindInput(null, this._contextViewProvider, { + this._findInput = this._register(new FindInput(null, this._contextViewProvider, { width: FindWidget.FIND_INPUT_AREA_WIDTH, label: NLS_FIND_INPUT_LABEL, placeholder: NLS_FIND_INPUT_PLACEHOLDER, @@ -321,39 +308,37 @@ export class FindWidget implements EditorBrowser.IOverlayWidget { return { content: e.message }; } } - }).on('keydown', (browserEvent:KeyboardEvent) => { - this._onFindInputKeyDown(new StandardKeyboardEvent(browserEvent)); - }).on(FindInput.OPTION_CHANGE, () => { + })); + this._register(this._findInput.onKeyDown((e) => this._onFindInputKeyDown(e))); + this._register(this._findInput.onDidOptionChange(() => { this._state.change({ isRegex: this._findInput.getRegex(), wholeWord: this._findInput.getWholeWords(), matchCase: this._findInput.getCaseSensitive() }, true); - }); + })); this._findInput.disable(); // Previous button - this._prevBtn = new SimpleButton({ + this._prevBtn = this._register(new SimpleButton({ label: NLS_PREVIOUS_MATCH_BTN_LABEL + this._keybindingLabelFor(FIND_IDS.PreviousMatchFindAction), className: 'previous', onTrigger: () => { this._codeEditor.getAction(FIND_IDS.PreviousMatchFindAction).run().done(null, Errors.onUnexpectedError); }, onKeyDown: (e) => {} - }); - this._toDispose.push(this._prevBtn); + })); // Next button - this._nextBtn = new SimpleButton({ + this._nextBtn = this._register(new SimpleButton({ label: NLS_NEXT_MATCH_BTN_LABEL + this._keybindingLabelFor(FIND_IDS.NextMatchFindAction), className: 'next', onTrigger: () => { this._codeEditor.getAction(FIND_IDS.NextMatchFindAction).run().done(null, Errors.onUnexpectedError); }, onKeyDown: (e) => {} - }); - this._toDispose.push(this._nextBtn); + })); let findPart = document.createElement('div'); findPart.className = 'find-part'; @@ -362,9 +347,9 @@ export class FindWidget implements EditorBrowser.IOverlayWidget { findPart.appendChild(this._nextBtn.domNode); // Toggle selection button - this._toggleSelectionFind = new Checkbox(findPart, NLS_TOGGLE_SELECTION_FIND_TITLE); + this._toggleSelectionFind = this._register(new Checkbox(findPart, NLS_TOGGLE_SELECTION_FIND_TITLE)); this._toggleSelectionFind.disable(); - this._toDispose.push(DomUtils.addStandardDisposableListener(this._toggleSelectionFind.checkbox, 'change', (e) => { + this._register(DomUtils.addStandardDisposableListener(this._toggleSelectionFind.checkbox, 'change', (e) => { if (this._toggleSelectionFind.checkbox.checked) { this._reseedFindScope(); } else { @@ -377,20 +362,21 @@ export class FindWidget implements EditorBrowser.IOverlayWidget { }); // Close button - this._closeBtn = new SimpleButton({ + this._closeBtn = this._register(new SimpleButton({ label: NLS_CLOSE_BTN_LABEL + this._keybindingLabelFor(FIND_IDS.CloseFindWidgetCommand), className: 'close-fw', onTrigger: () => { this._state.change({ isRevealed: false }, false); }, onKeyDown: (e) => { - if (this._isReplaceVisible) { - this._replaceBtn.focus(); - e.preventDefault(); + if (e.equals(CommonKeybindings.TAB)) { + if (this._isReplaceVisible) { + this._replaceBtn.focus(); + e.preventDefault(); + } } } - }); - this._toDispose.push(this._closeBtn); + })); findPart.appendChild(this._closeBtn.domNode); @@ -422,35 +408,38 @@ export class FindWidget implements EditorBrowser.IOverlayWidget { let replaceInput = document.createElement('div'); replaceInput.className = 'replace-input'; replaceInput.style.width = FindWidget.REPLACE_INPUT_AREA_WIDTH + 'px'; - this._replaceInputBox = new InputBox(replaceInput, null, { + this._replaceInputBox = this._register(new InputBox(replaceInput, null, { ariaLabel: NLS_REPLACE_INPUT_LABEL, placeholder: NLS_REPLACE_INPUT_PLACEHOLDER - }); + })); - this._toDispose.push(DomUtils.addStandardDisposableListener(this._replaceInputBox.inputElement, 'keydown', (e) => this._onReplaceInputKeyDown(e))); + this._register(DomUtils.addStandardDisposableListener(this._replaceInputBox.inputElement, 'keydown', (e) => this._onReplaceInputKeyDown(e))); this._replaceInputBox.disable(); // Replace one button - this._replaceBtn = new SimpleButton({ + this._replaceBtn = this._register(new SimpleButton({ label: NLS_REPLACE_BTN_LABEL, className: 'replace', onTrigger: () => { this._controller.replace(); }, - onKeyDown: (e) => {} - }); - this._toDispose.push(this._replaceBtn); + onKeyDown: (e) => { + if (e.equals(CommonKeybindings.SHIFT_TAB)) { + this._closeBtn.focus(); + e.preventDefault(); + } + } + })); // Replace all button - this._replaceAllBtn = new SimpleButton({ + this._replaceAllBtn = this._register(new SimpleButton({ label: NLS_REPLACE_ALL_BTN_LABEL, className: 'replace-all', onTrigger: () => { this._controller.replaceAll(); }, onKeyDown: (e) => {} - }); - this._toDispose.push(this._replaceAllBtn); + })); let replacePart = document.createElement('div'); replacePart.className = 'replace-part'; @@ -469,18 +458,17 @@ export class FindWidget implements EditorBrowser.IOverlayWidget { let replacePart = this._buildReplacePart(); // Toggle replace button - this._toggleReplaceBtn = new SimpleButton({ + this._toggleReplaceBtn = this._register(new SimpleButton({ label: NLS_TOGGLE_REPLACE_MODE_BTN_LABEL, className: 'toggle left', onTrigger: () => { this._state.change({ isReplaceRevealed: !this._isReplaceVisible }, true); }, onKeyDown: (e) => {} - }); + })); this._toggleReplaceBtn.toggleClass('expand', this._isReplaceVisible); this._toggleReplaceBtn.toggleClass('collapse', !this._isReplaceVisible); this._toggleReplaceBtn.setExpanded(this._isReplaceVisible); - this._toDispose.push(this._toggleReplaceBtn); // Widget this._domNode = document.createElement('div'); @@ -550,7 +538,7 @@ export class FindWidget implements EditorBrowser.IOverlayWidget { } } -export class Checkbox { +export class Checkbox extends Widget { private static _COUNTER = 0; @@ -559,6 +547,7 @@ export class Checkbox { private _label: HTMLLabelElement; constructor(parent: HTMLElement, title: string) { + super(); this._domNode = document.createElement('div'); this._domNode.className = 'monaco-checkbox'; this._domNode.title = title; @@ -607,39 +596,34 @@ interface ISimpleButtonOpts { onKeyDown: (e:DomUtils.IKeyboardEvent)=>void; } -class SimpleButton implements IDisposable { +class SimpleButton extends Widget { private _opts: ISimpleButtonOpts; private _domNode: HTMLElement; - private _toDispose: IDisposable[]; constructor(opts:ISimpleButtonOpts) { + super(); this._opts = opts; this._domNode = document.createElement('div'); this._domNode.title = this._opts.label; - this._domNode.tabIndex = -1; + this._domNode.tabIndex = 0; this._domNode.className = 'button ' + this._opts.className; this._domNode.setAttribute('role', 'button'); this._domNode.setAttribute('aria-label', this._opts.label); - this._toDispose = []; - this._toDispose.push(DomUtils.addStandardDisposableListener(this._domNode, 'click', (e) => { + this.onclick(this._domNode, (e) => { this._opts.onTrigger(); e.preventDefault(); - })); - this._toDispose.push(DomUtils.addStandardDisposableListener(this._domNode, 'keydown', (e) => { + }); + this.onkeydown(this._domNode, (e) => { if (e.equals(CommonKeybindings.SPACE) || e.equals(CommonKeybindings.ENTER)) { this._opts.onTrigger(); e.preventDefault(); return; } this._opts.onKeyDown(e); - })); - } - - public dispose(): void { - this._toDispose = disposeAll(this._toDispose); + }); } public get domNode(): HTMLElement { diff --git a/src/vs/workbench/parts/git/browser/views/changes/changesView.ts b/src/vs/workbench/parts/git/browser/views/changes/changesView.ts index 3a80c4f174b..ac6ed6173ce 100644 --- a/src/vs/workbench/parts/git/browser/views/changes/changesView.ts +++ b/src/vs/workbench/parts/git/browser/views/changes/changesView.ts @@ -138,7 +138,8 @@ export class ChangesView extends EventEmitter.EventEmitter implements GitView.IV flexibleHeight: true }); - this.addEmitter2(this.commitInputBox, 'commitInputBox'); + this.commitInputBox.onDidChange((value) => this.emit('change', value)); + this.commitInputBox.onDidHeightChange((value) => this.emit('heightchange', value)); $(this.commitInputBox.inputElement).on('keydown', (e:KeyboardEvent) => { var keyboardEvent = new Keyboard.StandardKeyboardEvent(e); @@ -179,7 +180,7 @@ export class ChangesView extends EventEmitter.EventEmitter implements GitView.IV this.tree.expandAll(this.gitService.getModel().getStatus().getGroups()); this.toDispose.push(this.tree.addListener2('selection', (e) => this.onSelection(e))); - this.toDispose.push(this.commitInputBox.addListener2('heightchange', () => this.layout())); + this.toDispose.push(this.commitInputBox.onDidHeightChange(() => this.layout())); } public focus():void { diff --git a/src/vs/workbench/parts/search/browser/searchViewlet.ts b/src/vs/workbench/parts/search/browser/searchViewlet.ts index 8d1afe63813..91969b5ff1d 100644 --- a/src/vs/workbench/parts/search/browser/searchViewlet.ts +++ b/src/vs/workbench/parts/search/browser/searchViewlet.ts @@ -702,8 +702,7 @@ export class SearchViewlet extends Viewlet { builder = div; }); - let onKeyUp = (e: KeyboardEvent) => { - let keyboardEvent = new StandardKeyboardEvent(e); + let onStandardKeyUp = (keyboardEvent: StandardKeyboardEvent) => { if (keyboardEvent.keyCode === KeyCode.Enter) { this.onQueryChanged(true); } else if (keyboardEvent.keyCode === KeyCode.Escape) { @@ -717,6 +716,10 @@ export class SearchViewlet extends Viewlet { } }; + let onKeyUp = (e: KeyboardEvent) => { + onStandardKeyUp(new StandardKeyboardEvent(e)); + }; + this.queryBox = builder.div({ 'class': 'query-box' }, (div) => { let options = { label: nls.localize('label.Search', 'Search Term'), @@ -738,22 +741,21 @@ export class SearchViewlet extends Viewlet { } } }; - this.findInput = new FindInput(div.getHTMLElement(), this.contextViewService, options) - .on(dom.EventType.KEY_UP, onKeyUp) - .on(dom.EventType.KEY_DOWN, (e: KeyboardEvent) => { - let keyboardEvent = new StandardKeyboardEvent(e); - if (keyboardEvent.keyCode === KeyCode.DownArrow) { - dom.EventHelper.stop(e); - if (this.showsFileTypes()) { - this.toggleFileTypes(true); - } else { - this.selectTreeIfNotSelected(keyboardEvent); - } + this.findInput = new FindInput(div.getHTMLElement(), this.contextViewService, options); + this.findInput.onKeyUp(onStandardKeyUp); + this.findInput.onKeyDown((keyboardEvent:StandardKeyboardEvent) => { + if (keyboardEvent.keyCode === KeyCode.DownArrow) { + dom.EventHelper.stop(keyboardEvent); + if (this.showsFileTypes()) { + this.toggleFileTypes(true); + } else { + this.selectTreeIfNotSelected(keyboardEvent); } - }) - .on(FindInput.OPTION_CHANGE, (e) => { - this.onQueryChanged(true); - }); + } + }); + this.findInput.onDidOptionChange(() => { + this.onQueryChanged(true); + }); this.findInput.setValue(contentPattern); this.findInput.setRegex(isRegex); this.findInput.setCaseSensitive(isCaseSensitive); From 8ad8e6c86d1bca3c2328a20e3a77df1b6cd727f5 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Fri, 8 Jan 2016 13:05:18 +0100 Subject: [PATCH 33/36] Fixes #1870 --- src/vs/editor/contrib/find/common/findDecorations.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/vs/editor/contrib/find/common/findDecorations.ts b/src/vs/editor/contrib/find/common/findDecorations.ts index 746c8b6e020..faa4688a5a4 100644 --- a/src/vs/editor/contrib/find/common/findDecorations.ts +++ b/src/vs/editor/contrib/find/common/findDecorations.ts @@ -121,6 +121,7 @@ export class FindDecorations implements Lifecycle.IDisposable { if (moveCursor && this.decorationIndex >= 0 && this.decorationIndex < this.decorations.length) { let range = this.editor.getModel().getDecorationRange(this.decorations[this.decorationIndex]); this.editor.setSelection(range); + this.editor.revealRangeInCenterIfOutsideViewport(range); } } From 35c1c7c6298671061777ae7130e3ad165014c771 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Fri, 8 Jan 2016 13:25:20 +0100 Subject: [PATCH 34/36] Fixes #1581: implement a consistent tab order in the find widget --- src/vs/base/browser/ui/checkbox/checkbox.ts | 7 ++ src/vs/base/browser/ui/findinput/findInput.ts | 6 + .../editor/contrib/find/browser/findWidget.ts | 117 +++++++++++------- 3 files changed, 84 insertions(+), 46 deletions(-) diff --git a/src/vs/base/browser/ui/checkbox/checkbox.ts b/src/vs/base/browser/ui/checkbox/checkbox.ts index c64c60b58af..e197d40836d 100644 --- a/src/vs/base/browser/ui/checkbox/checkbox.ts +++ b/src/vs/base/browser/ui/checkbox/checkbox.ts @@ -10,12 +10,14 @@ import 'vs/css!./checkbox'; import * as nls from 'vs/nls'; import {KeyCode} from 'vs/base/common/keyCodes'; import {Widget} from 'vs/base/browser/ui/widget'; +import {StandardKeyboardEvent} from 'vs/base/browser/keyboardEvent'; export interface ICheckboxOpts { actionClassName: string; title: string; isChecked: boolean; onChange: () => void; + onKeyDown?: (e:StandardKeyboardEvent) => void; } export class Checkbox extends Widget { @@ -51,6 +53,11 @@ export class Checkbox extends Widget { this.domNode.className = this._className(); this._opts.onChange(); keyboardEvent.preventDefault(); + return; + } + + if (this._opts.onKeyDown) { + this._opts.onKeyDown(keyboardEvent); } }); } diff --git a/src/vs/base/browser/ui/findinput/findInput.ts b/src/vs/base/browser/ui/findinput/findInput.ts index 20a9d008ced..0c82b7d3142 100644 --- a/src/vs/base/browser/ui/findinput/findInput.ts +++ b/src/vs/base/browser/ui/findinput/findInput.ts @@ -56,6 +56,9 @@ export class FindInput extends Widget { private _onKeyUp = this._register(new Emitter()); public onKeyUp: Event = this._onKeyUp.event; + private _onCaseSensitiveKeyDown = this._register(new Emitter()); + public onCaseSensitiveKeyDown: Event = this._onCaseSensitiveKeyDown.event; + constructor(parent:HTMLElement, contextViewProvider: IContextViewProvider, options?:IFindInputOptions) { super(); this.contextViewProvider = contextViewProvider; @@ -208,6 +211,9 @@ export class FindInput extends Widget { this.inputBox.focus(); this.setInputWidth(); this.validate(); + }, + onKeyDown: (e) => { + this._onCaseSensitiveKeyDown.fire(e); } })); this.setInputWidth(); diff --git a/src/vs/editor/contrib/find/browser/findWidget.ts b/src/vs/editor/contrib/find/browser/findWidget.ts index 34340233c46..197d2736c67 100644 --- a/src/vs/editor/contrib/find/browser/findWidget.ts +++ b/src/vs/editor/contrib/find/browser/findWidget.ts @@ -218,61 +218,70 @@ export class FindWidget extends Widget implements EditorBrowser.IOverlayWidget { private _onFindInputKeyDown(e:DomUtils.IKeyboardEvent): void { - let handled = false; + switch (e.asKeybinding()) { + case CommonKeybindings.ENTER: + this._codeEditor.getAction(FIND_IDS.NextMatchFindAction).run().done(null, Errors.onUnexpectedError); + e.preventDefault(); + return; - if (e.equals(CommonKeybindings.ENTER)) { - this._codeEditor.getAction(FIND_IDS.NextMatchFindAction).run().done(null, Errors.onUnexpectedError); - handled = true; - } else if (e.equals(CommonKeybindings.SHIFT_ENTER)) { - this._codeEditor.getAction(FIND_IDS.PreviousMatchFindAction).run().done(null, Errors.onUnexpectedError); - handled = true; - } else if (e.equals(CommonKeybindings.TAB)) { - if (this._isReplaceVisible) { - this._replaceInputBox.focus(); - } else { - this._findInput.focusOnCaseSensitive(); - } - handled = true; - } else if (e.equals(CommonKeybindings.CTRLCMD_DOWN_ARROW)) { - this._codeEditor.focus(); - handled = true; + case CommonKeybindings.SHIFT_ENTER: + this._codeEditor.getAction(FIND_IDS.PreviousMatchFindAction).run().done(null, Errors.onUnexpectedError); + e.preventDefault(); + return; + + case CommonKeybindings.TAB: + if (this._isReplaceVisible) { + this._replaceInputBox.focus(); + } else { + this._findInput.focusOnCaseSensitive(); + } + e.preventDefault(); + return; + + case CommonKeybindings.CTRLCMD_DOWN_ARROW: + this._codeEditor.focus(); + e.preventDefault(); + return; } - if (handled) { - e.preventDefault(); - } else { - // getValue() is not updated right away - setTimeout(() => { - this._state.change({ searchString: this._findInput.getValue() }, true); - }, 10); - } + // getValue() is not updated right away + setTimeout(() => { + this._state.change({ searchString: this._findInput.getValue() }, true); + }, 10); } private _onReplaceInputKeyDown(e:DomUtils.IKeyboardEvent): void { - let handled = false; + switch (e.asKeybinding()) { + case CommonKeybindings.ENTER: + this._controller.replace(); + e.preventDefault(); + return; - if (e.equals(CommonKeybindings.ENTER)) { - this._controller.replace(); - handled = true; - } else if (e.equals(CommonKeybindings.CTRLCMD_ENTER)) { - this._controller.replaceAll(); - handled = true; - } else if (e.equals(CommonKeybindings.TAB)) { - this._findInput.focusOnCaseSensitive(); - handled = true; - } else if (e.equals(CommonKeybindings.CTRLCMD_DOWN_ARROW)) { - this._codeEditor.focus(); - handled = true; + case CommonKeybindings.CTRLCMD_ENTER: + this._controller.replaceAll(); + e.preventDefault(); + return; + + case CommonKeybindings.TAB: + this._findInput.focusOnCaseSensitive(); + e.preventDefault(); + return; + + case CommonKeybindings.SHIFT_TAB: + this._findInput.focus(); + e.preventDefault(); + return; + + case CommonKeybindings.CTRLCMD_DOWN_ARROW: + this._codeEditor.focus(); + e.preventDefault(); + return; } - if (handled) { - e.preventDefault(); - } else { - setTimeout(() => { - this._state.change({ replaceString: this._replaceInputBox.value }, false); - }, 10); - } + setTimeout(() => { + this._state.change({ replaceString: this._replaceInputBox.value }, false); + }, 10); } // ----- initialization @@ -317,6 +326,14 @@ export class FindWidget extends Widget implements EditorBrowser.IOverlayWidget { matchCase: this._findInput.getCaseSensitive() }, true); })); + this._register(this._findInput.onCaseSensitiveKeyDown((e) => { + if (e.equals(CommonKeybindings.SHIFT_TAB)) { + if (this._isReplaceVisible) { + this._replaceInputBox.focus(); + e.preventDefault(); + } + } + })); this._findInput.disable(); @@ -371,7 +388,11 @@ export class FindWidget extends Widget implements EditorBrowser.IOverlayWidget { onKeyDown: (e) => { if (e.equals(CommonKeybindings.TAB)) { if (this._isReplaceVisible) { - this._replaceBtn.focus(); + if (this._replaceBtn.isEnabled()) { + this._replaceBtn.focus(); + } else { + this._codeEditor.focus(); + } e.preventDefault(); } } @@ -630,6 +651,10 @@ class SimpleButton extends Widget { return this._domNode; } + public isEnabled(): boolean { + return (this._domNode.tabIndex >= 0); + } + public focus(): void { this._domNode.focus(); } From 183bf66abbebd8dcbf80a64a240572bdc19ce7de Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Fri, 8 Jan 2016 13:48:17 +0100 Subject: [PATCH 35/36] default themes: fixes for csharp --- extensions/theme-colorful-defaults/package.json | 2 +- extensions/theme-colorful-defaults/themes/dark_plus.tmTheme | 4 ++-- extensions/theme-colorful-defaults/themes/light_plus.tmTheme | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/extensions/theme-colorful-defaults/package.json b/extensions/theme-colorful-defaults/package.json index 41318692e88..a090258fecb 100644 --- a/extensions/theme-colorful-defaults/package.json +++ b/extensions/theme-colorful-defaults/package.json @@ -3,7 +3,7 @@ "displayName": "Colorful Default Themes - Please provide feedback in issue 1849", "description": "The default VS Code Light and Dark themes with a touch of color. We are considering adding these to the default themes in the January release. Please provide feedback in issue 1849.", "categories": [ "Themes" ], - "version": "0.1.5", + "version": "0.1.6", "publisher": "aeschli", "engines": { "vscode": "*" }, "contributes": { diff --git a/extensions/theme-colorful-defaults/themes/dark_plus.tmTheme b/extensions/theme-colorful-defaults/themes/dark_plus.tmTheme index 11c83ebed50..e47345be8a6 100644 --- a/extensions/theme-colorful-defaults/themes/dark_plus.tmTheme +++ b/extensions/theme-colorful-defaults/themes/dark_plus.tmTheme @@ -10,7 +10,7 @@ name Function declarations scope - entity.name.function + entity.name.function, entity.method.name settings foreground @@ -21,7 +21,7 @@ name Types declaration and references scope - meta.parameter.type, entity.name.class, new.storage.type, meta.cast, cast.storage.type, heritage.storage.type, annotation.storage.type, var.annotation.storage.type + meta.parameter.type, name.class, storage.type.cs, return-type, new.storage.type, meta.cast, cast.storage.type, heritage.storage.type, annotation.storage.type, var.annotation.storage.type settings foreground diff --git a/extensions/theme-colorful-defaults/themes/light_plus.tmTheme b/extensions/theme-colorful-defaults/themes/light_plus.tmTheme index bcfd3eb590a..5881cb3060e 100644 --- a/extensions/theme-colorful-defaults/themes/light_plus.tmTheme +++ b/extensions/theme-colorful-defaults/themes/light_plus.tmTheme @@ -10,7 +10,7 @@ name Function declarations scope - entity.name.function + entity.name.function, entity.method.name settings foreground @@ -21,7 +21,7 @@ name Types declaration and references scope - meta.parameter.type, entity.name.class, new.storage.type, meta.cast, cast.storage.type, heritage.storage.type, annotation.storage.type, var.annotation.storage.type + meta.parameter.type, name.class, storage.type.cs, return-type, new.storage.type, meta.cast, cast.storage.type, heritage.storage.type, annotation.storage.type, var.annotation.storage.type settings settings foreground From 02d3a8fecc64b6ee4e2387f8ba8941e557a551f6 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Fri, 8 Jan 2016 15:43:18 +0100 Subject: [PATCH 36/36] remove instant progress feedback from ext quickopen fixes #1863 --- .../electron-browser/extensionsWorkbenchExtension.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/parts/extensions/electron-browser/extensionsWorkbenchExtension.ts b/src/vs/workbench/parts/extensions/electron-browser/extensionsWorkbenchExtension.ts index d3613eb8918..2461fc8d927 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/extensionsWorkbenchExtension.ts +++ b/src/vs/workbench/parts/extensions/electron-browser/extensionsWorkbenchExtension.ts @@ -63,8 +63,7 @@ export class ExtensionsWorkbenchExtension implements IWorkbenchContribution { 'vs/workbench/parts/extensions/electron-browser/extensionsQuickOpen', 'GalleryExtensionsHandler', 'ext install ', - nls.localize('galleryExtensionsCommands', "Install Gallery Extensions"), - true + nls.localize('galleryExtensionsCommands', "Install Gallery Extensions") ) ); @@ -75,8 +74,7 @@ export class ExtensionsWorkbenchExtension implements IWorkbenchContribution { 'vs/workbench/parts/extensions/electron-browser/extensionsQuickOpen', 'OutdatedExtensionsHandler', 'ext update ', - nls.localize('outdatedExtensionsCommands', "Update Outdated Extensions"), - true + nls.localize('outdatedExtensionsCommands', "Update Outdated Extensions") ) ); }