From 5bc8e0b1e8c18c0ded99c6db1b4a217bfbd9afa7 Mon Sep 17 00:00:00 2001 From: Brandon Lee Dring Date: Mon, 23 Sep 2019 10:37:22 -0700 Subject: [PATCH] Feat/text search (#3075) * Added persistent text box search * Toggle search box now working * Restyled search box * Linter and bug squashing * Added multi OS hotkey support * PR changes as requested * Added ability to use escape button to close search field * Woops forgot key mapping on non mac platforms * fixed bug where escape would open up search window * Removal of unused vars that died in conflict --- app/commands.js | 6 +++ app/keymaps/darwin.json | 2 + app/keymaps/linux.json | 2 + app/keymaps/win32.json | 2 + app/menus/menus/edit.js | 7 ++++ lib/actions/sessions.js | 24 ++++++++++- lib/components/searchBox.js | 78 ++++++++++++++++++++++++++++++++++++ lib/components/term-group.js | 3 ++ lib/components/term.js | 37 +++++++++++++++++ lib/components/terms.js | 2 + lib/constants/sessions.js | 2 + lib/containers/terms.js | 6 ++- lib/index.js | 8 ++++ lib/reducers/sessions.js | 11 ++++- 14 files changed, 187 insertions(+), 3 deletions(-) create mode 100644 lib/components/searchBox.js diff --git a/app/commands.js b/app/commands.js index 19632cd4..4791b8bf 100644 --- a/app/commands.js +++ b/app/commands.js @@ -101,6 +101,12 @@ const commands = { 'editor:break': focusedWindow => { focusedWindow && focusedWindow.rpc.emit('session break req'); }, + 'editor:search': focusedWindow => { + focusedWindow && focusedWindow.rpc.emit('session search'); + }, + 'editor:search-close': focusedWindow => { + focusedWindow && focusedWindow.rpc.emit('session search close'); + }, 'cli:install': () => { installCLI(true); }, diff --git a/app/keymaps/darwin.json b/app/keymaps/darwin.json index 908e71ab..30f0f276 100644 --- a/app/keymaps/darwin.json +++ b/app/keymaps/darwin.json @@ -39,6 +39,8 @@ "editor:copy": "command+c", "editor:paste": "command+v", "editor:selectAll": "command+a", + "editor:search": "command+f", + "editor:search-close": "esc", "editor:movePreviousWord": "alt+left", "editor:moveNextWord": "alt+right", "editor:moveBeginningLine": "command+left", diff --git a/app/keymaps/linux.json b/app/keymaps/linux.json index 1c9ff68d..ab07a2fd 100644 --- a/app/keymaps/linux.json +++ b/app/keymaps/linux.json @@ -36,6 +36,8 @@ "editor:copy": "ctrl+shift+c", "editor:paste": "ctrl+shift+v", "editor:selectAll": "ctrl+shift+a", + "editor:search": "ctrl+shift+f", + "editor:search-close": "esc", "editor:movePreviousWord": "ctrl+left", "editor:moveNextWord": "ctrl+right", "editor:moveBeginningLine": "home", diff --git a/app/keymaps/win32.json b/app/keymaps/win32.json index ce80196f..9d8fe435 100644 --- a/app/keymaps/win32.json +++ b/app/keymaps/win32.json @@ -40,6 +40,8 @@ "editor:copy": "ctrl+shift+c", "editor:paste": "ctrl+shift+v", "editor:selectAll": "ctrl+shift+a", + "editor:search": "ctrl+shift+f", + "editor:search-close": "esc", "editor:movePreviousWord": "ctrl+left", "editor:moveNextWord": "ctrl+right", "editor:moveBeginningLine": "Home", diff --git a/app/menus/menus/edit.js b/app/menus/menus/edit.js index bac65a3d..0c017447 100644 --- a/app/menus/menus/edit.js +++ b/app/menus/menus/edit.js @@ -113,6 +113,13 @@ module.exports = (commandKeys, execCommand) => { click(item, focusedWindow) { execCommand('editor:clearBuffer', focusedWindow); } + }, + { + label: 'Search', + accelerator: commandKeys['editor:search'], + click(item, focusedWindow) { + execCommand('editor:search', focusedWindow); + } } ]; diff --git a/lib/actions/sessions.js b/lib/actions/sessions.js index 42f96732..4d1bd95a 100644 --- a/lib/actions/sessions.js +++ b/lib/actions/sessions.js @@ -12,7 +12,9 @@ import { SESSION_SET_ACTIVE, SESSION_CLEAR_ACTIVE, SESSION_USER_DATA, - SESSION_SET_XTERM_TITLE + SESSION_SET_XTERM_TITLE, + SESSION_SEARCH, + SESSION_SEARCH_CLOSE } from '../constants/sessions'; export function addSession({uid, shell, pid, cols, rows, splitDirection}) { @@ -131,6 +133,26 @@ export function resizeSession(uid, cols, rows) { }; } +export function onSearch(uid) { + return (dispatch, getState) => { + const targetUid = uid || getState().sessions.activeUid; + dispatch({ + type: SESSION_SEARCH, + uid: targetUid + }); + }; +} + +export function closeSearch(uid) { + return (dispatch, getState) => { + const targetUid = uid || getState().sessions.activeUid; + dispatch({ + type: SESSION_SEARCH_CLOSE, + uid: targetUid + }); + }; +} + export function sendSessionData(uid, data, escaped) { return (dispatch, getState) => { dispatch({ diff --git a/lib/components/searchBox.js b/lib/components/searchBox.js new file mode 100644 index 00000000..08eee3ff --- /dev/null +++ b/lib/components/searchBox.js @@ -0,0 +1,78 @@ +import React from 'react'; + +const searchBoxStyling = { + float: 'right', + height: '28px', + backgroundColor: 'white', + position: 'absolute', + right: '10px', + top: '25px', + width: '224px', + zIndex: '9999' +}; + +const enterKey = 13; + +export default class SearchBox extends React.PureComponent { + constructor(props) { + super(props); + this.handleChange = this.handleChange.bind(this); + this.searchTerm = ''; + } + + handleChange(event) { + this.searchTerm = event.target.value; + if (event.keyCode === enterKey) { + this.props.search(event.target.value); + } + } + + render() { + return ( +
+ input && input.focus()} /> + this.props.prev(this.searchTerm)}> + {' '} + ←{' '} + + this.props.next(this.searchTerm)}> + {' '} + →{' '} + + this.props.close()}> + {' '} + x{' '} + + +
+ ); + } +} diff --git a/lib/components/term-group.js b/lib/components/term-group.js index f78c74df..af5d2dc5 100644 --- a/lib/components/term-group.js +++ b/lib/components/term-group.js @@ -77,6 +77,7 @@ class TermGroup_ extends React.PureComponent { padding: this.props.padding, url: session.url, cleared: session.cleared, + search: session.search, cols: session.cols, rows: session.rows, copyOnSelect: this.props.copyOnSelect, @@ -86,6 +87,8 @@ class TermGroup_ extends React.PureComponent { onResize: this.bind(this.props.onResize, null, uid), onTitle: this.bind(this.props.onTitle, null, uid), onData: this.bind(this.props.onData, null, uid), + onURLAbort: this.bind(this.props.onURLAbort, null, uid), + toggleSearch: this.bind(this.props.toggleSearch, null, uid), onContextMenu: this.bind(this.props.onContextMenu, null, uid), borderColor: this.props.borderColor, selectionColor: this.props.selectionColor, diff --git a/lib/components/term.js b/lib/components/term.js index 74694c5f..d65465c9 100644 --- a/lib/components/term.js +++ b/lib/components/term.js @@ -3,15 +3,18 @@ import React from 'react'; import {Terminal} from 'xterm'; import * as fit from 'xterm/lib/addons/fit/fit'; import * as webLinks from 'xterm/lib/addons/webLinks/webLinks'; +import * as search from 'xterm/lib/addons/search'; import * as winptyCompat from 'xterm/lib/addons/winptyCompat/winptyCompat'; import {clipboard} from 'electron'; import * as Color from 'color'; import terms from '../terms'; import processClipboard from '../utils/paste'; +import SearchBox from './searchBox'; Terminal.applyAddon(fit); Terminal.applyAddon(webLinks); Terminal.applyAddon(winptyCompat); +Terminal.applyAddon(search); // map old hterm constants to xterm.js const CURSOR_STYLES = { @@ -108,6 +111,10 @@ export default class Term extends React.PureComponent { this.onWindowPaste = this.onWindowPaste.bind(this); this.onTermWrapperRef = this.onTermWrapperRef.bind(this); this.onMouseUp = this.onMouseUp.bind(this); + this.search = this.search.bind(this); + this.searchNext = this.searchNext.bind(this); + this.searchPrevious = this.searchPrevious.bind(this); + this.closeSearchBox = this.closeSearchBox.bind(this); this.termOptions = {}; this.disposableListeners = []; } @@ -243,6 +250,22 @@ export default class Term extends React.PureComponent { this.term.reset(); } + search(searchTerm) { + this.term.findNext(searchTerm); + } + + searchNext(searchTerm) { + this.term.findNext(searchTerm); + } + + searchPrevious(searchTerm) { + this.term.findPrevious(searchTerm); + } + + closeSearchBox() { + this.props.toggleSearch(); + } + resize(cols, rows) { this.term.resize(cols, rows); } @@ -269,6 +292,10 @@ export default class Term extends React.PureComponent { } const nextTermOptions = getTermOptions(nextProps); + if (!this.props.search && nextProps.search) { + this.search(); + } + // Update only options that have changed. Object.keys(nextTermOptions) .filter(option => option !== 'theme' && nextTermOptions[option] !== this.termOptions[option]) @@ -358,6 +385,16 @@ export default class Term extends React.PureComponent { {this.props.customChildrenBefore}
{this.props.customChildren} + {this.props.search ? ( + + ) : ( + '' + )}