From c3b0c093b46759abb776e61b5d66d590c6cffb17 Mon Sep 17 00:00:00 2001 From: Bozhidar Batsov Date: Wed, 16 Oct 2013 19:28:06 +0300 Subject: Rename project to CIDER --- cider-repl.el | 864 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 864 insertions(+) create mode 100644 cider-repl.el (limited to 'cider-repl.el') diff --git a/cider-repl.el b/cider-repl.el new file mode 100644 index 00000000..f8c5290a --- /dev/null +++ b/cider-repl.el @@ -0,0 +1,864 @@ +;;; cider-repl-mode.el --- REPL interactions + +;; Copyright © 2012-2013 Tim King, Phil Hagelberg +;; Copyright © 2013 Bozhidar Batsov, Hugo Duncan, Steve Purcell +;; +;; Author: Tim King +;; Phil Hagelberg +;; Bozhidar Batsov +;; 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 'nrepl-client) +(require 'cider-interaction) +(require 'cider-version) + +(defgroup cider-repl nil + "Interaction with the REPL." + :prefix "cider-repl-" + :group 'nrepl) + +(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-output-face + '((t (:inherit font-lock-string-face))) + "Face for output in the REPL buffer." + :group 'cider-repl) + +(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-popup-stacktraces nil + "Non-nil means pop-up error stacktraces in the REPL buffer. +Nil means show only an error message in the minibuffer. This variable +overrides `cider-popup-stacktraces' in REPL buffers." + :type 'boolean + :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-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-tab-command 'cider-indent-and-complete-symbol + "Select the command to be invoked by the TAB key. +The default option is `cider-indent-and-complete-symbol'. If +you'd like to use the default Emacs behavior use +`indent-for-tab-command'." + :type 'symbol + :group 'cider-repl) + +;;;; REPL buffer local variables +(defvar cider-input-start-mark) + +(defvar cider-prompt-start-mark) + +(defvar cider-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 cider-input-history '() + "History list of strings read from the nREPL buffer.") + +(defvar cider-input-history-items-added 0 + "Variable counting the items added in the current session.") + +(defvar cider-output-start nil + "Marker for the start of output.") + +(defvar cider-output-end nil + "Marker for the end of output.") + +(nrepl-make-variables-buffer-local + 'cider-input-start-mark + 'cider-prompt-start-mark + 'cider-old-input-counter + 'cider-input-history + 'cider-input-history-items-added + 'cider-output-start + 'cider-output-end) + +(defun cider-tab () + "Invoked on TAB keystrokes in `cider-repl-mode' buffers." + (interactive) + (funcall cider-repl-tab-command)) + +(defun cider-reset-markers () + "Reset all REPL markers." + (dolist (markname '(cider-output-start + cider-output-end + cider-prompt-start-mark + cider-input-start-mark)) + (set markname (make-marker)) + (set-marker (symbol-value markname) (point)))) + +(defmacro cider-propertize-region (props &rest body) + "Add PROPS to all text inserted by executing BODY. +More precisely, PROPS are added to the region between the point's +positions before and after executing BODY." + (let ((start (make-symbol "start-pos"))) + `(let ((,start (point))) + (prog1 (progn ,@body) + (add-text-properties ,start (point) ,props))))) + +(put 'cider-propertize-region 'lisp-indent-function 1) + +;;; REPL init +(defun cider-repl-buffer-name () + "Generate a REPL buffer name based on current connection buffer." + (with-current-buffer (get-buffer (nrepl-current-connection-buffer)) + (nrepl-buffer-name nrepl-repl-buffer-name-template))) + +(defun cider-create-repl-buffer (process) + "Create a REPL buffer for PROCESS." + (cider-init-repl-buffer + process + (let ((buffer-name (cider-repl-buffer-name))) + (if cider-repl-pop-to-buffer-on-connect + (pop-to-buffer buffer-name) + (generate-new-buffer buffer-name)) + buffer-name))) + +(defun cider-make-repl (process) + "Make a REPL for the connection PROCESS." + (let ((connection-buffer (process-buffer process)) + (repl-buffer (cider-create-repl-buffer process))) + (with-current-buffer repl-buffer + (setq nrepl-connection-buffer (buffer-name connection-buffer))) + (with-current-buffer connection-buffer + (setq nrepl-repl-buffer (buffer-name repl-buffer))))) + +;;; Words of inspiration +(defun cider-user-first-name () + "Find the current user's first name." + (let ((name (if (string= (user-full-name) "") + (user-login-name) + (user-full-name)))) + (string-match "^[^ ]*" name) + (capitalize (match-string 0 name)))) + +(defvar cider-words-of-inspiration + `("The best way to predict the future is to invent it. -Alan Kay" + "A point of view is worth 80 IQ points. -Alan Kay" + "Lisp isn't a language, it's a building material. -Alan Kay" + "Simple things should be simple, complex things should be possible. -Alan Kay" + "Measuring programming progress by lines of code is like measuring aircraft building progress by weight. -Bill Gates" + "Controlling complexity is the essence of computer programming. -Brian Kernighan" + "The unavoidable price of reliability is simplicity. -C.A.R. Hoare" + "You're bound to be unhappy if you optimize everything. -Donald Knuth" + "Simplicity is prerequisite for reliability. -Edsger W. Dijkstra" + "Deleted code is debugged code. -Jeff Sickel" + "The key to performance is elegance, not battalions of special cases. -Jon Bentley and Doug McIlroy" + "First, solve the problem. Then, write the code. -John Johnson" + "Simplicity is the ultimate sophistication. -Leonardo da Vinci" + "Programming is not about typing... it's about thinking. -Rich Hickey" + "Design is about pulling things apart. -Rich Hickey" + "Programmers know the benefits of everything and the tradeoffs of nothing. -Rich Hickey" + "Code never lies, comments sometimes do. -Ron Jeffries" + "Take this nREPL, brother, and may it serve you well." + "Let the hacking commence!" + "Hacks and glory await!" + "Hack and be merry!" + "Your hacking starts... NOW!" + "May the Source be with you!" + "May the Source shine upon thy nREPL!" + "Code long and prosper!" + "Happy hacking!" + ,(format "%s, this could be the start of a beautiful program." + (cider-user-first-name))) + "Scientifically-proven optimal words of hackerish encouragement.") + +(defun cider-random-words-of-inspiration () + "Select a random entry from `cider-words-of-inspiration'." + (eval (nth (random (length cider-words-of-inspiration)) + cider-words-of-inspiration))) + +(defun cider--banner () + "Generate the welcome REPL buffer banner." + (format "; CIDER %s (Clojure %s, nREPL %s)" + (cider-version) + (cider--clojure-version) + (cider--backend-version))) + +(defun cider-insert-banner-and-prompt (ns) + "Insert REPL banner and REPL prompt, taking into account NS." + (when (zerop (buffer-size)) + (insert (propertize (cider--banner) 'face 'font-lock-comment-face))) + (goto-char (point-max)) + (cider-mark-output-start) + (cider-mark-input-start) + (cider-insert-prompt ns)) + + +(defun cider-init-repl-buffer (connection buffer &optional noprompt) + "Initialize the REPL for CONNECTION in BUFFER. +Insert a banner, unless NOPROMPT is non-nil." + (with-current-buffer buffer + (unless (eq major-mode 'cider-repl-mode) + (cider-repl-mode)) + ;; use the same requires by default as clojure.main does + (nrepl-send-string-sync nrepl-repl-requires-sexp) + (cider-reset-markers) + (unless noprompt + (cider-insert-banner-and-prompt nrepl-buffer-ns)) + (cider-remember-clojure-buffer cider-current-clojure-buffer) + (current-buffer))) + +(defun cider-find-or-create-repl-buffer () + "Return the REPL buffer, create it if necessary." + (let ((buffer (nrepl-current-repl-buffer))) + (if (null buffer) + (error "No active nREPL connection") + (let ((buffer (get-buffer buffer))) + (or (when (buffer-live-p buffer) buffer) + (let ((buffer (nrepl-current-connection-buffer))) + (if (null buffer) + (error "No active nREPL connection") + (cider-init-repl-buffer + (get-process buffer) + (get-buffer-create + (cider-repl-buffer-name)))))))))) + + +;;; REPL interaction +(defun cider-property-bounds (prop) + "Return the the positions of the previous and next change to PROP. +PROP is the name of a text property." + (assert (get-text-property (point) prop)) + (let ((end (next-single-char-property-change (point) prop))) + (list (previous-single-char-property-change end prop) end))) + +(defun cider-in-input-area-p () + "Return t if in input area." + (<= cider-input-start-mark (point))) + +(defun cider-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-input-start-mark + (if until-point-p + (point) + (point-max)))) + +(defun cider-previous-prompt () + "Move backward to the previous prompt." + (interactive) + (cider-find-prompt t)) + +(defun cider-next-prompt () + "Move forward to the next prompt." + (interactive) + (cider-find-prompt)) + +(defun cider-find-prompt (&optional backward) + "Find the next prompt. +If BACKWARD is non-nil look backward." + (let ((origin (point)) + (prop 'cider-prompt)) + (while (progn + (cider-search-property-change prop backward) + (not (or (cider-end-of-proprange-p prop) (bobp) (eobp))))) + (unless (cider-end-of-proprange-p prop) + (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 1 (1- (point))) property) + (not (get-char-property (point) property)))) + +(defun cider-mark-input-start () + "Mark the input start." + (set-marker cider-input-start-mark (point) (current-buffer))) + +(defun cider-mark-output-start () + "Mark the output start." + (set-marker cider-output-start (point)) + (set-marker cider-output-end (point))) + +(defun cider-mark-output-end () + "Marke the output end." + (add-text-properties cider-output-start cider-output-end + '(face cider-repl-output-face + rear-nonsticky (face)))) + +;;;;; History + +(defcustom cider-wrap-history nil + "T to wrap history around when the end is reached." + :type 'boolean + :group 'nrepl) + +;; These two vars contain the state of the last history search. We +;; only use them if `last-command' was 'cider-history-replace, +;; otherwise we reinitialize them. + +(defvar cider-input-history-position -1 + "Newer items have smaller indices.") + +(defvar cider-history-pattern nil + "The regexp most recently used for finding input history.") + +(defun cider-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-input-history))) + (push string cider-input-history) + (incf cider-input-history-items-added))) + +(defun cider-delete-current-input () + "Delete all text after the prompt." + (interactive) + (goto-char (point-max)) + (delete-region cider-input-start-mark (point-max))) + +(defun cider-replace-input (string) + "Replace the current REPL input with STRING." + (cider-delete-current-input) + (insert-and-inherit string)) + +(defun cider-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 (ecase direction + (forward -1) + (backward 1))) + (history cider-input-history) + (len (length history))) + (loop for pos = (+ start-pos step) then (+ pos step) + if (< pos 0) return -1 + if (<= len pos) return len + if (string-match regexp (nth pos history)) return pos))) + +(defun cider-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-history-pattern regexp) + (let* ((min-pos -1) + (max-pos (length cider-input-history)) + (pos0 (cond ((cider-history-search-in-progress-p) + cider-input-history-position) + (t min-pos))) + (pos (cider-position-in-history pos0 direction (or regexp ""))) + (msg nil)) + (cond ((and (< min-pos pos) (< pos max-pos)) + (cider-replace-input (nth pos cider-input-history)) + (setq msg (format "History item: %d" pos))) + ((not cider-wrap-history) + (setq msg (cond ((= pos min-pos) "End of history") + ((= pos max-pos) "Beginning of history")))) + (cider-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-input-history-position pos) + (setq this-command 'cider-history-replace))) + +(defun cider-history-search-in-progress-p () + "Return t if a current history search is in progress." + (eq last-command 'cider-history-replace)) + +(defun cider-terminate-history-search () + "Terminate the current history search." + (setq last-command this-command)) + +(defun cider-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-history-replace 'backward (cider-history-pattern t))) + +(defun cider-next-input () + "Cycle forwards through input history. +See `cider-previous-input'." + (interactive) + (cider-history-replace 'forward (cider-history-pattern t))) + +(defun cider-forward-input () + "Cycle forwards through input history." + (interactive) + (cider-history-replace 'forward (cider-history-pattern))) + +(defun cider-backward-input () + "Cycle backwards through input history." + (interactive) + (cider-history-replace 'backward (cider-history-pattern))) + +(defun cider-previous-matching-input (regexp) + "Find the previous input matching REGEXP." + (interactive "sPrevious element matching (regexp): ") + (cider-terminate-history-search) + (cider-history-replace 'backward regexp)) + +(defun cider-next-matching-input (regexp) + "Find then next input matching REGEXP." + (interactive "sNext element matching (regexp): ") + (cider-terminate-history-search) + (cider-history-replace 'forward regexp)) + +(defun cider-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-history-pattern) + (use-current-input + (assert (<= cider-input-start-mark (point))) + (let ((str (cider-current-input t))) + (cond ((string-match "^[ \n]*$" str) nil) + (t (concat "^" (regexp-quote str)))))) + (t nil))) + +;;; persistent history +(defcustom cider-history-size 500 + "The maximum number of items to keep in the REPL history." + :type 'integer + :safe 'integerp + :group 'cider-repl-mode) + +(defcustom cider-history-file nil + "File to save the persistent REPL history to." + :type 'string + :safe 'stringp + :group 'cider-repl-mode) + +(defun cider-history-read-filename () + "Ask the user which file to use, defaulting `cider-history-file'." + (read-file-name "Use nREPL history file: " + cider-history-file)) + +(defun cider-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) + (read (current-buffer))) + '())) + +(defun cider-history-load (&optional filename) + "Load history from FILENAME into current session. +FILENAME defaults to the value of `cider-history-file' but user +defined filenames can be used to read special history files. + +The value of `cider-input-history' is set by this function." + (interactive (list (cider-history-read-filename))) + (let ((f (or filename cider-history-file))) + ;; TODO: probably need to set cider-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-input-history (cider-history-read f)))) + +(defun cider-history-write (filename) + "Write history to FILENAME. +Currently coding system for writing the contents is hardwired to +utf-8-unix." + (let* ((mhist (cider-histories-merge cider-input-history + cider-input-history-items-added + (cider-history-read filename))) + ;; newest items are at the beginning of the list, thus 0 + (hist (cl-subseq mhist 0 (min (length mhist) cider-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 nREPL session\n") + (insert ";; Edit at your own risk\n\n") + (prin1 (mapcar #'substring-no-properties hist) (current-buffer)))))) + +(defun cider-history-save (&optional filename) + "Save the current nREPL input history to FILENAME. +FILENAME defaults to the value of `cider-history-file'." + (interactive (list (cider-history-read-filename))) + (let* ((file (or filename cider-history-file))) + (cider-history-write file))) + +(defun cider-history-just-save () + "Just save the history to `cider-history-file'. +This function is meant to be used in hooks to avoid lambda +constructs." + (cider-history-save cider-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-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-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)) + +;;; +(defun cider-same-line-p (pos1 pos2) + "Return t if buffer positions POS1 and POS2 are on the same line." + (save-excursion (goto-char (min pos1 pos2)) + (<= (max pos1 pos2) (line-end-position)))) + +(defun cider-bol-internal () + "Go to the beginning of line or the prompt." + (cond ((and (>= (point) cider-input-start-mark) + (cider-same-line-p (point) cider-input-start-mark)) + (goto-char cider-input-start-mark)) + (t (beginning-of-line 1)))) + +(defun cider-bol () + "Go to the beginning of line or the prompt." + (interactive) + (deactivate-mark) + (cider-bol-internal)) + +(defun cider-bol-mark () + "Set the mark and go to the beginning of line or the prompt." + (interactive) + (unless mark-active + (set-mark (point))) + (cider-bol-internal)) + +(defun cider-at-prompt-start-p () + "Return t if point is at the start of prompt. +This will not work on non-current prompts." + (= (point) cider-input-start-mark)) + +(defun cider-show-maximum-output () + "Put the end of the buffer at the bottom of the window." + (when (eobp) + (let ((win (get-buffer-window (current-buffer)))) + (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." + (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-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-input-start-mark) + (cider-save-marker cider-output-start + (cider-save-marker cider-output-end + (unless (bolp) (insert-before-markers "\n")) + (let ((prompt-start (point)) + (prompt (format "%s> " namespace))) + (cider-propertize-region + '(face cider-repl-prompt-face read-only t intangible t + cider-prompt t + rear-nonsticky (cider-prompt read-only face intangible)) + (insert-before-markers prompt)) + (set-marker cider-prompt-start-mark prompt-start) + prompt-start)))) + +(defun cider-emit-output-at-pos (buffer string position &optional bol) + "Using BUFFER, insert STRING at POSITION and mark it as output. +If BOL is non-nil insert at the beginning of line." + (with-current-buffer buffer + (save-excursion + (cider-save-marker cider-output-start + (cider-save-marker cider-output-end + (goto-char position) + (when (and bol (not (bolp))) (insert-before-markers "\n")) + (cider-propertize-region `(face cider-repl-output-face + rear-nonsticky (face)) + (insert-before-markers string) + (when (and (= (point) cider-prompt-start-mark) + (not (bolp))) + (insert-before-markers "\n") + (set-marker cider-output-end (1- (point)))))))) + (cider-show-maximum-output))) + +(defun cider-emit-interactive-output (string) + "Emit STRING as interactive output." + (with-current-buffer (nrepl-current-repl-buffer) + (let ((pos (1- (cider-input-line-beginning-position)))) + (cider-emit-output-at-pos (current-buffer) string pos t) + (ansi-color-apply-on-region pos (point-max)) + ))) + +(defun cider-emit-output (buffer string &optional bol) + "Using BUFFER, emit STRING. +If BOL is non-nil, emit at the beginning of the line." + (with-current-buffer buffer + (cider-emit-output-at-pos buffer string cider-input-start-mark bol))) + +(defun cider-emit-prompt (buffer) + "Emit the REPL prompt into BUFFER." + (with-current-buffer buffer + (save-excursion + (cider-save-marker cider-output-start + (cider-save-marker cider-output-end + (cider-insert-prompt nrepl-buffer-ns)))) + (cider-show-maximum-output))) + +(defun cider-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-output-start + (cider-save-marker cider-output-end + (goto-char cider-input-start-mark) + (when (and bol (not (bolp))) (insert-before-markers "\n")) + (cider-propertize-region `(face cider-repl-result-face + rear-nonsticky (face)) + (insert-before-markers string))))) + (cider-show-maximum-output))) + + +(defun cider-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-prompt-start-mark (point-max)) + (insert "\n") + (lisp-indent-line))) + +(defun cider-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-kill-input () + "Kill all text from the prompt to point." + (interactive) + (cond ((< (marker-position cider-input-start-mark) (point)) + (kill-region cider-input-start-mark (point))) + ((= (point) (marker-position cider-input-start-mark)) + (cider-delete-current-input)))) + +(defun cider-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 "\\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. + (loop do (skip-chars-forward " \t\r\n)") + until (eobp) + do (forward-sexp)) + t))) + (t t)))) + +(defun cider-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-in-input-area-p) + (error "No input at point")) + (goto-char (point-max)) + (let ((end (point))) ; end of input, without the newline + (cider-add-to-input-history (buffer-substring cider-input-start-mark end)) + (when newline + (insert "\n") + (cider-show-maximum-output)) + (let ((inhibit-modification-hooks t)) + (add-text-properties cider-input-start-mark + (point) + `(cider-old-input + ,(incf cider-old-input-counter)))) + (let ((overlay (make-overlay cider-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 'face 'cider-repl-input-face))) + (let* ((input (cider-current-input)) + (form (if (and (not (string-match "\\`[ \t\r\n]*\\'" input)) cider-repl-use-pretty-printing) + (format "(clojure.pprint/pprint %s)" input) input))) + (goto-char (point-max)) + (cider-mark-input-start) + (cider-mark-output-start) + (nrepl-send-string form (cider-handler (current-buffer)) nrepl-buffer-ns))) + +(defun cider-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-send-input)) + ((and (get-text-property (point) 'cider-old-input) + (< (point) cider-input-start-mark)) + (cider-grab-old-input end-of-input) + (cider-recenter-if-needed)) + ((cider-input-complete-p cider-input-start-mark (point-max)) + (cider-send-input t)) + (t + (cider-newline-and-indent) + (message "[input not complete]")))) + +(defun cider-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-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'." + (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-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-closing-return () + "Evaluate the current input string after closing all open lists." + (interactive) + (goto-char (point-max)) + (save-restriction + (narrow-to-region cider-input-start-mark (point)) + (while (ignore-errors (save-excursion (backward-up-list 1)) t) + (insert ")"))) + (cider-return)) + +(defun cider-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 nREPL %s." + (if cider-repl-use-pretty-printing "enabled" "disabled"))) + +(defvar cider-clear-buffer-hook) + +(defun cider-clear-buffer () + "Delete the output generated by the Clojure process." + (interactive) + (let ((inhibit-read-only t)) + (delete-region (point-min) cider-prompt-start-mark) + (delete-region cider-output-start cider-output-end) + (when (< (point) cider-input-start-mark) + (goto-char cider-input-start-mark)) + (recenter t)) + (run-hooks 'cider-clear-buffer-hook)) + +(defun cider-find-and-clear-repl-buffer () + "Find the current REPL buffer and clear it. +Returns to the buffer in which the command was invoked." + (interactive) + (let ((origin-buffer (current-buffer))) + (switch-to-buffer (nrepl-current-repl-buffer)) + (cider-clear-buffer) + (switch-to-buffer origin-buffer))) + +(defun cider-input-line-beginning-position () + "Return the position of the beginning of input." + (save-excursion + (goto-char cider-input-start-mark) + (line-beginning-position))) + +(defun cider-clear-output () + "Delete the output inserted since the last input." + (interactive) + (let ((start (save-excursion + (cider-previous-prompt) + (ignore-errors (forward-sexp)) + (forward-line) + (point))) + (end (1- (cider-input-line-beginning-position)))) + (when (< start end) + (let ((inhibit-read-only t)) + (delete-region start end) + (save-excursion + (goto-char start) + (insert + (propertize ";;; output cleared" 'face 'font-lock-comment-face))))))) + +(provide 'cider-repl) +;;; cider-repl.el ends here -- cgit v1.2.3