├── .gitignore
├── CHANGELOG.md
├── LICENSE.md
├── README.md
├── keymaps
└── terminal-plus.cson
├── lib
├── config-schema.coffee
├── core.coffee
├── dialog.coffee
├── fork.coffee
├── input-dialog.coffee
├── panel-view.coffee
├── rename-dialog.coffee
├── shell.coffee
├── status-bar.coffee
├── status-icon.coffee
├── tab-view.coffee
├── terminal-plus.coffee
├── terminal-view.coffee
└── terminal.coffee
├── menus
└── terminal-plus.cson
├── package.json
├── resources
├── demo copy.gif
├── demo.gif
├── full_screen_demo.png
├── insert_selected_text.gif
├── insert_text.png
├── map_terminals_to_auto_open.gif
├── map_terminals_to_file.gif
├── map_terminals_to_folder.gif
├── plus-icon.png
├── red-x.png
├── sorting.gif
├── special_keys.gif
├── status-bar.png
├── status-icon.png
├── status-icon_color_coding.png
├── status-icon_rename-dialog.png
├── status-icon_rename.png
├── terminal_title.png
└── terminal_with_status-bar.png
├── spec
├── terminal-plus-spec.coffee
└── terminal-plus-view-spec.coffee
└── styles
├── terminal-plus.less
├── themes.less
└── themes
├── dracula.less
├── grass.less
├── homebrew.less
├── inverse.less
├── man-page.less
├── novel.less
├── ocean.less
├── pro.less
├── red-sands.less
├── red.less
├── silver-aerogel.less
├── solid-colors.less
└── standard.less
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | npm-debug.log
3 | node_modules
4 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ## v0.14.5 - Patch
2 | * Fix key-presses deselecting lines from the terminal
3 | * Add alt-(arrow) and alt-(click) for moving cursor in terminal
4 | * Improve `clear` command handling
5 | * Make sure initial height is to the nearest row
6 |
7 | ## v0.14.4 - Patch
8 | * Fix `reset` and `clear` command issues
9 | * Improve `cmd-k` clear command
10 | * Insert text as a command by default
11 | * Match text editor font family for consistency
12 | * Add font size setting back
13 | * Destroy panel on terminal destroy
14 | * Allow any css value for default panel height
15 |
16 | ## v0.14.3 - Patch
17 | * Remove extra logs
18 | * Bug fixes
19 | * Match Atom's font size
20 |
21 | ## v0.14.2 - Patch
22 | * Bring back default selection process
23 | * Improvements to terminal (jeremyramin/term.js)
24 | * Fix several bugs
25 | * Clean up menus
26 |
27 | ## v0.14.1 - Patch
28 | * Recompile binaries for Atom 1.2.0
29 | * Fix focus error
30 | * Return focus to terminal on window focus
31 | * Minor fixes to text area and terminal resizing
32 |
33 | ## v0.14.0 - Beta Release
34 | * Added support for non-English characters
35 | * Thanks to @yoshiokatsuneo for [his work](https://github.com/jeremyramin/term.js/commit/e851ea232a114902ea6a8e5cc8f7d34d07969c42).
36 | * Dead keys now work in the terminal
37 | * CJK (IME) should now work in the terminal
38 | * Keep terminals maximized on new terminal view
39 | * Improve terminal title handling
40 | * Fix fullscreen not focusing
41 | * Refactor dialog classes
42 | * Update Windows binaries
43 |
44 | ## v0.13.1 - Patch
45 | * Only run inserted text if `Run Inserted Text` is enabled
46 | * Improve terminal title handling
47 | * Make sure the terminal title does not stay behind on new title
48 | * Open the correct project folder for the active file
49 | * Post-install clean-up script for pty.js (see issue #71)
50 |
51 | ## v0.13.0 - Beta Release
52 | * Add support for alt+key combinations
53 | * Right alt for escape sequences
54 | * Fix terminal mapping toggle error when `Auto Open a New Terminal` is false
55 | * Improve cursor:
56 | * Preserve the color of the character the cursor is over
57 | * Fix terminal cursor background-color being overwritten
58 | * Only intercept ctrl+key combinations if ctrl is the only key being pressed
59 |
60 | ## v0.12.5 - Patch
61 | * Fix `ctrl` key intercepting to ignore `ctrl-shift` combinations
62 | * Fix `ctrl-s` pausing terminal on Linux systems
63 |
64 | ## v0.12.4 - Patch
65 | * Map terminals while the active terminal is hidden
66 | * Fix prev/next terminal switch
67 | * Improve cursor blink animation
68 | * New commands:
69 | * terminal-plus:close-all for closing all terminals
70 | * terminal-plus:rename for renaming the active terminal
71 |
72 | ## v0.12.3 - Patch
73 | * Blur terminal window on window blur
74 | * Adjust default ANSI colors for terminal
75 | * Confirm that the active terminal is not null
76 |
77 | ## v0.12.2 - Patch
78 | * Prevent terminal from intercepting alt+(key) events
79 | * Fixes broken copy and paste on Linux and Windows
80 |
81 | ## v0.12.1 - Patch
82 | * Make sure status icon tooltip dismisses when the status icon is detached
83 | * Fix copy and pasting bug with tabs
84 | * Improve active terminal system
85 | * Fix terminal resizing removing lines
86 |
87 | ## v0.12.0 - Beta Release
88 | * Clean up tooltip
89 | * Prevent file path insertion for empty file paths (Atom tabs)
90 | * Add experimental support for tab view
91 |
92 | ## v0.11.2 - Patch
93 | * Fix tooltips staying after the terminal has been closed
94 |
95 | ## v0.11.1 - Patch
96 | * Fix broken links in README
97 |
98 | ## v0.11.0 - Beta Release
99 | * Add insert text dialog for inserting special characters and running commands
100 | * Users can enable `Run Inserted Text` in the settings to have Terminal-Plus run inserted text as a command
101 | * Users can use the insert text dialog to type special characters
102 | * Center terminal lines in the terminal-view
103 | * Improved terminal mapping
104 | * Improve terminal view focusing
105 | * Do not steal focus for the cursor blink
106 | * Do not steal focus for text input
107 |
108 | ## v0.10.1 - Patch
109 | * Fix resizing bug
110 | * Fix language overwrite bug
111 |
112 | ## v0.10.0 - Beta Release
113 | * Added automatic terminal switching
114 | * Add CMD+K to clear terminal [Term.js fork]
115 | * Fix terminal errors relating to Atom setting project path to `atom://config`
116 |
117 | ## v0.9.1 - Patch
118 | * Fix bug where Atom rebuilds Terminal-Plus for every update
119 | * Fix status icon colors keypath
120 |
121 | ## v0.9.0 - Beta Release
122 | * Add support for custom ANSI color set
123 | * Fix `ctrl+c` (SIGINT) not working in bash
124 | * Update winpty module (for Windows) in pty.js
125 | * Fix issues with maintaining focus on the terminal
126 |
127 | ## v0.8.2 - Patch
128 | * Detect system language on OS X
129 | * Even finer scrolling algorithm implemented
130 |
131 | ## v0.8.1 - Patch
132 | * Disable double click on status icons
133 |
134 | ## v0.8.0 - Beta Release
135 | * Implement finer scrolling in dependencies
136 |
137 | ## v0.7.1 - Patch
138 | * Block resize and input when there is no pty process to message
139 |
140 | ## v0.7.0 - Beta Release
141 | * Add support for international characters
142 | * Make sure to declare the terminal as xterm-256color
143 | * Improve colors in xterm-256color
144 | * Set TERM_PROGRAM to Terminal-Plus
145 |
146 | ## v0.6.5 - Patch
147 | * Focus bug fix
148 |
149 | ## v0.6.4 - Patch
150 | * Fix terminal not scrolling for zsh shell with plugins
151 |
152 | ## v0.6.3 - Patch
153 | * Call super after overriding focus
154 | * Update the author's note with Windows 10 fix
155 |
156 | ## v0.6.2 - Patch
157 | * Fix path variable overwrite bug
158 |
159 | ## v0.6.1 - Patch
160 | * Fix text-wrap overflow hiding prompt
161 |
162 | ## v0.6.0 - Beta Release
163 | * Dynamic terminal view resizing
164 |
165 | ## v0.5.1 - Patch
166 | * Remove trailing whitespace from terminal rename
167 |
168 | ## v0.5.0 - Beta Release
169 | * Add terminal naming via the status icon
170 |
171 | ## v0.4.3 - Patch
172 | * Rebuild pty.js binaries for electron release 0.30.6
173 | * Requires Atom >= 1.0.12
174 |
175 | ## v0.4.2 - Patch
176 | * Specify commit for pty.js prebuilt
177 |
178 | ## v0.4.1 - Patch
179 | * Make button toolbar smaller by keeping buttons minimal
180 | * No more names next to button
181 | * Make button fit to icon
182 | * Use --login shell argument by default for bash and zsh
183 |
184 | ## v0.4.0 - Beta Release
185 | * Add prebuilt binaries for pty.js
186 | * Better support for systems without the tools needed to compile (Windows)
187 |
188 | ## v0.3.1 - Patch
189 | * Add warning for custom font family (must use monospaced font)
190 |
191 | ## v0.3.0 - Beta Release
192 | * Refactor resizing to snap to row
193 | * Fix cursor line being removed if blank
194 | * Possible fix for refresh error
195 | * Fix for improper resizing when displaying the terminal for the first time
196 |
197 | ## v0.2.0 - Beta Release
198 | * Bump up to minor version 2
199 | * New settings and features added
200 | * Bug fixes listed below in v0.1.x patches
201 |
202 | ## v0.1.10 - Patch
203 | * Add option to auto close terminal on shell process exit
204 |
205 | ## v0.1.9 - Patch
206 | * Add insert selected text (see commit)
207 | * Remove login command
208 |
209 | ## v0.1.8 - Patch
210 | * Remove quiet option from login
211 | * Disable resize and input on terminal exit
212 |
213 | ## v0.1.7 - Patch
214 | * Resize terminal on maximize and minimize
215 | * Fix powershell.exe resolve
216 | * Fix shell launch bugs
217 |
218 | ## v0.1.6 - Patch
219 | * Make sure to properly resize terminal on open
220 |
221 | ## v0.1.5 - Patch
222 | * On shell process exit, disable input to prevent error
223 |
224 | ## v0.1.4 - Patch
225 | * Make terminal scroll to bottom on input
226 | * Don't close the terminal view on process exit
227 |
228 | ## v0.1.3 - Patch
229 | * Add more features to README.md
230 | * Fix issue #1
231 |
232 | ## v0.1.2 - Patch
233 | * Absolute image source paths in README.md
234 | * Update image in color coding section
235 |
236 | ## v0.1.1 - Patch
237 | * Update the README.md and CHANGELOG.md
238 |
239 | ## v0.1.0 - Beta Release
240 | * Initial release
241 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | Copyright (c) 2015 Jeremy Ebneyamin
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining
4 | a copy of this software and associated documentation files (the
5 | "Software"), to deal in the Software without restriction, including
6 | without limitation the rights to use, copy, modify, merge, publish,
7 | distribute, sublicense, and/or sell copies of the Software, and to
8 | permit persons to whom the Software is furnished to do so, subject to
9 | the following conditions:
10 |
11 | The above copyright notice and this permission notice shall be
12 | included in all copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## Author's Note
2 | * Please make sure you are on the [latest version of Atom](https://atom.io/releases) before reporting bugs!
3 | * This package requires that you have the dependencies for node-gyp.
4 | [See node-gyp install instructions](https://github.com/nodejs/node-gyp#installation).
5 | * You must use a monospaced font in order for the spacing to be right.
6 | * Having issues on Windows 10? [Try this](https://github.com/jeremyramin/terminal-plus/issues/15#issuecomment-144618245).
7 |
8 | # Terminal-Plus
9 | Terminal-Plus is a terminal package for Atom, complete with themes and more.
10 |
11 | 
12 |
13 | *[Nucleus Dark UI](https://atom.io/themes/nucleus-dark-ui) with [Atom Material Syntax](https://atom.io/themes/atom-material-syntax) and our Homebrew theme.*
14 |
15 | ## Usage
16 | Terminal-Plus stays in the bottom of your editor while you work.
17 |
18 | ### Status Bar
19 | 
20 | You can keep track of terminal instances via the status bar. Each terminal has a status icon (  ) in the status bar. The (  ) button creates a new terminal, while the (  ) button closes all terminals.
21 |
22 | Click on a status icon to toggle that terminal. Right click the status icon for a list of available commands. From the right-click menu you can color code the status icon as well as hide or close the terminal instance.
23 |
24 | ### Terminal
25 | You can open the last active terminal with the `terminal-plus:toggle` command (Default:`` ctrl-` ``). If no terminal instances are available, then a new one will be created. The same toggle command is used to hide the currently active terminal.
26 |
27 | From there you can begin typing into the terminal. By default the terminal will change directory into the project folder if possible. The default working directory can be changed in the settings to the home directory or to the active file directory.
28 |
29 | [See available commands below](#commands).
30 |
31 | ## Features
32 |
33 | ### Full Terminal
34 | Every terminal is loaded with your system’s default initialization files. This ensures that you have access to the same commands and aliases as you would in your standard terminal.
35 |
36 | ### Themes
37 | The terminal is preloaded with several themes that you can choose from. Not satisfied?
38 | Use the following template in your stylesheet:
39 | ```css
40 | .terminal-plus .xterm {
41 | background-color: ;
42 | color: ;
43 |
44 | ::selection {
45 | background-color: ;
46 | }
47 |
48 | .terminal-cursor {
49 | background-color: ;
50 | }
51 | }
52 | ```
53 |
54 | ### Process Titles
55 | By hovering over the terminal status icon, you can see which command process is currently running in the terminal.
56 |
57 | 
58 |
59 | ### Terminal Naming
60 | Need a faster way to figure out which terminal is which? Name your status icons!
61 |
62 | 
63 |
64 | Available via the status icon context menu.
65 |
66 | 
67 |
68 | ### Color Coding
69 | Color code your status icons!
70 |
71 | 
72 |
73 | The colors are customizable in the settings, however the color names remain the same in the context menu.
74 |
75 | ### Sorting
76 | Organize your open terminal instances by dragging and dropping them.
77 |
78 | 
79 |
80 | ### Resizable
81 | You can resize the view vertically, or just maximize it with the maximize button.
82 |
83 | ### Working Directory
84 | You can set the default working directory for new terminals. By default this will be the project folder.
85 |
86 | ### File Dropping
87 | Dropping a file on the terminal will insert the file path into the input. This works with external files, tabs from the Atom tab-view, and entries from the Atom tree-view.
88 |
89 | ### Insert Selected Text
90 | Insert and run selected text from your text editor by running the `terminal-plus:insert-selected-text` command (`ctrl-enter`).
91 |
92 | 
93 |
94 | If you have text selected, it will insert your selected text into the active terminal and run it.
95 | If you don't have text selected it, will run the text on the line where your cursor is then proceed to the next line.
96 |
97 | ### Quick Command Insert
98 | Quickly insert a command to your active terminal by executing the `terminal-plus:insert-text` command.
99 |
100 | 
101 |
102 | A dialog will pop up asking for the input to insert. If you have the `Run Inserted Text` option enabled in the settings (default is false), Terminal-Plus will automatically run the command for you.
103 |
104 | #### Support for Special Keys
105 | Support for IME, dead keys and other key combinations via the `Insert Text` dialog box. Just click the keyboard button in the top left of the terminal or set up a keymap to the `terminal-plus:insert-text` command.
106 |
107 | 
108 |
109 | Note: Make sure you have the `Run Inserted Command` toggle off otherwise it will run your inserted text.
110 |
111 | ### Map Terminal To
112 | Map your terminals to each file or folder you are working on for automatic terminal switching.
113 |
114 | #### File
115 | 
116 |
117 | #### Folder
118 | 
119 |
120 | Toggling the `Auto Open a New Terminal (For Terminal Mapping)` option will have the mapping create a new terminal automatically for files and folders that don't have a terminal. The toggle is located right under the `Map Terminals To` option.
121 |
122 | 
123 |
124 | ## Install
125 | Ready to install?
126 |
127 | You can install via apm: `apm install terminal-plus`
128 |
129 | Or navigate to the install tab in Atom’s settings view, and search for `terminal-plus`.
130 |
131 | ## Commands
132 | | Command | Action | Default Keybind |
133 | |---------|--------|:-----------------:|
134 | | terminal-plus:new | Create a new terminal instance. | `ctrl-shift-t`
or
`cmd-shift-t` |
135 | | terminal-plus:toggle | Toggle the last active terminal instance.
**Note:** This will create a new terminal if it needs to. | `` ctrl-` ``
(Control + Backtick) |
136 | | terminal-plus:prev | Switch to the terminal left of the last active terminal. | `ctrl-shift-j`
or
`cmd-shift-j` |
137 | | terminal-plus:next | Switch to the terminal right of the last active terminal. | `ctrl-shift-k`
or
`cmd-shift-k` |
138 | | terminal-plus:insert-selected-text | Run the selected text as a command in the active terminal. | `ctrl-enter` |
139 | | terminal-plus:insert-text | Bring up an input box for using IME and special keys. | –––––––––––– |
140 | | terminal-plus:close | Close the active terminal. | `ctrl-shift-x`
or
`cmd-shift-x` |
141 | | terminal-plus:close-all | Close all terminals. | –––––––––––– |
142 | | terminal-plus:rename | Rename the active terminal. | –––––––––––– |
143 |
144 | ## To-Do List
145 | - [ ] Possibly merge dependencies into Terminal-Plus?
146 | - [ ] Add support for dead keys and IME input
147 | - [x] Add support for terminal tabs
148 | - [x] Add support for automatic directory switching
149 | - [x] Fix Atom requesting a rebuild after every update
150 | - [x] Update winpty in pty.js dependency
151 | - [x] Add support for custom ANSI colors in terminal
152 | - [x] Fix `ctrl+c` for bash prompts on OS X and Linux
153 | - [x] Add support for status icon names
154 |
--------------------------------------------------------------------------------
/keymaps/terminal-plus.cson:
--------------------------------------------------------------------------------
1 | '.platform-darwin atom-workspace':
2 | 'cmd-shift-t': 'terminal-plus:new'
3 | 'cmd-shift-j': 'terminal-plus:prev'
4 | 'cmd-shift-k': 'terminal-plus:next'
5 | 'cmd-shift-x': 'terminal-plus:close'
6 | 'ctrl-enter': 'terminal-plus:insert-selected-text'
7 | 'ctrl-`': 'terminal-plus:toggle'
8 |
9 | '.platform-linux atom-workspace, .platform-win32 atom-workspace':
10 | 'alt-shift-t': 'terminal-plus:new'
11 | 'alt-shift-j': 'terminal-plus:prev'
12 | 'alt-shift-k': 'terminal-plus:next'
13 | 'alt-shift-x': 'terminal-plus:close'
14 | 'ctrl-enter': 'terminal-plus:insert-selected-text'
15 | 'ctrl-`': 'terminal-plus:toggle'
16 |
17 | '.platform-darwin .terminal-plus .terminal':
18 | 'cmd-c': 'terminal-plus:copy'
19 | 'cmd-v': 'terminal-plus:paste'
20 |
21 | '.platform-linux .terminal-plus .terminal, .platform-win32 .terminal-plus .terminal':
22 | 'alt-v': 'terminal-plus:paste'
23 | 'alt-c': 'terminal-plus:copy'
24 |
--------------------------------------------------------------------------------
/lib/config-schema.coffee:
--------------------------------------------------------------------------------
1 | path = require 'path'
2 |
3 | module.exports =
4 | toggles:
5 | type: 'object'
6 | order: 1
7 | properties:
8 | autoClose:
9 | title: 'Close Terminal on Exit'
10 | description: 'Should the terminal close if the shell exits?'
11 | type: 'boolean'
12 | default: false
13 | cursorBlink:
14 | title: 'Cursor Blink'
15 | description: 'Should the cursor blink when the terminal is active?'
16 | type: 'boolean'
17 | default: true
18 | runInsertedText:
19 | title: 'Run Inserted Text'
20 | description: 'Run text inserted via `terminal-plus:insert-text` as a command? **This will append an end-of-line character to input.**'
21 | type: 'boolean'
22 | default: true
23 | core:
24 | type: 'object'
25 | order: 2
26 | properties:
27 | autoRunCommand:
28 | title: 'Auto Run Command'
29 | description: 'Command to run on terminal initialization.'
30 | type: 'string'
31 | default: ''
32 | defaultView:
33 | title: 'Default View Type'
34 | description: 'Set the default view to open when creating a new terminal.'
35 | type: 'string'
36 | default: 'Match Active Terminal'
37 | enum: [
38 | 'Panel',
39 | 'Tab',
40 | 'Match Active Terminal'
41 | ]
42 | mapTerminalsTo:
43 | title: 'Map Terminals To'
44 | description: 'Map terminals to each file or folder. Default is no action or mapping at all. **Restart required.**'
45 | type: 'string'
46 | default: 'None'
47 | enum: ['None', 'File', 'Folder']
48 | mapTerminalsToAutoOpen:
49 | title: 'Auto Open a New Terminal (For Terminal Mapping)'
50 | description: 'Should a new terminal be opened for new items? **Note:** This works in conjunction with `Map Terminals To` above.'
51 | type: 'boolean'
52 | default: false
53 | scrollback:
54 | title: 'Scroll Back'
55 | description: 'How many lines of history should be kept?'
56 | type: 'integer'
57 | default: 1000
58 | shell:
59 | title: 'Shell Override'
60 | description: 'Override the default shell instance to launch.'
61 | type: 'string'
62 | default: do ->
63 | if process.platform is 'win32'
64 | path.resolve(process.env.SystemRoot, 'System32', 'WindowsPowerShell', 'v1.0', 'powershell.exe')
65 | else
66 | process.env.SHELL
67 | shellArguments:
68 | title: 'Shell Arguments'
69 | description: 'Specify some arguments to use when launching the shell.'
70 | type: 'string'
71 | default: ''
72 | statusBar:
73 | title: 'Status Bar'
74 | description: 'Choose how you want the status bar displayed.'
75 | type: 'string'
76 | default: 'Full'
77 | enum: ['Full', 'Collapsed', 'None']
78 | workingDirectory:
79 | title: 'Working Directory'
80 | description: 'Which directory should be the present working directory when a new terminal is made?'
81 | type: 'string'
82 | default: 'Project'
83 | enum: ['Home', 'Project', 'Active File']
84 | style:
85 | type: 'object'
86 | order: 3
87 | properties:
88 | animationSpeed:
89 | title: 'Animation Speed'
90 | description: 'How fast should the window animate?'
91 | type: 'number'
92 | default: '1'
93 | minimum: '0'
94 | maximum: '100'
95 | fontAntialiasing:
96 | title: 'Font Antialiasing'
97 | description: 'Set the type of font antialiasing for the terminal.'
98 | type: 'string'
99 | default: 'Antialiased'
100 | enum: ['Antialiased', 'Default', 'None']
101 | fontFamily:
102 | title: 'Font Family'
103 | description: 'Override the terminal\'s default font family. **You must use a [monospaced font](https://en.wikipedia.org/wiki/List_of_typefaces#Monospace)!**'
104 | type: 'string'
105 | default: ''
106 | fontSize:
107 | title: 'Font Size'
108 | description: 'Override the terminal\'s default font size.'
109 | type: 'string'
110 | default: ''
111 | defaultPanelHeight:
112 | title: 'Default Panel Height'
113 | description: 'Default height of a terminal panel. **You may enter a value in px, em, or %.**'
114 | type: 'string'
115 | default: '300px'
116 | theme:
117 | title: 'Theme'
118 | description: 'Select a theme for the terminal.'
119 | type: 'string'
120 | default: 'standard'
121 | enum: [
122 | 'standard',
123 | 'inverse',
124 | 'grass',
125 | 'homebrew',
126 | 'man-page',
127 | 'novel',
128 | 'ocean',
129 | 'pro',
130 | 'red',
131 | 'red-sands',
132 | 'silver-aerogel',
133 | 'solid-colors',
134 | 'dracula'
135 | ]
136 | ansiColors:
137 | type: 'object'
138 | order: 4
139 | properties:
140 | normal:
141 | type: 'object'
142 | order: 1
143 | properties:
144 | black:
145 | title: 'Black'
146 | description: 'Black color used for terminal ANSI color set.'
147 | type: 'color'
148 | default: '#000000'
149 | red:
150 | title: 'Red'
151 | description: 'Red color used for terminal ANSI color set.'
152 | type: 'color'
153 | default: '#CD0000'
154 | green:
155 | title: 'Green'
156 | description: 'Green color used for terminal ANSI color set.'
157 | type: 'color'
158 | default: '#00CD00'
159 | yellow:
160 | title: 'Yellow'
161 | description: 'Yellow color used for terminal ANSI color set.'
162 | type: 'color'
163 | default: '#CDCD00'
164 | blue:
165 | title: 'Blue'
166 | description: 'Blue color used for terminal ANSI color set.'
167 | type: 'color'
168 | default: '#0000CD'
169 | magenta:
170 | title: 'Magenta'
171 | description: 'Magenta color used for terminal ANSI color set.'
172 | type: 'color'
173 | default: '#CD00CD'
174 | cyan:
175 | title: 'Cyan'
176 | description: 'Cyan color used for terminal ANSI color set.'
177 | type: 'color'
178 | default: '#00CDCD'
179 | white:
180 | title: 'White'
181 | description: 'White color used for terminal ANSI color set.'
182 | type: 'color'
183 | default: '#E5E5E5'
184 | zBright:
185 | type: 'object'
186 | order: 2
187 | properties:
188 | brightBlack:
189 | title: 'Bright Black'
190 | description: 'Bright black color used for terminal ANSI color set.'
191 | type: 'color'
192 | default: '#7F7F7F'
193 | brightRed:
194 | title: 'Bright Red'
195 | description: 'Bright red color used for terminal ANSI color set.'
196 | type: 'color'
197 | default: '#FF0000'
198 | brightGreen:
199 | title: 'Bright Green'
200 | description: 'Bright green color used for terminal ANSI color set.'
201 | type: 'color'
202 | default: '#00FF00'
203 | brightYellow:
204 | title: 'Bright Yellow'
205 | description: 'Bright yellow color used for terminal ANSI color set.'
206 | type: 'color'
207 | default: '#FFFF00'
208 | brightBlue:
209 | title: 'Bright Blue'
210 | description: 'Bright blue color used for terminal ANSI color set.'
211 | type: 'color'
212 | default: '#0000FF'
213 | brightMagenta:
214 | title: 'Bright Magenta'
215 | description: 'Bright magenta color used for terminal ANSI color set.'
216 | type: 'color'
217 | default: '#FF00FF'
218 | brightCyan:
219 | title: 'Bright Cyan'
220 | description: 'Bright cyan color used for terminal ANSI color set.'
221 | type: 'color'
222 | default: '#00FFFF'
223 | brightWhite:
224 | title: 'Bright White'
225 | description: 'Bright white color used for terminal ANSI color set.'
226 | type: 'color'
227 | default: '#FFFFFF'
228 | iconColors:
229 | type: 'object'
230 | order: 5
231 | properties:
232 | red:
233 | title: 'Status Icon Red'
234 | description: 'Red color used for status icon.'
235 | type: 'color'
236 | default: 'red'
237 | orange:
238 | title: 'Status Icon Orange'
239 | description: 'Orange color used for status icon.'
240 | type: 'color'
241 | default: 'orange'
242 | yellow:
243 | title: 'Status Icon Yellow'
244 | description: 'Yellow color used for status icon.'
245 | type: 'color'
246 | default: 'yellow'
247 | green:
248 | title: 'Status Icon Green'
249 | description: 'Green color used for status icon.'
250 | type: 'color'
251 | default: 'green'
252 | blue:
253 | title: 'Status Icon Blue'
254 | description: 'Blue color used for status icon.'
255 | type: 'color'
256 | default: 'blue'
257 | purple:
258 | title: 'Status Icon Purple'
259 | description: 'Purple color used for status icon.'
260 | type: 'color'
261 | default: 'purple'
262 | pink:
263 | title: 'Status Icon Pink'
264 | description: 'Pink color used for status icon.'
265 | type: 'color'
266 | default: 'hotpink'
267 | cyan:
268 | title: 'Status Icon Cyan'
269 | description: 'Cyan color used for status icon.'
270 | type: 'color'
271 | default: 'cyan'
272 | magenta:
273 | title: 'Status Icon Magenta'
274 | description: 'Magenta color used for status icon.'
275 | type: 'color'
276 | default: 'magenta'
277 |
--------------------------------------------------------------------------------
/lib/core.coffee:
--------------------------------------------------------------------------------
1 | {CompositeDisposable} = require 'atom'
2 |
3 | PanelView = null
4 | TabView = null
5 | StatusBar = null
6 |
7 | Path = require 'path'
8 |
9 | class Core
10 | subscriptions: null
11 | activeTerminal: null
12 | terminals: []
13 | returnFocus: false
14 |
15 | constructor: ->
16 | @subscriptions = new CompositeDisposable()
17 |
18 | @registerCommands()
19 | @registerActiveItemSubscription()
20 | @registerWindowEvents()
21 |
22 | destroy: ->
23 | @subscriptions.dispose()
24 | for terminal in @terminals
25 | terminal.destroy()
26 |
27 | ###
28 | Section: Setup
29 | ###
30 |
31 | registerCommands: ->
32 | @subscriptions.add atom.commands.add 'atom-workspace',
33 | 'terminal-plus:new': => @newTerminalView()?.toggle()
34 |
35 | 'terminal-plus:toggle': => @toggle()
36 |
37 | 'terminal-plus:next': =>
38 | @activeTerminal.open() if @activateNextTerminal()
39 | 'terminal-plus:prev': =>
40 | @activeTerminal.open() if @activatePrevTerminal()
41 |
42 | 'terminal-plus:close': => @destroyActiveTerminal()
43 | 'terminal-plus:close-all': => @closeAll()
44 |
45 | 'terminal-plus:rename': =>
46 | @runInActiveTerminal (i) -> i.promptForRename()
47 | 'terminal-plus:insert-selected-text': =>
48 | @runInActiveTerminal (i) -> i.insertSelection()
49 | 'terminal-plus:insert-text': =>
50 | @runInActiveTerminal (i) -> i.promptForInput()
51 | 'terminal-plus:toggle-focus': =>
52 | @runInActiveTerminal (i) -> i.toggleFocus()
53 | 'terminal-plus:toggle-full-screen': =>
54 | @runInActiveTerminal (i) -> i.toggleFullscreen()
55 |
56 | @subscriptions.add atom.commands.add '.xterm',
57 | 'terminal-plus:paste': =>
58 | @runInActiveTerminal (i) -> i.paste()
59 | 'terminal-plus:copy': =>
60 | @runInActiveTerminal (i) -> i.copy()
61 |
62 | registerActiveItemSubscription: ->
63 | @subscriptions.add atom.workspace.onDidChangeActivePaneItem (item) =>
64 | return unless item?
65 |
66 | if item.constructor.name is "TabView"
67 | setTimeout item.focus, 100
68 | else if item.constructor.name is "TextEditor"
69 | mapping = atom.config.get('terminal-plus.core.mapTerminalsTo')
70 | return if mapping is 'None'
71 |
72 | switch mapping
73 | when 'File'
74 | nextTerminal = @findFirstTerminal (terminal) ->
75 | item.getPath() == terminal.getId().filePath
76 | when 'Folder'
77 | nextTerminal = @findFirstTerminal (terminal) ->
78 | Path.dirname(item.getPath()) == terminal.getId().folderPath
79 |
80 | prevTerminal = @getActiveTerminal()
81 | if prevTerminal != nextTerminal
82 | if not nextTerminal?
83 | if item.getTitle() isnt 'untitled'
84 | if atom.config.get('terminal-plus.core.mapTerminalsToAutoOpen')
85 | nextTerminal = @createTerminalView().getTerminal()
86 | else
87 | @setActiveTerminal(nextTerminal)
88 | if prevTerminal.getParentView()?.panel.isVisible()
89 | nextTerminal.getParentView().toggle()
90 |
91 | registerWindowEvents: ->
92 | handleBlur = =>
93 | if @activeTerminal and @activeTerminal.isFocused()
94 | @returnFocus = true
95 | @activeTerminal.getParentView().blur()
96 |
97 | handleFocus = =>
98 | if @returnFocus and @activeTerminal
99 | setTimeout =>
100 | @activeTerminal.focus()
101 | @returnFocus = false
102 | , 100
103 |
104 | window.addEventListener 'blur', handleBlur
105 | @subscriptions.add dispose: ->
106 | window.removeEventListener 'blur', handleBlur
107 |
108 | window.addEventListener 'focus', handleFocus
109 | @subscriptions.add dispose: ->
110 | window.removeEventListener 'focus', handleFocus
111 |
112 |
113 | ###
114 | Section: Command Handling
115 | ###
116 |
117 | activateNextTerminal: ->
118 | return false if (not @activeTerminal) or @activeTerminal.isAnimating()
119 |
120 | index = @terminals.indexOf(@activeTerminal)
121 | return false if index < 0
122 | @activateTerminalAtIndex index + 1
123 |
124 | activatePrevTerminal: ->
125 | return false if (not @activeTerminal) or @activeTerminal.isAnimating()
126 |
127 | index = @terminals.indexOf(@activeTerminal)
128 | return false if index < 0
129 | @activateTerminalAtIndex index - 1
130 |
131 | closeAll: =>
132 | panels = @getPanelViews()
133 | @terminals = @getTabViews()
134 |
135 | for panel in panels
136 | panel.getParentView().destroy()
137 |
138 | @activeTerminal = @terminals[0]
139 |
140 | destroyActiveTerminal: ->
141 | return unless @activeTerminal?
142 |
143 | index = @terminals.indexOf(@activeTerminal)
144 | @removeTerminalAt(index)
145 | if @activeTerminal.isTabView()
146 | parent = @activeTerminal.getParentView()
147 | if pane = atom.workspace.paneForItem(parent)
148 | pane.destroyItem(parent)
149 | else
150 | @activeTerminal.getParentView().destroy()
151 | @activeTerminal = null
152 |
153 | @activateAdjacentTerminal index
154 |
155 | newTerminalView: =>
156 | PanelView ?= require './panel-view'
157 | TabView ?= require './tab-view'
158 | StatusBar ?= require './status-bar'
159 | StatusBar.registerPaneSubscription()
160 |
161 | return null if @activeTerminal and @activeTerminal.isAnimating()
162 |
163 | terminalView = @createTerminalView()
164 | @terminals.push terminalView.getTerminal()
165 | return terminalView
166 |
167 | runInActiveTerminal: (callback) ->
168 | terminal = @getActiveTerminal()
169 | if terminal?
170 | return callback(terminal)
171 | return null
172 |
173 | toggle: ->
174 | return if @activeTerminal and @activeTerminal.isAnimating()
175 |
176 | if @terminals.length == 0
177 | @activeTerminal = @newTerminalView().getTerminal()
178 | else if @activeTerminal == null
179 | @activeTerminal = @terminals[0]
180 | @activeTerminal.toggle()
181 |
182 |
183 | ###
184 | Section: External Methods
185 | ###
186 |
187 | getActiveTerminal: ->
188 | return @activeTerminal
189 |
190 | getActiveTerminalView: ->
191 | return @activeTerminal.getParentView()
192 |
193 | getTerminals: ->
194 | return @terminals
195 |
196 | length: ->
197 | return @terminals.length
198 |
199 | removeTerminal: (terminal) ->
200 | index = @terminals.indexOf terminal
201 | return if index < 0
202 | @terminals.splice index, 1
203 |
204 | if terminal == @activeTerminal
205 | unless @activateAdjacentTerminal()
206 | @activeTerminal = null
207 |
208 | removeTerminalAt: (index) ->
209 | return if index < 0 or index > @terminals.length
210 | return @terminals.splice(index, 1)[0]
211 |
212 | removeTerminalView: (view) ->
213 | @removeTerminal view.getTerminal()
214 |
215 | setActiveTerminal: (terminal) ->
216 | @activeTerminal = terminal
217 |
218 | setActiveTerminalView: (view) ->
219 | @setActiveTerminal view.getTerminal()
220 |
221 | terminalAt: (index) ->
222 | return @terminals[index]
223 |
224 | moveTerminal: (fromIndex, toIndex) =>
225 | fromIndex = Math.max(0, fromIndex)
226 | toIndex = Math.min(toIndex, @terminals.length)
227 |
228 | terminal = @terminals.splice(fromIndex, 1)[0]
229 | @terminals.splice toIndex, 0, terminal
230 |
231 |
232 | ###
233 | Section: Helper Methods
234 | ###
235 |
236 | activateAdjacentTerminal: (index = 0) ->
237 | return false unless @terminals.length > 0
238 |
239 | index = Math.max(0, index - 1)
240 | @activeTerminal = @terminals[index]
241 |
242 | activateTerminalAtIndex: (index) ->
243 | return false if @terminals.length < 2
244 |
245 | if index >= @terminals.length
246 | index = 0
247 | if index < 0
248 | index = @terminals.length - 1
249 |
250 | @activeTerminal = @terminals[index]
251 | return true
252 |
253 | createTerminalView: ->
254 | projectFolder = atom.project.getPaths()[0]
255 | editorPath = atom.workspace.getActiveTextEditor()?.getPath()
256 |
257 | if editorPath?
258 | editorFolder = Path.dirname(editorPath)
259 | for directory in atom.project.getPaths()
260 | if editorPath.indexOf(directory) >= 0
261 | projectFolder = directory
262 |
263 | projectFolder = undefined if projectFolder?.indexOf('atom://') >= 0
264 |
265 | home = if process.platform is 'win32' then process.env.HOMEPATH else process.env.HOME
266 |
267 | switch atom.config.get('terminal-plus.core.workingDirectory')
268 | when 'Project' then pwd = projectFolder or editorFolder or home
269 | when 'Active File' then pwd = editorFolder or projectFolder or home
270 | else pwd = home
271 |
272 | id = editorPath or projectFolder or home
273 | id = filePath: id, folderPath: Path.dirname(id)
274 |
275 | shellPath = atom.config.get 'terminal-plus.core.shell'
276 |
277 | ViewType = null
278 | switch atom.config.get 'terminal-plus.core.defaultView'
279 | when "Panel" then ViewType = PanelView
280 | when "Tab" then ViewType = TabView
281 | when "Match Active Terminal"
282 | if @activeTerminal?.isTabView()
283 | ViewType = TabView
284 | else
285 | ViewType = PanelView
286 |
287 | return new ViewType {
288 | id, pwd, shellPath
289 | }
290 |
291 | findFirstTerminal: (filter) ->
292 | matches = @terminals.filter filter
293 | return matches[0]
294 |
295 | iconAtIndex: (index) ->
296 | @getStatusIcons().eq(index)
297 |
298 | getPanelViews: ->
299 | @terminals.filter (terminal) -> terminal.isPanelView()
300 |
301 | getTabViews: ->
302 | @terminals.filter (terminal) -> terminal.isTabView()
303 |
304 | module.exports = exports = new Core()
305 |
--------------------------------------------------------------------------------
/lib/dialog.coffee:
--------------------------------------------------------------------------------
1 | {TextEditorView, View} = require 'atom-space-pen-views'
2 |
3 | module.exports =
4 | class Dialog extends View
5 | @content: ({prompt} = {}) ->
6 | @div class: 'terminal-plus-dialog', =>
7 | @label prompt, class: 'icon', outlet: 'promptText'
8 | @subview 'miniEditor', new TextEditorView(mini: true)
9 | @label 'Escape (Esc) to exit', style: 'float: left;'
10 | @label 'Enter (\u21B5) to confirm', style: 'float: right;'
11 |
12 | initialize: ({iconClass, placeholderText, stayOpen} = {}) ->
13 | @promptText.addClass(iconClass) if iconClass
14 | atom.commands.add @element,
15 | 'core:confirm': => @onConfirm(@miniEditor.getText())
16 |
17 | atom.commands.add 'atom-workspace',
18 | 'core:cancel': => @cancel()
19 |
20 | unless stayOpen
21 | @miniEditor.on 'blur', => @close()
22 |
23 | if placeholderText
24 | @miniEditor.getModel().setText placeholderText
25 | @miniEditor.getModel().selectAll()
26 |
27 | attach: ->
28 | @panel = atom.workspace.addModalPanel(item: this.element)
29 | @miniEditor.focus()
30 | @miniEditor.getModel().scrollToCursorPosition()
31 |
32 | close: ->
33 | panelToDestroy = @panel
34 | @panel = null
35 | panelToDestroy?.destroy()
36 | atom.workspace.getActivePane().activate()
37 |
38 | cancel: ->
39 | @close()
40 |
--------------------------------------------------------------------------------
/lib/fork.coffee:
--------------------------------------------------------------------------------
1 | pty = require 'pty.js'
2 | path = require 'path'
3 | fs = require 'fs'
4 | _ = require 'underscore'
5 |
6 | systemLanguage = do ->
7 | language = "en_US.UTF-8"
8 | if process.platform is 'darwin'
9 | try
10 | command = 'plutil -convert json -o - ~/Library/Preferences/.GlobalPreferences.plist'
11 | language = "#{JSON.parse(child.execSync(command).toString()).AppleLocale}.UTF-8"
12 | return language
13 |
14 | filteredEnvironment = do ->
15 | env = _.omit process.env, 'ATOM_HOME', 'ATOM_SHELL_INTERNAL_RUN_AS_NODE', 'GOOGLE_API_KEY', 'NODE_ENV', 'NODE_PATH', 'userAgent', 'taskPath'
16 | env.LANG ?= systemLanguage
17 | env.TERM_PROGRAM = 'Terminal-Plus'
18 | return env
19 |
20 | module.exports = (pwd, shellPath, args) ->
21 | callback = @async()
22 |
23 | ptyProcess = pty.fork shellPath, args,
24 | cwd: pwd,
25 | env: filteredEnvironment,
26 | name: 'xterm-256color'
27 |
28 | title = shell = path.basename shellPath
29 |
30 | emitTitle = _.throttle ->
31 | emit('terminal-plus:title', ptyProcess.process)
32 | , 500, true
33 |
34 | ptyProcess.on 'data', (data) ->
35 | emit('terminal-plus:data', data)
36 | emitTitle()
37 |
38 | ptyProcess.on 'exit', ->
39 | emit('terminal-plus:exit')
40 | callback()
41 |
42 | process.on 'message', ({event, cols, rows, text}={}) ->
43 | switch event
44 | when 'resize' then ptyProcess.resize(cols, rows)
45 | when 'input' then ptyProcess.write(text)
46 |
--------------------------------------------------------------------------------
/lib/input-dialog.coffee:
--------------------------------------------------------------------------------
1 | Dialog = require "./dialog"
2 | os = require "os"
3 |
4 | module.exports =
5 | class InputDialog extends Dialog
6 | constructor: (@terminal) ->
7 | @focus = @terminal.isFocused()
8 | @terminal.blur()
9 |
10 | super
11 | prompt: "Insert Text"
12 | iconClass: "icon-keyboard"
13 | stayOpen: true
14 |
15 | onConfirm: (input) ->
16 | if atom.config.get('terminal-plus.toggles.runInsertedText')
17 | eol = os.EOL
18 | else
19 | eol = ''
20 |
21 | data = "#{input}#{eol}"
22 | @terminal.input data
23 | @cancel()
24 |
25 | cancel: ->
26 | @terminal.focus() if @focus
27 | super()
28 |
--------------------------------------------------------------------------------
/lib/panel-view.coffee:
--------------------------------------------------------------------------------
1 | {CompositeDisposable} = require 'atom'
2 | {$} = require 'atom-space-pen-views'
3 |
4 | TerminalView = require './terminal-view'
5 |
6 | lastOpenedTerminal = null
7 |
8 | defaultHeight = do ->
9 | height = atom.config.get('terminal-plus.style.defaultPanelHeight')
10 | if height.indexOf('%') > 0
11 | percent = Math.abs(Math.min(parseFloat(height) / 100.0, 1))
12 | bottomHeight = $('atom-panel.bottom').children(".terminal-view").height() or 0
13 | height = percent * ($('.item-views').height() + bottomHeight)
14 | return height
15 |
16 | module.exports =
17 | class PanelView extends TerminalView
18 | animating: false
19 | windowHeight: atom.getSize().height
20 |
21 | @getFocusedTerminal: ->
22 | return TerminalView.getFocusedTerminal()
23 |
24 | initialize: (options) ->
25 | super(options)
26 |
27 | @addDefaultButtons()
28 |
29 | @terminal.showIcon()
30 | @updateName(@terminal.getName())
31 |
32 | @attachResizeEvents()
33 | @attach()
34 |
35 | destroy: ({keepTerminal}={}) =>
36 | @detachResizeEvents()
37 |
38 | if @panel.isVisible() and not keepTerminal
39 | @onTransitionEnd =>
40 | @panel.destroy()
41 | @hide()
42 | else
43 | @panel.destroy()
44 |
45 | super(keepTerminal)
46 |
47 | ###
48 | Section: Setup
49 | ###
50 |
51 | addDefaultButtons: ->
52 | @closeBtn = @addButton 'right', @destroy, 'x'
53 | @hideBtn = @addButton 'right', @hide, 'chevron-down'
54 | @fullscreenBtn = @addButton 'right', @toggleFullscreen, 'screen-full'
55 | @inputBtn = @addButton 'left', @promptForInput, 'keyboard'
56 |
57 | @subscriptions.add atom.tooltips.add @closeBtn,
58 | title: 'Close'
59 | @subscriptions.add atom.tooltips.add @hideBtn,
60 | title: 'Hide'
61 | @subscriptions.add atom.tooltips.add @fullscreenBtn,
62 | title: 'Maximize'
63 | @subscriptions.add atom.tooltips.add @inputBtn,
64 | title: 'Insert Text'
65 |
66 | attach: ->
67 | return if @panel?
68 | @panel = atom.workspace.addBottomPanel(item: this, visible: false)
69 |
70 |
71 | ###
72 | Section: Resizing
73 | ###
74 |
75 | attachResizeEvents: ->
76 | @panelDivider.on 'mousedown', @resizeStarted
77 |
78 | detachResizeEvents: ->
79 | @panelDivider.off 'mousedown'
80 |
81 | onWindowResize: (event) =>
82 | @terminal.disableAnimation()
83 | delta = atom.getSize().height - @windowHeight
84 |
85 | if lines = (delta / @terminal.getRowHeight()|0)
86 | offset = lines * @terminal.getRowHeight()
87 | newHeight = @terminal.height() + offset
88 |
89 | if newHeight >= @terminal.getRowHeight()
90 | @terminal.height newHeight
91 | @maxHeight += offset
92 | @windowHeight += offset
93 | else
94 | @terminal.height @terminal.getRowHeight()
95 |
96 | @terminal.enableAnimation()
97 | super()
98 |
99 | resizeStarted: =>
100 | return if @maximized
101 | @maxHeight = @terminal.getPrevHeight() + $('.item-views').height()
102 | $(document).on('mousemove', @resizePanel)
103 | $(document).on('mouseup', @resizeStopped)
104 | @terminal.disableAnimation()
105 |
106 | resizeStopped: =>
107 | $(document).off('mousemove', @resizePanel)
108 | $(document).off('mouseup', @resizeStopped)
109 | @terminal.enableAnimation()
110 |
111 | resizePanel: (event) =>
112 | return @resizeStopped() unless event.which is 1
113 |
114 | mouseY = $(window).height() - event.pageY
115 | delta = mouseY - $('atom-panel-container.bottom').height()
116 | return unless Math.abs(delta) > (@terminal.getRowHeight() * 5 / 6)
117 |
118 | nearestRow = @nearestRow(@terminal.height() + delta)
119 | clamped = Math.max(nearestRow, @terminal.getRowHeight())
120 | return if clamped > @maxHeight
121 |
122 | @terminal.height clamped
123 | @terminal.recalibrateSize()
124 |
125 |
126 | ###
127 | Section: External Methods
128 | ###
129 |
130 | open: =>
131 | super()
132 | @terminal.getStatusIcon().activate()
133 |
134 | if lastOpenedTerminal and lastOpenedTerminal != @terminal
135 | lastOpenedTerminal.getParentView().hide({refocus: false})
136 | lastOpenedTerminal = @terminal
137 |
138 | @onTransitionEnd =>
139 | if @terminal.displayView()
140 | height = @nearestRow(@terminal.height())
141 | @terminal.height(height)
142 | @focus()
143 |
144 | @panel.show()
145 | @terminal.height 0
146 | @animating = true
147 | @terminal.height @terminal.getPrevHeight() or defaultHeight
148 |
149 | hide: ({refocus}={})=>
150 | refocus ?= true
151 | lastOpenedTerminal = null
152 | @terminal.getStatusIcon().deactivate()
153 |
154 | @onTransitionEnd =>
155 | @panel.hide()
156 | super(refocus)
157 |
158 | @terminal.height @terminal.getPrevHeight()
159 | @animating = true
160 | @terminal.height 0
161 |
162 | updateName: (name) ->
163 | @terminal.getStatusIcon().setName(name)
164 |
165 | toggleFullscreen: =>
166 | @destroy keepTerminal: true
167 | @terminal.clearHeight().disableAnimation()
168 | tabView = new (require './tab-view') {@terminal}
169 | tabView.toggle()
170 | @remove()
171 |
172 | isVisible: ->
173 | @panel.isVisible()
174 |
175 |
176 | ###
177 | Section: Helper Methods
178 | ###
179 |
180 | nearestRow: (value) ->
181 | rowHeight = @terminal.getRowHeight()
182 | return rowHeight * Math.round(value / rowHeight)
183 |
184 | onTransitionEnd: (callback) ->
185 | @terminal.one 'webkitTransitionEnd', =>
186 | callback()
187 | @animating = false
188 |
--------------------------------------------------------------------------------
/lib/rename-dialog.coffee:
--------------------------------------------------------------------------------
1 | Dialog = require "./dialog"
2 |
3 | module.exports =
4 | class RenameDialog extends Dialog
5 | constructor: (@terminal) ->
6 | super
7 | prompt: "Rename"
8 | iconClass: "icon-pencil"
9 | placeholderText: @terminal.getName()
10 |
11 | onConfirm: (newTitle) ->
12 | @terminal.setName newTitle.trim()
13 | @cancel()
14 |
--------------------------------------------------------------------------------
/lib/shell.coffee:
--------------------------------------------------------------------------------
1 | {Task} = require 'atom'
2 |
3 | Path = require 'path'
4 | fork = require.resolve './fork'
5 |
6 | module.exports =
7 | class Shell
8 | child: null
9 |
10 | constructor: ({pwd, shellPath}) ->
11 |
12 | disableInput = =>
13 | @fork = null
14 | @input = ->
15 | @resize = ->
16 |
17 | shellArguments = atom.config.get 'terminal-plus.core.shellArguments'
18 | args = shellArguments.split(/\s+/g).filter (arg) -> arg
19 | if /zsh|bash/.test(shellPath) and args.indexOf('--login') == -1
20 | args.unshift '--login'
21 |
22 | @fork = Task.once fork, Path.resolve(pwd), shellPath, args, disableInput
23 |
24 | destroy: ->
25 | @terminate()
26 |
27 |
28 | ###
29 | Section: External Methods
30 | ###
31 |
32 | input: (data) ->
33 | return unless @isStillAlive()
34 |
35 | @fork.send event: 'input', text: data
36 |
37 | resize: (cols, rows) ->
38 | return unless @isStillAlive()
39 |
40 | @fork.send {event: 'resize', rows, cols}
41 |
42 | isStillAlive: ->
43 | return false unless @fork and @fork.childProcess
44 | return @fork.childProcess.connected and !@fork.childProcess.killed
45 |
46 | terminate: ->
47 | @fork.terminate()
48 |
49 | on: (event, handler) ->
50 | @fork.on event, handler
51 |
52 | off: (event, handler) ->
53 | if handler
54 | @fork.off event, handler
55 | else
56 | @fork.off event
57 |
--------------------------------------------------------------------------------
/lib/status-bar.coffee:
--------------------------------------------------------------------------------
1 | {CompositeDisposable} = require 'atom'
2 | {$, View} = require 'atom-space-pen-views'
3 |
4 | class StatusBar extends View
5 | attached: false
6 | container: null
7 |
8 | @content: ->
9 | @div class: 'terminal-plus inline-block', style: 'width: 100%;', =>
10 | @div class: 'terminal-plus-status-bar', =>
11 | @i class: "icon icon-plus", click: 'newTerminalView', outlet: 'plusBtn'
12 | @ul {
13 | class: "status-container list-inline",
14 | tabindex: '-1',
15 | outlet: 'statusContainer'
16 | }
17 | @i {
18 | class: "icon icon-x",
19 | style: "color: red;",
20 | click: 'closeAll',
21 | outlet: 'closeBtn'
22 | }
23 |
24 | initialize: ->
25 | @core = require './core'
26 |
27 | @subscriptions = new CompositeDisposable()
28 |
29 | @subscriptions.add atom.tooltips.add @plusBtn, title: 'New Terminal'
30 | @subscriptions.add atom.tooltips.add @closeBtn, title: 'Close All'
31 |
32 | @statusContainer.on 'dblclick', (event) =>
33 | if event.target == event.delegateTarget
34 | @newTerminalView()
35 |
36 | @registerContextMenu()
37 | @registerDragDropInterface()
38 |
39 | destroy: ->
40 | @destroyContainer()
41 |
42 |
43 | ###
44 | Section: Setup
45 | ###
46 |
47 | registerContextMenu: ->
48 | findStatusIcon = (event) ->
49 | return $(event.target).closest('.status-icon')[0]
50 |
51 | @subscriptions.add atom.commands.add '.terminal-plus-status-bar',
52 | 'terminal-plus:status-red': @setStatusColor
53 | 'terminal-plus:status-orange': @setStatusColor
54 | 'terminal-plus:status-yellow': @setStatusColor
55 | 'terminal-plus:status-green': @setStatusColor
56 | 'terminal-plus:status-blue': @setStatusColor
57 | 'terminal-plus:status-purple': @setStatusColor
58 | 'terminal-plus:status-pink': @setStatusColor
59 | 'terminal-plus:status-cyan': @setStatusColor
60 | 'terminal-plus:status-magenta': @setStatusColor
61 | 'terminal-plus:status-default': @clearStatusColor
62 | 'terminal-plus:context-close': (event) ->
63 | findStatusIcon(event).getTerminalView().destroy()
64 | 'terminal-plus:context-hide': (event) ->
65 | statusIcon = findStatusIcon(event)
66 | statusIcon.getTerminalView().hide() if statusIcon.isActive()
67 | 'terminal-plus:context-rename': (event) ->
68 | findStatusIcon(event).getTerminal().promptForRename()
69 |
70 | registerDragDropInterface: ->
71 | @statusContainer.on 'dragstart', '.status-icon', @onDragStart
72 | @statusContainer.on 'dragend', '.status-icon', @onDragEnd
73 | @statusContainer.on 'dragleave', @onDragLeave
74 | @statusContainer.on 'dragover', @onDragOver
75 | @statusContainer.on 'drop', @onDrop
76 |
77 | registerPaneSubscription: ->
78 | return if @paneSubscription
79 |
80 | @subscriptions.add @paneSubscription = atom.workspace.observePanes (pane) =>
81 | paneElement = $(atom.views.getView(pane))
82 | tabBar = paneElement.find('ul')
83 |
84 | tabBar.on 'drop', (event) => @onDropTabBar(event, pane)
85 | tabBar.on 'dragstart', (event) ->
86 | return unless event.target.item?.constructor.name is 'TabView'
87 | event.originalEvent.dataTransfer.setData 'terminal-plus-tab', 'true'
88 | pane.onDidDestroy -> tabBar.off 'drop', @onDropTabBar
89 |
90 |
91 | ###
92 | Section: Button Handlers
93 | ###
94 |
95 | closeAll: ->
96 | @core.closeAll()
97 |
98 | newTerminalView: ->
99 | @core.newTerminalView()?.toggle()
100 |
101 |
102 | ###
103 | Section: Drag and drop
104 | ###
105 |
106 | onDragStart: (event) =>
107 | event.originalEvent.dataTransfer.setData 'terminal-plus-panel', 'true'
108 |
109 | element = $(event.target).closest('.status-icon')
110 | element.addClass 'is-dragging'
111 | event.originalEvent.dataTransfer.setData 'from-index', element.index()
112 |
113 | onDragLeave: (event) =>
114 | @removePlaceholder()
115 |
116 | onDragEnd: (event) =>
117 | @clearDropTarget()
118 |
119 | onDragOver: (event) =>
120 | event.preventDefault()
121 | event.stopPropagation()
122 | unless event.originalEvent.dataTransfer.getData('terminal-plus-panel') is 'true'
123 | return
124 |
125 | newDropTargetIndex = @getDropTargetIndex(event)
126 | return unless newDropTargetIndex?
127 | @removeDropTargetClasses()
128 | statusIcons = @getStatusIcons()
129 |
130 | if newDropTargetIndex < statusIcons.length
131 | element = statusIcons.eq(newDropTargetIndex).addClass 'is-drop-target'
132 | @getPlaceholder().insertBefore(element)
133 | else
134 | element = statusIcons.eq(newDropTargetIndex - 1).addClass 'drop-target-is-after'
135 | @getPlaceholder().insertAfter(element)
136 |
137 | onDrop: (event) =>
138 | {dataTransfer} = event.originalEvent
139 | panelEvent = dataTransfer.getData('terminal-plus-panel') is 'true'
140 | tabEvent = dataTransfer.getData('terminal-plus-tab') is 'true'
141 | return unless panelEvent or tabEvent
142 |
143 | event.preventDefault()
144 | event.stopPropagation()
145 |
146 | toIndex = @getDropTargetIndex(event)
147 | @clearDropTarget()
148 |
149 | if tabEvent
150 | fromIndex = parseInt(dataTransfer.getData('sortable-index'))
151 | paneIndex = parseInt(dataTransfer.getData('from-pane-index'))
152 | pane = atom.workspace.getPanes()[paneIndex]
153 | view = pane.itemAtIndex(fromIndex)
154 |
155 | pane.removeItem(view, false)
156 | view.toggleFullscreen()
157 | fromIndex = @core.length() - 1
158 | else
159 | fromIndex = parseInt(dataTransfer.getData('from-index'))
160 | @updateOrder(fromIndex, toIndex)
161 |
162 | onDropTabBar: (event, pane) =>
163 | {dataTransfer} = event.originalEvent
164 | return unless dataTransfer.getData('terminal-plus-panel') is 'true'
165 |
166 | event.preventDefault()
167 | event.stopPropagation()
168 | @clearDropTarget()
169 |
170 | fromIndex = parseInt(dataTransfer.getData('from-index'))
171 | terminal = @core.terminalAt(fromIndex)
172 | terminal.getParentView().toggleFullscreen()
173 |
174 |
175 | ###
176 | Section: External Methods
177 | ###
178 |
179 | addStatusIcon: (icon) ->
180 | @statusContainer.append icon
181 |
182 | destroyContainer: ->
183 | if @container
184 | @container.destroy()
185 | @container = null
186 |
187 | getContainer: ->
188 | return @container
189 |
190 | setContainer: (container) ->
191 | @container = container
192 | return this
193 |
194 |
195 | ###
196 | Section: Helper Methods
197 | ###
198 |
199 | clearDropTarget: ->
200 | element = @find('.is-dragging')
201 | element.removeClass 'is-dragging'
202 | @removeDropTargetClasses()
203 | @removePlaceholder()
204 |
205 | removeDropTargetClasses: ->
206 | @statusContainer.find('.is-drop-target').removeClass 'is-drop-target'
207 | @statusContainer.find('.drop-target-is-after').removeClass 'drop-target-is-after'
208 |
209 | getDropTargetIndex: (event) ->
210 | target = $(event.target)
211 | return if @isPlaceholder(target)
212 |
213 | statusIcons = @statusContainer.children('.status-icon')
214 | element = target.closest('.status-icon')
215 | element = statusIcons.last() if element.length is 0
216 |
217 | return 0 unless element.length
218 |
219 | elementCenter = element.offset().left + element.width() / 2
220 |
221 | if event.originalEvent.pageX < elementCenter
222 | statusIcons.index(element)
223 | else if element.next('.status-icon').length > 0
224 | statusIcons.index(element.next('.status-icon'))
225 | else
226 | statusIcons.index(element) + 1
227 |
228 | getPlaceholder: ->
229 | @placeholderEl ?= $('