diff --git a/src/vs/base/browser/ui/findinput/findInput.css b/src/vs/base/browser/ui/findinput/findInput.css index 54fcbc4058f..adfd38bfe71 100644 --- a/src/vs/base/browser/ui/findinput/findInput.css +++ b/src/vs/base/browser/ui/findinput/findInput.css @@ -33,6 +33,25 @@ right: 2px; } +.monaco-findInput > .controls > .matchCount { + margin-left: 2px; + float: left; + overflow: hidden; + max-width: 30px; + min-width: 20px; + text-align: center; + + background: #ddd; + border-radius: 5px; + padding: 0 4px; + + -webkit-box-sizing: border-box; + -o-box-sizing: border-box; + -moz-box-sizing: border-box; + -ms-box-sizing: border-box; + box-sizing: border-box; +} + .vs .monaco-findInput > .controls > .custom-checkbox.case-sensitive { background: url('case-sensitive.svg') center center no-repeat; } diff --git a/src/vs/base/browser/ui/findinput/findInput.ts b/src/vs/base/browser/ui/findinput/findInput.ts index 0c82b7d3142..ea8901bd2f5 100644 --- a/src/vs/base/browser/ui/findinput/findInput.ts +++ b/src/vs/base/browser/ui/findinput/findInput.ts @@ -14,6 +14,7 @@ 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'; +import {StandardMouseEvent} from 'vs/base/browser/mouseEvent'; export interface IFindInputOptions { placeholder?:string; @@ -26,6 +27,12 @@ export interface IFindInputOptions { appendRegexLabel?: string; } +export interface IMatchCountState { + count: string; + isVisible: boolean; + title: string; +} + const NLS_REGEX_CHECKBOX_LABEL = nls.localize('regexDescription', "Use Regular Expression"); const NLS_WHOLE_WORD_CHECKBOX_LABEL = nls.localize('wordsDescription', "Match Whole Word"); const NLS_CASE_SENSITIVE_CHECKBOX_LABEL = nls.localize('caseDescription', "Match Case"); @@ -44,6 +51,7 @@ export class FindInput extends Widget { private regex:Checkbox; private wholeWords:Checkbox; private caseSensitive:Checkbox; + private matchCount: MatchCount; public domNode: HTMLElement; public inputBox:InputBox; @@ -122,6 +130,11 @@ export class FindInput extends Widget { } } + public setMatchCountState(state:IMatchCountState): void { + this.matchCount.setState(state); + this.setInputWidth(); + } + public select(): void { this.inputBox.select(); } @@ -162,7 +175,7 @@ export class FindInput extends Widget { } private setInputWidth(): void { - let w = this.width - this.caseSensitive.width() - this.wholeWords.width() - this.regex.width(); + let w = this.width - this.matchCount.width() - this.caseSensitive.width() - this.wholeWords.width() - this.regex.width(); this.inputBox.width = w; } @@ -216,10 +229,18 @@ export class FindInput extends Widget { this._onCaseSensitiveKeyDown.fire(e); } })); + this.matchCount = this._register(new MatchCount({ + onClick: (e) => { + this.inputBox.focus(); + e.preventDefault(); + } + })); + this.setInputWidth(); let controls = document.createElement('div'); controls.className = 'controls'; + controls.appendChild(this.matchCount.domNode); controls.appendChild(this.caseSensitive.domNode); controls.appendChild(this.wholeWords.domNode); controls.appendChild(this.regex.domNode); @@ -243,3 +264,43 @@ export class FindInput extends Widget { this.inputBox.hideMessage(); } } + +interface IMatchCountOpts { + onClick: (e:StandardMouseEvent) => void; +} + +class MatchCount extends Widget { + + public domNode: HTMLElement; + private isVisible: boolean; + + constructor(opts:IMatchCountOpts) { + super(); + this.domNode = document.createElement('div'); + this.domNode.className = 'matchCount'; + + this.setState({ + isVisible: false, + count: '0', + title: '' + }); + this.onclick(this.domNode, opts.onClick); + } + + public width(): number { + return this.isVisible ? 30 : 0; + } + + public setState(state:IMatchCountState): void { + dom.clearNode(this.domNode); + this.domNode.appendChild(document.createTextNode(state.count)); + this.domNode.title = state.title; + + this.isVisible = state.isVisible; + if (this.isVisible) { + this.domNode.style.display = 'block'; + } else { + this.domNode.style.display = 'none'; + } + } +} diff --git a/src/vs/editor/contrib/find/browser/findWidget.ts b/src/vs/editor/contrib/find/browser/findWidget.ts index 614c9d4d8b4..4b9ad2f2d2a 100644 --- a/src/vs/editor/contrib/find/browser/findWidget.ts +++ b/src/vs/editor/contrib/find/browser/findWidget.ts @@ -15,7 +15,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 {FIND_IDS} from 'vs/editor/contrib/find/common/findModel'; +import {MATCHES_LIMIT, FIND_IDS} from 'vs/editor/contrib/find/common/findModel'; 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'; @@ -37,6 +37,7 @@ const NLS_REPLACE_INPUT_PLACEHOLDER = nls.localize('placeholder.replace', "Repla 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"); +const NLS_MATCHES_COUNT_LIMIT_TITLE = nls.localize('title.matchesCountLimit', "Only the first 999 results are highlighted, but all find operations work on the entire text."); export class FindWidget extends Widget implements EditorBrowser.IOverlayWidget { @@ -173,6 +174,21 @@ export class FindWidget extends Widget implements EditorBrowser.IOverlayWidget { if (e.searchString || e.matchesCount) { let showRedOutline = (this._state.searchString.length > 0 && this._state.matchesCount === 0); DomUtils.toggleClass(this._domNode, 'no-results', showRedOutline); + + let showMatchesCount = (this._state.searchString.length > 0); + + let matchesCount:string = String(this._state.matchesCount); + let matchesCountTitle = ''; + if (this._state.matchesCount >= MATCHES_LIMIT) { + matchesCountTitle = NLS_MATCHES_COUNT_LIMIT_TITLE; + matchesCount += '+'; + } + + this._findInput.setMatchCountState({ + isVisible: showMatchesCount, + count: matchesCount, + title: matchesCountTitle + }); } } diff --git a/src/vs/editor/contrib/find/common/findModel.ts b/src/vs/editor/contrib/find/common/findModel.ts index 6dd7ba4629c..c77077d4552 100644 --- a/src/vs/editor/contrib/find/common/findModel.ts +++ b/src/vs/editor/contrib/find/common/findModel.ts @@ -30,6 +30,8 @@ export const FIND_IDS = { ToggleRegexCommand: 'toggleFindRegex' } +export const MATCHES_LIMIT = 999; + export class FindModelBoundToEditorModel { private _editor:EditorCommon.ICommonCodeEditor; @@ -117,7 +119,7 @@ export class FindModelBoundToEditorModel { findScope = new Range(findScope.startLineNumber, 1, findScope.endLineNumber, this._editor.getModel().getLineMaxColumn(findScope.endLineNumber)); } - let findMatches = this._findMatches(findScope); + let findMatches = this._findMatches(findScope, MATCHES_LIMIT); this._decorations.set(findMatches, findScope); this._state.change({ matchesCount: findMatches.length }, false); @@ -317,7 +319,7 @@ export class FindModelBoundToEditorModel { } } - private _findMatches(findScope: EditorCommon.IEditorRange, limitResultCount?:number): EditorCommon.IEditorRange[] { + 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); }