-
Notifications
You must be signed in to change notification settings - Fork 11
/
vterm-toggle.el
473 lines (424 loc) · 17.6 KB
/
vterm-toggle.el
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
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
;;; vterm-toggle.el --- Toggles between the vterm buffer and other buffers. -*- lexical-binding: t; -*-
;; Author: jixiuf [email protected]
;; Keywords: vterm terminals
;; Version: 0.0.4
;; URL: https://github.com/jixiuf/vterm-toggle
;; Package-Requires: ((emacs "25.1") (vterm "0.0.1"))
;; Copyright (C) 2019, jixiuf, all rights reserved.
;; This file is NOT part of GNU Emacs.
;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;;; Commentary:
;;
;; Provides the command vterm-toggle which toggles between the
;; vterm buffer and whatever buffer you are editing.
;;
;; This is done in an "intelligent" way. Features are:
;; o Starts a vterm if none is existent.
;; o Minimum distortion of your window configuration.
;; o When done in the vterm-buffer you are returned to the same window
;; configuration you had before you toggled to the shell.
;; o If you desire, you automagically get a "cd" command in the shell to the
;; directory where your current buffers file exists( even in a ssh session); just call
;; vterm-toggle-cd instead of vterm-toggle.
;;
;;; Code:
(require 'cl-lib)
(require 'tramp)
(require 'tramp-sh)
(declare-function vterm "vterm")
(declare-function vterm-cursor-in-command-buffer-p "vterm")
(declare-function vterm-send-string "vterm")
(declare-function vterm-send-return "vterm")
(declare-function vterm-other-window "vterm")
(defvar vterm-buffer-name "*vterm*")
(defcustom vterm-toggle-show-hook nil
"Hooks when swith to vterm buffer."
:group 'vterm-toggle
:type 'symbolp)
(defcustom vterm-toggle-hide-hook nil
"Hooks when hide vterm buffer."
:group 'vterm-toggle
:type 'symbolp)
(defcustom vterm-toggle-fullscreen-p nil
"Open vterm buffer fullscreen or not."
:group 'vterm-toggle
:type 'boolean)
(defcustom vterm-toggle-scope nil
"`project' limit the scope only in the current project.
`frame' limit the scope only not in other frame.
`dedicated' use the dedicated vterm buffer."
:group 'vterm-toggle
:type '(radio
(const :tag "all" nil)
(const :tag "project" project)
(const :tag "frame" frame)
(const :tag "dedicated" dedicated)))
(defcustom vterm-toggle-project-root t
"Create a new vterm buffter at project root directory or not.
it only work when `vterm-toggle-scope' is `project'. "
:group 'vterm-toggle
:type 'boolean)
(defcustom vterm-toggle-cd-auto-create-buffer nil
"If the prompt of recent vterm buffer is not available,
`vterm-toggle-cd' would create a new vterm buffer."
:group 'vterm-toggle
:type 'boolean)
(defcustom vterm-toggle-reset-window-configration-after-exit 'kill-window-only
"Whether reset window configuration after vterm buffer is killed."
:group 'vterm-toggle
:type '(choice
(const :tag "Do nothing" nil)
(const :tag "Reset window configration after exit" t)
(const :tag "Kill Window only" kill-window-only)))
(defcustom vterm-toggle-hide-method 'delete-window
"How to hide the vterm buffer"
:group 'vterm-toggle
:type '(choice
(const :tag "Toggle without closing the vterm window(focus other window)" nil)
(const :tag "Reset Window configration" reset-window-configration)
(const :tag "Bury All vterm buffer" bury-all-vterm-buffer)
(const :tag "Quit window" quit-window)
(const :tag "Delete window" delete-window)))
(defvar vterm-toggle--window-configration nil)
(defvar vterm-toggle--vterm-dedicated-buffer nil)
(defvar-local vterm-toggle--dedicated-p nil)
(defvar vterm-toggle--buffer-list nil
"The list of non-dedicated terminal buffers managed by `vterm-toggle'.")
(defvar-local vterm-toggle--cd-cmd nil)
;;;###autoload
(defun vterm-toggle(&optional args)
"Vterm toggle.
Optional argument ARGS ."
(interactive "P")
(cond
((or (derived-mode-p 'vterm-mode)
(and (vterm-toggle--get-window)
vterm-toggle-hide-method))
(if (equal (prefix-numeric-value args) 1)
(vterm-toggle-hide)
(vterm vterm-buffer-name)))
((equal (prefix-numeric-value args) 1)
(vterm-toggle-show))
((equal (prefix-numeric-value args) 4)
(let ((vterm-toggle-fullscreen-p
(not vterm-toggle-fullscreen-p)))
(vterm-toggle-show)))))
;;;###autoload
(defun vterm-toggle-cd(&optional args)
"Vterm toggle and insert a cd command.
Optional argument ARGS ."
(interactive "P")
(cond
((or (derived-mode-p 'vterm-mode)
(and (vterm-toggle--get-window)
vterm-toggle-hide-method))
(if (equal (prefix-numeric-value args) 1)
(vterm-toggle-hide)
(vterm-toggle-show t)))
((equal (prefix-numeric-value args) 1)
(vterm-toggle-show t))
((equal (prefix-numeric-value args) 4)
(let ((vterm-toggle-fullscreen-p
(not vterm-toggle-fullscreen-p)))
(vterm-toggle-show t)))))
(defun vterm-toggle-hide (&optional _args)
"Hide the vterm buffer."
(interactive "P")
(or (derived-mode-p 'vterm-mode)
(select-window (vterm-toggle--get-window)))
(run-hooks 'vterm-toggle-hide-hook)
(cond
((eq vterm-toggle-hide-method 'reset-window-configration)
(when vterm-toggle--window-configration
(set-window-configuration vterm-toggle--window-configration)))
((eq vterm-toggle-hide-method 'bury-all-vterm-buffer)
(vterm-toggle--bury-all-vterm))
((eq vterm-toggle-hide-method 'quit-window)
(quit-window))
((eq vterm-toggle-hide-method 'delete-window)
(if (window-deletable-p)
(delete-window)
(vterm-toggle--bury-all-vterm)))
((not vterm-toggle-hide-method)
(let ((buf (vterm-toggle--recent-other-buffer)))
(when buf
(if (get-buffer-window buf)
(select-window (get-buffer-window buf))
(if vterm-toggle-fullscreen-p
(switch-to-buffer buf)
(switch-to-buffer-other-window buf))))))))
(defun vterm-toggle--get-window()
"Get the vterm window which is visible (active or inactive)."
(cl-find-if #'(lambda(w)
(provided-mode-derived-p
(buffer-local-value 'major-mode (window-buffer w))
'vterm-mode))
(window-list)))
(defun vterm-toggle--bury-all-vterm ()
"Bury all vterm buffer in order."
(dolist (buf (buffer-list))
(when (eq (buffer-local-value 'major-mode buf) 'vterm-mode)
(with-current-buffer buf
(bury-buffer)))))
(defun vterm-toggle-tramp-get-method-parameter (method param)
"Return the method parameter PARAM.
If the `tramp-methods' entry does not exist, return NIL."
(let ((entry (assoc param (assoc method tramp-methods))))
(when entry (cadr entry))))
(when (version<= emacs-version "26.3")
(with-eval-after-load 'tramp-sh
(defun tramp-get-sh-extra-args (shell)
"Find extra args for SHELL."
(let ((alist tramp-sh-extra-args)
item extra-args)
(while (and alist (null extra-args))
(setq item (pop alist))
(when (string-match-p (car item) shell)
(setq extra-args (cdr item))))
extra-args))))
(defun vterm-toggle-cd-show(&optional args)
"Switch to an idle vterm buffer and insert a cd command.
Or create 1 new vterm buffer.
Optional argument ARGS optional args."
(interactive "P")
(vterm-toggle-show (not args)))
(defun vterm-toggle-show(&optional make-cd)
"Show the vterm buffer.
Optional argument MAKE-CD whether insert a cd command."
(interactive "P")
(when (eq vterm-toggle-scope 'projectile)
(warn "the value of `vterm-toggle-scope' is 'projectile, please change it to 'project"))
(let* ((shell-buffer (vterm-toggle--get-buffer
make-cd (not vterm-toggle-cd-auto-create-buffer)))
(dir (expand-file-name default-directory))
cd-cmd cur-host vterm-dir vterm-host)
(if (ignore-errors (file-remote-p dir))
(with-parsed-tramp-file-name dir nil
(setq cur-host host)
(setq dir localname))
(setq cur-host (system-name)))
(setq cd-cmd (concat " cd " (shell-quote-argument dir)))
(if shell-buffer
(progn
(when (and (not (derived-mode-p 'vterm-mode))
(not (get-buffer-window shell-buffer)))
(setq vterm-toggle--window-configration (current-window-configuration)))
(if vterm-toggle-fullscreen-p
(progn
(delete-other-windows)
(switch-to-buffer shell-buffer))
(if (eq major-mode 'vterm-mode)
(switch-to-buffer shell-buffer nil t)
(pop-to-buffer shell-buffer)))
(with-current-buffer shell-buffer
(when (derived-mode-p 'vterm-mode)
(setq vterm-toggle--cd-cmd cd-cmd)
(if (ignore-errors (file-remote-p default-directory))
(with-parsed-tramp-file-name default-directory nil
(setq vterm-dir localname)
(setq vterm-host host))
(setq vterm-dir default-directory)
(setq vterm-host (system-name)))
(when (and (not (equal vterm-dir dir))
(equal vterm-host cur-host) make-cd)
(vterm-send-key "a" nil nil :ctrl)
(vterm-send-key "k" nil nil :ctrl)
(sleep-for 0.01)
(if (vterm-toggle--in-cmd-buffer-p)
(vterm-toggle-insert-cd)
(message "You can insert '%s' by M-x:vterm-toggle-insert-cd."
vterm-toggle--cd-cmd))))
(run-hooks 'vterm-toggle-show-hook)))
(unless (eq major-mode 'vterm-mode)
(setq vterm-toggle--window-configration (current-window-configuration)))
(with-current-buffer (setq shell-buffer (vterm-toggle--new))
(vterm-toggle--wait-prompt)
(when vterm-toggle-fullscreen-p
(delete-other-windows))
(run-hooks 'vterm-toggle-show-hook)))
shell-buffer))
(defun vterm-toggle--wait-prompt()
"Wait prompt."
(let ((wait-ms 0))
(cl-loop until (or (> (length (string-trim
(buffer-substring-no-properties
(point-min) (point-max)))) 0)
(> wait-ms 3000)) do
(sleep-for 0.01)
(setq wait-ms (+ wait-ms 10)))))
;;;###autoload
(defun vterm-toggle-insert-cd()
"Cd to the directory where your previous buffer file exists.
after you have toggle to the vterm buffer with `vterm-toggle'."
(interactive)
(if (eq major-mode 'vterm-mode)
(when vterm-toggle--cd-cmd
(vterm-send-string vterm-toggle--cd-cmd t)
(vterm-send-return))
(call-interactively #'vterm-toggle-cd-show)))
(defun vterm-toggle--new(&optional buffer-name)
"New vterm buffer."
(let ((default-directory default-directory)
(buffer-name (or buffer-name vterm-buffer-name))
project-root)
(when (and vterm-toggle-project-root
(eq vterm-toggle-scope 'project))
(setq project-root (vterm-toggle--project-root))
(when project-root
(setq default-directory project-root)))
(if vterm-toggle-fullscreen-p
(vterm buffer-name)
(if (eq major-mode 'vterm-mode)
(let ((display-buffer-alist nil))
(vterm buffer-name))
(vterm-other-window buffer-name)))))
(defun vterm-toggle--get-buffer(&optional make-cd ignore-prompt-p)
"Get vterm buffer.
Optional argument MAKE-CD make cd or not.
Optional argument ARGS optional args."
(cond
((eq vterm-toggle-scope 'dedicated)
(vterm-toggle--get-dedicated-buffer))
((eq vterm-toggle-scope 'project)
(let* ((project-root (vterm-toggle--project-root))
(buf (vterm-toggle--recent-vterm-buffer
make-cd ignore-prompt-p project-root)))
buf))
(t
(vterm-toggle--recent-vterm-buffer make-cd ignore-prompt-p))))
(defun vterm-toggle--get-dedicated-buffer()
"Get dedicated buffer."
(if (buffer-live-p vterm-toggle--vterm-dedicated-buffer)
vterm-toggle--vterm-dedicated-buffer
(setq vterm-toggle--vterm-dedicated-buffer (vterm-toggle--new))
(with-current-buffer vterm-toggle--vterm-dedicated-buffer
(vterm-toggle--wait-prompt)
(setq vterm-toggle--dedicated-p t)
vterm-toggle--vterm-dedicated-buffer)))
(defun vterm-toggle--not-in-other-frame(frame buf)
(let ((win (get-buffer-window buf t)))
(if win
(eq frame (window-frame win))
t)))
(defun vterm-toggle--recent-vterm-buffer(&optional make-cd ignore-prompt-p dir)
"Get recent vterm buffer.
Optional argument MAKE-CD make cd or not.
Optional argument ARGS optional args."
(let ((shell-buffer)
(curbuf (current-buffer))
(curframe (window-frame))
buffer-host
vterm-host)
(if (ignore-errors (file-remote-p default-directory))
(with-parsed-tramp-file-name default-directory nil
(setq buffer-host host))
(setq buffer-host (system-name)))
(cl-loop for buf in (buffer-list) do
(with-current-buffer buf
(when (and (derived-mode-p 'vterm-mode)
(not (eq curbuf buf))
(not vterm-toggle--dedicated-p)
(or (not vterm-toggle-scope)
(and (eq vterm-toggle-scope 'frame)
(vterm-toggle--not-in-other-frame curframe buf))
(and (eq vterm-toggle-scope 'project)
(equal (vterm-toggle--project-root) dir))))
(cond
((and make-cd (derived-mode-p 'vterm-mode))
(if (ignore-errors (file-remote-p default-directory))
(with-parsed-tramp-file-name default-directory nil
(setq vterm-host host))
(setq vterm-host (system-name)))
(when (and (or ignore-prompt-p
(vterm-toggle--in-cmd-buffer-p))
(equal buffer-host vterm-host))
(setq shell-buffer buf)))
(t (setq shell-buffer buf)))))
until shell-buffer)
shell-buffer))
(defun vterm-toggle--in-cmd-buffer-p()
(when (vterm-cursor-in-command-buffer-p)
(or (eq (vterm--get-prompt-point) (vterm--get-cursor-point))
(and (vterm--backward-char)
(vterm--forward-char)))))
(defun vterm-toggle--project-root()
(require 'project)
(let ((proj (project-current)))
(when proj
(project-root proj))))
(defun vterm-toggle--recent-other-buffer(&optional _args)
"Get last viewed buffer.
Optional argument ARGS optional args."
(let (shell-buffer)
(cl-loop for buf in (buffer-list) do
(with-current-buffer buf
(when (and (not (derived-mode-p 'vterm-mode))
(not (char-equal ?\ (aref (buffer-name) 0))))
(setq shell-buffer buf)))
until shell-buffer)
shell-buffer))
(defun vterm-toggle--exit-hook()
"Vterm exit hook."
(when (derived-mode-p 'vterm-mode)
(setq vterm-toggle--buffer-list
(delq (current-buffer) vterm-toggle--buffer-list))
(if (eq vterm-toggle-reset-window-configration-after-exit 'kill-window-only)
(cond
((eq (window-deletable-p) 'frame)
(delete-frame))
((eq (window-deletable-p) t)
(delete-window))
(t
(quit-window)))
(when (and vterm-toggle-reset-window-configration-after-exit
vterm-toggle--window-configration)
(set-window-configuration vterm-toggle--window-configration)))))
(add-hook 'kill-buffer-hook #'vterm-toggle--exit-hook)
;; (add-hook 'vterm-exit-functions #'vterm-toggle--exit-hook)
(defun vterm-toggle--mode-hook()
"Hook for `vterm-mode-hook'."
(add-to-list 'vterm-toggle--buffer-list (current-buffer)))
(add-hook 'vterm-mode-hook #'vterm-toggle--mode-hook)
(dolist (buf (buffer-list))
(when (eq (buffer-local-value 'major-mode buf) 'vterm-mode)
(add-to-list 'vterm-toggle--buffer-list buf t)))
(defun vterm-toggle--switch (direction offset)
"Internal `vterm-toggle' buffers switch function.
If DIRECTION is `forward', switch to the next term.
If DIRECTION `backward', switch to the previous term.
Option OFFSET for skip OFFSET number term buffer."
(if vterm-toggle--buffer-list
(let ((buffer-list-len (length vterm-toggle--buffer-list))
(index (cl-position (current-buffer) vterm-toggle--buffer-list)))
(if index
(let ((target-index (if (eq direction 'forward)
(mod (+ index offset) buffer-list-len)
(mod (- index offset) buffer-list-len))))
(switch-to-buffer (nth target-index vterm-toggle--buffer-list)))
(switch-to-buffer (car vterm-toggle--buffer-list))))
(call-interactively 'vterm)))
;;;###autoload
(defun vterm-toggle-forward (&optional offset)
"Go to the next term buffer.
If OFFSET is `non-nil', will goto next term buffer with OFFSET."
(interactive "P")
(vterm-toggle--switch 'forward (or offset 1)))
;;;###autoload
(defun vterm-toggle-backward (&optional offset)
"Go to the previous term buffer.
If OFFSET is `non-nil', will goto next term buffer with OFFSET."
(interactive "P")
(vterm-toggle--switch 'backward (or offset 1)))
(provide 'vterm-toggle)
(require 'vterm nil t) ; https://github.com/jixiuf/vterm-toggle/issues/24
;; Local Variables:
;; coding: utf-8
;; End:
;;; vterm-toggle.el ends here