mirror of
https://github.com/quine-global/hyper.git
synced 2026-01-12 20:18:41 -09:00
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
This commit is contained in:
parent
67a1fc4dbb
commit
5bc8e0b1e8
14 changed files with 187 additions and 3 deletions
|
|
@ -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);
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
|
|
|
|||
|
|
@ -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({
|
||||
|
|
|
|||
78
lib/components/searchBox.js
Normal file
78
lib/components/searchBox.js
Normal file
|
|
@ -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 (
|
||||
<div style={searchBoxStyling}>
|
||||
<input type="text" className="search-box" onKeyUp={this.handleChange} ref={input => input && input.focus()} />
|
||||
<span className="search-button" onClick={() => this.props.prev(this.searchTerm)}>
|
||||
{' '}
|
||||
←{' '}
|
||||
</span>
|
||||
<span className="search-button" onClick={() => this.props.next(this.searchTerm)}>
|
||||
{' '}
|
||||
→{' '}
|
||||
</span>
|
||||
<span className="search-button" onClick={() => this.props.close()}>
|
||||
{' '}
|
||||
x{' '}
|
||||
</span>
|
||||
<style jsx>
|
||||
{`
|
||||
.search-box {
|
||||
font-size: 18px;
|
||||
padding: 6px;
|
||||
width: 145px;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.search-box:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.search-button {
|
||||
background-color: #ffffff;
|
||||
color: black;
|
||||
padding: 7px;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
font-size: 16px;
|
||||
transition-duration: 0.4s;
|
||||
cursor: pointer;
|
||||
}
|
||||
.search-button:hover {
|
||||
background-color: #e7e7e7;
|
||||
}
|
||||
`}
|
||||
</style>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
<div ref={this.onTermWrapperRef} className="term_fit term_wrapper" />
|
||||
{this.props.customChildren}
|
||||
{this.props.search ? (
|
||||
<SearchBox
|
||||
search={this.search}
|
||||
next={this.searchNext}
|
||||
prev={this.searchPrevious}
|
||||
close={this.closeSearchBox}
|
||||
/>
|
||||
) : (
|
||||
''
|
||||
)}
|
||||
|
||||
<style jsx global>{`
|
||||
.term_fit {
|
||||
|
|
|
|||
|
|
@ -120,6 +120,8 @@ export default class Terms extends React.Component {
|
|||
onResize: this.props.onResize,
|
||||
onTitle: this.props.onTitle,
|
||||
onData: this.props.onData,
|
||||
toggleSearch: this.props.toggleSearch,
|
||||
onURLAbort: this.props.onURLAbort,
|
||||
onContextMenu: this.props.onContextMenu,
|
||||
quickEdit: this.props.quickEdit,
|
||||
webGLRenderer: this.props.webGLRenderer,
|
||||
|
|
|
|||
|
|
@ -12,3 +12,5 @@ export const SESSION_URL_SET = 'SESSION_URL_SET';
|
|||
export const SESSION_URL_UNSET = 'SESSION_URL_UNSET';
|
||||
export const SESSION_SET_XTERM_TITLE = 'SESSION_SET_XTERM_TITLE';
|
||||
export const SESSION_SET_CWD = 'SESSION_SET_CWD';
|
||||
export const SESSION_SEARCH = 'SESSION_SEARCH';
|
||||
export const SESSION_SEARCH_CLOSE = 'SESSION_SEARCH_CLOSE';
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import Terms from '../components/terms';
|
||||
import {connect} from '../utils/plugins';
|
||||
import {resizeSession, sendSessionData, setSessionXtermTitle, setActiveSession} from '../actions/sessions';
|
||||
import {resizeSession, sendSessionData, setSessionXtermTitle, setActiveSession, onSearch} from '../actions/sessions';
|
||||
|
||||
import {openContextMenu} from '../actions/ui';
|
||||
import getRootGroups from '../selectors';
|
||||
|
||||
|
|
@ -61,6 +62,9 @@ const TermsContainer = connect(
|
|||
onActive(uid) {
|
||||
dispatch(setActiveSession(uid));
|
||||
},
|
||||
toggleSearch(uid) {
|
||||
dispatch(onSearch(uid));
|
||||
},
|
||||
|
||||
onContextMenu(uid, selection) {
|
||||
dispatch(setActiveSession(uid));
|
||||
|
|
|
|||
|
|
@ -106,6 +106,14 @@ rpc.on('session break req', () => {
|
|||
store_.dispatch(sessionActions.sendSessionData(null, '\x03'));
|
||||
});
|
||||
|
||||
rpc.on('session search', () => {
|
||||
store_.dispatch(sessionActions.onSearch());
|
||||
});
|
||||
|
||||
rpc.on('session search close', () => {
|
||||
store_.dispatch(sessionActions.closeSearch());
|
||||
});
|
||||
|
||||
rpc.on('termgroup add req', () => {
|
||||
store_.dispatch(termGroupActions.requestTermGroup());
|
||||
});
|
||||
|
|
|
|||
|
|
@ -9,7 +9,9 @@ import {
|
|||
SESSION_CLEAR_ACTIVE,
|
||||
SESSION_RESIZE,
|
||||
SESSION_SET_XTERM_TITLE,
|
||||
SESSION_SET_CWD
|
||||
SESSION_SET_CWD,
|
||||
SESSION_SEARCH,
|
||||
SESSION_SEARCH_CLOSE
|
||||
} from '../constants/sessions';
|
||||
|
||||
const initialState = Immutable({
|
||||
|
|
@ -25,6 +27,7 @@ function Session(obj) {
|
|||
rows: null,
|
||||
url: null,
|
||||
cleared: false,
|
||||
search: false,
|
||||
shell: '',
|
||||
pid: null
|
||||
}).merge(obj);
|
||||
|
|
@ -47,6 +50,12 @@ const reducer = (state = initialState, action) => {
|
|||
case SESSION_SET_ACTIVE:
|
||||
return state.set('activeUid', action.uid);
|
||||
|
||||
case SESSION_SEARCH:
|
||||
return state.setIn(['sessions', action.uid, 'search'], !state.sessions[action.uid].search);
|
||||
|
||||
case SESSION_SEARCH_CLOSE:
|
||||
return state.setIn(['sessions', action.uid, 'search'], false);
|
||||
|
||||
case SESSION_CLEAR_ACTIVE:
|
||||
return state.merge(
|
||||
{
|
||||
|
|
|
|||
Loading…
Reference in a new issue