diff --git a/README.md b/README.md index 39e60ee7..09752c2c 100644 --- a/README.md +++ b/README.md @@ -81,6 +81,21 @@ text editor or on a terminal. Finally, terminal tabs are automatically reopened at the spot you placed them when you last exited Atom. +## Active Terminal + +The active terminal is the terminal that will be used when sending commands to +the terminal with commands like `x-terminal:insert-selected-text` and +`x-terminal:run-selected-text` + +The active terminal will always have an astrix (`*`) in front of the title. +By default when a terminal is hidden it becomes inactive and the last used +visible terminal will become active. If there are no visible terminals none are +active. + +The `Allow Hidden Terminal To Stay Active` setting will change the +default behavior and keep a terminal that is hidden active until another +terminal is focused. + ## Organizing Terminals To quickly organize your terminal tabs, simply use the main menu. You can also diff --git a/keymaps/x-terminal.json b/keymaps/x-terminal.json index d2e91829..fe2e2b6a 100644 --- a/keymaps/x-terminal.json +++ b/keymaps/x-terminal.json @@ -10,7 +10,9 @@ "ctrl-alt-shift-right": "x-terminal:open-split-right", "ctrl-alt-shift-i": "x-terminal:open-split-bottom-dock", "ctrl-alt-shift-u": "x-terminal:open-split-left-dock", - "ctrl-alt-shift-o": "x-terminal:open-split-right-dock" + "ctrl-alt-shift-o": "x-terminal:open-split-right-dock", + "ctrl-alt-r": "x-terminal:run-selected-text", + "ctrl-alt-i": "x-terminal:insert-selected-text" }, ".platform-win32 atom-workspace": { "ctrl-`": "x-terminal:open", @@ -23,7 +25,9 @@ "ctrl-alt-shift-right": "x-terminal:open-split-right", "ctrl-alt-shift-i": "x-terminal:open-split-bottom-dock", "ctrl-alt-shift-u": "x-terminal:open-split-left-dock", - "ctrl-alt-shift-o": "x-terminal:open-split-right-dock" + "ctrl-alt-shift-o": "x-terminal:open-split-right-dock", + "ctrl-alt-r": "x-terminal:run-selected-text", + "ctrl-alt-i": "x-terminal:insert-selected-text" }, ".platform-darwin atom-workspace": { "cmd-`": "x-terminal:open", @@ -36,7 +40,9 @@ "cmd-alt-shift-right": "x-terminal:open-split-right", "cmd-alt-shift-i": "x-terminal:open-split-bottom-dock", "cmd-alt-shift-u": "x-terminal:open-split-left-dock", - "cmd-alt-shift-o": "x-terminal:open-split-right-dock" + "cmd-alt-shift-o": "x-terminal:open-split-right-dock", + "cmd-alt-r": "x-terminal:run-selected-text", + "cmd-alt-i": "x-terminal:insert-selected-text" }, ".platform-linux x-terminal": { "ctrl-insert": "x-terminal:copy", diff --git a/spec/config-spec.js b/spec/config-spec.js index 60f94857..ca868387 100644 --- a/spec/config-spec.js +++ b/spec/config-spec.js @@ -323,6 +323,12 @@ describe('Call to colorBrightWhite()', () => { }) }) +describe('Call to allowHiddenToStayActive()', () => { + it('return false', () => { + expect(configDefaults.allowHiddenToStayActive).toBe(false) + }) +}) + describe('Call to leaveOpenAfterExit()', () => { it('return true', () => { expect(configDefaults.leaveOpenAfterExit).toBe(true) diff --git a/spec/element-spec.js b/spec/element-spec.js index 5084669c..845fde15 100644 --- a/spec/element-spec.js +++ b/spec/element-spec.js @@ -1787,7 +1787,9 @@ describe('XTerminalElement', () => { it('focusOnTerminal()', () => { spyOn(this.element.terminal, 'focus') + spyOn(this.element.model, 'setActive') this.element.focusOnTerminal() + expect(this.element.model.setActive).toHaveBeenCalled() expect(this.element.terminal.focus).toHaveBeenCalled() }) diff --git a/spec/model-spec.js b/spec/model-spec.js index f052b982..2a7655a4 100644 --- a/spec/model-spec.js +++ b/spec/model-spec.js @@ -243,6 +243,11 @@ describe('XTerminalModel', () => { expect(this.model.getTitle()).toBe(expected) }) + it('getTitle() when active', () => { + spyOn(this.model, 'isActiveTerminal').and.returnValue(true) + expect(this.model.getTitle()).toBe('* X Terminal') + }) + it('getElement()', () => { const expected = { somekey: 'somevalue' } this.model.element = expected @@ -472,17 +477,112 @@ describe('XTerminalModel', () => { expect(this.model.element.ptyProcess.write.calls.allArgs()).toEqual([[expectedText]]) }) - it('setNewPane(event)', async () => { + it('setActive()', async function () { + const pane = atom.workspace.getCenter().getActivePane() const uri = 'x-terminal://somesessionid/' const terminalsSet = new Set() - const model = new XTerminalModel({ + const model1 = new XTerminalModel({ uri: uri, terminals_set: terminalsSet, }) - await model.initializedPromise - const expected = {} - model.setNewPane(expected) - expect(model.pane).toBe(expected) + await model1.initializedPromise + pane.addItem(model1) + model1.setNewPane(pane) + const model2 = new XTerminalModel({ + uri: uri, + terminals_set: terminalsSet, + }) + await model2.initializedPromise + pane.addItem(model2) + model2.setNewPane(pane) + expect(model1.activeIndex).toBe(0) + expect(model2.activeIndex).toBe(1) + model2.setActive() + expect(model1.activeIndex).toBe(1) + expect(model2.activeIndex).toBe(0) + }) + + describe('setNewPane', () => { + it('(mock)', async () => { + const expected = { getContainer: () => ({ getLocation: () => {} }) } + this.model.setNewPane(expected) + expect(this.model.pane).toBe(expected) + expect(this.model.dock).toBe(null) + }) + + it('(center)', async () => { + const pane = atom.workspace.getCenter().getActivePane() + this.model.setNewPane(pane) + expect(this.model.pane).toBe(pane) + expect(this.model.dock).toBe(null) + }) + + it('(left)', async () => { + const dock = atom.workspace.getLeftDock() + const pane = dock.getActivePane() + this.model.setNewPane(pane) + expect(this.model.pane).toBe(pane) + expect(this.model.dock).toBe(dock) + }) + + it('(right)', async () => { + const dock = atom.workspace.getRightDock() + const pane = dock.getActivePane() + this.model.setNewPane(pane) + expect(this.model.pane).toBe(pane) + expect(this.model.dock).toBe(dock) + }) + + it('(bottom)', async () => { + const dock = atom.workspace.getBottomDock() + const pane = dock.getActivePane() + this.model.setNewPane(pane) + expect(this.model.pane).toBe(pane) + expect(this.model.dock).toBe(dock) + }) + }) + + it('isVisible() in pane', () => { + const pane = atom.workspace.getCenter().getActivePane() + this.model.setNewPane(pane) + expect(this.model.isVisible()).toBe(false) + pane.setActiveItem(this.model) + expect(this.model.isVisible()).toBe(true) + }) + + it('isVisible() in dock', () => { + const dock = atom.workspace.getBottomDock() + const pane = dock.getActivePane() + this.model.setNewPane(pane) + pane.setActiveItem(this.model) + expect(this.model.isVisible()).toBe(false) + dock.show() + expect(this.model.isVisible()).toBe(true) + }) + + it('isActiveTerminal() visible and active', () => { + this.model.activeIndex = 0 + spyOn(this.model, 'isVisible').and.returnValue(true) + expect(this.model.isActiveTerminal()).toBe(true) + }) + + it('isActiveTerminal() visible and not active', () => { + this.model.activeIndex = 1 + spyOn(this.model, 'isVisible').and.returnValue(true) + expect(this.model.isActiveTerminal()).toBe(false) + }) + + it('isActiveTerminal() invisible and active', () => { + this.model.activeIndex = 0 + spyOn(this.model, 'isVisible').and.returnValue(false) + expect(this.model.isActiveTerminal()).toBe(false) + }) + + it('isActiveTerminal() allowHiddenToStayActive', () => { + atom.config.set('x-terminal.terminalSettings.allowHiddenToStayActive', true) + this.model.activeIndex = 0 + spyOn(this.model, 'isVisible').and.returnValue(false) + expect(this.model.isActiveTerminal()).toBe(true) }) it('toggleProfileMenu()', () => { diff --git a/spec/utils-spec.js b/spec/utils-spec.js index fb03a43c..e7176ef0 100644 --- a/spec/utils-spec.js +++ b/spec/utils-spec.js @@ -43,4 +43,67 @@ describe('Utilities', () => { expect(hLine.classList.contains('x-terminal-profile-menu-element-hline')).toBe(true) expect(hLine.textContent).toBe('.') }) + + describe('recalculateActive()', () => { + const createTerminals = (num = 1) => { + const terminals = [] + for (let i = 0; i < num; i++) { + terminals.push({ + activeIndex: i, + isVisible () {}, + emitter: { + emit () {}, + }, + }) + } + return terminals + } + + it('active first', () => { + const terminals = createTerminals(2) + const terminalsSet = new Set(terminals) + utils.recalculateActive(terminalsSet, terminals[1]) + expect(terminals[0].activeIndex).toBe(1) + expect(terminals[1].activeIndex).toBe(0) + }) + + it('visible before hidden', () => { + const terminals = createTerminals(2) + const terminalsSet = new Set(terminals) + spyOn(terminals[1], 'isVisible').and.returnValue(true) + utils.recalculateActive(terminalsSet) + expect(terminals[0].activeIndex).toBe(1) + expect(terminals[1].activeIndex).toBe(0) + }) + + it('allowHiddenToStayActive', () => { + atom.config.set('x-terminal.terminalSettings.allowHiddenToStayActive', true) + const terminals = createTerminals(2) + const terminalsSet = new Set(terminals) + spyOn(terminals[1], 'isVisible').and.returnValue(true) + utils.recalculateActive(terminalsSet) + expect(terminals[0].activeIndex).toBe(0) + expect(terminals[1].activeIndex).toBe(1) + }) + + it('lower active index first', () => { + const terminals = createTerminals(2) + const terminalsSet = new Set(terminals) + terminals[0].activeIndex = 1 + terminals[1].activeIndex = 0 + utils.recalculateActive(terminalsSet) + expect(terminals[0].activeIndex).toBe(1) + expect(terminals[1].activeIndex).toBe(0) + }) + + it('emit did-change-title', () => { + const terminals = createTerminals(2) + const terminalsSet = new Set(terminals) + spyOn(terminals[0].emitter, 'emit') + spyOn(terminals[1].emitter, 'emit') + utils.recalculateActive(terminalsSet) + expect(terminals[0].emitter.emit).toHaveBeenCalledWith('did-change-title') + expect(terminals[1].emitter.emit).toHaveBeenCalledWith('did-change-title') + }) + }) }) diff --git a/spec/x-terminal-spec.js b/spec/x-terminal-spec.js new file mode 100644 index 00000000..6f12b93a --- /dev/null +++ b/spec/x-terminal-spec.js @@ -0,0 +1,48 @@ +/** @babel */ + +import * as xTerminal from '../src/x-terminal' + +const xTerminalInstance = xTerminal.getInstance() + +describe('x-terminal', () => { + describe('getSelectedText()', () => { + it('returns selection', () => { + spyOn(atom.workspace, 'getActiveTextEditor').and.returnValue({ + getSelectedText () { + return 'selection' + }, + }) + const selection = xTerminalInstance.getSelectedText() + expect(selection).toBe('selection') + }) + + it('returns removes newlines at the end', () => { + spyOn(atom.workspace, 'getActiveTextEditor').and.returnValue({ + getSelectedText () { + return 'line1\r\nline2\r\n' + }, + }) + const selection = xTerminalInstance.getSelectedText() + expect(selection).toBe('line1\r\nline2') + }) + + it('returns entire line if nothing selected and moves down', () => { + const moveDown = jasmine.createSpy('moveDown') + spyOn(atom.workspace, 'getActiveTextEditor').and.returnValue({ + getSelectedText () { + return '' + }, + getCursorBufferPosition () { + return { row: 1, column: 1 } + }, + lineTextForBufferRow (row) { + return `line${row}` + }, + moveDown, + }) + const selection = xTerminalInstance.getSelectedText() + expect(selection).toBe('line1') + expect(moveDown).toHaveBeenCalledWith(1) + }) + }) +}) diff --git a/src/config.js b/src/config.js index e9123ac9..7db3cd00 100644 --- a/src/config.js +++ b/src/config.js @@ -62,6 +62,7 @@ export function resetConfigDefaults () { colorBrightMagenta: '#ad7fa8', colorBrightCyan: '#34e2e2', colorBrightWhite: '#eeeeec', + allowHiddenToStayActive: false, leaveOpenAfterExit: true, allowRelaunchingTerminalsOnStartup: true, relaunchTerminalOnStartup: true, @@ -384,6 +385,12 @@ export const config = configOrder({ toMenuSetting: (val) => val, }, }, + allowHiddenToStayActive: { + title: 'Allow Hidden Terminal To Stay Active', + description: 'When an active terminal is hidden keep it active until another terminal is focused.', + type: 'boolean', + default: configDefaults.allowHiddenToStayActive, + }, leaveOpenAfterExit: { title: 'Leave Open After Exit', description: 'Whether to leave terminal emulators open after their shell processes have exited.', diff --git a/src/element.js b/src/element.js index c4e7f9fc..eff51e23 100644 --- a/src/element.js +++ b/src/element.js @@ -704,6 +704,7 @@ class XTerminalElementImpl extends HTMLElement { focusOnTerminal () { if (this.terminal) { + this.model.setActive() this.terminal.focus() } } diff --git a/src/model.js b/src/model.js index 908ca96c..7841e4da 100644 --- a/src/model.js +++ b/src/model.js @@ -21,6 +21,7 @@ import { Emitter } from 'atom' +import { recalculateActive } from './utils' import { XTerminalProfilesSingleton } from './profiles' import fs from 'fs-extra' @@ -48,6 +49,7 @@ class XTerminalModel { this.profilesSingleton = XTerminalProfilesSingleton.instance this.profile = this.profilesSingleton.createProfileDataFromUri(this.uri) this.terminals_set = this.options.terminals_set + this.activeIndex = this.terminals_set.size this.element = null this.pane = null this.title = DEFAULT_TITLE @@ -125,7 +127,8 @@ class XTerminalModel { } getTitle () { - return this.title + return (this.isActiveTerminal() ? '* ' : '') + this.title + // return this.activeIndex + '|' + this.title } getElement () { @@ -230,8 +233,34 @@ class XTerminalModel { this.element.ptyProcess.write(text) } + setActive () { + recalculateActive(this.terminals_set, this) + } + + isVisible () { + return this.pane && this.pane.getActiveItem() === this && (!this.dock || this.dock.isVisible()) + } + + isActiveTerminal () { + return this.activeIndex === 0 && (atom.config.get('x-terminal.terminalSettings.allowHiddenToStayActive') || this.isVisible()) + } + setNewPane (pane) { this.pane = pane + const location = this.pane.getContainer().getLocation() + switch (location) { + case 'left': + this.dock = atom.workspace.getLeftDock() + break + case 'right': + this.dock = atom.workspace.getRightDock() + break + case 'bottom': + this.dock = atom.workspace.getBottomDock() + break + default: + this.dock = null + } } toggleProfileMenu () { diff --git a/src/utils.js b/src/utils.js index 74b757d8..dead996c 100644 --- a/src/utils.js +++ b/src/utils.js @@ -31,3 +31,32 @@ export function createHorizontalLine () { hLine.appendChild(document.createTextNode('.')) return hLine } + +export function recalculateActive (terminalsSet, active) { + const allowHidden = atom.config.get('x-terminal.terminalSettings.allowHiddenToStayActive') + const terminals = [...terminalsSet] + terminals.sort((a, b) => { + // active before other + if (active && a === active) { + return -1 + } + if (active && b === active) { + return 1 + } + if (!allowHidden) { + // visible before hidden + if (a.isVisible() && !b.isVisible()) { + return -1 + } + if (!a.isVisible() && b.isVisible()) { + return 1 + } + } + // lower activeIndex before higher activeIndex + return a.activeIndex - b.activeIndex + }) + terminals.forEach((t, i) => { + t.activeIndex = i + t.emitter.emit('did-change-title') + }) +} diff --git a/src/x-terminal.js b/src/x-terminal.js index 8aed0116..83d4206c 100644 --- a/src/x-terminal.js +++ b/src/x-terminal.js @@ -23,6 +23,7 @@ import { CompositeDisposable } from 'atom' import { CONFIG_DATA } from './config' +import { recalculateActive } from './utils' import { XTerminalElement } from './element' import { XTerminalModel, isXTerminalModel } from './model' import { X_TERMINAL_BASE_URI, XTerminalProfilesSingleton } from './profiles' @@ -74,110 +75,145 @@ class XTerminalSingleton { })) } - // Register view provider for terminal emulator item. - this.disposables.add(atom.views.addViewProvider(XTerminalModel, (atomXtermModel) => { - const atomXtermElement = new XTerminalElement() - atomXtermElement.initialize(atomXtermModel) - return atomXtermElement - })) - - // Register view provider for terminal emulator profile menu item. - this.disposables.add(atom.views.addViewProvider(XTerminalProfileMenuModel, (atomXtermProfileMenuModel) => { - const atomXtermProfileMenuElement = new XTerminalProfileMenuElement() - atomXtermProfileMenuElement.initialize(atomXtermProfileMenuModel) - return atomXtermProfileMenuElement - })) - - // Register view profile for modal items. - this.disposables.add(atom.views.addViewProvider(XTerminalDeleteProfileModel, (atomXtermDeleteProfileModel) => { - const atomXtermDeleteProfileElement = new XTerminalDeleteProfileElement() - atomXtermDeleteProfileElement.initialize(atomXtermDeleteProfileModel) - return atomXtermDeleteProfileElement - })) - this.disposables.add(atom.views.addViewProvider(XTerminalOverwriteProfileModel, (atomXtermOverwriteProfileModel) => { - const atomXtermOverwriteProfileElement = new XTerminalOverwriteProfileElement() - atomXtermOverwriteProfileElement.initialize(atomXtermOverwriteProfileModel) - return atomXtermOverwriteProfileElement - })) - this.disposables.add(atom.views.addViewProvider(XTerminalSaveProfileModel, (atomXtermSaveProfileModel) => { - const atomXtermSaveProfileElement = new XTerminalSaveProfileElement() - atomXtermSaveProfileElement.initialize(atomXtermSaveProfileModel) - return atomXtermSaveProfileElement - })) - - // Add opener for terminal emulator item. - this.disposables.add(atom.workspace.addOpener((uri) => { - if (uri.startsWith(X_TERMINAL_BASE_URI)) { - const item = new XTerminalModel({ - uri: uri, - terminals_set: this.terminals_set, - }) - return item - } - })) - - // Set callback to run on current and future panes. - this.disposables.add(atom.workspace.observePanes((pane) => { - // In callback, set another callback to run on current and future items. - this.disposables.add(pane.observeItems((item) => { - // In callback, set current pane for terminal items. + this.disposables.add( + // Register view provider for terminal emulator item. + atom.views.addViewProvider(XTerminalModel, (atomXtermModel) => { + const atomXtermElement = new XTerminalElement() + atomXtermElement.initialize(atomXtermModel) + return atomXtermElement + }), + // Register view provider for terminal emulator profile menu item. + atom.views.addViewProvider(XTerminalProfileMenuModel, (atomXtermProfileMenuModel) => { + const atomXtermProfileMenuElement = new XTerminalProfileMenuElement() + atomXtermProfileMenuElement.initialize(atomXtermProfileMenuModel) + return atomXtermProfileMenuElement + }), + // Register view profile for modal items. + atom.views.addViewProvider(XTerminalDeleteProfileModel, (atomXtermDeleteProfileModel) => { + const atomXtermDeleteProfileElement = new XTerminalDeleteProfileElement() + atomXtermDeleteProfileElement.initialize(atomXtermDeleteProfileModel) + return atomXtermDeleteProfileElement + }), + atom.views.addViewProvider(XTerminalOverwriteProfileModel, (atomXtermOverwriteProfileModel) => { + const atomXtermOverwriteProfileElement = new XTerminalOverwriteProfileElement() + atomXtermOverwriteProfileElement.initialize(atomXtermOverwriteProfileModel) + return atomXtermOverwriteProfileElement + }), + atom.views.addViewProvider(XTerminalSaveProfileModel, (atomXtermSaveProfileModel) => { + const atomXtermSaveProfileElement = new XTerminalSaveProfileElement() + atomXtermSaveProfileElement.initialize(atomXtermSaveProfileModel) + return atomXtermSaveProfileElement + }), + + // Add opener for terminal emulator item. + atom.workspace.addOpener((uri) => { + if (uri.startsWith(X_TERMINAL_BASE_URI)) { + const item = new XTerminalModel({ + uri: uri, + terminals_set: this.terminals_set, + }) + return item + } + }), + + // Set callback to run on current and future panes. + atom.workspace.observePanes((pane) => { + // In callback, set another callback to run on current and future items. + this.disposables.add(pane.observeItems((item) => { + // In callback, set current pane for terminal items. + if (isXTerminalModel(item)) { + item.setNewPane(pane) + } + recalculateActive(this.terminals_set) + })) + recalculateActive(this.terminals_set) + }), + + // Add callbacks to run for current and future active items on active panes. + atom.workspace.observeActivePaneItem((item) => { + // In callback, focus specifically on terminal when item is terminal item. if (isXTerminalModel(item)) { - item.setNewPane(pane) + item.focusOnTerminal() } - })) - })) - - // Add callbacks to run for current and future active items on active panes. - this.disposables.add(atom.workspace.observeActivePaneItem((item) => { - // In callback, focus specifically on terminal when item is terminal item. - if (isXTerminalModel(item)) { - item.focusOnTerminal() - } - })) - - // Add commands. - this.disposables.add(atom.commands.add('atom-workspace', { - 'x-terminal:open': () => this.open( - this.profilesSingleton.generateNewUri(), - this.addDefaultPosition(), - ), - 'x-terminal:open-center': () => this.openInCenterOrDock(atom.workspace), - 'x-terminal:open-split-up': () => this.open( - this.profilesSingleton.generateNewUri(), - { split: 'up' }, - ), - 'x-terminal:open-split-down': () => this.open( - this.profilesSingleton.generateNewUri(), - { split: 'down' }, - ), - 'x-terminal:open-split-left': () => this.open( - this.profilesSingleton.generateNewUri(), - { split: 'left' }, - ), - 'x-terminal:open-split-right': () => this.open( - this.profilesSingleton.generateNewUri(), - { split: 'right' }, - ), - 'x-terminal:open-split-bottom-dock': () => this.openInCenterOrDock(atom.workspace.getBottomDock()), - 'x-terminal:open-split-left-dock': () => this.openInCenterOrDock(atom.workspace.getLeftDock()), - 'x-terminal:open-split-right-dock': () => this.openInCenterOrDock(atom.workspace.getRightDock()), - 'x-terminal:toggle-profile-menu': () => this.toggleProfileMenu(), - 'x-terminal:reorganize': () => this.reorganize('current'), - 'x-terminal:reorganize-top': () => this.reorganize('top'), - 'x-terminal:reorganize-bottom': () => this.reorganize('bottom'), - 'x-terminal:reorganize-left': () => this.reorganize('left'), - 'x-terminal:reorganize-right': () => this.reorganize('right'), - 'x-terminal:reorganize-bottom-dock': () => this.reorganize('bottom-dock'), - 'x-terminal:reorganize-left-dock': () => this.reorganize('left-dock'), - 'x-terminal:reorganize-right-dock': () => this.reorganize('right-dock'), - 'x-terminal:close-all': () => this.exitAllTerminals(), - })) - this.disposables.add(atom.commands.add('x-terminal', { - 'x-terminal:close': () => this.close(), - 'x-terminal:restart': () => this.restart(), - 'x-terminal:copy': () => this.copy(), - 'x-terminal:paste': () => this.paste(), - })) + recalculateActive(this.terminals_set) + }), + + atom.workspace.getRightDock().observeVisible((visible) => { + if (visible) { + const item = atom.workspace.getRightDock().getActivePaneItem() + if (isXTerminalModel(item)) { + item.focusOnTerminal() + } + } + recalculateActive(this.terminals_set) + }), + + atom.workspace.getLeftDock().observeVisible((visible) => { + if (visible) { + const item = atom.workspace.getLeftDock().getActivePaneItem() + if (isXTerminalModel(item)) { + item.focusOnTerminal() + } + } + recalculateActive(this.terminals_set) + }), + + atom.workspace.getBottomDock().observeVisible((visible) => { + if (visible) { + const item = atom.workspace.getBottomDock().getActivePaneItem() + if (isXTerminalModel(item)) { + item.focusOnTerminal() + } + } + recalculateActive(this.terminals_set) + }), + + // Add commands. + atom.commands.add('atom-workspace', { + 'x-terminal:open': () => this.open( + this.profilesSingleton.generateNewUri(), + this.addDefaultPosition(), + ), + 'x-terminal:open-center': () => this.openInCenterOrDock(atom.workspace), + 'x-terminal:open-split-up': () => this.open( + this.profilesSingleton.generateNewUri(), + { split: 'up' }, + ), + 'x-terminal:open-split-down': () => this.open( + this.profilesSingleton.generateNewUri(), + { split: 'down' }, + ), + 'x-terminal:open-split-left': () => this.open( + this.profilesSingleton.generateNewUri(), + { split: 'left' }, + ), + 'x-terminal:open-split-right': () => this.open( + this.profilesSingleton.generateNewUri(), + { split: 'right' }, + ), + 'x-terminal:open-split-bottom-dock': () => this.openInCenterOrDock(atom.workspace.getBottomDock()), + 'x-terminal:open-split-left-dock': () => this.openInCenterOrDock(atom.workspace.getLeftDock()), + 'x-terminal:open-split-right-dock': () => this.openInCenterOrDock(atom.workspace.getRightDock()), + 'x-terminal:toggle-profile-menu': () => this.toggleProfileMenu(), + 'x-terminal:reorganize': () => this.reorganize('current'), + 'x-terminal:reorganize-top': () => this.reorganize('top'), + 'x-terminal:reorganize-bottom': () => this.reorganize('bottom'), + 'x-terminal:reorganize-left': () => this.reorganize('left'), + 'x-terminal:reorganize-right': () => this.reorganize('right'), + 'x-terminal:reorganize-bottom-dock': () => this.reorganize('bottom-dock'), + 'x-terminal:reorganize-left-dock': () => this.reorganize('left-dock'), + 'x-terminal:reorganize-right-dock': () => this.reorganize('right-dock'), + 'x-terminal:close-all': () => this.exitAllTerminals(), + 'x-terminal:insert-selected-text': () => this.insertSelection(), + 'x-terminal:run-selected-text': () => this.runSelection(), + }), + atom.commands.add('x-terminal', { + 'x-terminal:close': () => this.close(), + 'x-terminal:restart': () => this.restart(), + 'x-terminal:copy': () => this.copy(), + 'x-terminal:paste': () => this.paste(), + }), + ) } deactivate () { @@ -236,6 +272,49 @@ class XTerminalSingleton { } } + getSelectedText () { + const editor = atom.workspace.getActiveTextEditor() + if (!editor) { + return '' + } + + let selectedText = '' + const selection = editor.getSelectedText() + if (selection) { + selectedText = selection.replace(/[\r\n]+$/, '') + } else { + const cursor = editor.getCursorBufferPosition() + if (cursor) { + const line = editor.lineTextForBufferRow(cursor.row) + selectedText = line + editor.moveDown(1) + } + } + + return selectedText + } + + getActiveTerminal () { + const terminals = [...this.terminals_set] + return terminals.find(t => t.isActiveTerminal()) + } + + insertSelection () { + const selection = this.getSelectedText() + const terminal = this.getActiveTerminal() + if (selection && terminal) { + terminal.pasteToTerminal(selection) + } + } + + runSelection () { + const selection = this.getSelectedText() + const terminal = this.getActiveTerminal() + if (selection && terminal) { + terminal.runCommand(selection) + } + } + async open (uri, options = {}) { const url = new URL(uri) let relaunchTerminalOnStartup = url.searchParams.get('relaunchTerminalOnStartup') @@ -491,6 +570,10 @@ class XTerminalSingleton { export { config } from './config' +export function getInstance () { + return XTerminalSingleton.instance +} + export function activate (state) { return XTerminalSingleton.instance.activate(state) }