diff --git a/keymaps/ink.cson b/keymaps/ink.cson index b874bd22..27006646 100644 --- a/keymaps/ink.cson +++ b/keymaps/ink.cson @@ -49,7 +49,14 @@ '.platform-win32 ink-terminal, .platform-linux ink-terminal': 'ctrl-shift-c': 'ink-terminal:copy' 'ctrl-shift-v': 'ink-terminal:paste' + 'ctrl-shift-f': 'ink-terminal:show-search' '.platform-darwin ink-terminal': 'cmd-c': 'ink-terminal:copy' 'cmd-v': 'ink-terminal:paste' + 'cmd-f': 'ink-terminal:show-search' + +'ink-terminal .search .searchinput atom-text-editor': + 'enter': 'ink-terminal:find-next' + 'shift-enter': 'ink-terminal:find-previous' + 'escape': 'ink-terminal:close-search' diff --git a/lib/console2/console.js b/lib/console2/console.js index 0eee3a32..0e62f7f5 100644 --- a/lib/console2/console.js +++ b/lib/console2/console.js @@ -8,18 +8,21 @@ import { Terminal } from 'xterm' import * as fit from 'xterm/lib/addons/fit/fit' import * as webLinks from 'xterm/lib/addons/webLinks/webLinks' import * as winptyCompat from 'xterm/lib/addons/winptyCompat/winptyCompat' +import * as search from 'xterm/lib/addons/search/search' import TerminalElement from './view' import PaneItem from '../util/pane-item' import ResizeDetector from 'element-resize-detector' import { debounce, throttle } from 'underscore-plus' import { closest } from './helpers' import { openExternal } from 'shell' +import SearchUI from './searchui' let getTerminal = el => closest(el, 'ink-terminal').getModel() Terminal.applyAddon(fit) Terminal.applyAddon(webLinks) Terminal.applyAddon(winptyCompat) +Terminal.applyAddon(search) var subs @@ -36,10 +39,30 @@ export default class InkTerminal extends PaneItem { let term = getTerminal(target) if (term != undefined) { term.paste(process.platform != 'win32') + }}, + 'ink-terminal:show-search': ({target}) => { + let term = getTerminal(target) + if (term != undefined) { + term.searchui.show() + }}, + 'ink-terminal:find-next': ({target}) => { + let term = getTerminal(target) + if (term != undefined) { + term.searchui.find(true) + }}, + 'ink-terminal:find-previous': ({target}) => { + let term = getTerminal(target) + if (term != undefined) { + term.searchui.find(false) + }}, + 'ink-terminal:close-search': ({target}) => { + let term = getTerminal(target) + if (term != undefined) { + term.searchui.hide() }} })) - subs.add(atom.workspace.onDidStopChangingActivePaneItem((item) => { + subs.add(atom.workspace.onDidChangeActivePaneItem((item) => { if (item instanceof InkTerminal) { item.view.initialize(item) item.terminal.focus() @@ -92,6 +115,8 @@ export default class InkTerminal extends PaneItem { this.view = new TerminalElement + this.searchui = new SearchUI(this.terminal) + etch.initialize(this) etch.update(this).then(() => { this.view.initialize(this) @@ -215,7 +240,7 @@ export default class InkTerminal extends PaneItem { bracketed && this.ty.write('\x1b[201~') // disable bracketed paste mode } - show (view) { + show () { this.terminal.focus() } diff --git a/lib/console2/searchui.js b/lib/console2/searchui.js new file mode 100644 index 00000000..bef07d51 --- /dev/null +++ b/lib/console2/searchui.js @@ -0,0 +1,168 @@ +'use babel' +/** @jsx etch.dom */ + +import etch from 'etch' +import { Raw, Button, toView } from '../util/etch.js' +import { CompositeDisposable, Emitter, TextEditor } from 'atom' +import { Terminal } from 'xterm' +import * as fit from 'xterm/lib/addons/fit/fit' +import * as webLinks from 'xterm/lib/addons/webLinks/webLinks' +import * as winptyCompat from 'xterm/lib/addons/winptyCompat/winptyCompat' +import * as search from 'xterm/lib/addons/search/search' +import TerminalElement from './view' +import PaneItem from '../util/pane-item' +import ResizeDetector from 'element-resize-detector' +import { debounce, throttle } from 'underscore-plus' +import { closest } from './helpers' +import { openExternal } from 'shell' + +export default class TerminalSearchUI { + constructor (terminal) { + this.terminal = terminal + this.editor = new TextEditor( {mini: true, placeholderText: 'Find in Terminal'} ) + + this.useRegex = false + this.matchCase = false + this.wholeWord = false + + this.initialized = false + this.errorMessage = '' + + etch.initialize(this) + } + + update () {} + + toggleRegex () { + this.useRegex = !this.useRegex + this.refs.toggleRegex.element.classList.toggle('selected') + } + + toggleCase () { + this.matchCase = !this.matchCase + this.refs.toggleCase.element.classList.toggle('selected') + } + + toggleWhole () { + this.wholeWord = !this.wholeWord + this.refs.toggleWhole.element.classList.toggle('selected') + } + + toggleError (show) { + let el = this.refs.errorMessage + show ? el.classList.remove('hidden') : el.classList.add('hidden') + etch.update(this) + } + + find (next) { + + let text = this.editor.getText() + if (this.useRegex) { + let msg = null + try { + new RegExp(text) + } catch (err) { + msg = err.message + } + + if (msg !== null) { + this.errorMessage = msg + this.toggleError(true) + this.blinkRed() + return false + } + } + this.toggleError(false) + + let found + if (next) { + found = this.terminal.findNext(text, { + regex: this.useRegex, + wholeWord: this.wholeWord, + caseSensitive: this.matchCase, + incremental: false + }) + } else { + found = this.terminal.findPrevious(text, { + regex: this.useRegex, + wholeWord: this.wholeWord, + caseSensitive: this.matchCase + }) + } + + if (!found) this.blinkRed() + } + + blinkRed () { + this.element.classList.add('nothingfound') + setTimeout(() => this.element.classList.remove('nothingfound'), 200) + } + + render () { + return