From 4aa824d72579d5de3a4e528e34f37497105a5714 Mon Sep 17 00:00:00 2001 From: Eugene Pankov Date: Sun, 3 Apr 2022 15:19:22 +0200 Subject: [PATCH] search improvements, added highlighting - fixes #5940, fixes #5032, fixes #5152 --- .../src/api/baseTerminalTab.component.ts | 4 +- .../components/baseTerminalTab.component.scss | 2 +- .../src/components/searchPanel.component.pug | 48 ++++++++++--------- .../src/components/searchPanel.component.scss | 7 ++- .../src/components/searchPanel.component.ts | 14 +++--- tabby-terminal/src/frontends/frontend.ts | 9 +++- tabby-terminal/src/frontends/xterm.css | 10 ++++ tabby-terminal/src/frontends/xtermFrontend.ts | 29 +++++++++-- 8 files changed, 84 insertions(+), 39 deletions(-) diff --git a/tabby-terminal/src/api/baseTerminalTab.component.ts b/tabby-terminal/src/api/baseTerminalTab.component.ts index 165655a9..48bdf5b7 100644 --- a/tabby-terminal/src/api/baseTerminalTab.component.ts +++ b/tabby-terminal/src/api/baseTerminalTab.component.ts @@ -261,7 +261,9 @@ export class BaseTerminalTabComponent extends BaseTabComponent implements OnInit case 'search': this.showSearchPanel = true setImmediate(() => { - this.element.nativeElement.querySelector('.search-input').focus() + const input = this.element.nativeElement.querySelector('.search-input') + input?.focus() + input?.select() }) break case 'pane-focus-all': diff --git a/tabby-terminal/src/components/baseTerminalTab.component.scss b/tabby-terminal/src/components/baseTerminalTab.component.scss index 0b829645..af8e6d9b 100644 --- a/tabby-terminal/src/components/baseTerminalTab.component.scss +++ b/tabby-terminal/src/components/baseTerminalTab.component.scss @@ -2,7 +2,7 @@ flex: auto; display: flex; flex-direction: column; - overflow: hidden; + // overflow: hidden; position: relative; &> .content { diff --git a/tabby-terminal/src/components/searchPanel.component.pug b/tabby-terminal/src/components/searchPanel.component.pug index 7f367a27..29922250 100644 --- a/tabby-terminal/src/components/searchPanel.component.pug +++ b/tabby-terminal/src/components/searchPanel.component.pug @@ -1,27 +1,31 @@ -input.search-input.form-control( - type='text', - [(ngModel)]='query', - (ngModelChange)='onQueryChange()', - [class.text-danger]='notFound', - (click)='$event.stopPropagation()', - (keyup.enter)='findPrevious()', - (keyup.esc)='close.emit()', - [placeholder]='"Search"|translate' -) +.input-group + input.search-input.form-control( + type='text', + [(ngModel)]='query', + (ngModelChange)='onQueryChange()', + [class.text-danger]='state.resultCount == 0', + (click)='$event.stopPropagation()', + (keyup.enter)='findPrevious()', + (keyup.esc)='close.emit()', + [placeholder]='"Search"|translate' + ) + .input-group-append(*ngIf='state.resultCount > 0') + .input-group-text.result-counter {{state.resultIndex + 1}} / {{state.resultCount}} -button.btn.btn-link( - (click)='findPrevious()', - ngbTooltip='Search up', - placement='bottom', - [fastHtmlBind]='icons.arrowUp' -) +ng-container(*ngIf='state.resultCount > 0') + button.btn.btn-link( + (click)='findPrevious()', + ngbTooltip='Search up', + placement='bottom', + [fastHtmlBind]='icons.arrowUp' + ) -button.btn.btn-link( - (click)='findNext()', - ngbTooltip='Search down', - placement='bottom', - [fastHtmlBind]='icons.arrowDown' -) + button.btn.btn-link( + (click)='findNext()', + ngbTooltip='Search down', + placement='bottom', + [fastHtmlBind]='icons.arrowDown' + ) .mr-2 diff --git a/tabby-terminal/src/components/searchPanel.component.scss b/tabby-terminal/src/components/searchPanel.component.scss index d5623696..c61df4b0 100644 --- a/tabby-terminal/src/components/searchPanel.component.scss +++ b/tabby-terminal/src/components/searchPanel.component.scss @@ -1,6 +1,6 @@ :host { position: fixed; - width: 400px; + width: 600px; right: 40px; z-index: 5; border-radius: 0 0 5px 5px; @@ -15,3 +15,8 @@ flex-shrink: 0; } } + +.result-counter { + font-size: 0.7rem; + opacity: .5; +} diff --git a/tabby-terminal/src/components/searchPanel.component.ts b/tabby-terminal/src/components/searchPanel.component.ts index a5aedbca..606206f8 100644 --- a/tabby-terminal/src/components/searchPanel.component.ts +++ b/tabby-terminal/src/components/searchPanel.component.ts @@ -1,5 +1,5 @@ import { Component, Input, Output, EventEmitter } from '@angular/core' -import { Frontend, SearchOptions } from '../frontends/frontend' +import { Frontend, SearchOptions, SearchState } from '../frontends/frontend' import { ConfigService, NotificationsService, TranslateService } from 'tabby-core' @Component({ @@ -10,7 +10,7 @@ import { ConfigService, NotificationsService, TranslateService } from 'tabby-cor export class SearchPanelComponent { @Input() query: string @Input() frontend: Frontend - notFound = false + state: SearchState = { resultCount: 0 } options: SearchOptions = { incremental: true, ...this.config.store.terminal.searchOptions, @@ -34,7 +34,7 @@ export class SearchPanelComponent { ) { } onQueryChange (): void { - this.notFound = false + this.state = { resultCount: 0 } this.findPrevious(true) } @@ -42,8 +42,8 @@ export class SearchPanelComponent { if (!this.query) { return } - if (!this.frontend.findNext(this.query, { ...this.options, incremental: incremental || undefined })) { - this.notFound = true + this.state = this.frontend.findNext(this.query, { ...this.options, incremental: incremental || undefined }) + if (!this.state.resultCount) { this.notifications.notice(this.translate.instant('Not found')) } } @@ -52,8 +52,8 @@ export class SearchPanelComponent { if (!this.query) { return } - if (!this.frontend.findPrevious(this.query, { ...this.options, incremental: incremental || undefined })) { - this.notFound = true + this.state = this.frontend.findPrevious(this.query, { ...this.options, incremental: incremental || undefined }) + if (!this.state.resultCount) { this.notifications.notice(this.translate.instant('Not found')) } } diff --git a/tabby-terminal/src/frontends/frontend.ts b/tabby-terminal/src/frontends/frontend.ts index 19d37290..d1362993 100644 --- a/tabby-terminal/src/frontends/frontend.ts +++ b/tabby-terminal/src/frontends/frontend.ts @@ -9,6 +9,11 @@ export interface SearchOptions { incremental?: true } +export interface SearchState { + resultIndex?: number + resultCount: number +} + /** * Extend to add support for a different VT frontend implementation */ @@ -75,8 +80,8 @@ export abstract class Frontend { abstract configure (): void abstract setZoom (zoom: number): void - abstract findNext (term: string, searchOptions?: SearchOptions): boolean - abstract findPrevious (term: string, searchOptions?: SearchOptions): boolean + abstract findNext (term: string, searchOptions?: SearchOptions): SearchState + abstract findPrevious (term: string, searchOptions?: SearchOptions): SearchState abstract cancelSearch (): void abstract saveState (): any diff --git a/tabby-terminal/src/frontends/xterm.css b/tabby-terminal/src/frontends/xterm.css index ad736f2f..f894ee86 100644 --- a/tabby-terminal/src/frontends/xterm.css +++ b/tabby-terminal/src/frontends/xterm.css @@ -16,3 +16,13 @@ .xterm-decoration-overview-ruler { right: 6px; } + +.xterm-find-result-decoration { + box-sizing: content-box; + + border-radius: 3px; + padding: 2px; + margin: -2px; + outline: 2px solid yellow; + backdrop-filter: contrast(2); +} diff --git a/tabby-terminal/src/frontends/xtermFrontend.ts b/tabby-terminal/src/frontends/xtermFrontend.ts index 3906fe0d..4544c3b8 100644 --- a/tabby-terminal/src/frontends/xtermFrontend.ts +++ b/tabby-terminal/src/frontends/xtermFrontend.ts @@ -1,6 +1,6 @@ import { Injector } from '@angular/core' import { ConfigService, getCSSFontFamily, HostAppService, HotkeysService, Platform, PlatformService } from 'tabby-core' -import { Frontend, SearchOptions } from './frontend' +import { Frontend, SearchOptions, SearchState } from './frontend' import { takeUntil } from 'rxjs' import { Terminal, ITheme } from 'xterm' import { FitAddon } from 'xterm-addon-fit' @@ -34,6 +34,7 @@ export class XTermFrontend extends Frontend { private configuredTheme: ITheme = {} private copyOnSelect = false private search = new SearchAddon() + private searchState: SearchState = { resultCount: 0 } private fitAddon = new FitAddon() private serializeAddon = new SerializeAddon() private ligaturesAddon?: LigaturesAddon @@ -178,6 +179,10 @@ export class XTermFrontend extends Frontend { this.xterm.loadAddon(this.search) + this.search.onDidChangeResults(state => { + this.searchState = state ?? { resultCount: 0 } + }) + window.addEventListener('resize', this.resizeHandler) this.resizeHandler() @@ -327,16 +332,30 @@ export class XTermFrontend extends Frontend { decorations: { matchOverviewRuler: '#cccc00', activeMatchColorOverviewRuler: '#ffff00', + matchBorder: '#cc0', + activeMatchBorder: '#ff0', + activeMatchBackground: 'rgba(255, 255, 0, 0.3)', }, } } - findNext (term: string, searchOptions?: SearchOptions): boolean { - return this.search.findNext(term, this.getSearchOptions(searchOptions)) + private wrapSearchResult (result: boolean): SearchState { + if (!result) { + return { resultCount: 0 } + } + return this.searchState } - findPrevious (term: string, searchOptions?: SearchOptions): boolean { - return this.search.findPrevious(term, this.getSearchOptions(searchOptions)) + findNext (term: string, searchOptions?: SearchOptions): SearchState { + return this.wrapSearchResult( + this.search.findNext(term, this.getSearchOptions(searchOptions)) + ) + } + + findPrevious (term: string, searchOptions?: SearchOptions): SearchState { + return this.wrapSearchResult( + this.search.findPrevious(term, this.getSearchOptions(searchOptions)) + ) } cancelSearch (): void {