blob: 5c9640daec0c999f232451d5f0303f95e57734aa (
about) (
plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
|
;;; terminal-here.el --- Run an external terminal in current directory -*- lexical-binding: t; -*-
;; Copyright © 2017 David Shepherd
;; Author: David Shepherd <davidshepherd7@gmail.com>
;; Version: 1.0
;; Package-Requires: ((emacs "24") (cl-lib "0.5"))
;; Keywords: tools, frames
;; URL: https://github.com/davidshepherd7/terminal-here
;;; Commentary:
;; Provides commands to help open external terminal emulators in the
;; directory of the current buffer.
;;; Code:
(require 'cl-lib)
;; TODO: it would be nice not to need to load all of tramp just for the file
;; name parsing. I'm not sure if that's possible though.
(require 'tramp)
(defgroup terminal-here nil
"Open external terminal emulators in the current buffer's directory."
:group 'external
:prefix "terminal-here-")
(defcustom terminal-here-terminal-emulators
'("x-terminal-emulator" "sl" "urxvt" "gnome-terminal"
"xfce4-terminal" "konsole" "xterm")
"List of terminal emulators."
:group 'terminal-here
:type 'list)
(defun terminal-here-find-terminal ()
(file-name-nondirectory
(cl-some (lambda (executable)
(executable-find executable))
terminal-here-terminal-emulators)))
(defun terminal-here-default-terminal-command (_dir)
"Pick a good default command to use for DIR."
(cond
((eq system-type 'darwin)
(list "open" "-a" "Terminal.app" "."))
;; From http://stackoverflow.com/a/13509208/874671
((memq system-type '(windows-nt ms-dos cygwin))
(list "cmd.exe" "/C" "start" "cmd.exe"))
;; Probably X11!
(t (list (terminal-here-find-terminal)))))
(defcustom terminal-here-terminal-command
#'terminal-here-default-terminal-command
"The command used to start a terminal.
Either a list of strings: (terminal-binary arg1 arg2 ...); or a
function taking a directory and returning such a list."
:group 'terminal-here
:type '(choice (repeat string)
(function)))
(defcustom terminal-here-project-root-function
(cl-find-if 'fboundp '(projectile-project-root vc-root-dir))
"Function called to find the current project root directory.
Good options include `projectile-project-root', which requires
you install the `projectile' package, or `vc-root-dir' which is
available in Emacs >= 25.1.
The function should return nil or signal an error if the current
buffer is not in a project."
:group 'terminal-here
:type 'function)
(defcustom terminal-here-command-flag
"-e"
"The flag to tell your terminal to treat the rest of the line as a command to run
Typically this is -e, gnome-terminal uses -x."
:group 'terminal-here
:type 'string)
(defun terminal-here--parse-ssh-dir (dir)
(when (string-prefix-p "/ssh:" dir)
(cdr (split-string dir ":"))))
(defun terminal-here--ssh-command (remote dir)
(append (terminal-here--term-command "") (list terminal-here-command-flag "ssh" "-t" remote "cd" dir "&&" "exec" "$SHELL" "-")))
(defun terminal-here--term-command (dir)
(let ((ssh-data (terminal-here--parse-ssh-dir dir)))
(cond
(ssh-data (terminal-here--ssh-command (car ssh-data) (cadr ssh-data)))
(t (if (functionp terminal-here-terminal-command)
(funcall terminal-here-terminal-command dir)
terminal-here-terminal-command)))))
(defun terminal-here-launch-in-directory (dir)
"Launch a terminal in directory DIR.
Handles tramp paths sensibly."
(let ((term-command (terminal-here--term-command dir)))
(terminal-here--run-command term-command
(or (terminal-here-maybe-tramp-path-to-directory dir) dir))))
(defun terminal-here-maybe-tramp-path-to-directory (dir)
"Extract the local part of a local tramp path.
Given a tramp path returns the local part, otherwise returns nil."
(when (tramp-tramp-file-p dir)
(let ((file-name-struct (tramp-dissect-file-name dir)))
(cond
;; sudo: just strip the extra tramp stuff
((equal (tramp-file-name-method file-name-struct) "sudo")
(tramp-file-name-localname file-name-struct))
;; ssh: run with a custom command handled later
((equal (tramp-file-name-method file-name-struct) "ssh") dir)
(t (user-error "Terminal here cannot currently handle tramp files other than sudo and ssh"))))))
(defun terminal-here--run-command (command dir)
(let* ((default-directory dir)
(process-name (car command))
(proc (apply #'start-process process-name nil command)))
(set-process-sentinel
proc
(lambda (proc _)
(when (and (eq (process-status proc) 'exit) (/= (process-exit-status proc) 0))
(message "Error: in terminal here, command `%s` exited with error code %d"
(mapconcat #'identity command " ")
(process-exit-status proc)))))
;; Don't close when emacs closes, seems to only be necessary on Windows.
(set-process-query-on-exit-flag proc nil)))
;;;###autoload
(defun terminal-here-launch ()
"Launch a terminal in the current working directory.
This is the directory of the current buffer unless you have
changed it by running `cd'."
(interactive)
(terminal-here-launch-in-directory default-directory))
;;;###autoload
(defalias 'terminal-here 'terminal-here-launch)
;;;###autoload
(defun terminal-here-project-launch ()
"Launch a terminal in the current project root.
If projectile is installed the projectile root will be used,
Otherwise `vc-root-dir' will be used."
(interactive)
(when (not terminal-here-project-root-function)
(user-error "No `terminal-here-project-root-function' is set."))
(let ((root (funcall terminal-here-project-root-function)))
(when (not root)
(user-error "Not in any project according to `terminal-here-project-root-function'"))
(terminal-here-launch-in-directory root)))
(provide 'terminal-here)
;;; terminal-here.el ends here
|