;;; cider-repl.el --- REPL interactions -*- lexical-binding: t -*- ;; Copyright © 2012-2013 Tim King, Phil Hagelberg, Bozhidar Batsov ;; Copyright © 2013-2016 Bozhidar Batsov, Artur Malabarba and CIDER contributors ;; ;; Author: Tim King ;; Phil Hagelberg ;; Bozhidar Batsov ;; Artur Malabarba ;; Hugo Duncan ;; Steve Purcell ;; 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. ;; You should have received a copy of the GNU General Public License ;; along with this program. If not, see . ;; This file is not part of GNU Emacs. ;;; Commentary: ;; REPL interactions. ;;; Code: (require 'cider-client) (require 'cider-doc) (require 'cider-test) (require 'cider-eldoc) ; for cider-eldoc-setup (require 'cider-common) (require 'cider-compat) (require 'cider-util) (require 'cider-resolve) (require 'clojure-mode) (require 'easymenu) (require 'cl-lib) (eval-when-compile (defvar paredit-version) (defvar paredit-space-for-delimiter-predicates)) (defgroup cider-repl nil "Interaction with the REPL." :prefix "cider-repl-" :group 'cider) (defface cider-repl-prompt-face '((t (:inherit font-lock-keyword-face))) "Face for the prompt in the REPL buffer." :group 'cider-repl) (defface cider-repl-stdout-face '((t (:inherit font-lock-string-face))) "Face for STDOUT output in the REPL buffer." :group 'cider-repl) (defface cider-repl-stderr-face '((t (:inherit font-lock-warning-face))) "Face for STDERR output in the REPL buffer." :group 'cider-repl :package-version '(cider . "0.6.0")) (defface cider-repl-input-face '((t (:bold t))) "Face for previous input in the REPL buffer." :group 'cider-repl) (defface cider-repl-result-face '((t ())) "Face for the result of an evaluation in the REPL buffer." :group 'cider-repl) (defcustom cider-repl-pop-to-buffer-on-connect t "Controls whether to pop to the REPL buffer on connect. When set to nil the buffer will only be created." :type 'boolean :group 'cider-repl) (defcustom cider-repl-display-in-current-window nil "Controls whether the REPL buffer is displayed in the current window." :type 'boolean :group 'cider-repl) (defcustom cider-repl-scroll-on-output t "Controls whether the REPL buffer auto-scolls on new output. When set to t (the default), if the REPL buffer contains more lines than the size of the window, the buffer is automatically re-centered upon completion of evaluating an expression, so that the bottom line of output is on the bottom line of the window. If this is set to nil, no re-centering takes place." :type 'boolean :group 'cider-repl :package-version '(cider . "0.11.0")) (defcustom cider-repl-use-pretty-printing nil "Control whether the results in REPL are pretty-printed or not. The `cider-toggle-pretty-printing' command can be used to interactively change the setting's value." :type 'boolean :group 'cider-repl) (defcustom cider-repl-use-clojure-font-lock t "Non-nil means to use Clojure mode font-locking for input and result. Nil means that `cider-repl-input-face' and `cider-repl-result-face' will be used." :type 'boolean :group 'cider-repl :package-version '(cider . "0.10.0")) (defcustom cider-repl-result-prefix "" "The prefix displayed in the REPL before a result value." :type 'string :group 'cider :package-version '(cider . "0.5.0")) (defcustom cider-repl-tab-command 'cider-repl-indent-and-complete-symbol "Select the command to be invoked by the TAB key. The default option is `cider-repl-indent-and-complete-symbol'. If you'd like to use the default Emacs behavior use `indent-for-tab-command'." :type 'symbol :group 'cider-repl) (defcustom cider-repl-display-help-banner t "When non-nil a bit of help text will be displayed on REPL start." :type 'boolean :group 'cider-repl :package-version '(cider . "0.11.0")) ;;;; REPL buffer local variables (defvar-local cider-repl-input-start-mark nil) (defvar-local cider-repl-prompt-start-mark nil) (defvar-local cider-repl-old-input-counter 0 "Counter used to generate unique `cider-old-input' properties. This property value must be unique to avoid having adjacent inputs be joined together.") (defvar-local cider-repl-input-history '() "History list of strings read from the REPL buffer.") (defvar-local cider-repl-input-history-items-added 0 "Variable counting the items added in the current session.") (defvar-local cider-repl-output-start nil "Marker for the start of output. Currently its only purpose is to facilitate `cider-repl-clear-buffer'.") (defvar-local cider-repl-output-end nil "Marker for the end of output. Currently its only purpose is to facilitate `cider-repl-clear-buffer'.") (defun cider-repl-tab () "Invoked on TAB keystrokes in `cider-repl-mode' buffers." (interactive) (funcall cider-repl-tab-command)) (defun cider-repl-reset-markers () "Reset all REPL markers." (dolist (markname '(cider-repl-output-start cider-repl-output-end cider-repl-prompt-start-mark cider-repl-input-start-mark)) (set markname (make-marker)) (set-marker (symbol-value markname) (point)))) ;;; REPL init (defvar-local cider-repl-ns-cache nil "A dict holding information about all currently loaded namespaces. This cache is stored in the connection buffer. Other buffer's access it via `cider-current-connection'.") (defvar cider-mode) (declare-function cider-refresh-dynamic-font-lock "cider-mode") (defun cider-repl--state-handler (response) "Handle the server state contained in RESPONSE. Currently, this is only used to keep `cider-repl-type' updated." (with-demoted-errors "Error in `cider-repl--state-handler': %s" (when (member "state" (nrepl-dict-get response "status")) (nrepl-dbind-response response (repl-type changed-namespaces) (when repl-type (setq cider-repl-type repl-type)) (unless (nrepl-dict-empty-p changed-namespaces) (setq cider-repl-ns-cache (nrepl-dict-merge cider-repl-ns-cache changed-namespaces)) (dolist (b (buffer-list)) (with-current-buffer b ;; Metadata changed, so signatures may have changed too. (setq cider-eldoc-last-symbol nil) (when (or cider-mode (derived-mode-p 'cider-repl-mode)) (when-let ((ns-dict (or (nrepl-dict-get changed-namespaces (cider-current-ns)) (let ((ns-dict (cider-resolve--get-in (cider-current-ns)))) (when (seq-find (lambda (ns) (nrepl-dict-get changed-namespaces ns)) (nrepl-dict-get ns-dict "aliases")) ns-dict))))) (cider-refresh-dynamic-font-lock ns-dict)))))))))) (declare-function cider-default-err-handler "cider-interaction") (defun cider-repl-create (endpoint) "Create a REPL buffer and install `cider-repl-mode'. ENDPOINT is a plist as returned by `nrepl-connect'." ;; Connection might not have been set as yet. Please don't send requests here. (let* ((reuse-buff (not (eq 'new nrepl-use-this-as-repl-buffer))) (buff-name (nrepl-make-buffer-name nrepl-repl-buffer-name-template nil (plist-get endpoint :host) (plist-get endpoint :port) reuse-buff))) ;; when reusing, rename the buffer accordingly (when (and reuse-buff (not (equal buff-name nrepl-use-this-as-repl-buffer))) ;; uniquify as it might be Nth connection to the same endpoint (setq buff-name (generate-new-buffer-name buff-name)) (with-current-buffer nrepl-use-this-as-repl-buffer (rename-buffer buff-name))) (with-current-buffer (get-buffer-create buff-name) (unless (derived-mode-p 'cider-repl-mode) (cider-repl-mode)) (setq nrepl-err-handler #'cider-default-err-handler) (cider-repl-reset-markers) (add-hook 'nrepl-response-handler-functions #'cider-repl--state-handler nil 'local) (add-hook 'nrepl-connected-hook 'cider--connected-handler nil 'local) (add-hook 'nrepl-disconnected-hook 'cider--disconnected-handler nil 'local) (current-buffer)))) (defun cider-repl-require-repl-utils () "Require standard REPL util functions into the current REPL." (interactive) (cider-nrepl-request:eval "(when (clojure.core/resolve 'clojure.main/repl-requires) (clojure.core/map clojure.core/require clojure.main/repl-requires))" (lambda (_response) nil))) (declare-function cider-set-buffer-ns "cider-mode") (defun cider-repl-set-initial-ns (buffer) "Set the REPL BUFFER's initial namespace (by altering `cider-buffer-ns'). This is \"user\" by default but can be overridden in apps like lein (:init-ns)." ;; we don't want to get a timeout during init (let ((nrepl-sync-request-timeout nil)) (with-current-buffer buffer (let ((initial-ns (or (read (nrepl-dict-get (cider-nrepl-sync-request:eval "(str *ns*)") "value")) "user"))) (cider-set-buffer-ns initial-ns))))) (defvar cider-current-clojure-buffer nil "This variable holds current buffer temporarily when connecting to a REPL. It is set to current buffer when `cider' or `cider-jack-in' is called. After the REPL buffer is created, the value of this variable is used to call `cider-remember-clojure-buffer'.") (declare-function cider-remember-clojure-buffer "cider-mode") (defun cider-repl-init (buffer &optional no-banner) "Initialize the REPL in BUFFER. BUFFER must be a REPL buffer with `cider-repl-mode' and a running client process connection. Unless NO-BANNER is non-nil, insert a banner." (cider-repl-set-initial-ns buffer) (cider-repl-require-repl-utils) (unless no-banner (cider-repl--insert-banner-and-prompt buffer)) (when cider-repl-display-in-current-window (add-to-list 'same-window-buffer-names (buffer-name buffer))) (when cider-repl-pop-to-buffer-on-connect (pop-to-buffer buffer)) (cider-remember-clojure-buffer cider-current-clojure-buffer) buffer) (defun cider-repl--banner () "Generate the welcome REPL buffer banner." (let ((host (cider--connection-host (current-buffer))) (port (cider--connection-port (current-buffer)))) (format ";; Connected to nREPL server - nrepl://%s:%s ;; CIDER %s, nREPL %s ;; Clojure %s, Java %s ;; Docs: (doc function-name) ;; (find-doc part-of-name) ;; Source: (source function-name) ;; Javadoc: (javadoc java-object-or-class) ;; Exit: ;; Results: Stored in vars *1, *2, *3, an exception in *e;" host port (cider--version) (cider--nrepl-version) (cider--clojure-version) (cider--java-version)))) (defun cider-repl--help-banner () "Generate the help banner." (substitute-command-keys "\n;; ====================================================================== ;; If you’re new to CIDER it is highly recommended to go through its ;; manual first. Type to view it. ;; In case you’re seeing any warnings you should consult the manual’s ;; \"Troubleshooting\" section. ;; ;; Here are few tips to get you started: ;; ;; * Press <\\[describe-mode]> to see a list of the keybindings available (this ;; will work in every Emacs buffer) ;; * Press <\\[cider-repl-handle-shortcut]> to quickly invoke some REPL command ;; * Press <\\[cider-switch-to-last-clojure-buffer]> to switch between the REPL and a Clojure file ;; * Press <\\[cider-find-var]> to jump to the source of something (e.g. a var, a ;; Java method) ;; * Press <\\[cider-doc]> to view the documentation for something (e.g. ;; a var, a Java method) ;; * Enable `eldoc-mode' to display function & method signatures in the minibuffer. ;; * Print CIDER's refcard and keep it close to your keyboard. ;; ;; CIDER is super customizable - try to ;; get a feel for this. If you’re thirsty for knowledge you should try ;; . ;; ;; If you think you’ve encountered a bug (or have some suggestions for ;; improvements) use to report it. ;; ;; Above all else - don’t panic! In case of an emergency - procure ;; some (hard) cider and enjoy it responsibly! ;; ;; You can remove this message with the `cider-repl-clear-help-banner' command. ;; You can disable it from appearing on start by setting ;; `cider-repl-display-help-banner' to nil. ;; ====================================================================== ")) (defun cider-repl--insert-banner-and-prompt (buffer) "Insert REPL banner and REPL prompt in BUFFER." (with-current-buffer buffer (when (zerop (buffer-size)) (insert (propertize (cider-repl--banner) 'font-lock-face 'font-lock-comment-face)) (when cider-repl-display-help-banner (insert (propertize (cider-repl--help-banner) 'font-lock-face 'font-lock-comment-face)))) (goto-char (point-max)) (cider-repl--mark-output-start) (cider-repl--mark-input-start) (cider-repl--insert-prompt cider-buffer-ns))) ;;; REPL interaction (defun cider-repl--in-input-area-p () "Return t if in input area." (<= cider-repl-input-start-mark (point))) (defun cider-repl--current-input (&optional until-point-p) "Return the current input as string. The input is the region from after the last prompt to the end of buffer. If UNTIL-POINT-P is non-nil, the input is until the current point." (buffer-substring-no-properties cider-repl-input-start-mark (if until-point-p (point) (point-max)))) (defun cider-repl-previous-prompt () "Move backward to the previous prompt." (interactive) (cider-repl--find-prompt t)) (defun cider-repl-next-prompt () "Move forward to the next prompt." (interactive) (cider-repl--find-prompt)) (defun cider-repl--find-prompt (&optional backward) "Find the next prompt. If BACKWARD is non-nil look backward." (let ((origin (point)) (cider-repl-prompt-property 'field)) (while (progn (cider-search-property-change cider-repl-prompt-property backward) (not (or (cider-end-of-proprange-p cider-repl-prompt-property) (bobp) (eobp))))) (unless (cider-end-of-proprange-p cider-repl-prompt-property) (goto-char origin)))) (defun cider-search-property-change (prop &optional backward) "Search forward for a property change to PROP. If BACKWARD is non-nil search backward." (cond (backward (goto-char (previous-single-char-property-change (point) prop))) (t (goto-char (next-single-char-property-change (point) prop))))) (defun cider-end-of-proprange-p (property) "Return t if at the the end of a property range for PROPERTY." (and (get-char-property (max (point-min) (1- (point))) property) (not (get-char-property (point) property)))) (defun cider-repl--mark-input-start () "Mark the input start." (set-marker cider-repl-input-start-mark (point) (current-buffer))) (defun cider-repl--mark-output-start () "Mark the output start." (set-marker cider-repl-output-start (point)) (set-marker cider-repl-output-end (point))) (defun cider-repl-mode-beginning-of-defun (&optional arg) "Move to the beginning of defun. If given a negative value of ARG, move to the end of defun." (if (and arg (< arg 0)) (cider-repl-mode-end-of-defun (- arg)) (dotimes (_ (or arg 1)) (cider-repl-previous-prompt)))) (defun cider-repl-mode-end-of-defun (&optional arg) "Move to the end of defun. If given a negative value of ARG, move to the beginning of defun." (if (and arg (< arg 0)) (cider-repl-mode-beginning-of-defun (- arg)) (dotimes (_ (or arg 1)) (cider-repl-next-prompt)))) (defun cider-repl-beginning-of-defun () "Move to beginning of defun." (interactive) ;; We call `beginning-of-defun' if we're at the start of a prompt ;; already, to trigger `cider-repl-mode-beginning-of-defun' by means ;; of the locally bound `beginning-of-defun-function', in order to ;; jump to the start of the previous prompt. (if (and (not (cider-repl--at-prompt-start-p)) (cider-repl--in-input-area-p)) (goto-char cider-repl-input-start-mark) (beginning-of-defun))) (defun cider-repl-end-of-defun () "Move to end of defun." (interactive) ;; C.f. `cider-repl-beginning-of-defun' (if (and (not (= (point) (point-max))) (cider-repl--in-input-area-p)) (goto-char (point-max)) (end-of-defun))) (defun cider-repl-bol-mark () "Set the mark and go to the beginning of line or the prompt." (interactive) (unless mark-active (set-mark (point))) (move-beginning-of-line 1)) (defun cider-repl--at-prompt-start-p () "Return t if point is at the start of prompt. This will not work on non-current prompts." (= (point) cider-repl-input-start-mark)) (defun cider-repl--show-maximum-output () "Put the end of the buffer at the bottom of the window." (when (and cider-repl-scroll-on-output (eobp)) (let ((win (get-buffer-window (current-buffer) t))) (when win (with-selected-window win (set-window-point win (point-max)) (recenter -1)))))) (defmacro cider-save-marker (marker &rest body) "Save MARKER and execute BODY." (declare (debug t)) (let ((pos (make-symbol "pos"))) `(let ((,pos (marker-position ,marker))) (prog1 (progn . ,body) (set-marker ,marker ,pos))))) (put 'cider-save-marker 'lisp-indent-function 1) (defun cider-repl-prompt-default (namespace) "Return a prompt string that mentions NAMESPACE." (format "%s> " namespace)) (defun cider-repl-prompt-abbreviated (namespace) "Return a prompt string that abbreviates NAMESPACE." (format "%s> " (cider-abbreviate-ns namespace))) (defun cider-repl-prompt-lastname (namespace) "Return a prompt string with the last name in NAMESPACE." (format "%s> " (cider-last-ns-segment namespace))) (defcustom cider-repl-prompt-function #'cider-repl-prompt-default "A function that returns a prompt string. Takes one argument, a namespace name. For convenience, three functions are already provided for this purpose: `cider-repl-prompt-lastname', `cider-repl-prompt-abbreviated', and `cider-repl-prompt-default'" :type '(choice (const :tag "Full namespace" cider-repl-prompt-default) (const :tag "Abbreviated namespace" cider-repl-prompt-abbreviated) (const :tag "Last name in namespace" cider-repl-prompt-lastname) (function :tag "Custom function")) :group 'cider-repl :package-version '(cider . "0.9.0")) (defun cider-repl--insert-prompt (namespace) "Insert the prompt (before markers!), taking into account NAMESPACE. Set point after the prompt. Return the position of the prompt beginning." (goto-char cider-repl-input-start-mark) (cider-save-marker cider-repl-output-start (cider-save-marker cider-repl-output-end (unless (bolp) (insert-before-markers "\n")) (let ((prompt-start (point)) (prompt (funcall cider-repl-prompt-function namespace))) (cider-propertize-region '(font-lock-face cider-repl-prompt-face read-only t intangible t field cider-repl-prompt rear-nonsticky (field read-only font-lock-face intangible)) (insert-before-markers prompt)) (set-marker cider-repl-prompt-start-mark prompt-start) prompt-start)))) (defun cider-repl--emit-output-at-pos (buffer string output-face position &optional bol) "Using BUFFER, insert STRING (applying to it OUTPUT-FACE) at POSITION. If BOL is non-nil insert at the beginning of line." (with-current-buffer buffer (save-excursion (cider-save-marker cider-repl-output-start (cider-save-marker cider-repl-output-end (goto-char position) ;; TODO: Review the need for bol (when (and bol (not (bolp))) (insert-before-markers "\n")) (insert-before-markers (ansi-color-apply (propertize string 'font-lock-face output-face 'rear-nonsticky '(font-lock-face)))) (when (and (= (point) cider-repl-prompt-start-mark) (not (bolp))) (insert-before-markers "\n") (set-marker cider-repl-output-end (1- (point))))))) (cider-repl--show-maximum-output))) (defun cider-repl--emit-interactive-output (string face) "Emit STRING as interactive output using FACE." (with-current-buffer (cider-current-repl-buffer) (let ((pos (cider-repl--end-of-line-before-input-start)) (string (replace-regexp-in-string "\n\\'" "" string))) (cider-repl--emit-output-at-pos (current-buffer) string face pos t)))) (defun cider-repl-emit-interactive-stdout (string) "Emit STRING as interactive output." (cider-repl--emit-interactive-output string 'cider-repl-stdout-face)) (defun cider-repl-emit-interactive-stderr (string) "Emit STRING as interactive err output." (cider-repl--emit-interactive-output string 'cider-repl-stderr-face)) (defun cider-repl-manual-warning (section-id format &rest args) "Emit a warning to the REPL and link to the online manual. SECTION-ID is the section to link to. The link is added on the last line. FORMAT is a format string to compile with ARGS and display on the REPL." (let ((message (apply #'format format args))) (cider-repl-emit-interactive-stderr (concat "WARNING: " message "\n " (cider--manual-button "More information" section-id) ".")))) (defun cider-repl--emit-output (buffer string face &optional bol) "Using BUFFER, emit STRING font-locked with FACE. If BOL is non-nil, emit at the beginning of the line." (with-current-buffer buffer (cider-repl--emit-output-at-pos buffer string face cider-repl-input-start-mark bol))) (defun cider-repl-emit-stdout (buffer string) "Using BUFFER, emit STRING as standard output." (cider-repl--emit-output buffer string 'cider-repl-stdout-face)) (defun cider-repl-emit-stderr (buffer string) "Using BUFFER, emit STRING as error output." (cider-repl--emit-output buffer string 'cider-repl-stderr-face)) (defun cider-repl-emit-prompt (buffer) "Emit the REPL prompt into BUFFER." (with-current-buffer buffer (save-excursion (cider-save-marker cider-repl-output-start (cider-save-marker cider-repl-output-end (cider-repl--insert-prompt cider-buffer-ns)))) (cider-repl--show-maximum-output))) (defun cider-repl-emit-result (buffer string &optional bol) "Emit into BUFFER the result STRING and mark it as an evaluation result. If BOL is non-nil insert at the beginning of the line." (with-current-buffer buffer (save-excursion (cider-save-marker cider-repl-output-start (cider-save-marker cider-repl-output-end (goto-char cider-repl-input-start-mark) (when (and bol (not (bolp))) (insert-before-markers "\n")) (insert-before-markers (propertize cider-repl-result-prefix 'font-lock-face 'font-lock-comment-face)) (if cider-repl-use-clojure-font-lock (insert-before-markers (cider-font-lock-as-clojure string)) (cider-propertize-region '(font-lock-face cider-repl-result-face rear-nonsticky (font-lock-face)) (insert-before-markers string)))))) (cider-repl--show-maximum-output))) (defun cider-repl-newline-and-indent () "Insert a newline, then indent the next line. Restrict the buffer from the prompt for indentation, to avoid being confused by strange characters (like unmatched quotes) appearing earlier in the buffer." (interactive) (save-restriction (narrow-to-region cider-repl-prompt-start-mark (point-max)) (insert "\n") (lisp-indent-line))) (defun cider-repl-indent-and-complete-symbol () "Indent the current line and perform symbol completion. First indent the line. If indenting doesn't move point, complete the symbol." (interactive) (let ((pos (point))) (lisp-indent-line) (when (= pos (point)) (if (save-excursion (re-search-backward "[^() \n\t\r]+\\=" nil t)) (completion-at-point))))) (defun cider-repl-kill-input () "Kill all text from the prompt to point." (interactive) (cond ((< (marker-position cider-repl-input-start-mark) (point)) (kill-region cider-repl-input-start-mark (point))) ((= (point) (marker-position cider-repl-input-start-mark)) (cider-repl-delete-current-input)))) (defun cider-repl--input-complete-p (start end) "Return t if the region from START to END is a complete sexp." (save-excursion (goto-char start) (cond ((looking-at-p "\\s *[@'`#]?[(\"]") (ignore-errors (save-restriction (narrow-to-region start end) ;; Keep stepping over blanks and sexps until the end of ;; buffer is reached or an error occurs. Tolerate extra ;; close parens. (cl-loop do (skip-chars-forward " \t\r\n)") until (eobp) do (forward-sexp)) t))) (t t)))) (defun cider-repl-handler (buffer) "Make an nREPL evaluation handler for the REPL BUFFER." (nrepl-make-response-handler buffer (lambda (buffer value) (cider-repl-emit-result buffer value t)) (lambda (buffer out) (cider-repl-emit-stdout buffer out)) (lambda (buffer err) (cider-repl-emit-stderr buffer err)) (lambda (buffer) (cider-repl-emit-prompt buffer)) nrepl-err-handler (lambda (buffer pprint-out) (cider-repl-emit-result buffer pprint-out nil)))) (defun cider-repl--send-input (&optional newline) "Go to the end of the input and send the current input. If NEWLINE is true then add a newline at the end of the input." (unless (cider-repl--in-input-area-p) (error "No input at point")) (goto-char (point-max)) (let ((end (point))) ; end of input, without the newline (cider-repl--add-to-input-history (buffer-substring cider-repl-input-start-mark end)) (when newline (insert "\n") (cider-repl--show-maximum-output)) (let ((inhibit-modification-hooks t)) (add-text-properties cider-repl-input-start-mark (point) `(cider-old-input ,(cl-incf cider-repl-old-input-counter)))) (unless cider-repl-use-clojure-font-lock (let ((overlay (make-overlay cider-repl-input-start-mark end))) ;; These properties are on an overlay so that they won't be taken ;; by kill/yank. (overlay-put overlay 'read-only t) (overlay-put overlay 'font-lock-face 'cider-repl-input-face)))) (let ((input (cider-repl--current-input)) (input-start (save-excursion (cider-repl-beginning-of-defun) (point)))) (goto-char (point-max)) (cider-repl--mark-input-start) (cider-repl--mark-output-start) (cider-nrepl-request:eval input (cider-repl-handler (current-buffer)) (cider-current-ns) (line-number-at-pos input-start) (cider-column-number-at-pos input-start) (unless (or (not cider-repl-use-pretty-printing) (string-match-p "\\`[ \t\r\n]*\\'" input)) (cider--nrepl-pprint-request-plist (1- (window-width))))))) (defun cider-repl-return (&optional end-of-input) "Evaluate the current input string, or insert a newline. Send the current input ony if a whole expression has been entered, i.e. the parenthesis are matched. When END-OF-INPUT is non-nil, send the input even if the parentheses are not balanced." (interactive "P") (cond (end-of-input (cider-repl--send-input)) ((and (get-text-property (point) 'cider-old-input) (< (point) cider-repl-input-start-mark)) (cider-repl--grab-old-input end-of-input) (cider-repl--recenter-if-needed)) ((cider-repl--input-complete-p cider-repl-input-start-mark (point-max)) (cider-repl--send-input t)) (t (cider-repl-newline-and-indent) (message "[input not complete]")))) (defun cider-repl--recenter-if-needed () "Make sure that the point is visible." (unless (pos-visible-in-window-p (point-max)) (save-excursion (goto-char (point-max)) (recenter -1)))) (defun cider-repl--grab-old-input (replace) "Resend the old REPL input at point. If REPLACE is non-nil the current input is replaced with the old input; otherwise the new input is appended. The old input has the text property `cider-old-input'." (cl-multiple-value-bind (beg end) (cider-property-bounds 'cider-old-input) (let ((old-input (buffer-substring beg end)) ;;preserve ;;properties, they will be removed later (offset (- (point) beg))) ;; Append the old input or replace the current input (cond (replace (goto-char cider-repl-input-start-mark)) (t (goto-char (point-max)) (unless (eq (char-before) ?\ ) (insert " ")))) (delete-region (point) (point-max)) (save-excursion (insert old-input) (when (equal (char-before) ?\n) (delete-char -1))) (forward-char offset)))) (defun cider-repl-closing-return () "Evaluate the current input string after closing all open lists." (interactive) (goto-char (point-max)) (save-restriction (narrow-to-region cider-repl-input-start-mark (point)) (while (ignore-errors (save-excursion (backward-up-list 1)) t) (insert ")"))) (cider-repl-return)) (defun cider-repl-toggle-pretty-printing () "Toggle pretty-printing in the REPL." (interactive) (setq cider-repl-use-pretty-printing (not cider-repl-use-pretty-printing)) (message "Pretty printing in REPL %s." (if cider-repl-use-pretty-printing "enabled" "disabled"))) (defun cider-repl-switch-to-other () "Switch between the Clojure and ClojureScript REPLs for the current project." (interactive) (if-let (other-connection (cider-other-connection)) (switch-to-buffer other-connection) (message "There's no other REPL for the current project"))) (defvar cider-repl-clear-buffer-hook) (defun cider-repl--clear-region (start end) "Delete the output and its overlays between START and END." (mapc #'delete-overlay (overlays-in start end)) (delete-region start end)) (defun cider-repl-clear-buffer () "Delete the output generated by the Clojure process." (interactive) (let ((inhibit-read-only t)) (cider-repl--clear-region (point-min) cider-repl-prompt-start-mark) (cider-repl--clear-region cider-repl-output-start cider-repl-output-end) (when (< (point) cider-repl-input-start-mark) (goto-char cider-repl-input-start-mark)) (recenter t)) (run-hooks 'cider-repl-clear-buffer-hook)) (defun cider-repl--end-of-line-before-input-start () "Return the position of the end of the line preceding the beginning of input." (1- (previous-single-property-change cider-repl-input-start-mark 'field nil (1+ (point-min))))) (defun cider-repl-clear-output (&optional clear-repl) "Delete the output inserted since the last input. With a prefix argument CLEAR-REPL it will clear the entire REPL buffer instead." (interactive "P") (if clear-repl (cider-repl-clear-buffer) (let ((start (save-excursion (cider-repl-previous-prompt) (ignore-errors (forward-sexp)) (forward-line) (point))) (end (cider-repl--end-of-line-before-input-start))) (when (< start end) (let ((inhibit-read-only t)) (cider-repl--clear-region start end) (save-excursion (goto-char start) (insert (propertize ";; output cleared" 'font-lock-face 'font-lock-comment-face)))))))) (defun cider-repl-clear-banners () "Delete the REPL banners." (interactive) ;; TODO: Improve the boundaries detecting logic ;; probably it should be based on text properties ;; the current implemetation will clear warnings as well (let ((start (point-min)) (end (save-excursion (goto-char (point-min)) (cider-repl-next-prompt) (forward-line -1) (end-of-line) (point)))) (when (< start end) (let ((inhibit-read-only t)) (cider-repl--clear-region start (1+ end)))))) (defun cider-repl-clear-help-banner () "Delete the help REPL banner." (interactive) ;; TODO: Improve the boundaries detecting logic ;; probably it should be based on text properties (let ((start (save-excursion (goto-char (point-min)) (search-forward ";; =") (beginning-of-line) (point))) (end (save-excursion (goto-char (point-min)) (cider-repl-next-prompt) (search-backward ";; =") (end-of-line) (point)))) (when (< start end) (let ((inhibit-read-only t)) (cider-repl--clear-region start (1+ end)))))) (defun cider-repl-switch-ns-handler (buffer) "Make an nREPL evaluation handler for the REPL BUFFER's ns switching." (nrepl-make-response-handler buffer (lambda (_buffer _value)) (lambda (buffer out) (cider-repl-emit-stdout buffer out)) (lambda (buffer err) (cider-repl-emit-stderr buffer err)) (lambda (buffer) (cider-repl-emit-prompt buffer)))) (defun cider-repl-set-ns (ns) "Switch the namespace of the REPL buffer to NS. If called from a cljc or cljx buffer act on both the Clojure and ClojureScript REPL if there are more than one REPL present. If invoked in a REPL buffer the command will prompt for the name of the namespace to switch to." (interactive (list (if (or (derived-mode-p 'cider-repl-mode) (null (cider-ns-form))) (completing-read "Switch to namespace: " (cider-sync-request:ns-list)) (cider-current-ns)))) (when (or (not ns) (equal ns "")) (user-error "No namespace selected")) (cider-map-connections (lambda (connection) (cider-nrepl-request:eval (format "(in-ns '%s)" ns) (cider-repl-switch-ns-handler connection))) :both)) ;;;;; History (defcustom cider-repl-wrap-history nil "T to wrap history around when the end is reached." :type 'boolean :group 'cider-repl) ;; These two vars contain the state of the last history search. We ;; only use them if `last-command' was `cider-repl--history-replace', ;; otherwise we reinitialize them. (defvar cider-repl-input-history-position -1 "Newer items have smaller indices.") (defvar cider-repl-history-pattern nil "The regexp most recently used for finding input history.") (defun cider-repl--add-to-input-history (string) "Add STRING to the input history. Empty strings and duplicates are ignored." (unless (or (equal string "") (equal string (car cider-repl-input-history))) (push string cider-repl-input-history) (cl-incf cider-repl-input-history-items-added))) (defun cider-repl-delete-current-input () "Delete all text after the prompt." (goto-char (point-max)) (delete-region cider-repl-input-start-mark (point-max))) (defun cider-repl--replace-input (string) "Replace the current REPL input with STRING." (cider-repl-delete-current-input) (insert-and-inherit string)) (defun cider-repl--position-in-history (start-pos direction regexp) "Return the position of the history item starting at START-POS. Search in DIRECTION for REGEXP. Return -1 resp the length of the history if no item matches." ;; Loop through the history list looking for a matching line (let* ((step (cl-ecase direction (forward -1) (backward 1))) (history cider-repl-input-history) (len (length history))) (cl-loop for pos = (+ start-pos step) then (+ pos step) if (< pos 0) return -1 if (<= len pos) return len if (string-match-p regexp (nth pos history)) return pos))) (defun cider-repl--history-replace (direction &optional regexp) "Replace the current input with the next line in DIRECTION. DIRECTION is 'forward' or 'backward' (in the history list). If REGEXP is non-nil, only lines matching REGEXP are considered." (setq cider-repl-history-pattern regexp) (let* ((min-pos -1) (max-pos (length cider-repl-input-history)) (pos0 (cond ((cider-history-search-in-progress-p) cider-repl-input-history-position) (t min-pos))) (pos (cider-repl--position-in-history pos0 direction (or regexp ""))) (msg nil)) (cond ((and (< min-pos pos) (< pos max-pos)) (cider-repl--replace-input (nth pos cider-repl-input-history)) (setq msg (format "History item: %d" pos))) ((not cider-repl-wrap-history) (setq msg (cond ((= pos min-pos) "End of history") ((= pos max-pos) "Beginning of history")))) (cider-repl-wrap-history (setq pos (if (= pos min-pos) max-pos min-pos)) (setq msg "Wrapped history"))) (when (or (<= pos min-pos) (<= max-pos pos)) (when regexp (setq msg (concat msg "; no matching item")))) (message "%s%s" msg (cond ((not regexp) "") (t (format "; current regexp: %s" regexp)))) (setq cider-repl-input-history-position pos) (setq this-command 'cider-repl--history-replace))) (defun cider-history-search-in-progress-p () "Return t if a current history search is in progress." (eq last-command 'cider-repl--history-replace)) (defun cider-terminate-history-search () "Terminate the current history search." (setq last-command this-command)) (defun cider-repl-previous-input () "Cycle backwards through input history. If the `last-command' was a history navigation command use the same search pattern for this command. Otherwise use the current input as search pattern." (interactive) (cider-repl--history-replace 'backward (cider-repl-history-pattern t))) (defun cider-repl-next-input () "Cycle forwards through input history. See `cider-previous-input'." (interactive) (cider-repl--history-replace 'forward (cider-repl-history-pattern t))) (defun cider-repl-forward-input () "Cycle forwards through input history." (interactive) (cider-repl--history-replace 'forward (cider-repl-history-pattern))) (defun cider-repl-backward-input () "Cycle backwards through input history." (interactive) (cider-repl--history-replace 'backward (cider-repl-history-pattern))) (defun cider-repl-previous-matching-input (regexp) "Find the previous input matching REGEXP." (interactive "sPrevious element matching (regexp): ") (cider-terminate-history-search) (cider-repl--history-replace 'backward regexp)) (defun cider-repl-next-matching-input (regexp) "Find then next input matching REGEXP." (interactive "sNext element matching (regexp): ") (cider-terminate-history-search) (cider-repl--history-replace 'forward regexp)) (defun cider-repl-history-pattern (&optional use-current-input) "Return the regexp for the navigation commands. If USE-CURRENT-INPUT is non-nil, use the current input." (cond ((cider-history-search-in-progress-p) cider-repl-history-pattern) (use-current-input (cl-assert (<= cider-repl-input-start-mark (point))) (let ((str (cider-repl--current-input t))) (cond ((string-match-p "^[ \n]*$" str) nil) (t (concat "^" (regexp-quote str)))))) (t nil))) ;;; persistent history (defcustom cider-repl-history-size 500 "The maximum number of items to keep in the REPL history." :type 'integer :safe 'integerp :group 'cider-repl) (defcustom cider-repl-history-file nil "File to save the persistent REPL history to." :type 'string :safe 'stringp :group 'cider-repl) (defun cider-repl--history-read-filename () "Ask the user which file to use, defaulting `cider-repl-history-file'." (read-file-name "Use CIDER REPL history file: " cider-repl-history-file)) (defun cider-repl--history-read (filename) "Read history from FILENAME and return it. It does not yet set the input history." (if (file-readable-p filename) (with-temp-buffer (insert-file-contents filename) (when (> (buffer-size (current-buffer)) 0) (read (current-buffer)))) '())) (defun cider-repl-history-load (&optional filename) "Load history from FILENAME into current session. FILENAME defaults to the value of `cider-repl-history-file' but user defined filenames can be used to read special history files. The value of `cider-repl-input-history' is set by this function." (interactive (list (cider-repl--history-read-filename))) (let ((f (or filename cider-repl-history-file))) ;; TODO: probably need to set cider-repl-input-history-position as well. ;; in a fresh connection the newest item in the list is currently ;; not available. After sending one input, everything seems to work. (setq cider-repl-input-history (cider-repl--history-read f)))) (defun cider-repl--history-write (filename) "Write history to FILENAME. Currently coding system for writing the contents is hardwired to utf-8-unix." (let* ((mhist (cider-repl--histories-merge cider-repl-input-history cider-repl-input-history-items-added (cider-repl--history-read filename))) ;; newest items are at the beginning of the list, thus 0 (hist (cl-subseq mhist 0 (min (length mhist) cider-repl-history-size)))) (unless (file-writable-p filename) (error (format "History file not writable: %s" filename))) (let ((print-length nil) (print-level nil)) (with-temp-file filename ;; TODO: really set cs for output ;; TODO: does cs need to be customizable? (insert ";; -*- coding: utf-8-unix -*-\n") (insert ";; Automatically written history of CIDER REPL session\n") (insert ";; Edit at your own risk\n\n") (prin1 (mapcar #'substring-no-properties hist) (current-buffer)))))) (defun cider-repl-history-save (&optional filename) "Save the current REPL input history to FILENAME. FILENAME defaults to the value of `cider-repl-history-file'." (interactive (list (cider-repl--history-read-filename))) (let* ((file (or filename cider-repl-history-file))) (cider-repl--history-write file))) (defun cider-repl-history-just-save () "Just save the history to `cider-repl-history-file'. This function is meant to be used in hooks to avoid lambda constructs." (cider-repl-history-save cider-repl-history-file)) ;; SLIME has different semantics and will not save any duplicates. ;; we keep track of how many items were added to the history in the ;; current session in `cider-repl--add-to-input-history' and merge only the ;; new items with the current history found in the file, which may ;; have been changed in the meantime by another session. (defun cider-repl--histories-merge (session-hist n-added-items file-hist) "Merge histories from SESSION-HIST adding N-ADDED-ITEMS into FILE-HIST." (append (cl-subseq session-hist 0 n-added-items) file-hist)) ;;; REPL shortcuts (defcustom cider-repl-shortcut-dispatch-char ?\, "Character used to distinguish REPL commands from Lisp forms." :type '(character) :group 'cider-repl) (defvar cider-repl-shortcuts (make-hash-table :test 'equal)) (defun cider-repl-add-shortcut (name handler) "Add a REPL shortcut command, defined by NAME and HANDLER." (puthash name handler cider-repl-shortcuts)) (declare-function cider-restart "cider-interaction") (declare-function cider-quit "cider-interaction") (declare-function cider-toggle-trace-ns "cider-interaction") (declare-function cider-undef "cider-interaction") (declare-function cider-browse-ns "cider-browse-ns") (declare-function cider-classpath "cider-classpath") (declare-function cider-run "cider-interaction") (declare-function cider-refresh "cider-interaction") (cider-repl-add-shortcut "clear-output" #'cider-repl-clear-output) (cider-repl-add-shortcut "clear" #'cider-repl-clear-buffer) (cider-repl-add-shortcut "clear-banners" #'cider-repl-clear-banners) (cider-repl-add-shortcut "clear-help-banner" #'cider-repl-clear-help-banner) (cider-repl-add-shortcut "ns" #'cider-repl-set-ns) (cider-repl-add-shortcut "toggle-pretty" #'cider-repl-toggle-pretty-printing) (cider-repl-add-shortcut "browse-ns" (lambda () (cider-browse-ns (cider-current-ns)))) (cider-repl-add-shortcut "classpath" #'cider-classpath) (cider-repl-add-shortcut "trace-ns" #'cider-toggle-trace-ns) (cider-repl-add-shortcut "undef" #'cider-undef) (cider-repl-add-shortcut "refresh" #'cider-refresh) (cider-repl-add-shortcut "help" #'cider-repl-shortcuts-help) (cider-repl-add-shortcut "test-ns" #'cider-test-run-ns-tests) (cider-repl-add-shortcut "test-all" #'cider-test-run-loaded-tests) (cider-repl-add-shortcut "test-project" #'cider-test-run-project-tests) (cider-repl-add-shortcut "test-report" #'cider-test-show-report) (cider-repl-add-shortcut "run" #'cider-run) (cider-repl-add-shortcut "conn-info" #'cider-display-connection-info) (cider-repl-add-shortcut "conn-rotate" #'cider-rotate-default-connection) (cider-repl-add-shortcut "hasta la vista" #'cider-quit) (cider-repl-add-shortcut "adios" #'cider-quit) (cider-repl-add-shortcut "sayonara" #'cider-quit) (cider-repl-add-shortcut "quit" #'cider-quit) (cider-repl-add-shortcut "restart" #'cider-restart) (cider-repl-add-shortcut "version" #'cider-version) (defconst cider-repl-shortcuts-help-buffer "*CIDER REPL Shortcuts Help*") (defun cider-repl-shortcuts-help () "Display a help buffer." (interactive) (ignore-errors (kill-buffer cider-repl-shortcuts-help-buffer)) (with-current-buffer (get-buffer-create cider-repl-shortcuts-help-buffer) (insert "CIDER REPL shortcuts:\n\n") (maphash (lambda (k v) (insert (format "%s:\n\t%s\n" k v))) cider-repl-shortcuts) (goto-char (point-min)) (help-mode) (display-buffer (current-buffer) t)) (cider-repl-handle-shortcut) (current-buffer)) (defun cider-repl--available-shortcuts () "Return the available REPL shortcuts." (cider-util--hash-keys cider-repl-shortcuts)) (defun cider-repl-handle-shortcut () "Execute a REPL shortcut." (interactive) (if (> (point) cider-repl-input-start-mark) (insert (string cider-repl-shortcut-dispatch-char)) (let ((command (completing-read "Command: " (cider-repl--available-shortcuts)))) (if (not (equal command "")) (let ((command-func (gethash command cider-repl-shortcuts))) (if command-func (call-interactively (gethash command cider-repl-shortcuts)) (error "Unknown command %S. Available commands: %s" command-func (mapconcat 'identity (cider-repl--available-shortcuts) ", ")))) (error "No command selected"))))) ;;;;; CIDER REPL mode (defvar cider-repl-mode-hook nil "Hook executed when entering `cider-repl-mode'.") (defvar cider-repl-mode-syntax-table (copy-syntax-table clojure-mode-syntax-table)) (declare-function cider-eval-region "cider-interaction") (declare-function cider-eval-last-sexp "cider-interaction") (declare-function cider-refresh "cider-interaction") (declare-function cider-toggle-trace-ns "cider-interaction") (declare-function cider-toggle-trace-var "cider-interaction") (declare-function cider-find-resource "cider-interaction") (declare-function cider-restart "cider-interaction") (declare-function cider-find-ns "cider-interaction") (declare-function cider-switch-to-last-clojure-buffer "cider-mode") (defvar cider-repl-mode-map (let ((map (make-sparse-keymap))) (define-key map (kbd "C-c C-d") 'cider-doc-map) (define-key map (kbd "C-c ,") 'cider-test-commands-map) (define-key map (kbd "C-c C-t") 'cider-test-commands-map) (define-key map (kbd "M-.") #'cider-find-var) (define-key map (kbd "C-c C-.") #'cider-find-ns) (define-key map (kbd "M-,") #'cider-pop-back) (define-key map (kbd "C-c M-.") #'cider-find-resource) (define-key map (kbd "RET") #'cider-repl-return) (define-key map (kbd "TAB") #'cider-repl-tab) (define-key map (kbd "C-") #'cider-repl-closing-return) (define-key map (kbd "C-j") #'cider-repl-newline-and-indent) (define-key map (kbd "C-c C-o") #'cider-repl-clear-output) (define-key map (kbd "C-c M-n") #'cider-repl-set-ns) (define-key map (kbd "C-c C-u") #'cider-repl-kill-input) (define-key map (kbd "C-S-a") #'cider-repl-bol-mark) (define-key map [S-home] #'cider-repl-bol-mark) (define-key map (kbd "C-") #'cider-repl-backward-input) (define-key map (kbd "C-") #'cider-repl-forward-input) (define-key map (kbd "M-p") #'cider-repl-previous-input) (define-key map (kbd "M-n") #'cider-repl-next-input) (define-key map (kbd "M-r") #'cider-repl-previous-matching-input) (define-key map (kbd "M-s") #'cider-repl-next-matching-input) (define-key map (kbd "C-c C-n") #'cider-repl-next-prompt) (define-key map (kbd "C-c C-p") #'cider-repl-previous-prompt) (define-key map (kbd "C-c C-b") #'cider-interrupt) (define-key map (kbd "C-c C-c") #'cider-interrupt) (define-key map (kbd "C-c C-m") #'cider-macroexpand-1) (define-key map (kbd "C-c M-m") #'cider-macroexpand-all) (define-key map (kbd "C-c C-z") #'cider-switch-to-last-clojure-buffer) (define-key map (kbd "C-c M-o") #'cider-repl-switch-to-other) (define-key map (kbd "C-c M-s") #'cider-selector) (define-key map (kbd "C-c M-d") #'cider-display-connection-info) (define-key map (kbd "C-c C-q") #'cider-quit) (define-key map (kbd "C-c M-i") #'cider-inspect) (define-key map (kbd "C-c M-t v") #'cider-toggle-trace-var) (define-key map (kbd "C-c M-t n") #'cider-toggle-trace-ns) (define-key map (kbd "C-c C-x") #'cider-refresh) (define-key map (kbd "C-x C-e") #'cider-eval-last-sexp) (define-key map (kbd "C-c C-r") #'cider-eval-region) (define-key map (string cider-repl-shortcut-dispatch-char) #'cider-repl-handle-shortcut) (easy-menu-define cider-repl-mode-menu map "Menu for CIDER's REPL mode" `("REPL" ["Complete symbol" complete-symbol] "--" ,cider-doc-menu "--" ("Find" ["Find definition" cider-find-var] ["Find resource" cider-find-resource] ["Go back" cider-pop-back]) "--" ["Switch to Clojure buffer" cider-switch-to-last-clojure-buffer] ["Switch to other REPL" cider-repl-switch-to-other] "--" ("Macroexpand" ["Macroexpand-1" cider-macroexpand-1] ["Macroexpand-all" cider-macroexpand-all]) "--" ,cider-test-menu "--" ["Run project (-main function)" cider-run] ["Inspect" cider-inspect] ["Toggle var tracing" cider-toggle-trace-var] ["Toggle ns tracing" cider-toggle-trace-ns] ["Refresh loaded code" cider-refresh] "--" ["Set REPL ns" cider-repl-set-ns] ["Toggle pretty printing" cider-repl-toggle-pretty-printing] "--" ["Browse classpath" cider-classpath] ["Browse classpath entry" cider-open-classpath-entry] ["Browse namespace" cider-browse-ns] ["Browse all namespaces" cider-browse-ns-all] "--" ["Next prompt" cider-repl-next-prompt] ["Previous prompt" cider-repl-previous-prompt] ["Clear output" cider-repl-clear-output] ["Clear buffer" cider-repl-clear-buffer] ["Clear banners" cider-repl-clear-banners] ["Clear help banner" cider-repl-clear-help-banner] ["Kill input" cider-repl-kill-input] "--" ["Interrupt evaluation" cider-interrupt] "--" ["Connection info" cider-display-connection-info] "--" ["Close ancillary buffers" cider-close-ancillary-buffers] ["Quit" cider-quit] ["Restart" cider-restart] "--" ["A sip of CIDER" cider-drink-a-sip] ["View manual online" cider-view-manual] ["View refcard online" cider-view-refcard] ["Report a bug" cider-report-bug] ["Version info" cider-version])) map)) (defun cider-repl-wrap-fontify-function (func) "Return a function that will call FUNC narrowed to input region." (lambda (beg end &rest rest) (when (and cider-repl-input-start-mark (> end cider-repl-input-start-mark)) (save-restriction (narrow-to-region cider-repl-input-start-mark (point-max)) (let ((font-lock-dont-widen t)) (apply func (max beg cider-repl-input-start-mark) end rest)))))) (declare-function cider-complete-at-point "cider-interaction") (defvar cider--static-font-lock-keywords) (define-derived-mode cider-repl-mode fundamental-mode "REPL" "Major mode for Clojure REPL interactions. \\{cider-repl-mode-map}" (clojure-mode-variables) (setq-local lisp-indent-function #'clojure-indent-function) (setq-local indent-line-function #'lisp-indent-line) (clojure-font-lock-setup) (font-lock-add-keywords nil cider--static-font-lock-keywords) (setq-local font-lock-fontify-region-function (cider-repl-wrap-fontify-function font-lock-fontify-region-function)) (setq-local font-lock-unfontify-region-function (cider-repl-wrap-fontify-function font-lock-unfontify-region-function)) (make-local-variable 'completion-at-point-functions) (add-to-list 'completion-at-point-functions #'cider-complete-at-point) (set-syntax-table cider-repl-mode-syntax-table) (cider-eldoc-setup) ;; At the REPL, we define beginning-of-defun and end-of-defun to be ;; the start of the previous prompt or next prompt respectively. ;; Notice the interplay with `cider-repl-beginning-of-defun'. (setq-local beginning-of-defun-function #'cider-repl-mode-beginning-of-defun) (setq-local end-of-defun-function #'cider-repl-mode-end-of-defun) (setq-local prettify-symbols-alist clojure--prettify-symbols-alist) ;; apply dir-local variables to REPL buffers (hack-dir-local-variables-non-file-buffer) (when cider-repl-history-file (cider-repl-history-load cider-repl-history-file) (add-hook 'kill-buffer-hook #'cider-repl-history-just-save t t) (add-hook 'kill-emacs-hook #'cider-repl-history-just-save)) (add-hook 'paredit-mode-hook (lambda () (clojure-paredit-setup cider-repl-mode-map)))) (provide 'cider-repl) ;;; cider-repl.el ends here