(setq helm-ff-preferred-shell-mode 'shell-mode) ;;; Shared global history. (defvar ambrevar/shell-history-global-ring nil "The history ring shared across shell sessions.") (defun ambrevar/shell-use-global-history () "Make shell history shared across different sessions." (unless ambrevar/shell-history-global-ring (when comint-input-ring-file-name (comint-read-input-ring)) (setq ambrevar/shell-history-global-ring (or comint-input-ring (make-ring comint-input-ring-size)))) (setq comint-input-ring ambrevar/shell-history-global-ring)) (defun ambrevar/shell-history-remove-duplicates () (require 'functions) ; For `ambrevar/ring-delete-first-item-duplicates'. (ambrevar/ring-delete-first-item-duplicates comint-input-ring)) (defvar ambrevar/comint-input-history-ignore (concat "^" (regexp-opt '("#" " " "cd "))) "`comint-input-history-ignore' can only be customized globally because `comint-read-input-ring' uses a temp buffer.") (defun ambrevar/shell-remove-ignored-inputs-from-ring () "Discard last command from history if it matches `ambrevar/comint-input-history-ignore'." (unless (ring-empty-p comint-input-ring) (when (string-match ambrevar/comint-input-history-ignore (ring-ref comint-input-ring 0)) (ring-remove comint-input-ring 0)))) (defun ambrevar/shell-sync-input-ring (_) (ambrevar/shell-history-remove-duplicates) (ambrevar/shell-remove-ignored-inputs-from-ring) (comint-write-input-ring)) (defun ambrevar/shell-setup () (setq comint-input-ring-file-name (expand-file-name "shell-history" user-emacs-directory)) (ambrevar/shell-use-global-history) ;; Write history on every command, not just on exit. (add-hook 'comint-input-filter-functions 'ambrevar/shell-sync-input-ring nil t) ;; Only ending with '#' or '$' but seems slower: ;; (setq comint-prompt-regexp "^[^#$]* ;; [^#$]*[#$>] +") (setq comint-prompt-regexp "^[^#$%>]* \[^#$%>]*[#$%>] +")) (add-hook 'shell-mode-hook 'ambrevar/shell-setup) (defun ambrevar/shell-prompt-begin-position () ;; We need this convoluted function because `looking-at-p' does not work on ;; multiline regexps _and_ `re-search-backward' skips the current line. (save-excursion (let ((old-point (point))) (max (save-excursion ;; Right result if not on prompt. (call-interactively #'comint-previous-prompt) (re-search-backward comint-prompt-regexp) (point)) (save-excursion ;; Right result if on first char after prompt. (re-search-backward comint-prompt-regexp) (point)) (save-excursion ;; Right result if on prompt. (call-interactively #'comint-next-prompt) (re-search-backward comint-prompt-regexp) (if (<= (point) old-point) (point) (point-min))))))) (defun ambrevar/shell-prompt-end-position () (save-excursion (goto-char (ambrevar/shell-prompt-begin-position)) (call-interactively #'comint-next-prompt) (point))) (defun ambrevar/shell-prompt () (buffer-substring-no-properties (ambrevar/shell-prompt-begin-position) (ambrevar/shell-prompt-end-position))) (defun ambrevar/shell-propertize-prompt () ; Inspired by `shx--propertize-prompt'. "Add a mouseover timestamp to the last prompt." (let ((inhibit-read-only t) (inhibit-field-text-motion t)) (add-text-properties (save-excursion (re-search-backward comint-prompt-regexp nil :noerror) (point)) (process-mark (get-buffer-process (current-buffer))) `(help-echo ,(format-time-string "%F %T"))))) (defun ambrevar/shell-send-input () "Send or parse the input currently written at the prompt. In normal circumstances this input is additionally filtered by `shx-filter-input' via `comint-mode'." (interactive) (ambrevar/shell-propertize-prompt) (comint-send-input)) (define-key shell-mode-map (kbd "") 'ambrevar/shell-send-input) (defun ambrevar/shell-command-duration () "Return duration of command at point in a `shell' buffer." (interactive) (let ((begin (ignore-errors (parse-time-string (get-text-property (ambrevar/shell-prompt-begin-position) 'help-echo)))) (end (parse-time-string (save-excursion (goto-char (ambrevar/shell-prompt-end-position)) (call-interactively #'comint-next-prompt) (ambrevar/shell-prompt))))) (if begin (message "Command took %.0f seconds." (- (float-time (apply 'encode-time end)) (float-time (apply 'encode-time begin)))) (message "No timestamp.")))) (defun ambrevar/shell-narrow-to-prompt () "Narrow buffer to prompt at point." (interactive) (let ((begin (ambrevar/shell-prompt-begin-position))) (narrow-to-region begin (save-excursion (goto-char (ambrevar/shell-prompt-end-position)) (call-interactively #'comint-next-prompt) (if (= begin (ambrevar/shell-prompt-begin-position)) (point-max) (ambrevar/shell-prompt-begin-position)))))) (define-key shell-mode-map (kbd "C-x n d") 'ambrevar/shell-narrow-to-prompt) (define-key shell-mode-map (kbd "C-x M-O") 'comint-truncate-buffer) (provide 'init-shell)