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 --- Cask | 2 +- Makefile | 2 +- README.md | 112 ++--- cider-eldoc.el | 121 ++++++ cider-interaction-mode.el | 116 +++++ cider-interaction.el | 1043 +++++++++++++++++++++++++++++++++++++++++++++ cider-macroexpansion.el | 159 +++++++ cider-repl-mode.el | 156 +++++++ cider-repl.el | 864 +++++++++++++++++++++++++++++++++++++ cider-selector.el | 140 ++++++ cider-version.el | 76 ++++ cider.el | 82 ++++ nrepl-client.el | 26 +- nrepl-eldoc.el | 121 ------ nrepl-interaction-mode.el | 116 ----- nrepl-interaction.el | 1035 -------------------------------------------- nrepl-macroexpansion.el | 159 ------- nrepl-repl-mode.el | 156 ------- nrepl-repl.el | 864 ------------------------------------- nrepl-selector.el | 137 ------ nrepl-version.el | 77 ---- nrepl.el | 70 --- test/nrepl-tests.el | 186 ++++---- test/run-tests | 12 +- 24 files changed, 2921 insertions(+), 2911 deletions(-) create mode 100644 cider-eldoc.el create mode 100644 cider-interaction-mode.el create mode 100644 cider-interaction.el create mode 100644 cider-macroexpansion.el create mode 100644 cider-repl-mode.el create mode 100644 cider-repl.el create mode 100644 cider-selector.el create mode 100644 cider-version.el create mode 100644 cider.el delete mode 100644 nrepl-eldoc.el delete mode 100644 nrepl-interaction-mode.el delete mode 100644 nrepl-interaction.el delete mode 100644 nrepl-macroexpansion.el delete mode 100644 nrepl-repl-mode.el delete mode 100644 nrepl-repl.el delete mode 100644 nrepl-selector.el delete mode 100644 nrepl-version.el delete mode 100644 nrepl.el diff --git a/Cask b/Cask index 01487502..dbfe5246 100644 --- a/Cask +++ b/Cask @@ -1,6 +1,6 @@ (source melpa) -(package "nrepl" "2.0.0" "Emacs Lisp client for nREPL") +(package "cider" "2.0.0" "Emacs Lisp client for nREPL") (depends-on "clojure-mode" "2.0.0") (depends-on "dash" "1.6.0") diff --git a/Makefile b/Makefile index 01f542f5..d413f95f 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ EMACSFLAGS = CASK = cask VAGRANT = vagrant -OBJECTS = nrepl.elc +OBJECTS = cider.elc elpa: $(CASK) install diff --git a/README.md b/README.md index 39dc2865..5a6268a7 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ -[![Build Status](https://travis-ci.org/clojure-emacs/nrepl.el.png?branch=master)](https://travis-ci.org/clojure-emacs/nrepl.el) +[![Build Status](https://travis-ci.org/clojure-emacs/cider.png?branch=master)](https://travis-ci.org/clojure-emacs/cider.el) -# nrepl.el +# CIDER (formerly nrepl.el) -`nrepl.el` is an Emacs client for +`CIDER` is Clojure IDE and REPL for Emacs, built on top of [nREPL](https://github.com/clojure/tools.nrepl), the Clojure networked REPL server. It's a great alternative to the now deprecated combination of SLIME + [swank-clojure](https://github.com/technomancy/swank-clojure). @@ -14,9 +14,9 @@ of SLIME + [swank-clojure](https://github.com/technomancy/swank-clojure). `package.el` is the built-in package manager in Emacs 24+. On Emacs 23 you will need to get [package.el](http://bit.ly/pkg-el23) yourself if you wish to use it. -`nrepl.el` is available on both major `package.el` community +`CIDER` is available on both major `package.el` community maintained repos - -[Marmalade](http://marmalade-repo.org/packages/nrepl) and +[Marmalade](http://marmalade-repo.org/packages/cider) and [MELPA](http://melpa.milkbox.net). If you're not already using Marmalade, add this to your @@ -38,15 +38,15 @@ For MELPA the code you need to add is: (package-initialize) ``` -And then you can install `nrepl.el` with the following command: +And then you can install `CIDER` with the following command: -M-x package-install [RET] nrepl [RET] +M-x package-install [RET] cider [RET] or by adding this bit of Emacs Lisp code to your Emacs initialization file(`.emacs` or `init.el`): ```el -(unless (package-installed-p 'nrepl) - (package-install 'nrepl)) +(unless (package-installed-p 'cider) + (package-install 'cider)) ``` If the installation doesn't work try refreshing the package list: @@ -60,39 +60,39 @@ If you're an el-get user just do M-x el-get-install. ### Manual -You can install `nrepl.el` manually by placing `nrepl.el` on your `load-path` +You can install `CIDER` manually by placing `CIDER` on your `load-path` and `require`ing it. Many people favour the folder `~/.emacs.d/vendor`: ```el (add-to-list 'load-path "~/emacs.d/vendor") -(require 'nrepl) +(require 'cider) ``` -Keep in mind that `nrepl.el` depends on `clojure-mode`, `dash.el` and +Keep in mind that `CIDER` depends on `clojure-mode`, `dash.el` and `pkg-info` so you'll have to install them as well. ### Emacs Prelude -`nrepl.el` comes bundled in +`CIDER` comes bundled in [Emacs Prelude](https://github.com/bbatsov/prelude). If you're a Prelude user you can start using it right away. ### Emacs Live -`nrepl.el` comes bundled in +`CIDER` comes bundled in [Emacs Live](https://github.com/overtone/emacs-live). If you're using Emacs Live you're already good to go. ## Configuration -You can certainly use `nrepl.el` without configuring it any further, -but here are some ways other folks are adjusting their `nrepl.el` +You can certainly use `CIDER` without configuring it any further, +but here are some ways other folks are adjusting their `CIDER` experience. * Enable `eldoc` in Clojure buffers: ```el -(add-hook 'nrepl-interaction-mode-hook 'nrepl-turn-on-eldoc-mode) +(add-hook 'cider-interaction-mode-hook 'cider-turn-on-eldoc-mode) ``` * You can hide the `*nrepl-connection*` and `*nrepl-server*` buffers @@ -108,44 +108,44 @@ make the hidden buffers visible. They'll always be visible in `list-buffers` (C-x C-b). * You can control the TAB key behavior in the REPL via the -`nrepl-tab-command` variable. While the default command -`nrepl-indent-and-complete-symbol` should be an adequate choice for +`cider-tab-command` variable. While the default command +`cider-indent-and-complete-symbol` should be an adequate choice for most users, it's very easy to switch to another command if you wish to. For instance if you'd like TAB to only indent (maybe because you're used to completing with M-TAB) use the following snippet: ```el -(setq nrepl-repl-tab-command 'indent-for-tab-command) +(setq cider-repl-tab-command 'indent-for-tab-command) ``` * Prevent the auto-display of the REPL buffer in a separate window after connection is established: ```el -(setq nrepl-repl-pop-to-buffer-on-connect nil +(setq cider-repl-pop-to-buffer-on-connect nil ``` * Stop the error buffer from popping up while working in buffers other than the REPL: ```el -(setq nrepl-popup-stacktraces nil) +(setq cider-popup-stacktraces nil) ``` * Enable error buffer popping also in the REPL: ```el -(setq nrepl-repl-popup-stacktraces t) +(setq cider-repl-popup-stacktraces t) ``` * To auto-select the error buffer when it's displayed: ```el -(setq nrepl-auto-select-error-buffer t) +(setq cider-auto-select-error-buffer t) ``` -* The REPL buffer name takes the format `*nrepl project-name*`. +* The REPL buffer name takes the format `*cider project-name*`. Change the separator from space to something else by overriding `nrepl-buffer-name-separator`. ```el @@ -153,16 +153,16 @@ Change the separator from space to something else by overriding `nrepl-buffer-na ``` * The REPL buffer name can also display the port on which the nrepl server is running. -Buffer name will look like *nrepl project-name:port*. +Buffer name will look like *cider project-name:port*. ```el (setq nrepl-buffer-name-show-port t) ``` -* Make C-c C-z switch to the `*nrepl*` buffer in the current window: +* Make C-c C-z switch to the `*cider*` buffer in the current window: ```el -(add-to-list 'same-window-buffer-names "*nrepl*") +(add-to-list 'same-window-buffer-names "*cider*") ``` * Enabling `CamelCase` support for editing commands(like @@ -171,7 +171,7 @@ we often have to deal with Java class and method names. The built-in Emacs minor mode `subword-mode` provides such functionality: ```el -(add-hook 'nrepl-repl-mode-hook 'subword-mode) +(add-hook 'cider-repl-mode-hook 'subword-mode) ``` * The use of [paredit](http://mumble.net/~campbell/emacs/paredit.html) @@ -181,7 +181,7 @@ buffers (if you're not you probably should). You might also want to enable `paredit` in the nREPL buffer as well: ```el -(add-hook 'nrepl-repl-mode-hook 'paredit-mode) +(add-hook 'cider-repl-mode-hook 'paredit-mode) ``` * [smartparens](https://github.com/Fuco1/smartparens) is an excellent @@ -190,7 +190,7 @@ enable `paredit` in the nREPL buffer as well: `smartparens` in the REPL buffer use the following code: ```el -(add-hook 'nrepl-repl-mode-hook 'smartparens-strict-mode) +(add-hook 'cider-repl-mode-hook 'smartparens-strict-mode) ``` * [RainbowDelimiters](https://github.com/jlr/rainbow-delimiters) is a @@ -202,7 +202,7 @@ enable `paredit` in the nREPL buffer as well: enable it in the REPL like this: ```el -(add-hook 'nrepl-repl-mode-hook 'rainbow-delimiters-mode) +(add-hook 'cider-repl-mode-hook 'rainbow-delimiters-mode) ``` * [ac-nrepl](https://github.com/clojure-emacs/ac-nrepl) provides @@ -213,10 +213,10 @@ enable `paredit` in the nREPL buffer as well: ## Basic Usage -The only requirement to use nrepl.el is to have a nREPL server to +The only requirement to use CIDER is to have a nREPL server to which it may connect. Many Clojurians favour the use of the Leiningen tool to start a nREPL server, but the use of Leiningen is not a prerequisite to use -nrepl.el (but it's required if you want to use the `nrepl-jack-in` command). +CIDER (but it's required if you want to use the `cider-jack-in` command). ### Setting up a Leiningen project (optional) @@ -225,7 +225,7 @@ build/project management tool for Clojure. It has a similar scope to the Maven build tool favoured by Java developers (Leiningen actually reuses many things from the Maven ecosystem). -nrepl.el features a command called `nrepl-jack-in` that will start an nREPL server +CIDER features a command called `cider-jack-in` that will start an nREPL server for a particular Leiningen project and connect to it automatically. This functionality depends on Leiningen 2. Older versions are not supported. Follow the installation instructions on Leiningen's web site to get it up and running and afterwards @@ -240,10 +240,10 @@ The two main ways to obtain an nREPL connection are discussed in the following s ### Launch a nREPL server and client from Emacs Simply open in Emacs a file belonging to your `lein` project (like -`foo.clj`) and type M-x nrepl-jack-in. This will start a nREPL with -all the deps loaded in, plus an `nrepl.el` client connected to it. +`foo.clj`) and type M-x cider-jack-in. This will start a nREPL with +all the deps loaded in, plus an `CIDER` client connected to it. -Alternative you can use C-u M-x nrepl-jack-in to specify the name of +Alternative you can use C-u M-x cider-jack-in to specify the name of a lein project, without having to visit any file in it. ### Connect to a running nrepl server @@ -255,18 +255,18 @@ You can go to your project's dir in a terminal and type there $ lein repl ``` -Alternatively you can start nrepl.el either manually or by the facilities provided by your +Alternatively you can start nREPL either manually or by the facilities provided by your project build tool (Maven, etc). After you get your nREPL server running go back to Emacs. -Typing there M-x nrepl will allow you to connect to the running nrepl session. +Typing there M-x cider will allow you to connect to the running nrepl session. -### Using the nrepl minor mode +### Using the cider minor mode -`nrepl.el` comes with a handy minor mode called `nrepl-interaction-mode` (complementing +`CIDER` comes with a handy minor mode called `cider-interaction-mode` (complementing `clojure-mode`) that allows you to evaluate code in your Clojure source files and load it directly in the REPL. A list of all -available commands is available in the nREPL menu and in the following +available commands is available in the cider menu and in the following section of this manual. ### Pretty printing in the REPL @@ -276,16 +276,16 @@ that this will not work correctly with forms such as `(def a 1) (def b2)` and it expects `clojure.pprint` to have been required already (the default in more recent versions of Clojure): -M-x nrepl-toggle-pretty-printing +M-x cider-toggle-pretty-printing ## Keyboard shortcuts -* M-x nrepl-jack-in: Launch an nrepl server and a repl client. +* M-x cider-jack-in: Launch an nREPL server and a repl client. Prompts for a project root if given a prefix argument. -* M-x nrepl: Connect to an already-running nrepl server. +* M-x cider: Connect to an already-running nrepl server. -While you're in `clojure-mode`, `nrepl-jack-in` is bound for -convenience to C-c M-j and `nrepl` is bound to C-c +While you're in `clojure-mode`, `cider-jack-in` is bound for +convenience to C-c M-j and `cider` is bound to C-c M-c. ### Clojure buffer commands: @@ -350,11 +350,11 @@ Keyboard shortcut | Description ### Managing multiple sessions -You can connect to multiple nREPL servers and use M-x nrepl-jack-in multiple +You can connect to multiple nREPL servers and use M-x cider-jack-in multiple times. To close a single nREPL session, use M-x nrepl-close. M-x nrepl-quit closes all sessions. -nrepl.el commands in a Clojure buffer use the default connection. To make a +CIDER commands in a Clojure buffer use the default connection. To make a connection default, switch to it's repl buffer and use M-x nrepl-make-repl-connection-default. @@ -364,7 +364,7 @@ You can display the current nREPL connection using C-c M-d and rotate ## Requirements -* [Leiningen](http://leiningen.org) 2.x (only for `nrepl-jack-in`) +* [Leiningen](http://leiningen.org) 2.x (only for `cider-jack-in`) * [GNU Emacs](http://www.gnu.org/software/emacs/emacs.html) 23.2+ or 24. * [Clojure](http://clojure.org) 1.4.0+ @@ -374,7 +374,7 @@ An extensive changelog is available [here](CHANGELOG.md). ## Extensions -There are a couple of nrepl.el extensions that add some extra functionality to it: +There are a couple of CIDER extensions that add some extra functionality to it: * [nrepl-tracing](https://github.com/clojure-emacs/nrepl-tracing) adds basic tracing support * [nrepl-decompile](https://github.com/clojure-emacs/nrepl-decompile) adds some Java bytecode decompilation commands @@ -389,8 +389,8 @@ There are a couple of nrepl.el extensions that add some extra functionality to i ## Contributing -* Mailing list: [https://groups.google.com/forum/#!forum/nrepl-el](https://groups.google.com/forum/#!forum/nrepl-el) -* Please report issues on the [GitHub issue tracker](https://github.com/clojure-emacs/nrepl.el/issues) or the mailing list. +* Mailing list: [https://groups.google.com/forum/#!forum/nrepl-el](https://groups.google.com/forum/#!forum/cider) +* Please report issues on the [GitHub issue tracker](https://github.com/clojure-emacs/cider/issues) or the mailing list. Bug reports and suggestions for improvements are always welcome. GitHub pull requests are even better! :-) @@ -401,7 +401,7 @@ Install [cask](https://github.com/rejeep/cask.el) if you haven't already, then: ``` -$ cd /path/to/nrepl +$ cd /path/to/cider $ cask ``` @@ -415,6 +415,6 @@ $ make test Copyright © 2012-2013 Tim King, Phil Hagelberg, Bozhidar Batsov, Hugo Duncan, Steve Purcell and -[contributors](https://github.com/kingtim/nrepl.el/contributors). +[contributors](https://github.com/clojure-emacs/cider/contributors). Distributed under the GNU General Public License, version 3 diff --git a/cider-eldoc.el b/cider-eldoc.el new file mode 100644 index 00000000..d0669181 --- /dev/null +++ b/cider-eldoc.el @@ -0,0 +1,121 @@ +;;; cider-eldoc.el --- eldoc support for Clojure + +;; 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: + +;; eldoc support for Clojure. + +;;; Code: + +(require 'nrepl-client) + +(require 'eldoc) +(require 'dash) + +(defvar cider-extra-eldoc-commands '("cider-complete" "yas/expand") + "Extra commands to be added to eldoc's safe commands list.") + +(defun cider-eldoc-format-thing (thing) + "Format the eldoc THING." + (propertize thing 'face 'font-lock-function-name-face)) + +(defun cider-highlight-args (arglist pos) + "Format the the function ARGLIST for eldoc. +POS is the index of the currently highlighted argument." + (let* ((rest-pos (cider--find-rest-args-position arglist)) + (i 0)) + (mapconcat + (lambda (arg) + (let ((argstr (format "%s" arg))) + (if (eq arg '&) + argstr + (prog1 + (if (or (= (1+ i) pos) + (and rest-pos (> (+ 1 i) rest-pos) + (> pos rest-pos))) + (propertize argstr 'face + 'eldoc-highlight-function-argument) + argstr) + (setq i (1+ i)))))) arglist " "))) + +(defun cider--find-rest-args-position (arglist) + "Find the position of & in the ARGLIST vector." + (-elem-index '& (append arglist ()))) + +(defun cider-highlight-arglist (arglist pos) + "Format the ARGLIST for eldoc. +POS is the index of the argument to highlight." + (concat "[" (cider-highlight-args arglist pos) "]")) + +(defun cider-eldoc-format-arglist (arglist pos) + "Format all the ARGLIST for eldoc. +POS is the index of current argument." + (concat "(" + (mapconcat (lambda (args) (cider-highlight-arglist args pos)) + (read arglist) " ") ")")) + +(defun cider-eldoc-info-in-current-sexp () + "Return a list of the current sexp and the current argument index." + (save-excursion + (let ((argument-index (1- (eldoc-beginning-of-sexp)))) + ;; If we are at the beginning of function name, this will be -1. + (when (< argument-index 0) + (setq argument-index 0)) + ;; Don't do anything if current word is inside a string. + (if (= (or (char-after (1- (point))) 0) ?\") + nil + (list (cider-symbol-at-point) argument-index))))) + +(defun cider-eldoc () + "Backend function for eldoc to show argument list in the echo area." + (when (nrepl-current-connection-buffer) + (let* ((info (cider-eldoc-info-in-current-sexp)) + (thing (car info)) + (pos (cadr info)) + (form (format "(try + (:arglists + (clojure.core/meta + (clojure.core/resolve + (clojure.core/read-string \"%s\")))) + (catch Throwable t nil))" thing)) + (result (when thing + (nrepl-send-string-sync form + nrepl-buffer-ns + (nrepl-current-tooling-session)))) + (value (plist-get result :value))) + (unless (string= value "nil") + (format "%s: %s" + (cider-eldoc-format-thing thing) + (cider-eldoc-format-arglist value pos)))))) + +(defun cider-turn-on-eldoc-mode () + "Turn on eldoc mode in the current buffer." + (setq-local eldoc-documentation-function 'cider-eldoc) + (apply 'eldoc-add-command cider-extra-eldoc-commands) + (turn-on-eldoc-mode)) + +(provide 'cider-eldoc) +;;; cider-eldoc ends here diff --git a/cider-interaction-mode.el b/cider-interaction-mode.el new file mode 100644 index 00000000..f9826850 --- /dev/null +++ b/cider-interaction-mode.el @@ -0,0 +1,116 @@ +;;; cider-interaction-mode.el --- Minor mode for 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: + +;; Minor mode for REPL interactions. + +;;; Code: + +(require 'cider-interaction) + +(defvar cider-interaction-mode-map + (let ((map (make-sparse-keymap))) + (define-key map (kbd "M-.") 'cider-jump) + (define-key map (kbd "M-,") 'cider-jump-back) + (define-key map (kbd "M-TAB") 'complete-symbol) + (define-key map (kbd "C-M-x") 'cider-eval-expression-at-point) + (define-key map (kbd "C-c C-c") 'cider-eval-expression-at-point) + (define-key map (kbd "C-x C-e") 'cider-eval-last-expression) + (define-key map (kbd "C-c C-e") 'cider-eval-last-expression) + (define-key map (kbd "C-c C-p") 'cider-pprint-eval-last-expression) + (define-key map (kbd "C-c C-r") 'cider-eval-region) + (define-key map (kbd "C-c C-n") 'cider-eval-ns-form) + (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 M-n") 'cider-set-ns) + (define-key map (kbd "C-c C-d") 'cider-doc) + (define-key map (kbd "C-c C-s") 'cider-src) + (define-key map (kbd "C-c C-z") 'cider-switch-to-repl-buffer) + (define-key map (kbd "C-c C-Z") 'cider-switch-to-relevant-repl-buffer) + (define-key map (kbd "C-c M-o") 'cider-find-and-clear-repl-buffer) + (define-key map (kbd "C-c C-k") 'cider-load-current-buffer) + (define-key map (kbd "C-c C-l") 'cider-load-file) + (define-key map (kbd "C-c C-b") 'cider-interrupt) + (define-key map (kbd "C-c C-j") 'cider-javadoc) + (define-key map (kbd "C-c M-s") 'cider-selector) + (define-key map (kbd "C-c M-r") 'cider-rotate-connection) + (define-key map (kbd "C-c M-d") 'cider-display-current-connection-info) + (define-key map (kbd "C-c C-q") 'cider-quit) + map)) + +;;;###autoload +(define-minor-mode cider-interaction-mode + "Minor mode for REPL interaction from a Clojure buffer. + +\\{cider-interaction-mode-map}" + nil + " cider" + cider-interaction-mode-map + (make-local-variable 'completion-at-point-functions) + (add-to-list 'completion-at-point-functions + 'cider-complete-at-point)) + +(easy-menu-define cider-interaction-mode-menu cider-interaction-mode-map + "Menu for CIDER interaction mode" + '("CIDER" + ["Jump" cider-jump] + ["Jump back" cider-jump-back] + "--" + ["Complete symbol" complete-symbol] + "--" + ["Eval expression at point" cider-eval-expression-at-point] + ["Eval last expression" cider-eval-last-expression] + ["Eval last expression in popup buffer" cider-pprint-eval-last-expression] + ["Eval region" cider-eval-region] + ["Eval ns form" cider-eval-ns-form] + "--" + ["Load current buffer" cider-load-current-buffer] + ["Load file" cider-load-file] + "--" + ["Macroexpand-1 last expression" cider-macroexpand-1] + ["Macroexpand-all last expression" cider-macroexpand-all] + "--" + ["Display documentation" cider-doc] + ["Display Source" cider-src] + ["Display JavaDoc" cider-javadoc] + "--" + ["Set ns" cider-set-ns] + ["Switch to REPL" cider-switch-to-repl-buffer] + ["Switch to Relevant REPL" cider-switch-to-relevant-repl-buffer] + ["Toggle REPL Pretty Print" cider-pretty-toggle] + ["Clear REPL" cider-find-and-clear-repl-buffer] + ["Interrupt" cider-interrupt] + ["Quit" cider-quit] + ["Restart" cider-restart] + "--" + ["Display current nrepl connection" cider-display-current-connection-info] + ["Rotate current nrepl connection" cider-rotate-connection] + "--" + ["Version info" cider-version])) + +(provide 'cider-interaction-mode) +;;; cider-interaction-mode.el ends here diff --git a/cider-interaction.el b/cider-interaction.el new file mode 100644 index 00000000..fbb4fd9a --- /dev/null +++ b/cider-interaction.el @@ -0,0 +1,1043 @@ +;;; cider-interaction.el --- IDE for Clojure + +;; 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: + +;; Provides an Emacs Lisp client to connect to Clojure nREPL servers. + +;;; Code: + +(require 'nrepl-client) + +(require 'clojure-mode) +(require 'dash) +(require 'thingatpt) +(require 'etags) +(require 'arc-mode) +(require 'ansi-color) +(require 'cl-lib) +(require 'compile) +(require 'tramp) + +(defconst cider-error-buffer "*cider-error*") +(defconst cider-doc-buffer "*cider-doc*") +(defconst cider-src-buffer "*cider-src*") +(defconst cider-result-buffer "*cider-result*") + +(defcustom cider-use-local-resources t + "Use local resources under HOME if possible." + :type 'boolean + :group 'nrepl) + +(defcustom cider-popup-stacktraces t + "Non-nil means pop-up error stacktraces for evaluation errors. +Nil means show only an error message in the minibuffer. See also +`cider-repl-popup-stacktraces', which overrides this setting +for REPL buffers." + :type 'boolean + :group 'nrepl) + +(defcustom cider-popup-on-error t + "When `cider-popup-on-error' is set to t, stacktraces will be displayed. +When set to nil, stactraces will not be displayed, but will be available +in the `cider-error-buffer', which defaults to *cider-error*." + :type 'boolean + :group 'nrepl) + +(defcustom cider-auto-select-error-buffer nil + "Controls whether to auto-select the error popup buffer." + :type 'boolean + :group 'nrepl) + +(defface cider-error-highlight-face + '((((supports :underline (:style wave))) + (:underline (:style wave :color "red") :inherit unspecified)) + (t (:inherit font-lock-warning-face :underline t))) + "Face used to highlight compilation errors in Clojure buffers." + :group 'nrepl) + +(defface cider-warning-highlight-face + '((((supports :underline (:style wave))) + (:underline (:style wave :color "yellow") :inherit unspecified)) + (t (:inherit font-lock-warning-face :underline (:color "yellow")))) + "Face used to highlight compilation warnings in Clojure buffers." + :group 'nrepl) + +;;; Connection info +(defun cider--clojure-version () + "Retrieve the underlying connection's Clojure version." + (let ((version-string (plist-get (nrepl-send-string-sync "(clojure-version)") :value))) + (substring version-string 1 (1- (length version-string))))) + +(defun cider--backend-version () + "Retrieve the underlying connection's nREPL version." + (let ((version-string (plist-get (nrepl-send-string-sync "(:version-string clojure.tools.nrepl/version)") :value))) + (substring version-string 1 (1- (length version-string))))) + +(defun cider--connection-info (nrepl-connection-buffer) + "Return info about NREPL-CONNECTION-BUFFER. + +Info contains project name, current REPL namespace, host:port endpoint and Clojure version." + (with-current-buffer (get-buffer nrepl-connection-buffer) + (format "Active nrepl connection: %s:%s, %s:%s (Clojure %s, nREPL %s)" + (or (nrepl--project-name nrepl-project-dir) "") + nrepl-buffer-ns + (car nrepl-endpoint) + (cadr nrepl-endpoint) + (cider--clojure-version) + (cider--backend-version)))) + +(defun cider-display-current-connection-info () + "Display information about the current connection." + (interactive) + (message (cider--connection-info (nrepl-current-connection-buffer)))) + +(defun cider-rotate-connection () + "Rotate and display the current nrepl connection." + (interactive) + (setq nrepl-connection-list + (append (cdr nrepl-connection-list) + (list (car nrepl-connection-list)))) + (message (cider--connection-info (car nrepl-connection-list)))) + +;;; Switching between REPL & source buffers +(make-variable-buffer-local + (defvar cider-last-clojure-buffer nil + "A buffer-local variable holding the last Clojure source buffer. +`cider-switch-to-last-clojure-buffer' uses this variable to jump +back to last Clojure source buffer.")) + +(defvar cider-current-clojure-buffer nil + "This variable holds current buffer temporarily when connecting to a REPL. +It is set to current buffer when `nrepl' 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'.") + +(defun cider-remember-clojure-buffer (buffer) + "Try to remember the BUFFER from which the user jumps. +The BUFFER needs to be a Clojure buffer and current major mode needs +to be `cider-repl-mode'. The user can use `cider-switch-to-last-clojure-buffer' +to jump back to the last Clojure source buffer." + (when (and buffer + (eq 'clojure-mode (with-current-buffer buffer major-mode)) + (eq 'cider-repl-mode major-mode)) + (setq cider-last-clojure-buffer buffer))) + +(defun cider-switch-to-repl-buffer (arg) + "Select the REPL buffer, when possible in an existing window. + +Hint: You can use `display-buffer-reuse-frames' and +`special-display-buffer-names' to customize the frame in which +the buffer should appear. + +With a prefix ARG sets the name of the REPL buffer to the one +of the current source file." + (interactive "P") + (if (not (get-buffer (nrepl-current-connection-buffer))) + (message "No active nREPL connection.") + (progn + (let ((buffer (current-buffer))) + (when arg + (nrepl-set-ns (nrepl-current-ns))) + (pop-to-buffer (cider-find-or-create-repl-buffer)) + (cider-remember-clojure-buffer buffer) + (goto-char (point-max)))))) + +(defun cider-switch-to-relevant-repl-buffer (arg) + "Select the REPL buffer, when possible in an existing window. +The buffer chosen is based on the file open in the current buffer. + +Hint: You can use `display-buffer-reuse-frames' and +`special-display-buffer-names' to customize the frame in which +the buffer should appear. + +With a prefix ARG sets the name of the REPL buffer to the one +of the current source file. + +With a second prefix ARG the chosen REPL buffer is based on a +supplied project directory." + (interactive "P") + (if (not (get-buffer (nrepl-current-connection-buffer))) + (message "No active nREPL connection.") + (progn + (let ((project-directory + (or (when arg + (ido-read-directory-name "Project: ")) + (nrepl-project-directory-for (nrepl-current-dir))))) + (if project-directory + (let ((buf (car (-filter + (lambda (conn) + (let ((conn-proj-dir (with-current-buffer (get-buffer conn) + nrepl-project-dir))) + (when conn-proj-dir + (equal (file-truename project-directory) + (file-truename conn-proj-dir))))) + nrepl-connection-list)))) + (if buf + (setq nrepl-connection-list + (cons buf (delq buf nrepl-connection-list))) + (message "No relevant nREPL connection found. Switching to default connection."))) + (message "No project directory found. Switching to default nREPL connection."))) + (cider-switch-to-repl-buffer '())))) + +(defun cider-switch-to-last-clojure-buffer () + "Switch to the last Clojure buffer. +The default keybinding for this command is +the same as `cider-switch-to-repl-buffer', +so that it is very convenient to jump between a +Clojure buffer and the REPL buffer." + (interactive) + (if (and (eq 'cider-repl-mode major-mode) + (buffer-live-p cider-last-clojure-buffer)) + (pop-to-buffer cider-last-clojure-buffer) + (message "Don't know the original Clojure buffer"))) + +;;; Evaluating +(defun cider-eval-region (start end) + "Evaluate the region. +The two arguments START and END are character positions; +they can be in either order." + (interactive "r") + (cider-interactive-eval (buffer-substring-no-properties start end))) + +(defun cider-eval-buffer () + "Evaluate the current buffer." + (interactive) + (cider-eval-region (point-min) (point-max))) + +(defun cider-expression-at-point () + "Return the text of the expr at point." + (apply #'buffer-substring-no-properties + (cider-region-for-expression-at-point))) + +(defun cider-region-for-expression-at-point () + "Return the start and end position of defun at point." + (save-excursion + (save-match-data + (end-of-defun) + (let ((end (point))) + (beginning-of-defun) + (list (point) end))))) + +(defun cider-eval-expression-at-point (&optional prefix) + "Evaluate the current toplevel form, and print result in the mini-buffer. +With a PREFIX argument, print the result in the current buffer." + (interactive "P") + (let ((form (cider-expression-at-point))) + (if prefix + (cider-interactive-eval-print form) + (cider-interactive-eval form)))) + +(defun cider-eval-ns-form () + "Evaluate the current buffer's namespace form." + (interactive) + (when (clojure-find-ns) + (save-excursion + (goto-char (match-beginning 0)) + (cider-eval-expression-at-point)))) + +(defun cider-bounds-of-sexp-at-point () + "Return the bounds sexp at point as a pair (or nil)." + (or (and (equal (char-after) ?\() + (member (char-before) '(?\' ?\, ?\@)) + ;; hide stuff before ( to avoid quirks with '( etc. + (save-restriction + (narrow-to-region (point) (point-max)) + (bounds-of-thing-at-point 'sexp))) + (bounds-of-thing-at-point 'sexp))) + +(defun cider-symbol-at-point () + "Return the name of the symbol at point, otherwise nil." + (let ((str (thing-at-point 'symbol))) + (and str + (not (equal str (concat (nrepl-find-ns) "> "))) + (not (equal str "")) + (substring-no-properties str)))) + +(defun cider-sexp-at-point () + "Return the sexp at point as a string, otherwise nil." + (let ((bounds (cider-bounds-of-sexp-at-point))) + (if bounds + (buffer-substring-no-properties (car bounds) + (cdr bounds))))) + +(defun cider-sexp-at-point-with-bounds () + "Return a list containing the sexp at point and its bounds." + (let ((bounds (cider-bounds-of-sexp-at-point))) + (if bounds + (let ((start (car bounds)) + (end (cdr bounds))) + (list (buffer-substring-no-properties start end) + (cons (set-marker (make-marker) start) + (set-marker (make-marker) end))))))) + +(defun cider-last-expression () + "Return the last sexp." + (buffer-substring-no-properties + (save-excursion (backward-sexp) (point)) + (point))) + +;;; +(defun cider-tramp-prefix () + "Top element on `find-tag-marker-ring` used to determine Clojure host." + (let ((jump-origin (buffer-file-name + (marker-buffer + (ring-ref find-tag-marker-ring 0))))) + (when (tramp-tramp-file-p jump-origin) + (let ((vec (tramp-dissect-file-name jump-origin))) + (tramp-make-tramp-file-name (tramp-file-name-method vec) + (tramp-file-name-user vec) + (tramp-file-name-host vec) + nil))))) + +(defun cider-home-prefix-adjustment (resource) + "System-dependent HOME location will be adjusted in RESOURCE. +Removes any leading slash if on Windows." + (save-match-data + (cond ((string-match "^\\/\\(Users\\|home\\)\\/\\w+\\(\\/.+\\)" resource) + (concat (getenv "HOME") (match-string 2 resource))) + ((and (eq system-type 'windows-nt) + (string-match "^/" resource) + (not (tramp-tramp-file-p resource))) + (substring resource 1)) + (t + resource)))) + +(defun cider-emacs-or-clojure-side-adjustment (resource) + "Fix the RESOURCE path depending on `cider-use-local-resources`." + (let ((resource (cider-home-prefix-adjustment resource)) + (clojure-side-res (concat (cider-tramp-prefix) resource)) + (emacs-side-res resource)) + (cond ((equal resource "") resource) + ((and cider-use-local-resources + (file-exists-p emacs-side-res)) + emacs-side-res) + ((file-exists-p clojure-side-res) + clojure-side-res) + (t + resource)))) + +(defun cider-find-file (filename) + "Switch to a buffer visiting FILENAME. +Adjusts for HOME location using `cider-home-prefix-adjustment'. Uses `find-file'." + (find-file (cider-emacs-or-clojure-side-adjustment filename))) + +(defun cider-find-resource (resource) + "Find and display RESOURCE." + (cond ((string-match "^file:\\(.+\\)" resource) + (cider-find-file (match-string 1 resource))) + ((string-match "^\\(jar\\|zip\\):file:\\(.+\\)!/\\(.+\\)" resource) + (let* ((jar (match-string 2 resource)) + (path (match-string 3 resource)) + (buffer-already-open (get-buffer (file-name-nondirectory jar)))) + (cider-find-file jar) + (goto-char (point-min)) + (search-forward path) + (let ((opened-buffer (current-buffer))) + (archive-extract) + (when (not buffer-already-open) + (kill-buffer opened-buffer))))) + (t (error "Unknown resource path %s" resource)))) + +(defun cider-jump-to-def-for (location) + "Jump to LOCATION's definition in the source code." + ;; ugh; elisp destructuring doesn't work for vectors + (let ((resource (aref location 0)) + (path (aref location 1)) + (line (aref location 2))) + (if (and path (file-exists-p path)) + (find-file path) + (cider-find-resource resource)) + (goto-char (point-min)) + (forward-line (1- line)))) + +(defun cider-jump-to-def-handler (buffer) + "Create a handler for jump-to-def in BUFFER." + ;; TODO: got to be a simpler way to do this + (nrepl-make-response-handler buffer + (lambda (buffer value) + (with-current-buffer buffer + (ring-insert find-tag-marker-ring (point-marker))) + (cider-jump-to-def-for + (car (read-from-string value)))) + (lambda (buffer out) (message out)) + (lambda (buffer err) (message err)) + nil)) + +(defun cider-jump-to-def (var) + "Jump to the definition of the VAR at point." + (let ((form (format "(let [ns-symbol '%s + ns-var '%s + ns-file (clojure.core/comp :file + clojure.core/meta + clojure.core/second + clojure.core/first + clojure.core/ns-publics) + resource-str (clojure.core/comp clojure.core/str + clojure.java.io/resource + ns-file) + file-str (clojure.core/comp clojure.core/str + clojure.java.io/file + ns-file)] + (cond ((clojure.core/ns-aliases ns-symbol) ns-var) + (let [resolved-ns ((clojure.core/ns-aliases ns-symbol) ns-var)] + [(resource-str resolved-ns) + (file-str resolved-ns) + 1]) + + (find-ns ns-var) + [(resource-str ns-var) + (file-str ns-var) + 1] + + (clojure.core/ns-resolve ns-symbol ns-var) + ((clojure.core/juxt + (clojure.core/comp clojure.core/str + clojure.java.io/resource + :file) + (clojure.core/comp clojure.core/str + clojure.java.io/file + :file) + :line) + (clojure.core/meta (clojure.core/ns-resolve ns-symbol ns-var)))))" + (nrepl-current-ns) var))) + (nrepl-send-string form + (cider-jump-to-def-handler (current-buffer)) + nrepl-buffer-ns + (nrepl-current-tooling-session)))) + +(defun cider-jump (query) + "Jump to the definition of QUERY." + (interactive "P") + (cider-read-symbol-name "Symbol: " 'cider-jump-to-def query)) + +(defalias 'cider-jump-back 'pop-tag-mark) + +(defun cider-completion-complete-core-fn (str) + "Return a list of completions for STR using complete.core/completions." + (let ((strlst (plist-get + (nrepl-send-string-sync + (format "(require 'complete.core) (complete.core/completions \"%s\" *ns*)" str) + nrepl-buffer-ns + (nrepl-current-tooling-session)) + :value))) + (when strlst + (car (read-from-string strlst))))) + +(defun cider-completion-complete-op-fn (str) + "Return a list of completions for STR using the nREPL \"complete\" op." + (lexical-let ((strlst (plist-get + (nrepl-send-request-sync + (list "op" "complete" + "session" (nrepl-current-tooling-session) + "ns" nrepl-buffer-ns + "symbol" str)) + :value))) + (when strlst + (car strlst)))) + +(defun cider-dispatch-complete-symbol (str) + "Return a list of completions for STR. +Dispatch to the nREPL \"complete\" op if supported, +otherwise dispatch to internal completion function." + (if (nrepl-op-supported-p "complete") + (cider-completion-complete-op-fn str) + (cider-completion-complete-core-fn str))) + +(defun cider-complete-at-point () + "Complete the symbol at point." + (let ((sap (symbol-at-point))) + (when (and sap (not (in-string-p))) + (let ((bounds (bounds-of-thing-at-point 'symbol))) + (list (car bounds) (cdr bounds) + (completion-table-dynamic #'cider-dispatch-complete-symbol)))))) + + +;;; JavaDoc Browsing +;;; Assumes local-paths are accessible in the VM. +(defvar cider-javadoc-local-paths nil + "List of paths to directories with Javadoc.") + +(defun cider-javadoc-op (symbol-name) + "Invoke the nREPL \"javadoc\" op on SYMBOL-NAME." + (cider-send-op + "javadoc" + `("symbol" ,symbol-name "ns" ,nrepl-buffer-ns + "local-paths" ,(mapconcat #'identity cider-javadoc-local-paths " ")) + (nrepl-make-response-handler + (current-buffer) + (lambda (buffer url) + (if url + (browse-url url) + (error "No javadoc url for %s" symbol-name))) + nil nil nil))) + +(defun cider-javadoc-handler (symbol-name) + "Invoke the nREPL \"javadoc\" op on SYMBOL-NAME if available." + (when symbol-name + (let ((bounds (bounds-of-thing-at-point 'symbol))) + (if (nrepl-op-supported-p "javadoc") + (cider-javadoc-op symbol-name) + (message "No Javadoc middleware available"))))) + +(defun cider-javadoc (query) + "Browse Javadoc on the Java class QUERY at point." + (interactive "P") + (cider-read-symbol-name "Javadoc for: " 'cider-javadoc-handler query)) + +(defun cider-stdin-handler (buffer) + "Make a stdin response handler for BUFFER." + (nrepl-make-response-handler buffer + (lambda (buffer value) + (cider-emit-result buffer value t)) + (lambda (buffer out) + (cider-emit-output buffer out t)) + (lambda (buffer err) + (cider-emit-output buffer err t)) + nil)) + +(defun cider-handler (buffer) + "Make a nrepl evaluation handler for BUFFER." + (nrepl-make-response-handler buffer + (lambda (buffer value) + (cider-emit-result buffer value t)) + (lambda (buffer out) + (cider-emit-output buffer out t)) + (lambda (buffer err) + (cider-emit-output buffer err t)) + (lambda (buffer) + (cider-emit-prompt buffer)))) + +(defun cider-interactive-eval-handler (buffer) + "Make an interactive eval handler for BUFFER." + (nrepl-make-response-handler buffer + (lambda (buffer value) + (message "%s" value)) + (lambda (buffer value) + (cider-emit-interactive-output value)) + (lambda (buffer err) + (message "%s" err) + (cider-highlight-compilation-errors + buffer err)) + '())) + +(defun cider-load-file-handler (buffer) + "Make a load file handler for BUFFER." + (let (current-ns (nrepl-current-ns)) + (nrepl-make-response-handler buffer + (lambda (buffer value) + (message "%s" value) + (with-current-buffer buffer + (setq nrepl-buffer-ns (clojure-find-ns)) + (run-hooks 'cider-file-loaded-hook))) + (lambda (buffer value) + (cider-emit-interactive-output value)) + (lambda (buffer err) + (message "%s" err) + (cider-highlight-compilation-errors + buffer err)) + '() + (lambda (buffer ex root-ex session) + (let ((cider-popup-on-error nil)) + (funcall nrepl-err-handler + buffer ex root-ex session)))))) + +(defun cider-interactive-eval-print-handler (buffer) + "Make a handler for evaluating and printing result in BUFFER." + (nrepl-make-response-handler buffer + (lambda (buffer value) + (with-current-buffer buffer + (insert (format "%s" value)))) + '() + (lambda (buffer err) + (message "%s" err)) + '())) + +(defun cider-popup-eval-print-handler (buffer) + "Make a handler for evaluating and printing result in popup BUFFER." + (nrepl-make-response-handler buffer + (lambda (buffer str) + (cider-emit-into-popup-buffer buffer str)) + '() + (lambda (buffer str) + (cider-emit-into-popup-buffer buffer str)) + '())) + +(defun cider-popup-eval-out-handler (buffer) + "Make a handler for evaluating and printing stdout/stderr in popup BUFFER." + (nrepl-make-response-handler buffer + '() + (lambda (buffer str) + (cider-emit-into-popup-buffer buffer str)) + (lambda (buffer str) + (cider-emit-into-popup-buffer buffer str)) + '())) + +(defun cider-visit-error-buffer () + "Visit the `cider-error-buffer' (usually *cider-error*) if it exists." + (interactive) + (let ((buffer (get-buffer cider-error-buffer))) + (when buffer + (cider-popup-buffer-display buffer)))) + +(defun cider-find-property (property &optional backward) + "Find the next text region which has the specified PROPERTY. +If BACKWARD is t, then search backward. +Returns the position at which PROPERTY was found, or nil if not found." + (let ((p (if backward + (previous-single-char-property-change (point) property) + (next-single-char-property-change (point) property)))) + (when (and (not (= p (point-min))) (not (= p (point-max)))) + p))) + +(defun cider-jump-to-compilation-error (&optional arg reset) + "Jump to the line causing the current compilation error. + +ARG and RESET are ignored, as there is only ever one compilation error. +They exist for compatibility with `next-error'." + (interactive) + (cl-labels ((goto-next-note-boundary + () + (let ((p (or (cider-find-property 'cider-note-p) + (cider-find-property 'cider-note-p t)))) + (when p + (goto-char p) + (message (get-char-property p 'cider-note)))))) + ;; if we're already on a compilation error, first jump to the end of + ;; it, so that we find the next error. + (when (get-char-property (point) 'cider-note-p) + (goto-next-note-boundary)) + (goto-next-note-boundary))) + +(defun cider-default-err-handler (buffer ex root-ex session) + "Make an error handler for BUFFER, EX, ROOT-EX and SESSION." + ;; TODO: use ex and root-ex as fallback values to display when pst/print-stack-trace-not-found + (let ((replp (equal 'cider-repl-mode (buffer-local-value 'major-mode buffer)))) + (if (or (and cider-repl-popup-stacktraces replp) + (and cider-popup-stacktraces (not replp))) + (lexical-let ((cider-popup-on-error cider-popup-on-error)) + (with-current-buffer buffer + (nrepl-send-string "(if-let [pst+ (clojure.core/resolve 'clj-stacktrace.repl/pst+)] + (pst+ *e) (clojure.stacktrace/print-stack-trace *e))" + (nrepl-make-response-handler + (cider-make-popup-buffer cider-error-buffer) + nil + (lambda (buffer value) + (cider-emit-into-color-buffer buffer value) + (when cider-popup-on-error + (cider-popup-buffer-display buffer cider-auto-select-error-buffer))) + nil nil) nil session)) + (with-current-buffer cider-error-buffer + (compilation-minor-mode +1)))))) + +(defvar cider-compilation-regexp + '("\\(?:.*\\(warning, \\)\\|.*?\\(, compiling\\):(\\)\\([^:]*\\):\\([[:digit:]]+\\)\\(?::\\([[:digit:]]+\\)\\)?\\(\\(?: - \\(.*\\)\\)\\|)\\)" 3 4 5 (1)) + "Specifications for matching errors and warnings in Clojure stacktraces. +See `compilation-error-regexp-alist' for help on their format.") + +(add-to-list 'compilation-error-regexp-alist-alist + (cons 'nrepl cider-compilation-regexp)) +(add-to-list 'compilation-error-regexp-alist 'nrepl) + +(defun cider-extract-error-info (regexp message) + "Extract error information with REGEXP against MESSAGE." + (let ((file (nth 1 regexp)) + (line (nth 2 regexp)) + (col (nth 3 regexp)) + (type (nth 4 regexp)) + (pat (car regexp))) + (when (string-match pat message) + ;; special processing for type (1.2) style + (setq type (if (consp type) + (or (and (car type) (match-end (car type)) 1) + (and (cdr type) (match-end (cdr type)) 0) + 2))) + (list + (when file + (let ((val (match-string-no-properties file message))) + (unless (string= val "NO_SOURCE_PATH") val))) + (when line (string-to-number (match-string-no-properties line message))) + (when col + (let ((val (match-string-no-properties col message))) + (when val (string-to-number val)))) + (aref [cider-warning-highlight-face + cider-warning-highlight-face + cider-error-highlight-face] + (or type 2)) + message)))) + +(defun cider-highlight-compilation-errors (buffer message) + "Highlight compilation error line in BUFFER, using MESSAGE." + (with-current-buffer buffer + (let ((info (cider-extract-error-info cider-compilation-regexp message))) + (when info + (let ((file (nth 0 info)) + (line (nth 1 info)) + (col (nth 2 info)) + (face (nth 3 info)) + (note (nth 4 info))) + (save-excursion + ;; when we don't have a filename the line number + ;; is relative to form start + (if file + (goto-char (point-min)) ; start of file + (beginning-of-defun)) + (forward-line (1- line)) + ;; if have column, highlight sexp at that point otherwise whole line. + (move-to-column (or col 0)) + (let ((begin (progn (if col (backward-up-list) (back-to-indentation)) (point))) + (end (progn (if col (forward-sexp) (move-end-of-line nil)) (point)))) + (let ((overlay (make-overlay begin end))) + (overlay-put overlay 'cider-note-p t) + (overlay-put overlay 'face face) + (overlay-put overlay 'cider-note note) + (overlay-put overlay 'help-echo note))))))))) + +(defun cider-need-input (buffer) + "Handle an need-input request from BUFFER." + (with-current-buffer buffer + (nrepl-send-stdin (concat (read-from-minibuffer "Stdin: ") "\n") + (cider-stdin-handler buffer)))) + + +;;;; Popup buffers +(define-minor-mode cider-popup-buffer-mode + "Mode for nrepl popup buffers" + nil + (" cider-tmp") + '(("q" . cider-popup-buffer-quit-function))) + +(make-variable-buffer-local + (defvar cider-popup-buffer-quit-function 'cider-popup-buffer-quit + "The function that is used to quit a temporary popup buffer.")) + +(defun cider-popup-buffer-quit-function (&optional kill-buffer-p) + "Wrapper to invoke the function `cider-popup-buffer-quit-function'. +KILL-BUFFER-P is passed along." + (interactive) + (funcall cider-popup-buffer-quit-function kill-buffer-p)) + +(defun cider-popup-buffer (name &optional select) + "Create new popup buffer called NAME. +If SELECT is non-nil, select the newly created window" + (with-current-buffer (cider-make-popup-buffer name) + (setq buffer-read-only t) + (cider-popup-buffer-display (current-buffer) select))) + +(defun cider-popup-buffer-display (popup-buffer &optional select) + "Display POPUP-BUFFER. +If SELECT is non-nil, select the newly created window" + (with-current-buffer popup-buffer + (let ((new-window (display-buffer (current-buffer)))) + (set-window-point new-window (point)) + (when select + (select-window new-window)) + (current-buffer)))) + +(defun cider-popup-buffer-quit (&optional kill-buffer-p) + "Quit the current (temp) window and bury its buffer using `quit-window'. +If prefix argument KILL-BUFFER-P is non-nil, kill the buffer instead of burying it." + (interactive) + (quit-window kill-buffer-p (selected-window))) + +(defun cider-make-popup-buffer (name) + "Create a temporary buffer called NAME." + (with-current-buffer (get-buffer-create name) + (kill-all-local-variables) + (setq buffer-read-only nil) + (erase-buffer) + (set-syntax-table clojure-mode-syntax-table) + (cider-popup-buffer-mode 1) + (current-buffer))) + +(defun cider-emit-into-popup-buffer (buffer value) + "Emit into BUFFER the provided VALUE." + (with-current-buffer buffer + (let ((inhibit-read-only t) + (buffer-undo-list t)) + (insert (format "%s" value)) + (indent-sexp) + (font-lock-fontify-buffer)))) + +(defun cider-emit-into-color-buffer (buffer value) + "Emit into color BUFFER the provided VALUE." + (with-current-buffer buffer + (let ((inhibit-read-only t) + (buffer-undo-list t)) + (goto-char (point-max)) + (insert (format "%s" value)) + (ansi-color-apply-on-region (point-min) (point-max))) + (goto-char (point-min)))) + + +(defun cider-popup-eval-print (form) + "Evaluate the given FORM and print value in current buffer." + (let ((buffer (current-buffer))) + (nrepl-send-string form + (cider-popup-eval-print-handler buffer) + (nrepl-current-ns)))) + +(defun cider-interactive-eval-print (form) + "Evaluate the given FORM and print value in current buffer." + (let ((buffer (current-buffer))) + (nrepl-send-string form + (cider-interactive-eval-print-handler buffer) + (nrepl-current-ns)))) + +(defun cider-interactive-eval (form) + "Evaluate the given FORM and print value in minibuffer." + (remove-overlays (point-min) (point-max) 'cider-note-p t) + (let ((buffer (current-buffer))) + (nrepl-send-string form + (cider-interactive-eval-handler buffer) + (nrepl-current-ns)))) + +(defun cider-send-op (op attributes handler) + "Send the specified OP with ATTRIBUTES and response HANDLER." + (let ((buffer (current-buffer))) + (nrepl-send-request (append + (list "op" op + "session" (nrepl-current-session) + "ns" nrepl-buffer-ns) + attributes) + handler))) + +(defun cider-send-load-file (file-contents file-path file-name) + "Perform the nREPL \"load-file\" op. +FILE-CONTENTS, FILE-PATH and FILE-NAME are details of the file to be +loaded." + (let ((buffer (current-buffer))) + (nrepl-send-request (list "op" "load-file" + "session" (nrepl-current-session) + "file" file-contents + "file-path" file-path + "file-name" file-name) + (cider-load-file-handler buffer)))) + +(defun cider-eval-last-expression (&optional prefix) + "Evaluate the expression preceding point. +If invoked with a PREFIX argument, print the result in the current buffer." + (interactive "P") + (if prefix + (cider-interactive-eval-print (cider-last-expression)) + (cider-interactive-eval (cider-last-expression)))) + +(defun cider-eval-print-last-expression () + "Evaluate the expression preceding point. +Print its value into the current buffer" + (interactive) + (cider-interactive-eval-print (cider-last-expression))) + +(defun cider-pprint-eval-last-expression () + "Evaluate the expression preceding point and pprint its value in a popup buffer." + (interactive) + (let ((form (cider-last-expression)) + (result-buffer (cider-popup-buffer cider-result-buffer nil))) + (nrepl-send-string (format "(clojure.pprint/pprint %s)" form) + (cider-popup-eval-out-handler result-buffer) + (nrepl-current-ns) + (nrepl-current-tooling-session)))) + +(defun clojure-enable-nrepl () + "Turn on nrepl interaction mode (see command `cider-interaction-mode'). +Useful in hooks." + (cider-interaction-mode 1) + (setq next-error-function 'cider-jump-to-compilation-error)) + +(defun clojure-disable-nrepl () + "Turn off nrepl interaction mode (see command `cider-interaction-mode'). +Useful in hooks." + (cider-interaction-mode -1)) + +;; this is horrible, but with async callbacks we can't rely on dynamic scope +(defvar cider-ido-ns nil) + +(defun cider-ido-form (ns) + "Construct a Clojure form for ido read using NS." + `(concat (if (find-ns (symbol ,ns)) + (map name (concat (keys (ns-interns (symbol ,ns))) + (keys (ns-refers (symbol ,ns)))))) + (if (not= "" ,ns) [".."]) + (->> (all-ns) + (map (fn [n] + (re-find (re-pattern (str "^" (if (not= ,ns "") + (str ,ns "\\.")) + "[^\\.]+")) + (str n)))) + (filter identity) + (map (fn [n] (str n "/"))) + (into (hash-set))))) + +(defun cider-ido-up-ns (ns) + "Perform up using NS." + (mapconcat 'identity (butlast (split-string ns "\\.")) ".")) + +(defun cider-ido-select (selected targets callback) + "Peform ido select using SELECTED, TARGETS and CALLBACK." + ;; TODO: immediate RET gives "" as selected for some reason + ;; this is an OK workaround though + (cond ((equal "" selected) + (cider-ido-select (car targets) targets callback)) + ((equal "/" (substring selected -1)) ; selected a namespace + (cider-ido-read-var (substring selected 0 -1) callback)) + ((equal ".." selected) + (cider-ido-read-var (cider-ido-up-ns cider-ido-ns) callback)) + ;; non ido variable selection techniques don't return qualified symbols, so this shouldn't either + (t (funcall callback selected)))) + +(defun cider-ido-read-var-handler (ido-callback buffer) + "Create an ido read var handler with IDO-CALLBACK for BUFFER." + (lexical-let ((ido-callback ido-callback)) + (nrepl-make-response-handler buffer + (lambda (buffer value) + ;; make sure to eval the callback in the buffer that the symbol was requested from so we get the right namespace + (with-current-buffer buffer + (let* ((targets (car (read-from-string value))) + (selected (ido-completing-read "Var: " targets nil t))) + (cider-ido-select selected targets ido-callback)))) + nil nil nil))) + +(defun cider-ido-read-var (ns ido-callback) + "Perform ido read var in NS using IDO-CALLBACK." + ;; Have to be stateful =( + (setq cider-ido-ns ns) + (nrepl-send-string (prin1-to-string (cider-ido-form cider-ido-ns)) + (cider-ido-read-var-handler ido-callback (current-buffer)) + nrepl-buffer-ns + (nrepl-current-tooling-session))) + +(defun cider-read-symbol-name (prompt callback &optional query) + "Either read a symbol name using PROMPT or choose the one at point. +Use CALLBACK as the ido read var callback. +The user is prompted with PROMPT if a prefix argument is in effect, +if there is no symbol at point, or if QUERY is non-nil." + (let ((symbol-name (cider-symbol-at-point))) + (cond ((not (or current-prefix-arg query (not symbol-name))) + (funcall callback symbol-name)) + (ido-mode (cider-ido-read-var nrepl-buffer-ns callback)) + (t (funcall callback (read-from-minibuffer prompt symbol-name)))))) + +(defun cider-doc-handler (symbol) + "Create a handler to lookup documentation for SYMBOL." + (let ((form (format "(clojure.repl/doc %s)" symbol)) + (doc-buffer (cider-popup-buffer cider-doc-buffer t))) + (nrepl-send-string form + (cider-popup-eval-out-handler doc-buffer) + nrepl-buffer-ns + (nrepl-current-tooling-session)))) + +(defun cider-doc (query) + "Open a window with the docstring for the given QUERY. +Defaults to the symbol at point. With prefix arg or no symbol +under point, prompts for a var." + (interactive "P") + (cider-read-symbol-name "Symbol: " 'cider-doc-handler query)) + +(defun cider-src-handler (symbol) + "Create a handler to lookup source for SYMBOL." + (let ((form (format "(clojure.repl/source %s)" symbol)) + (src-buffer (cider-popup-buffer cider-src-buffer t))) + (with-current-buffer src-buffer + (clojure-mode) + (cider-popup-buffer-mode +1)) + (nrepl-send-string form + (cider-popup-eval-out-handler src-buffer) + nrepl-buffer-ns + (nrepl-current-tooling-session)))) + +(defun cider-src (query) + "Open a window with the source for the given QUERY. +Defaults to the symbol at point. With prefix arg or no symbol +under point, prompts for a var." + (interactive "P") + (cider-read-symbol-name "Symbol: " 'cider-src-handler query)) + +;; TODO: implement reloading ns +(defun cider-eval-load-file (form) + "Load FORM." + (let ((buffer (current-buffer))) + (nrepl-send-string form (cider-interactive-eval-handler buffer)))) + +(defun cider-file-string (file) + "Read the contents of a FILE and return as a string." + (with-current-buffer (find-file-noselect file) + (buffer-string))) + +(defun cider-load-file-op (filename) + "Send \"load-file\" op for FILENAME." + (cider-send-load-file (cider-file-string filename) + filename + (file-name-nondirectory filename))) + +(defun cider-load-file-core (filename) + "Load the Clojure file FILENAME." + (let ((fn (replace-regexp-in-string + "\\\\" "\\\\\\\\" + (convert-standard-filename (expand-file-name filename))))) + (cider-eval-load-file + (format "(clojure.core/load-file \"%s\")\n(in-ns '%s)\n" + fn (nrepl-find-ns))))) + +(defun cider-dispatch-load-file (filename) + "Dispatch the load file operation for FILENAME." + (if (nrepl-op-supported-p "load-file") + (cider-load-file-op filename) + (cider-load-file-core filename))) + +(defun cider-load-file (filename) + "Load the Clojure file FILENAME." + (interactive (list + (read-file-name "Load file: " nil nil + nil (if (buffer-file-name) + (file-name-nondirectory + (buffer-file-name)))))) + (remove-overlays (point-min) (point-max) 'cider-note-p t) + (cider-dispatch-load-file filename) + (message "Loading %s..." filename)) + +(defun cider-load-current-buffer () + "Load current buffer's file." + (interactive) + (check-parens) + (unless buffer-file-name + (error "Buffer %s is not associated with a file" (buffer-name))) + (when (and (buffer-modified-p) + (y-or-n-p (format "Save file %s? " (buffer-file-name)))) + (save-buffer)) + (cider-load-file (buffer-file-name))) + +(defun cider-recently-visited-buffer (mode) + "Return the most recently visited buffer whose `major-mode' is MODE. +Only considers buffers that are not already visible." + (loop for buffer in (buffer-list) + when (and (with-current-buffer buffer (eq major-mode mode)) + (not (string-match "^ " (buffer-name buffer))) + (null (get-buffer-window buffer 'visible))) + return buffer + finally (error "Can't find unshown buffer in %S" mode))) + +(provide 'cider-interaction) +;;; cider-interaction.el ends here diff --git a/cider-macroexpansion.el b/cider-macroexpansion.el new file mode 100644 index 00000000..fa68d2fe --- /dev/null +++ b/cider-macroexpansion.el @@ -0,0 +1,159 @@ +;;; cider-macroexpansion.el --- Macro expansion support + +;; 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: + +;; Macro expansion support. + +;;; Code: + +(defconst cider-macroexpansion-buffer "*cider-macroexpansion*") + +(defun cider-macroexpand-undo (&optional arg) + "Undo the last macroexpansion, using `undo-only'. +ARG is passed along to `undo-only'." + (interactive) + (let ((inhibit-read-only t)) + (undo-only arg))) + +(defvar cider-last-macroexpand-expression nil + "Specify the last macroexpansion preformed. +This variable specifies both what was expanded and the expander.") + +(defun cider-macroexpand-form (expander expr) + "Macroexpand, using EXPANDER, the given EXPR." + (format + "(clojure.pprint/write (%s '%s) :suppress-namespaces false :dispatch clojure.pprint/code-dispatch)" + expander expr)) + +(defun cider-macroexpand-expr (expander expr &optional buffer) + "Macroexpand, use EXPANDER, the given EXPR from BUFFER." + (let* ((form (cider-macroexpand-form expander expr)) + (expansion (plist-get (nrepl-send-string-sync form nrepl-buffer-ns) :stdout))) + (setq cider-last-macroexpand-expression form) + (cider-initialize-macroexpansion-buffer expansion nrepl-buffer-ns))) + +(defun cider-macroexpand-expr-inplace (expander) + "Substitute the current form at point with its macroexpansion using EXPANDER." + (interactive) + (let ((form-with-bounds (cider-sexp-at-point-with-bounds))) + (if form-with-bounds + (destructuring-bind (expr bounds) form-with-bounds + (let* ((form (cider-macroexpand-form expander expr)) + (expansion (plist-get (nrepl-send-string-sync form nrepl-buffer-ns) :stdout))) + (cider-redraw-macroexpansion-buffer + expansion (current-buffer) (car bounds) (cdr bounds) (point))))))) + +(defun cider-macroexpand-again () + "Repeat the last macroexpansion." + (interactive) + (let ((expansion + (plist-get (nrepl-send-string-sync cider-last-macroexpand-expression nrepl-buffer-ns) :stdout))) + (cider-initialize-macroexpansion-buffer expansion nrepl-buffer-ns))) + +(defun cider-macroexpand-1 (&optional prefix) + "Invoke 'macroexpand-1' on the expression at point. +If invoked with a PREFIX argument, use 'macroexpand' instead of +'macroexpand-1'." + (interactive "P") + (let ((expander (if prefix 'macroexpand 'macroexpand-1))) + (cider-macroexpand-expr expander (cider-sexp-at-point)))) + +(defun cider-macroexpand-1-inplace (&optional prefix) + "Perform inplace 'macroexpand-1' on the expression at point. +If invoked with a PREFIX argument, use 'macroexpand' instead of +'macroexpand-1'." + (interactive "P") + (let ((expander (if prefix 'macroexpand 'macroexpand-1))) + (cider-macroexpand-expr-inplace expander))) + +(defun cider-macroexpand-all () + "Invoke 'clojure.walk/macroexpand-all' on the expression at point." + (interactive) + (cider-macroexpand-expr + 'clojure.walk/macroexpand-all (cider-sexp-at-point))) + +(defun cider-macroexpand-all-inplace () + "Perform inplace 'clojure.walk/macroexpand-all' on the expression at point." + (interactive) + (cider-macroexpand-expr-inplace 'clojure.walk/macroexpand-all)) + +(defun cider-initialize-macroexpansion-buffer (expansion ns) + "Create a new Macroexpansion buffer with EXPANSION and namespace NS." + (pop-to-buffer (cider-create-macroexpansion-buffer)) + (setq nrepl-buffer-ns ns) + (setq buffer-undo-list nil) + (let ((inhibit-read-only t) + (buffer-undo-list t)) + (erase-buffer) + (insert (format "%s" expansion)) + (goto-char (point-min)) + (font-lock-fontify-buffer))) + +(defun cider-redraw-macroexpansion-buffer (expansion buffer start end current-point) + "Redraw the macroexpansion with new EXPANSION. +Text in BUFFER from START to END is replaced with new expansion, +and point is placed at CURRENT-POINT." + (with-current-buffer buffer + (let ((buffer-read-only nil)) + (goto-char start) + (delete-region start end) + (insert (format "%s" expansion)) + (goto-char start) + (indent-sexp) + (goto-char current-point)))) + +(defun cider-create-macroexpansion-buffer () + "Create a new macroexpansion buffer." + (with-current-buffer (cider-popup-buffer cider-macroexpansion-buffer t) + (clojure-mode) + (clojure-disable-nrepl) + (cider-macroexpansion-minor-mode 1) + (current-buffer))) + +(defvar cider-macroexpansion-minor-mode-map + (let ((map (make-sparse-keymap))) + (define-key map (kbd "g") 'cider-macroexpand-again) + (define-key map (kbd "q") 'cider-popup-buffer-quit-function) + (cl-labels ((redefine-key (from to) + (dolist (mapping (where-is-internal from cider-interaction-mode-map)) + (define-key map mapping to)))) + (redefine-key 'cider-macroexpand-1 'cider-macroexpand-1-inplace) + (redefine-key 'cider-macroexpand-all 'cider-macroexpand-all-inplace) + (redefine-key 'advertised-undo 'cider-macroexpand-undo) + (redefine-key 'undo 'cider-macroexpand-undo)) + map)) + +(define-minor-mode cider-macroexpansion-minor-mode + "Minor mode for nrepl macroexpansion. + +\\{cider-macroexpansion-minor-mode-map}" + nil + " Macroexpand" + cider-macroexpansion-minor-mode-map) + +(provide 'cider-macroexpansion) +;;; cider-macroexpansion.el ends here diff --git a/cider-repl-mode.el b/cider-repl-mode.el new file mode 100644 index 00000000..a8e026fa --- /dev/null +++ b/cider-repl-mode.el @@ -0,0 +1,156 @@ +;;; cider-repl-mode.el --- Major mode for 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: + +;; Major mode for REPL interactions. + +;;; Code: + +(require 'cider-repl) + +(require 'clojure-mode) +(require 'easymenu) + +(eval-when-compile + (defvar paredit-version) + (defvar paredit-space-for-delimiter-predicates)) + +;;; Prevent paredit from inserting some inappropriate spaces. +;;; C.f. clojure-mode.el +(defun cider-space-for-delimiter-p (endp delim) + "Hook for paredit's `paredit-space-for-delimiter-predicates'. + +Decides if paredit should insert a space after/before (if/unless +ENDP) DELIM." + (if (eq major-mode 'cider-repl-mode) + (save-excursion + (backward-char) + (if (and (or (char-equal delim ?\() + (char-equal delim ?\") + (char-equal delim ?{)) + (not endp)) + (if (char-equal (char-after) ?#) + (and (not (bobp)) + (or (char-equal ?w (char-syntax (char-before))) + (char-equal ?_ (char-syntax (char-before))))) + t) + t)) + t)) + +(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)) + +(defvar cider-repl-mode-map + (let ((map (make-sparse-keymap))) + (set-keymap-parent map clojure-mode-map) + (define-key map (kbd "M-.") 'cider-jump) + (define-key map (kbd "M-,") 'cider-jump-back) + (define-key map (kbd "RET") 'cider-return) + (define-key map (kbd "TAB") 'cider-tab) + (define-key map (kbd "C-") 'cider-closing-return) + (define-key map (kbd "C-j") 'cider-newline-and-indent) + (define-key map (kbd "C-c C-d") 'cider-doc) + (define-key map (kbd "C-c C-s") 'cider-src) + (define-key map (kbd "C-c C-o") 'cider-clear-output) + (define-key map (kbd "C-c M-o") 'cider-clear-buffer) + (define-key map (kbd "C-c C-u") 'cider-kill-input) + (define-key map (kbd "C-a") 'cider-bol) + (define-key map (kbd "C-S-a") 'cider-bol-mark) + (define-key map [home] 'cider-bol) + (define-key map [S-home] 'cider-bol-mark) + (define-key map (kbd "C-") 'cider-backward-input) + (define-key map (kbd "C-") 'cider-forward-input) + (define-key map (kbd "M-p") 'cider-previous-input) + (define-key map (kbd "M-n") 'cider-next-input) + (define-key map (kbd "M-r") 'cider-previous-matching-input) + (define-key map (kbd "M-s") 'cider-next-matching-input) + (define-key map (kbd "C-c C-n") 'cider-next-prompt) + (define-key map (kbd "C-c C-p") 'cider-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-j") 'cider-javadoc) + (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-s") 'cider-selector) + (define-key map (kbd "C-c M-r") 'cider-rotate-connection) + (define-key map (kbd "C-c M-d") 'cider-display-current-connection-info) + (define-key map (kbd "C-c C-q") 'cider-quit) + map)) + +(define-derived-mode cider-repl-mode fundamental-mode "REPL" + "Major mode for nREPL interactions. + +\\{cider-repl-mode-map}" + (setq-local lisp-indent-function 'clojure-indent-function) + (setq-local indent-line-function 'lisp-indent-line) + (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-turn-on-eldoc-mode) + (if (fboundp 'hack-dir-local-variables-non-file-buffer) + (hack-dir-local-variables-non-file-buffer)) + (when cider-history-file + (cider-history-load cider-history-file) + (add-hook 'kill-buffer-hook 'cider-history-just-save t t) + (add-hook 'kill-emacs-hook 'cider-history-just-save)) + (add-hook 'paredit-mode-hook + (lambda () + (when (>= paredit-version 21) + (define-key cider-repl-mode-map "{" 'paredit-open-curly) + (define-key cider-repl-mode-map "}" 'paredit-close-curly) + (add-to-list 'paredit-space-for-delimiter-predicates + 'cider-space-for-delimiter-p))))) + +(easy-menu-define cider-repl-mode-menu cider-repl-mode-map + "Menu for CIDER's REPL mode" + '("REPL" + ["Jump" cider-jump] + ["Jump back" cider-jump-back] + "--" + ["Complete symbol" complete-symbol] + "--" + ["Display documentation" cider-doc] + ["Display source" cider-src] + ["Display JavaDoc" cider-javadoc] + "--" + ["Toggle pretty printing of results" cider-toggle-pretty-printing] + ["Clear output" cider-clear-output] + ["Clear buffer" cider-clear-buffer] + ["Kill input" cider-kill-input] + ["Interrupt" cider-interrupt] + ["Quit" cider-quit] + ["Restart" cider-restart] + "--" + ["Version info" cider-version])) + +(provide 'cider-repl-mode) +;;; cider-repl-mode.el ends here 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 diff --git a/cider-selector.el b/cider-selector.el new file mode 100644 index 00000000..25e5ac1f --- /dev/null +++ b/cider-selector.el @@ -0,0 +1,140 @@ +;;; cider-selector.el --- Buffer selection command inspired by SLIME's selector + +;; 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: + +;; Buffer selection command inspired by SLIME's selector. + +;;; Code: + +(require 'nrepl-client) +(require 'cider-interaction) + +(defvar cider-selector-methods nil + "List of buffer-selection methods for the `cider-select' command. +Each element is a list (KEY DESCRIPTION FUNCTION). +DESCRIPTION is a one-line description of what the key selects.") + +(defvar cider-selector-other-window nil + "If non-nil use `switch-to-buffer-other-window'.") + +(defun cider-selector (&optional other-window) + "Select a new buffer by type, indicated by a single character. +The user is prompted for a single character indicating the method by +which to choose a new buffer. The `?' character describes then +available methods. OTHER-WINDOW provides an optional target. + +See `def-cider-selector-method' for defining new methods." + (interactive) + (message "Select [%s]: " + (apply #'string (mapcar #'car cider-selector-methods))) + (let* ((cider-selector-other-window other-window) + (ch (save-window-excursion + (select-window (minibuffer-window)) + (read-char))) + (method (cl-find ch cider-selector-methods :key #'car))) + (cond (method + (funcall (cl-caddr method))) + (t + (message "No method for character: ?\\%c" ch) + (ding) + (sleep-for 1) + (discard-input) + (cider-selector))))) + +(defmacro def-cider-selector-method (key description &rest body) + "Define a new `cider-select' buffer selection method. + +KEY is the key the user will enter to choose this method. + +DESCRIPTION is a one-line sentence describing how the method +selects a buffer. + +BODY is a series of forms which are evaluated when the selector +is chosen. The returned buffer is selected with +`switch-to-buffer'." + (let ((method `(lambda () + (let ((buffer (progn ,@body))) + (cond ((not (get-buffer buffer)) + (message "No such buffer: %S" buffer) + (ding)) + ((get-buffer-window buffer) + (select-window (get-buffer-window buffer))) + (cider-selector-other-window + (switch-to-buffer-other-window buffer)) + (t + (switch-to-buffer buffer))))))) + `(setq cider-selector-methods + (cl-sort (cons (list ,key ,description ,method) + (cl-remove ,key cider-selector-methods :key #'car)) + #'< :key #'car)))) + +(def-cider-selector-method ?? "Selector help buffer." + (ignore-errors (kill-buffer "*Select Help*")) + (with-current-buffer (get-buffer-create "*Select Help*") + (insert "Select Methods:\n\n") + (loop for (key line nil) in cider-selector-methods + do (insert (format "%c:\t%s\n" key line))) + (goto-char (point-min)) + (help-mode) + (display-buffer (current-buffer) t)) + (cider-selector) + (current-buffer)) + +(pushnew (list ?4 "Select in other window" (lambda () (cider-selector t))) + cider-selector-methods :key #'car) + +(def-cider-selector-method ?c + "most recently visited clojure-mode buffer." + (cider-recently-visited-buffer 'clojure-mode)) + +(def-cider-selector-method ?e + "most recently visited emacs-lisp-mode buffer." + (cider-recently-visited-buffer 'emacs-lisp-mode)) + +(def-cider-selector-method ?q "Abort." + (top-level)) + +(def-cider-selector-method ?r + "Current *nrepl* buffer." + (cider-find-or-create-repl-buffer)) + +(def-cider-selector-method ?n + "NREPL connections buffer." + (nrepl-connection-browser) + nrepl--connection-browser-buffer-name) + +(def-cider-selector-method ?v + "*nrepl-events* buffer." + nrepl-event-buffer-name) + +(def-cider-selector-method ?s + "Cycle to the next Clojure connection." + (cider-rotate-connections) + (cider-find-or-create-repl-buffer)) + +(provide 'cider-selector) +;;; cider-selector.el ends here diff --git a/cider-version.el b/cider-version.el new file mode 100644 index 00000000..f43ec2c3 --- /dev/null +++ b/cider-version.el @@ -0,0 +1,76 @@ +;;; cider-version.el --- Version information + +;; 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: + +;; Version information. + +;;; Code: + +(require 'pkg-info) + +;;; Version information +(defun cider--library-version () + "Get the version in the nrepl library header." + (-when-let (version (pkg-info-library-version 'cider)) + (pkg-info-format-version version))) + +(defun cider--package-version () + "Get the package version of nrepl. + +This is the version number of the installed nrepl package." + (-when-let (version (pkg-info-package-version 'cider)) + (pkg-info-format-version version))) + +(defun cider-version (&optional show-version) + "Get the CIDER version as string. + +If called interactively or if SHOW-VERSION is non-nil, show the +version in the echo area and the messages buffer. + +The returned string includes both, the version from package.el +and the library version, if both a present and different. + +If the version number could not be determined, signal an error, +if called interactively, or if SHOW-VERSION is non-nil, otherwise +just return nil." + (interactive (list (not (or executing-kbd-macro noninteractive)))) + (let* ((lib-version (cider--library-version)) + (pkg-version (cider--package-version)) + (version (cond + ((and lib-version pkg-version + (not (string= lib-version pkg-version))) + (format "%s (package: %s)" lib-version pkg-version)) + ((or pkg-version lib-version) + (format "%s" (or pkg-version lib-version)))))) + (when show-version + (unless version + (error "Could not find out nrepl version")) + (message "nrepl version: %s" version)) + version)) + +(provide 'cider-version) +;;; cider-version.el ends here diff --git a/cider.el b/cider.el new file mode 100644 index 00000000..07c1a39d --- /dev/null +++ b/cider.el @@ -0,0 +1,82 @@ +;;; cider.el --- Client for Clojure nREPL + +;; 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 +;; URL: http://www.github.com/clojure-emacs/nrepl.el +;; Version: 0.3.0-cvs +;; Keywords: languages, clojure, nrepl +;; Package-Requires: ((clojure-mode "2.0.0") (cl-lib "0.3") (dash "2.1.0") (pkg-info "0.1")) + +;; 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: + +;; Provides an elisp client to connect to Clojure nREPL servers. + +;;; Installation: + +;; Available as a package in marmalade-repo.org and melpa.milkbox.net. + +;; (add-to-list 'package-archives +;; '("marmalade" . "http://marmalade-repo.org/packages/")) +;; +;; or +;; +;; (add-to-list 'package-archives +;; '("melpa" . "http://melpa.milkbox.net/packages/") t) +;; +;; M-x package-install nrepl + +;;; Usage: + +;; M-x nrepl-jack-in + +;;; Code: + +;; needed for make test +(eval-when-compile + (add-to-list 'load-path default-directory)) + +(require 'nrepl-client) +(require 'cider-version) +(require 'cider-interaction) +(require 'cider-eldoc) +(require 'cider-repl) +(require 'cider-repl-mode) +(require 'cider-selector) +(require 'cider-interaction-mode) +(require 'cider-macroexpansion) + +;;;###autoload +(defalias 'cider 'nrepl) + +;;;###autoload +(defalias 'cider-jack-in 'nrepl-jack-in) + +;;;###autoload +(eval-after-load 'clojure-mode + '(progn + (define-key clojure-mode-map (kbd "C-c M-j") 'nrepl-jack-in) + (define-key clojure-mode-map (kbd "C-c M-c") 'nrepl))) + +(provide 'cider) +;;; cider.el ends here diff --git a/nrepl-client.el b/nrepl-client.el index 8c1ae651..173714c1 100644 --- a/nrepl-client.el +++ b/nrepl-client.el @@ -105,7 +105,7 @@ to specific the full path to it. Localhost is assumed." (defvar nrepl-endpoint nil) (defvar nrepl-project-dir nil) -(defconst nrepl-repl-buffer-name-template "*nrepl%s*") +(defconst nrepl-repl-buffer-name-template "*cider%s*") (defconst nrepl-connection-buffer-name-template "*nrepl-connection%s*") (defconst nrepl-server-buffer-name-template "*nrepl-server%s*") @@ -434,6 +434,8 @@ This is bound for the duration of the handling of that message") (defvar nrepl-connection-list nil "A list of connections.") + + (defun nrepl-make-connection-buffer () "Create an nREPL connection buffer." (let ((buffer (generate-new-buffer (nrepl-connection-buffer-name)))) @@ -502,11 +504,11 @@ Also closes associated REPL and server buffers." (define-key map (kbd "RET") 'nrepl-connections-goto-connection) map)) -(define-derived-mode nrepl-connections-buffer-mode nrepl-popup-buffer-mode +(define-derived-mode nrepl-connections-buffer-mode cider-popup-buffer-mode "nREPL-Connections" "nREPL Connections Buffer Mode. \\{nrepl-connections-buffer-mode-map} -\\{nrepl-popup-buffer-mode-map}" +\\{cider-popup-buffer-mode-map}" (setq-local truncate-lines t)) (defvar nrepl--connection-ewoc) @@ -748,14 +750,6 @@ search for and read a `ns' form." (format "(in-ns '%s)" ns) (nrepl-handler (current-buffer)))) (message "Sorry, I don't know what the current namespace is."))) -(defun nrepl-symbol-at-point () - "Return the name of the symbol at point, otherwise nil." - (let ((str (thing-at-point 'symbol))) - (and str - (not (equal str (concat (nrepl-find-ns) "> "))) - (not (equal str "")) - (substring-no-properties str)))) - ;;; interrupt (defun nrepl-interrupt-handler (buffer) "Create an interrupt response handler for BUFFER." @@ -1001,11 +995,11 @@ When NO-REPL-P is truthy, suppress creation of a REPL buffer." (remhash id nrepl-requests) (cond (new-session (lexical-let ((connection-buffer (process-buffer process))) - (message "Connected. %s" (nrepl-random-words-of-inspiration)) + (message "Connected. %s" (cider-random-words-of-inspiration)) (setq nrepl-session new-session nrepl-connection-buffer connection-buffer) (unless no-repl-p - (nrepl-make-repl process) + (cider-make-repl process) (nrepl-make-repl-connection-default connection-buffer)) (run-hooks 'nrepl-connected-hook)))))))) @@ -1059,11 +1053,5 @@ Falls back to `nrepl-port' if not found." (when (nrepl-check-for-repl-buffer `(,host ,port) nil) (nrepl-connect host port))) -;;;###autoload -(eval-after-load 'clojure-mode - '(progn - (define-key clojure-mode-map (kbd "C-c M-j") 'nrepl-jack-in) - (define-key clojure-mode-map (kbd "C-c M-c") 'nrepl))) - (provide 'nrepl-client) ;;; nrepl-client.el ends here diff --git a/nrepl-eldoc.el b/nrepl-eldoc.el deleted file mode 100644 index 54f96e5d..00000000 --- a/nrepl-eldoc.el +++ /dev/null @@ -1,121 +0,0 @@ -;;; nrepl-eldoc.el --- eldoc support for Clojure - -;; 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: - -;; eldoc support for Clojure. - -;;; Code: - -(require 'nrepl-client) - -(require 'eldoc) -(require 'dash) - -(defvar nrepl-extra-eldoc-commands '("nrepl-complete" "yas/expand") - "Extra commands to be added to eldoc's safe commands list.") - -(defun nrepl-eldoc-format-thing (thing) - "Format the eldoc THING." - (propertize thing 'face 'font-lock-function-name-face)) - -(defun nrepl-highlight-args (arglist pos) - "Format the the function ARGLIST for eldoc. -POS is the index of the currently highlighted argument." - (let* ((rest-pos (nrepl--find-rest-args-position arglist)) - (i 0)) - (mapconcat - (lambda (arg) - (let ((argstr (format "%s" arg))) - (if (eq arg '&) - argstr - (prog1 - (if (or (= (1+ i) pos) - (and rest-pos (> (+ 1 i) rest-pos) - (> pos rest-pos))) - (propertize argstr 'face - 'eldoc-highlight-function-argument) - argstr) - (setq i (1+ i)))))) arglist " "))) - -(defun nrepl--find-rest-args-position (arglist) - "Find the position of & in the ARGLIST vector." - (-elem-index '& (append arglist ()))) - -(defun nrepl-highlight-arglist (arglist pos) - "Format the ARGLIST for eldoc. -POS is the index of the argument to highlight." - (concat "[" (nrepl-highlight-args arglist pos) "]")) - -(defun nrepl-eldoc-format-arglist (arglist pos) - "Format all the ARGLIST for eldoc. -POS is the index of current argument." - (concat "(" - (mapconcat (lambda (args) (nrepl-highlight-arglist args pos)) - (read arglist) " ") ")")) - -(defun nrepl-eldoc-info-in-current-sexp () - "Return a list of the current sexp and the current argument index." - (save-excursion - (let ((argument-index (1- (eldoc-beginning-of-sexp)))) - ;; If we are at the beginning of function name, this will be -1. - (when (< argument-index 0) - (setq argument-index 0)) - ;; Don't do anything if current word is inside a string. - (if (= (or (char-after (1- (point))) 0) ?\") - nil - (list (nrepl-symbol-at-point) argument-index))))) - -(defun nrepl-eldoc () - "Backend function for eldoc to show argument list in the echo area." - (when (nrepl-current-connection-buffer) - (let* ((info (nrepl-eldoc-info-in-current-sexp)) - (thing (car info)) - (pos (cadr info)) - (form (format "(try - (:arglists - (clojure.core/meta - (clojure.core/resolve - (clojure.core/read-string \"%s\")))) - (catch Throwable t nil))" thing)) - (result (when thing - (nrepl-send-string-sync form - nrepl-buffer-ns - (nrepl-current-tooling-session)))) - (value (plist-get result :value))) - (unless (string= value "nil") - (format "%s: %s" - (nrepl-eldoc-format-thing thing) - (nrepl-eldoc-format-arglist value pos)))))) - -(defun nrepl-turn-on-eldoc-mode () - "Turn on eldoc mode in the current buffer." - (setq-local eldoc-documentation-function 'nrepl-eldoc) - (apply 'eldoc-add-command nrepl-extra-eldoc-commands) - (turn-on-eldoc-mode)) - -(provide 'nrepl-eldoc) -;;; nrepl-eldoc ends here diff --git a/nrepl-interaction-mode.el b/nrepl-interaction-mode.el deleted file mode 100644 index 1e4c920e..00000000 --- a/nrepl-interaction-mode.el +++ /dev/null @@ -1,116 +0,0 @@ -;;; nrepl-interaction-mode.el --- Minor mode for 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: - -;; Minor mode for REPL interactions. - -;;; Code: - -(require 'nrepl-interaction) - -(defvar nrepl-interaction-mode-map - (let ((map (make-sparse-keymap))) - (define-key map (kbd "M-.") 'nrepl-jump) - (define-key map (kbd "M-,") 'nrepl-jump-back) - (define-key map (kbd "M-TAB") 'complete-symbol) - (define-key map (kbd "C-M-x") 'nrepl-eval-expression-at-point) - (define-key map (kbd "C-c C-c") 'nrepl-eval-expression-at-point) - (define-key map (kbd "C-x C-e") 'nrepl-eval-last-expression) - (define-key map (kbd "C-c C-e") 'nrepl-eval-last-expression) - (define-key map (kbd "C-c C-p") 'nrepl-pprint-eval-last-expression) - (define-key map (kbd "C-c C-r") 'nrepl-eval-region) - (define-key map (kbd "C-c C-n") 'nrepl-eval-ns-form) - (define-key map (kbd "C-c C-m") 'nrepl-macroexpand-1) - (define-key map (kbd "C-c M-m") 'nrepl-macroexpand-all) - (define-key map (kbd "C-c M-n") 'nrepl-set-ns) - (define-key map (kbd "C-c C-d") 'nrepl-doc) - (define-key map (kbd "C-c C-s") 'nrepl-src) - (define-key map (kbd "C-c C-z") 'nrepl-switch-to-repl-buffer) - (define-key map (kbd "C-c C-Z") 'nrepl-switch-to-relevant-repl-buffer) - (define-key map (kbd "C-c M-o") 'nrepl-find-and-clear-repl-buffer) - (define-key map (kbd "C-c C-k") 'nrepl-load-current-buffer) - (define-key map (kbd "C-c C-l") 'nrepl-load-file) - (define-key map (kbd "C-c C-b") 'nrepl-interrupt) - (define-key map (kbd "C-c C-j") 'nrepl-javadoc) - (define-key map (kbd "C-c M-s") 'nrepl-selector) - (define-key map (kbd "C-c M-r") 'nrepl-rotate-connection) - (define-key map (kbd "C-c M-d") 'nrepl-display-current-connection-info) - (define-key map (kbd "C-c C-q") 'nrepl-quit) - map)) - -;;;###autoload -(define-minor-mode nrepl-interaction-mode - "Minor mode for nrepl interaction from a Clojure buffer. - -\\{nrepl-interaction-mode-map}" - nil - " nREPL/i" - nrepl-interaction-mode-map - (make-local-variable 'completion-at-point-functions) - (add-to-list 'completion-at-point-functions - 'nrepl-complete-at-point)) - -(easy-menu-define nrepl-interaction-mode-menu nrepl-interaction-mode-map - "Menu for nREPL interaction mode" - '("nREPL" - ["Jump" nrepl-jump] - ["Jump back" nrepl-jump-back] - "--" - ["Complete symbol" complete-symbol] - "--" - ["Eval expression at point" nrepl-eval-expression-at-point] - ["Eval last expression" nrepl-eval-last-expression] - ["Eval last expression in popup buffer" nrepl-pprint-eval-last-expression] - ["Eval region" nrepl-eval-region] - ["Eval ns form" nrepl-eval-ns-form] - "--" - ["Load current buffer" nrepl-load-current-buffer] - ["Load file" nrepl-load-file] - "--" - ["Macroexpand-1 last expression" nrepl-macroexpand-1] - ["Macroexpand-all last expression" nrepl-macroexpand-all] - "--" - ["Display documentation" nrepl-doc] - ["Display Source" nrepl-src] - ["Display JavaDoc" nrepl-javadoc] - "--" - ["Set ns" nrepl-set-ns] - ["Switch to REPL" nrepl-switch-to-repl-buffer] - ["Switch to Relevant REPL" nrepl-switch-to-relevant-repl-buffer] - ["Toggle REPL Pretty Print" nrepl-pretty-toggle] - ["Clear REPL" nrepl-find-and-clear-repl-buffer] - ["Interrupt" nrepl-interrupt] - ["Quit" nrepl-quit] - ["Restart" nrepl-restart] - "--" - ["Display current nrepl connection" nrepl-display-current-connection-info] - ["Rotate current nrepl connection" nrepl-rotate-connection] - "--" - ["Version info" nrepl-version])) - -(provide 'nrepl-interaction-mode) -;;; nrepl-interaction-mode.el ends here diff --git a/nrepl-interaction.el b/nrepl-interaction.el deleted file mode 100644 index 38b77798..00000000 --- a/nrepl-interaction.el +++ /dev/null @@ -1,1035 +0,0 @@ -;;; nrepl-interaction.el --- IDE for Clojure - -;; 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: - -;; Provides an Emacs Lisp client to connect to Clojure nREPL servers. - -;;; Code: - -(require 'nrepl-client) - -(require 'clojure-mode) -(require 'dash) -(require 'thingatpt) -(require 'etags) -(require 'arc-mode) -(require 'ansi-color) -(require 'cl-lib) -(require 'compile) -(require 'tramp) - -(defconst nrepl-error-buffer "*nrepl-error*") -(defconst nrepl-doc-buffer "*nrepl-doc*") -(defconst nrepl-src-buffer "*nrepl-src*") -(defconst nrepl-result-buffer "*nrepl-result*") - -(defcustom nrepl-use-local-resources t - "Use local resources under HOME if possible." - :type 'boolean - :group 'nrepl) - -(defcustom nrepl-popup-stacktraces t - "Non-nil means pop-up error stacktraces for evaluation errors. -Nil means show only an error message in the minibuffer. See also -`nrepl-repl-popup-stacktraces', which overrides this setting -for REPL buffers." - :type 'boolean - :group 'nrepl) - -(defcustom nrepl-popup-on-error t - "When `nrepl-popup-on-error' is set to t, stacktraces will be displayed. -When set to nil, stactraces will not be displayed, but will be available -in the `nrepl-error-buffer', which defaults to *nrepl-error*." - :type 'boolean - :group 'nrepl) - -(defcustom nrepl-auto-select-error-buffer nil - "Controls whether to auto-select the error popup buffer." - :type 'boolean - :group 'nrepl) - -(defface nrepl-error-highlight-face - '((((supports :underline (:style wave))) - (:underline (:style wave :color "red") :inherit unspecified)) - (t (:inherit font-lock-warning-face :underline t))) - "Face used to highlight compilation errors in Clojure buffers." - :group 'nrepl) - -(defface nrepl-warning-highlight-face - '((((supports :underline (:style wave))) - (:underline (:style wave :color "yellow") :inherit unspecified)) - (t (:inherit font-lock-warning-face :underline (:color "yellow")))) - "Face used to highlight compilation warnings in Clojure buffers." - :group 'nrepl) - -;;; Connection info -(defun nrepl--clojure-version () - "Retrieve the underlying connection's Clojure version." - (let ((version-string (plist-get (nrepl-send-string-sync "(clojure-version)") :value))) - (substring version-string 1 (1- (length version-string))))) - -(defun nrepl--backend-version () - "Retrieve the underlying connection's nREPL version." - (let ((version-string (plist-get (nrepl-send-string-sync "(:version-string clojure.tools.nrepl/version)") :value))) - (substring version-string 1 (1- (length version-string))))) - -(defun nrepl--connection-info (nrepl-connection-buffer) - "Return info about NREPL-CONNECTION-BUFFER. - -Info contains project name, current REPL namespace, host:port endpoint and Clojure version." - (with-current-buffer (get-buffer nrepl-connection-buffer) - (format "Active nrepl connection: %s:%s, %s:%s (Clojure %s, nREPL %s)" - (or (nrepl--project-name nrepl-project-dir) "") - nrepl-buffer-ns - (car nrepl-endpoint) - (cadr nrepl-endpoint) - (nrepl--clojure-version) - (nrepl--backend-version)))) - -(defun nrepl-display-current-connection-info () - "Display information about the current connection." - (interactive) - (message (nrepl--connection-info (nrepl-current-connection-buffer)))) - -(defun nrepl-rotate-connection () - "Rotate and display the current nrepl connection." - (interactive) - (setq nrepl-connection-list - (append (cdr nrepl-connection-list) - (list (car nrepl-connection-list)))) - (message (nrepl--connection-info (car nrepl-connection-list)))) - -;;; Switching between REPL & source buffers -(make-variable-buffer-local - (defvar nrepl-last-clojure-buffer nil - "A buffer-local variable holding the last Clojure source buffer. -`nrepl-switch-to-last-clojure-buffer' uses this variable to jump -back to last Clojure source buffer.")) - -(defvar nrepl-current-clojure-buffer nil - "This variable holds current buffer temporarily when connecting to a REPL. -It is set to current buffer when `nrepl' or `nrepl-jack-in' is called. -After the REPL buffer is created, the value of this variable is used -to call `nrepl-remember-clojure-buffer'.") - -(defun nrepl-remember-clojure-buffer (buffer) - "Try to remember the BUFFER from which the user jumps. -The BUFFER needs to be a Clojure buffer and current major mode needs -to be `nrepl-repl-mode'. The user can use `nrepl-switch-to-last-clojure-buffer' -to jump back to the last Clojure source buffer." - (when (and buffer - (eq 'clojure-mode (with-current-buffer buffer major-mode)) - (eq 'nrepl-repl-mode major-mode)) - (setq nrepl-last-clojure-buffer buffer))) - -(defun nrepl-switch-to-repl-buffer (arg) - "Select the REPL buffer, when possible in an existing window. - -Hint: You can use `display-buffer-reuse-frames' and -`special-display-buffer-names' to customize the frame in which -the buffer should appear. - -With a prefix ARG sets the name of the REPL buffer to the one -of the current source file." - (interactive "P") - (if (not (get-buffer (nrepl-current-connection-buffer))) - (message "No active nREPL connection.") - (progn - (let ((buffer (current-buffer))) - (when arg - (nrepl-set-ns (nrepl-current-ns))) - (pop-to-buffer (nrepl-find-or-create-repl-buffer)) - (nrepl-remember-clojure-buffer buffer) - (goto-char (point-max)))))) - -(defun nrepl-switch-to-relevant-repl-buffer (arg) - "Select the REPL buffer, when possible in an existing window. -The buffer chosen is based on the file open in the current buffer. - -Hint: You can use `display-buffer-reuse-frames' and -`special-display-buffer-names' to customize the frame in which -the buffer should appear. - -With a prefix ARG sets the name of the REPL buffer to the one -of the current source file. - -With a second prefix ARG the chosen REPL buffer is based on a -supplied project directory." - (interactive "P") - (if (not (get-buffer (nrepl-current-connection-buffer))) - (message "No active nREPL connection.") - (progn - (let ((project-directory - (or (when arg - (ido-read-directory-name "Project: ")) - (nrepl-project-directory-for (nrepl-current-dir))))) - (if project-directory - (let ((buf (car (-filter - (lambda (conn) - (let ((conn-proj-dir (with-current-buffer (get-buffer conn) - nrepl-project-dir))) - (when conn-proj-dir - (equal (file-truename project-directory) - (file-truename conn-proj-dir))))) - nrepl-connection-list)))) - (if buf - (setq nrepl-connection-list - (cons buf (delq buf nrepl-connection-list))) - (message "No relevant nREPL connection found. Switching to default connection."))) - (message "No project directory found. Switching to default nREPL connection."))) - (nrepl-switch-to-repl-buffer '())))) - -(defun nrepl-switch-to-last-clojure-buffer () - "Switch to the last Clojure buffer. -The default keybinding for this command is -the same as `nrepl-switch-to-repl-buffer', -so that it is very convenient to jump between a -Clojure buffer and the REPL buffer." - (interactive) - (if (and (eq 'nrepl-repl-mode major-mode) - (buffer-live-p nrepl-last-clojure-buffer)) - (pop-to-buffer nrepl-last-clojure-buffer) - (message "Don't know the original Clojure buffer"))) - -;;; Evaluating -(defun nrepl-eval-region (start end) - "Evaluate the region. -The two arguments START and END are character positions; -they can be in either order." - (interactive "r") - (nrepl-interactive-eval (buffer-substring-no-properties start end))) - -(defun nrepl-eval-buffer () - "Evaluate the current buffer." - (interactive) - (nrepl-eval-region (point-min) (point-max))) - -(defun nrepl-expression-at-point () - "Return the text of the expr at point." - (apply #'buffer-substring-no-properties - (nrepl-region-for-expression-at-point))) - -(defun nrepl-region-for-expression-at-point () - "Return the start and end position of defun at point." - (save-excursion - (save-match-data - (end-of-defun) - (let ((end (point))) - (beginning-of-defun) - (list (point) end))))) - -(defun nrepl-eval-expression-at-point (&optional prefix) - "Evaluate the current toplevel form, and print result in the mini-buffer. -With a PREFIX argument, print the result in the current buffer." - (interactive "P") - (let ((form (nrepl-expression-at-point))) - (if prefix - (nrepl-interactive-eval-print form) - (nrepl-interactive-eval form)))) - -(defun nrepl-eval-ns-form () - "Evaluate the current buffer's namespace form." - (interactive) - (when (clojure-find-ns) - (save-excursion - (goto-char (match-beginning 0)) - (nrepl-eval-expression-at-point)))) - -(defun nrepl-bounds-of-sexp-at-point () - "Return the bounds sexp at point as a pair (or nil)." - (or (and (equal (char-after) ?\() - (member (char-before) '(?\' ?\, ?\@)) - ;; hide stuff before ( to avoid quirks with '( etc. - (save-restriction - (narrow-to-region (point) (point-max)) - (bounds-of-thing-at-point 'sexp))) - (bounds-of-thing-at-point 'sexp))) - -(defun nrepl-sexp-at-point () - "Return the sexp at point as a string, otherwise nil." - (let ((bounds (nrepl-bounds-of-sexp-at-point))) - (if bounds - (buffer-substring-no-properties (car bounds) - (cdr bounds))))) - -(defun nrepl-sexp-at-point-with-bounds () - "Return a list containing the sexp at point and its bounds." - (let ((bounds (nrepl-bounds-of-sexp-at-point))) - (if bounds - (let ((start (car bounds)) - (end (cdr bounds))) - (list (buffer-substring-no-properties start end) - (cons (set-marker (make-marker) start) - (set-marker (make-marker) end))))))) - -(defun nrepl-last-expression () - "Return the last sexp." - (buffer-substring-no-properties - (save-excursion (backward-sexp) (point)) - (point))) - -;;; -(defun nrepl-tramp-prefix () - "Top element on `find-tag-marker-ring` used to determine Clojure host." - (let ((jump-origin (buffer-file-name - (marker-buffer - (ring-ref find-tag-marker-ring 0))))) - (when (tramp-tramp-file-p jump-origin) - (let ((vec (tramp-dissect-file-name jump-origin))) - (tramp-make-tramp-file-name (tramp-file-name-method vec) - (tramp-file-name-user vec) - (tramp-file-name-host vec) - nil))))) - -(defun nrepl-home-prefix-adjustment (resource) - "System-dependent HOME location will be adjusted in RESOURCE. -Removes any leading slash if on Windows." - (save-match-data - (cond ((string-match "^\\/\\(Users\\|home\\)\\/\\w+\\(\\/.+\\)" resource) - (concat (getenv "HOME") (match-string 2 resource))) - ((and (eq system-type 'windows-nt) - (string-match "^/" resource) - (not (tramp-tramp-file-p resource))) - (substring resource 1)) - (t - resource)))) - -(defun nrepl-emacs-or-clojure-side-adjustment (resource) - "Fix the RESOURCE path depending on `nrepl-use-local-resources`." - (let ((resource (nrepl-home-prefix-adjustment resource)) - (clojure-side-res (concat (nrepl-tramp-prefix) resource)) - (emacs-side-res resource)) - (cond ((equal resource "") resource) - ((and nrepl-use-local-resources - (file-exists-p emacs-side-res)) - emacs-side-res) - ((file-exists-p clojure-side-res) - clojure-side-res) - (t - resource)))) - -(defun nrepl-find-file (filename) - "Switch to a buffer visiting FILENAME. -Adjusts for HOME location using `nrepl-home-prefix-adjustment'. Uses `find-file'." - (find-file (nrepl-emacs-or-clojure-side-adjustment filename))) - -(defun nrepl-find-resource (resource) - "Find and display RESOURCE." - (cond ((string-match "^file:\\(.+\\)" resource) - (nrepl-find-file (match-string 1 resource))) - ((string-match "^\\(jar\\|zip\\):file:\\(.+\\)!/\\(.+\\)" resource) - (let* ((jar (match-string 2 resource)) - (path (match-string 3 resource)) - (buffer-already-open (get-buffer (file-name-nondirectory jar)))) - (nrepl-find-file jar) - (goto-char (point-min)) - (search-forward path) - (let ((opened-buffer (current-buffer))) - (archive-extract) - (when (not buffer-already-open) - (kill-buffer opened-buffer))))) - (t (error "Unknown resource path %s" resource)))) - -(defun nrepl-jump-to-def-for (location) - "Jump to LOCATION's definition in the source code." - ;; ugh; elisp destructuring doesn't work for vectors - (let ((resource (aref location 0)) - (path (aref location 1)) - (line (aref location 2))) - (if (and path (file-exists-p path)) - (find-file path) - (nrepl-find-resource resource)) - (goto-char (point-min)) - (forward-line (1- line)))) - -(defun nrepl-jump-to-def-handler (buffer) - "Create a handler for jump-to-def in BUFFER." - ;; TODO: got to be a simpler way to do this - (nrepl-make-response-handler buffer - (lambda (buffer value) - (with-current-buffer buffer - (ring-insert find-tag-marker-ring (point-marker))) - (nrepl-jump-to-def-for - (car (read-from-string value)))) - (lambda (buffer out) (message out)) - (lambda (buffer err) (message err)) - nil)) - -(defun nrepl-jump-to-def (var) - "Jump to the definition of the VAR at point." - (let ((form (format "(let [ns-symbol '%s - ns-var '%s - ns-file (clojure.core/comp :file - clojure.core/meta - clojure.core/second - clojure.core/first - clojure.core/ns-publics) - resource-str (clojure.core/comp clojure.core/str - clojure.java.io/resource - ns-file) - file-str (clojure.core/comp clojure.core/str - clojure.java.io/file - ns-file)] - (cond ((clojure.core/ns-aliases ns-symbol) ns-var) - (let [resolved-ns ((clojure.core/ns-aliases ns-symbol) ns-var)] - [(resource-str resolved-ns) - (file-str resolved-ns) - 1]) - - (find-ns ns-var) - [(resource-str ns-var) - (file-str ns-var) - 1] - - (clojure.core/ns-resolve ns-symbol ns-var) - ((clojure.core/juxt - (clojure.core/comp clojure.core/str - clojure.java.io/resource - :file) - (clojure.core/comp clojure.core/str - clojure.java.io/file - :file) - :line) - (clojure.core/meta (clojure.core/ns-resolve ns-symbol ns-var)))))" - (nrepl-current-ns) var))) - (nrepl-send-string form - (nrepl-jump-to-def-handler (current-buffer)) - nrepl-buffer-ns - (nrepl-current-tooling-session)))) - -(defun nrepl-jump (query) - "Jump to the definition of QUERY." - (interactive "P") - (nrepl-read-symbol-name "Symbol: " 'nrepl-jump-to-def query)) - -(defalias 'nrepl-jump-back 'pop-tag-mark) - -(defun nrepl-completion-complete-core-fn (str) - "Return a list of completions for STR using complete.core/completions." - (let ((strlst (plist-get - (nrepl-send-string-sync - (format "(require 'complete.core) (complete.core/completions \"%s\" *ns*)" str) - nrepl-buffer-ns - (nrepl-current-tooling-session)) - :value))) - (when strlst - (car (read-from-string strlst))))) - -(defun nrepl-completion-complete-op-fn (str) - "Return a list of completions for STR using the nREPL \"complete\" op." - (lexical-let ((strlst (plist-get - (nrepl-send-request-sync - (list "op" "complete" - "session" (nrepl-current-tooling-session) - "ns" nrepl-buffer-ns - "symbol" str)) - :value))) - (when strlst - (car strlst)))) - -(defun nrepl-dispatch-complete-symbol (str) - "Return a list of completions for STR. -Dispatch to the nREPL \"complete\" op if supported, -otherwise dispatch to internal completion function." - (if (nrepl-op-supported-p "complete") - (nrepl-completion-complete-op-fn str) - (nrepl-completion-complete-core-fn str))) - -(defun nrepl-complete-at-point () - "Complete the symbol at point." - (let ((sap (symbol-at-point))) - (when (and sap (not (in-string-p))) - (let ((bounds (bounds-of-thing-at-point 'symbol))) - (list (car bounds) (cdr bounds) - (completion-table-dynamic #'nrepl-dispatch-complete-symbol)))))) - - -;;; JavaDoc Browsing -;;; Assumes local-paths are accessible in the VM. -(defvar nrepl-javadoc-local-paths nil - "List of paths to directories with Javadoc.") - -(defun nrepl-javadoc-op (symbol-name) - "Invoke the nREPL \"javadoc\" op on SYMBOL-NAME." - (nrepl-send-op - "javadoc" - `("symbol" ,symbol-name "ns" ,nrepl-buffer-ns - "local-paths" ,(mapconcat #'identity nrepl-javadoc-local-paths " ")) - (nrepl-make-response-handler - (current-buffer) - (lambda (buffer url) - (if url - (browse-url url) - (error "No javadoc url for %s" symbol-name))) - nil nil nil))) - -(defun nrepl-javadoc-handler (symbol-name) - "Invoke the nREPL \"javadoc\" op on SYMBOL-NAME if available." - (when symbol-name - (let ((bounds (bounds-of-thing-at-point 'symbol))) - (if (nrepl-op-supported-p "javadoc") - (nrepl-javadoc-op symbol-name) - (message "No Javadoc middleware available"))))) - -(defun nrepl-javadoc (query) - "Browse Javadoc on the Java class QUERY at point." - (interactive "P") - (nrepl-read-symbol-name "Javadoc for: " 'nrepl-javadoc-handler query)) - -(defun nrepl-stdin-handler (buffer) - "Make a stdin response handler for BUFFER." - (nrepl-make-response-handler buffer - (lambda (buffer value) - (nrepl-emit-result buffer value t)) - (lambda (buffer out) - (nrepl-emit-output buffer out t)) - (lambda (buffer err) - (nrepl-emit-output buffer err t)) - nil)) - -(defun nrepl-handler (buffer) - "Make a nrepl evaluation handler for BUFFER." - (nrepl-make-response-handler buffer - (lambda (buffer value) - (nrepl-emit-result buffer value t)) - (lambda (buffer out) - (nrepl-emit-output buffer out t)) - (lambda (buffer err) - (nrepl-emit-output buffer err t)) - (lambda (buffer) - (nrepl-emit-prompt buffer)))) - -(defun nrepl-interactive-eval-handler (buffer) - "Make an interactive eval handler for BUFFER." - (nrepl-make-response-handler buffer - (lambda (buffer value) - (message "%s" value)) - (lambda (buffer value) - (nrepl-emit-interactive-output value)) - (lambda (buffer err) - (message "%s" err) - (nrepl-highlight-compilation-errors - buffer err)) - '())) - -(defun nrepl-load-file-handler (buffer) - "Make a load file handler for BUFFER." - (let (current-ns (nrepl-current-ns)) - (nrepl-make-response-handler buffer - (lambda (buffer value) - (message "%s" value) - (with-current-buffer buffer - (setq nrepl-buffer-ns (clojure-find-ns)) - (run-hooks 'nrepl-file-loaded-hook))) - (lambda (buffer value) - (nrepl-emit-interactive-output value)) - (lambda (buffer err) - (message "%s" err) - (nrepl-highlight-compilation-errors - buffer err)) - '() - (lambda (buffer ex root-ex session) - (let ((nrepl-popup-on-error nil)) - (funcall nrepl-err-handler - buffer ex root-ex session)))))) - -(defun nrepl-interactive-eval-print-handler (buffer) - "Make a handler for evaluating and printing result in BUFFER." - (nrepl-make-response-handler buffer - (lambda (buffer value) - (with-current-buffer buffer - (insert (format "%s" value)))) - '() - (lambda (buffer err) - (message "%s" err)) - '())) - -(defun nrepl-popup-eval-print-handler (buffer) - "Make a handler for evaluating and printing result in popup BUFFER." - (nrepl-make-response-handler buffer - (lambda (buffer str) - (nrepl-emit-into-popup-buffer buffer str)) - '() - (lambda (buffer str) - (nrepl-emit-into-popup-buffer buffer str)) - '())) - -(defun nrepl-popup-eval-out-handler (buffer) - "Make a handler for evaluating and printing stdout/stderr in popup BUFFER." - (nrepl-make-response-handler buffer - '() - (lambda (buffer str) - (nrepl-emit-into-popup-buffer buffer str)) - (lambda (buffer str) - (nrepl-emit-into-popup-buffer buffer str)) - '())) - -(defun nrepl-visit-error-buffer () - "Visit the `nrepl-error-buffer' (usually *nrepl-error*) if it exists." - (interactive) - (let ((buffer (get-buffer nrepl-error-buffer))) - (when buffer - (nrepl-popup-buffer-display buffer)))) - -(defun nrepl-find-property (property &optional backward) - "Find the next text region which has the specified PROPERTY. -If BACKWARD is t, then search backward. -Returns the position at which PROPERTY was found, or nil if not found." - (let ((p (if backward - (previous-single-char-property-change (point) property) - (next-single-char-property-change (point) property)))) - (when (and (not (= p (point-min))) (not (= p (point-max)))) - p))) - -(defun nrepl-jump-to-compilation-error (&optional arg reset) - "Jump to the line causing the current compilation error. - -ARG and RESET are ignored, as there is only ever one compilation error. -They exist for compatibility with `next-error'." - (interactive) - (cl-labels ((goto-next-note-boundary - () - (let ((p (or (nrepl-find-property 'nrepl-note-p) - (nrepl-find-property 'nrepl-note-p t)))) - (when p - (goto-char p) - (message (get-char-property p 'nrepl-note)))))) - ;; if we're already on a compilation error, first jump to the end of - ;; it, so that we find the next error. - (when (get-char-property (point) 'nrepl-note-p) - (goto-next-note-boundary)) - (goto-next-note-boundary))) - -(defun nrepl-default-err-handler (buffer ex root-ex session) - "Make an error handler for BUFFER, EX, ROOT-EX and SESSION." - ;; TODO: use ex and root-ex as fallback values to display when pst/print-stack-trace-not-found - (let ((replp (equal 'nrepl-repl-mode (buffer-local-value 'major-mode buffer)))) - (if (or (and nrepl-repl-popup-stacktraces replp) - (and nrepl-popup-stacktraces (not replp))) - (lexical-let ((nrepl-popup-on-error nrepl-popup-on-error)) - (with-current-buffer buffer - (nrepl-send-string "(if-let [pst+ (clojure.core/resolve 'clj-stacktrace.repl/pst+)] - (pst+ *e) (clojure.stacktrace/print-stack-trace *e))" - (nrepl-make-response-handler - (nrepl-make-popup-buffer nrepl-error-buffer) - nil - (lambda (buffer value) - (nrepl-emit-into-color-buffer buffer value) - (when nrepl-popup-on-error - (nrepl-popup-buffer-display buffer nrepl-auto-select-error-buffer))) - nil nil) nil session)) - (with-current-buffer nrepl-error-buffer - (compilation-minor-mode +1)))))) - -(defvar nrepl-compilation-regexp - '("\\(?:.*\\(warning, \\)\\|.*?\\(, compiling\\):(\\)\\([^:]*\\):\\([[:digit:]]+\\)\\(?::\\([[:digit:]]+\\)\\)?\\(\\(?: - \\(.*\\)\\)\\|)\\)" 3 4 5 (1)) - "Specifications for matching errors and warnings in Clojure stacktraces. -See `compilation-error-regexp-alist' for help on their format.") - -(add-to-list 'compilation-error-regexp-alist-alist - (cons 'nrepl nrepl-compilation-regexp)) -(add-to-list 'compilation-error-regexp-alist 'nrepl) - -(defun nrepl-extract-error-info (regexp message) - "Extract error information with REGEXP against MESSAGE." - (let ((file (nth 1 regexp)) - (line (nth 2 regexp)) - (col (nth 3 regexp)) - (type (nth 4 regexp)) - (pat (car regexp))) - (when (string-match pat message) - ;; special processing for type (1.2) style - (setq type (if (consp type) - (or (and (car type) (match-end (car type)) 1) - (and (cdr type) (match-end (cdr type)) 0) - 2))) - (list - (when file - (let ((val (match-string-no-properties file message))) - (unless (string= val "NO_SOURCE_PATH") val))) - (when line (string-to-number (match-string-no-properties line message))) - (when col - (let ((val (match-string-no-properties col message))) - (when val (string-to-number val)))) - (aref [nrepl-warning-highlight-face - nrepl-warning-highlight-face - nrepl-error-highlight-face] - (or type 2)) - message)))) - -(defun nrepl-highlight-compilation-errors (buffer message) - "Highlight compilation error line in BUFFER, using MESSAGE." - (with-current-buffer buffer - (let ((info (nrepl-extract-error-info nrepl-compilation-regexp message))) - (when info - (let ((file (nth 0 info)) - (line (nth 1 info)) - (col (nth 2 info)) - (face (nth 3 info)) - (note (nth 4 info))) - (save-excursion - ;; when we don't have a filename the line number - ;; is relative to form start - (if file - (goto-char (point-min)) ; start of file - (beginning-of-defun)) - (forward-line (1- line)) - ;; if have column, highlight sexp at that point otherwise whole line. - (move-to-column (or col 0)) - (let ((begin (progn (if col (backward-up-list) (back-to-indentation)) (point))) - (end (progn (if col (forward-sexp) (move-end-of-line nil)) (point)))) - (let ((overlay (make-overlay begin end))) - (overlay-put overlay 'nrepl-note-p t) - (overlay-put overlay 'face face) - (overlay-put overlay 'nrepl-note note) - (overlay-put overlay 'help-echo note))))))))) - -(defun nrepl-need-input (buffer) - "Handle an need-input request from BUFFER." - (with-current-buffer buffer - (nrepl-send-stdin (concat (read-from-minibuffer "Stdin: ") "\n") - (nrepl-stdin-handler buffer)))) - - -;;;; Popup buffers -(define-minor-mode nrepl-popup-buffer-mode - "Mode for nrepl popup buffers" - nil - (" nREPL-tmp") - '(("q" . nrepl-popup-buffer-quit-function))) - -(make-variable-buffer-local - (defvar nrepl-popup-buffer-quit-function 'nrepl-popup-buffer-quit - "The function that is used to quit a temporary popup buffer.")) - -(defun nrepl-popup-buffer-quit-function (&optional kill-buffer-p) - "Wrapper to invoke the function `nrepl-popup-buffer-quit-function'. -KILL-BUFFER-P is passed along." - (interactive) - (funcall nrepl-popup-buffer-quit-function kill-buffer-p)) - -(defun nrepl-popup-buffer (name &optional select) - "Create new popup buffer called NAME. -If SELECT is non-nil, select the newly created window" - (with-current-buffer (nrepl-make-popup-buffer name) - (setq buffer-read-only t) - (nrepl-popup-buffer-display (current-buffer) select))) - -(defun nrepl-popup-buffer-display (popup-buffer &optional select) - "Display POPUP-BUFFER. -If SELECT is non-nil, select the newly created window" - (with-current-buffer popup-buffer - (let ((new-window (display-buffer (current-buffer)))) - (set-window-point new-window (point)) - (when select - (select-window new-window)) - (current-buffer)))) - -(defun nrepl-popup-buffer-quit (&optional kill-buffer-p) - "Quit the current (temp) window and bury its buffer using `quit-window'. -If prefix argument KILL-BUFFER-P is non-nil, kill the buffer instead of burying it." - (interactive) - (quit-window kill-buffer-p (selected-window))) - -(defun nrepl-make-popup-buffer (name) - "Create a temporary buffer called NAME." - (with-current-buffer (get-buffer-create name) - (kill-all-local-variables) - (setq buffer-read-only nil) - (erase-buffer) - (set-syntax-table clojure-mode-syntax-table) - (nrepl-popup-buffer-mode 1) - (current-buffer))) - -(defun nrepl-emit-into-popup-buffer (buffer value) - "Emit into BUFFER the provided VALUE." - (with-current-buffer buffer - (let ((inhibit-read-only t) - (buffer-undo-list t)) - (insert (format "%s" value)) - (indent-sexp) - (font-lock-fontify-buffer)))) - -(defun nrepl-emit-into-color-buffer (buffer value) - "Emit into color BUFFER the provided VALUE." - (with-current-buffer buffer - (let ((inhibit-read-only t) - (buffer-undo-list t)) - (goto-char (point-max)) - (insert (format "%s" value)) - (ansi-color-apply-on-region (point-min) (point-max))) - (goto-char (point-min)))) - - -(defun nrepl-popup-eval-print (form) - "Evaluate the given FORM and print value in current buffer." - (let ((buffer (current-buffer))) - (nrepl-send-string form - (nrepl-popup-eval-print-handler buffer) - (nrepl-current-ns)))) - -(defun nrepl-interactive-eval-print (form) - "Evaluate the given FORM and print value in current buffer." - (let ((buffer (current-buffer))) - (nrepl-send-string form - (nrepl-interactive-eval-print-handler buffer) - (nrepl-current-ns)))) - -(defun nrepl-interactive-eval (form) - "Evaluate the given FORM and print value in minibuffer." - (remove-overlays (point-min) (point-max) 'nrepl-note-p t) - (let ((buffer (current-buffer))) - (nrepl-send-string form - (nrepl-interactive-eval-handler buffer) - (nrepl-current-ns)))) - -(defun nrepl-send-op (op attributes handler) - "Send the specified OP with ATTRIBUTES and response HANDLER." - (let ((buffer (current-buffer))) - (nrepl-send-request (append - (list "op" op - "session" (nrepl-current-session) - "ns" nrepl-buffer-ns) - attributes) - handler))) - -(defun nrepl-send-load-file (file-contents file-path file-name) - "Perform the nREPL \"load-file\" op. -FILE-CONTENTS, FILE-PATH and FILE-NAME are details of the file to be -loaded." - (let ((buffer (current-buffer))) - (nrepl-send-request (list "op" "load-file" - "session" (nrepl-current-session) - "file" file-contents - "file-path" file-path - "file-name" file-name) - (nrepl-load-file-handler buffer)))) - -(defun nrepl-eval-last-expression (&optional prefix) - "Evaluate the expression preceding point. -If invoked with a PREFIX argument, print the result in the current buffer." - (interactive "P") - (if prefix - (nrepl-interactive-eval-print (nrepl-last-expression)) - (nrepl-interactive-eval (nrepl-last-expression)))) - -(defun nrepl-eval-print-last-expression () - "Evaluate the expression preceding point. -Print its value into the current buffer" - (interactive) - (nrepl-interactive-eval-print (nrepl-last-expression))) - -(defun nrepl-pprint-eval-last-expression () - "Evaluate the expression preceding point and pprint its value in a popup buffer." - (interactive) - (let ((form (nrepl-last-expression)) - (result-buffer (nrepl-popup-buffer nrepl-result-buffer nil))) - (nrepl-send-string (format "(clojure.pprint/pprint %s)" form) - (nrepl-popup-eval-out-handler result-buffer) - (nrepl-current-ns) - (nrepl-current-tooling-session)))) - -(defun clojure-enable-nrepl () - "Turn on nrepl interaction mode (see command `nrepl-interaction-mode'). -Useful in hooks." - (nrepl-interaction-mode 1) - (setq next-error-function 'nrepl-jump-to-compilation-error)) - -(defun clojure-disable-nrepl () - "Turn off nrepl interaction mode (see command `nrepl-interaction-mode'). -Useful in hooks." - (nrepl-interaction-mode -1)) - -;; this is horrible, but with async callbacks we can't rely on dynamic scope -(defvar nrepl-ido-ns nil) - -(defun nrepl-ido-form (ns) - "Construct a Clojure form for ido read using NS." - `(concat (if (find-ns (symbol ,ns)) - (map name (concat (keys (ns-interns (symbol ,ns))) - (keys (ns-refers (symbol ,ns)))))) - (if (not= "" ,ns) [".."]) - (->> (all-ns) - (map (fn [n] - (re-find (re-pattern (str "^" (if (not= ,ns "") - (str ,ns "\\.")) - "[^\\.]+")) - (str n)))) - (filter identity) - (map (fn [n] (str n "/"))) - (into (hash-set))))) - -(defun nrepl-ido-up-ns (ns) - "Perform up using NS." - (mapconcat 'identity (butlast (split-string ns "\\.")) ".")) - -(defun nrepl-ido-select (selected targets callback) - "Peform ido select using SELECTED, TARGETS and CALLBACK." - ;; TODO: immediate RET gives "" as selected for some reason - ;; this is an OK workaround though - (cond ((equal "" selected) - (nrepl-ido-select (car targets) targets callback)) - ((equal "/" (substring selected -1)) ; selected a namespace - (nrepl-ido-read-var (substring selected 0 -1) callback)) - ((equal ".." selected) - (nrepl-ido-read-var (nrepl-ido-up-ns nrepl-ido-ns) callback)) - ;; non ido variable selection techniques don't return qualified symbols, so this shouldn't either - (t (funcall callback selected)))) - -(defun nrepl-ido-read-var-handler (ido-callback buffer) - "Create an ido read var handler with IDO-CALLBACK for BUFFER." - (lexical-let ((ido-callback ido-callback)) - (nrepl-make-response-handler buffer - (lambda (buffer value) - ;; make sure to eval the callback in the buffer that the symbol was requested from so we get the right namespace - (with-current-buffer buffer - (let* ((targets (car (read-from-string value))) - (selected (ido-completing-read "Var: " targets nil t))) - (nrepl-ido-select selected targets ido-callback)))) - nil nil nil))) - -(defun nrepl-ido-read-var (ns ido-callback) - "Perform ido read var in NS using IDO-CALLBACK." - ;; Have to be stateful =( - (setq nrepl-ido-ns ns) - (nrepl-send-string (prin1-to-string (nrepl-ido-form nrepl-ido-ns)) - (nrepl-ido-read-var-handler ido-callback (current-buffer)) - nrepl-buffer-ns - (nrepl-current-tooling-session))) - -(defun nrepl-read-symbol-name (prompt callback &optional query) - "Either read a symbol name using PROMPT or choose the one at point. -Use CALLBACK as the ido read var callback. -The user is prompted with PROMPT if a prefix argument is in effect, -if there is no symbol at point, or if QUERY is non-nil." - (let ((symbol-name (nrepl-symbol-at-point))) - (cond ((not (or current-prefix-arg query (not symbol-name))) - (funcall callback symbol-name)) - (ido-mode (nrepl-ido-read-var nrepl-buffer-ns callback)) - (t (funcall callback (read-from-minibuffer prompt symbol-name)))))) - -(defun nrepl-doc-handler (symbol) - "Create a handler to lookup documentation for SYMBOL." - (let ((form (format "(clojure.repl/doc %s)" symbol)) - (doc-buffer (nrepl-popup-buffer nrepl-doc-buffer t))) - (nrepl-send-string form - (nrepl-popup-eval-out-handler doc-buffer) - nrepl-buffer-ns - (nrepl-current-tooling-session)))) - -(defun nrepl-doc (query) - "Open a window with the docstring for the given QUERY. -Defaults to the symbol at point. With prefix arg or no symbol -under point, prompts for a var." - (interactive "P") - (nrepl-read-symbol-name "Symbol: " 'nrepl-doc-handler query)) - -(defun nrepl-src-handler (symbol) - "Create a handler to lookup source for SYMBOL." - (let ((form (format "(clojure.repl/source %s)" symbol)) - (src-buffer (nrepl-popup-buffer nrepl-src-buffer t))) - (with-current-buffer src-buffer - (clojure-mode) - (nrepl-popup-buffer-mode +1)) - (nrepl-send-string form - (nrepl-popup-eval-out-handler src-buffer) - nrepl-buffer-ns - (nrepl-current-tooling-session)))) - -(defun nrepl-src (query) - "Open a window with the source for the given QUERY. -Defaults to the symbol at point. With prefix arg or no symbol -under point, prompts for a var." - (interactive "P") - (nrepl-read-symbol-name "Symbol: " 'nrepl-src-handler query)) - -;; TODO: implement reloading ns -(defun nrepl-eval-load-file (form) - "Load FORM." - (let ((buffer (current-buffer))) - (nrepl-send-string form (nrepl-interactive-eval-handler buffer)))) - -(defun nrepl-file-string (file) - "Read the contents of a FILE and return as a string." - (with-current-buffer (find-file-noselect file) - (buffer-string))) - -(defun nrepl-load-file-op (filename) - "Send \"load-file\" op for FILENAME." - (nrepl-send-load-file (nrepl-file-string filename) - filename - (file-name-nondirectory filename))) - -(defun nrepl-load-file-core (filename) - "Load the Clojure file FILENAME." - (let ((fn (replace-regexp-in-string - "\\\\" "\\\\\\\\" - (convert-standard-filename (expand-file-name filename))))) - (nrepl-eval-load-file - (format "(clojure.core/load-file \"%s\")\n(in-ns '%s)\n" - fn (nrepl-find-ns))))) - -(defun nrepl-dispatch-load-file (filename) - "Dispatch the load file operation for FILENAME." - (if (nrepl-op-supported-p "load-file") - (nrepl-load-file-op filename) - (nrepl-load-file-core filename))) - -(defun nrepl-load-file (filename) - "Load the Clojure file FILENAME." - (interactive (list - (read-file-name "Load file: " nil nil - nil (if (buffer-file-name) - (file-name-nondirectory - (buffer-file-name)))))) - (remove-overlays (point-min) (point-max) 'nrepl-note-p t) - (nrepl-dispatch-load-file filename) - (message "Loading %s..." filename)) - -(defun nrepl-load-current-buffer () - "Load current buffer's file." - (interactive) - (check-parens) - (unless buffer-file-name - (error "Buffer %s is not associated with a file" (buffer-name))) - (when (and (buffer-modified-p) - (y-or-n-p (format "Save file %s? " (buffer-file-name)))) - (save-buffer)) - (nrepl-load-file (buffer-file-name))) - -(defun nrepl-recently-visited-buffer (mode) - "Return the most recently visited buffer whose `major-mode' is MODE. -Only considers buffers that are not already visible." - (loop for buffer in (buffer-list) - when (and (with-current-buffer buffer (eq major-mode mode)) - (not (string-match "^ " (buffer-name buffer))) - (null (get-buffer-window buffer 'visible))) - return buffer - finally (error "Can't find unshown buffer in %S" mode))) - -(provide 'nrepl-interaction) -;;; nrepl-interaction.el ends here diff --git a/nrepl-macroexpansion.el b/nrepl-macroexpansion.el deleted file mode 100644 index ba08187f..00000000 --- a/nrepl-macroexpansion.el +++ /dev/null @@ -1,159 +0,0 @@ -;;; nrepl-macroexpansion.el --- Macro expansion support - -;; 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: - -;; Macro expansion support. - -;;; Code: - -(defconst nrepl-macroexpansion-buffer "*nrepl-macroexpansion*") - -(defun nrepl-macroexpand-undo (&optional arg) - "Undo the last macroexpansion, using `undo-only'. -ARG is passed along to `undo-only'." - (interactive) - (let ((inhibit-read-only t)) - (undo-only arg))) - -(defvar nrepl-last-macroexpand-expression nil - "Specify the last macroexpansion preformed. -This variable specifies both what was expanded and the expander.") - -(defun nrepl-macroexpand-form (expander expr) - "Macroexpand, using EXPANDER, the given EXPR." - (format - "(clojure.pprint/write (%s '%s) :suppress-namespaces false :dispatch clojure.pprint/code-dispatch)" - expander expr)) - -(defun nrepl-macroexpand-expr (expander expr &optional buffer) - "Macroexpand, use EXPANDER, the given EXPR from BUFFER." - (let* ((form (nrepl-macroexpand-form expander expr)) - (expansion (plist-get (nrepl-send-string-sync form nrepl-buffer-ns) :stdout))) - (setq nrepl-last-macroexpand-expression form) - (nrepl-initialize-macroexpansion-buffer expansion nrepl-buffer-ns))) - -(defun nrepl-macroexpand-expr-inplace (expander) - "Substitute the current form at point with its macroexpansion using EXPANDER." - (interactive) - (let ((form-with-bounds (nrepl-sexp-at-point-with-bounds))) - (if form-with-bounds - (destructuring-bind (expr bounds) form-with-bounds - (let* ((form (nrepl-macroexpand-form expander expr)) - (expansion (plist-get (nrepl-send-string-sync form nrepl-buffer-ns) :stdout))) - (nrepl-redraw-macroexpansion-buffer - expansion (current-buffer) (car bounds) (cdr bounds) (point))))))) - -(defun nrepl-macroexpand-again () - "Repeat the last macroexpansion." - (interactive) - (let ((expansion - (plist-get (nrepl-send-string-sync nrepl-last-macroexpand-expression nrepl-buffer-ns) :stdout))) - (nrepl-initialize-macroexpansion-buffer expansion nrepl-buffer-ns))) - -(defun nrepl-macroexpand-1 (&optional prefix) - "Invoke 'macroexpand-1' on the expression at point. -If invoked with a PREFIX argument, use 'macroexpand' instead of -'macroexpand-1'." - (interactive "P") - (let ((expander (if prefix 'macroexpand 'macroexpand-1))) - (nrepl-macroexpand-expr expander (nrepl-sexp-at-point)))) - -(defun nrepl-macroexpand-1-inplace (&optional prefix) - "Perform inplace 'macroexpand-1' on the expression at point. -If invoked with a PREFIX argument, use 'macroexpand' instead of -'macroexpand-1'." - (interactive "P") - (let ((expander (if prefix 'macroexpand 'macroexpand-1))) - (nrepl-macroexpand-expr-inplace expander))) - -(defun nrepl-macroexpand-all () - "Invoke 'clojure.walk/macroexpand-all' on the expression at point." - (interactive) - (nrepl-macroexpand-expr - 'clojure.walk/macroexpand-all (nrepl-sexp-at-point))) - -(defun nrepl-macroexpand-all-inplace () - "Perform inplace 'clojure.walk/macroexpand-all' on the expression at point." - (interactive) - (nrepl-macroexpand-expr-inplace 'clojure.walk/macroexpand-all)) - -(defun nrepl-initialize-macroexpansion-buffer (expansion ns) - "Create a new Macroexpansion buffer with EXPANSION and namespace NS." - (pop-to-buffer (nrepl-create-macroexpansion-buffer)) - (setq nrepl-buffer-ns ns) - (setq buffer-undo-list nil) - (let ((inhibit-read-only t) - (buffer-undo-list t)) - (erase-buffer) - (insert (format "%s" expansion)) - (goto-char (point-min)) - (font-lock-fontify-buffer))) - -(defun nrepl-redraw-macroexpansion-buffer (expansion buffer start end current-point) - "Redraw the macroexpansion with new EXPANSION. -Text in BUFFER from START to END is replaced with new expansion, -and point is placed at CURRENT-POINT." - (with-current-buffer buffer - (let ((buffer-read-only nil)) - (goto-char start) - (delete-region start end) - (insert (format "%s" expansion)) - (goto-char start) - (indent-sexp) - (goto-char current-point)))) - -(defun nrepl-create-macroexpansion-buffer () - "Create a new macroexpansion buffer." - (with-current-buffer (nrepl-popup-buffer nrepl-macroexpansion-buffer t) - (clojure-mode) - (clojure-disable-nrepl) - (nrepl-macroexpansion-minor-mode 1) - (current-buffer))) - -(defvar nrepl-macroexpansion-minor-mode-map - (let ((map (make-sparse-keymap))) - (define-key map (kbd "g") 'nrepl-macroexpand-again) - (define-key map (kbd "q") 'nrepl-popup-buffer-quit-function) - (cl-labels ((redefine-key (from to) - (dolist (mapping (where-is-internal from nrepl-interaction-mode-map)) - (define-key map mapping to)))) - (redefine-key 'nrepl-macroexpand-1 'nrepl-macroexpand-1-inplace) - (redefine-key 'nrepl-macroexpand-all 'nrepl-macroexpand-all-inplace) - (redefine-key 'advertised-undo 'nrepl-macroexpand-undo) - (redefine-key 'undo 'nrepl-macroexpand-undo)) - map)) - -(define-minor-mode nrepl-macroexpansion-minor-mode - "Minor mode for nrepl macroexpansion. - -\\{nrepl-macroexpansion-minor-mode-map}" - nil - " Macroexpand" - nrepl-macroexpansion-minor-mode-map) - -(provide 'nrepl-macroexpansion) -;;; nrepl-macroexpansion.el ends here diff --git a/nrepl-repl-mode.el b/nrepl-repl-mode.el deleted file mode 100644 index cecfba3d..00000000 --- a/nrepl-repl-mode.el +++ /dev/null @@ -1,156 +0,0 @@ -;;; nrepl-repl-mode.el --- Major mode for 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: - -;; Major mode for REPL interactions. - -;;; Code: - -(require 'nrepl-repl) - -(require 'clojure-mode) -(require 'easymenu) - -(eval-when-compile - (defvar paredit-version) - (defvar paredit-space-for-delimiter-predicates)) - -;;; Prevent paredit from inserting some inappropriate spaces. -;;; C.f. clojure-mode.el -(defun nrepl-space-for-delimiter-p (endp delim) - "Hook for paredit's `paredit-space-for-delimiter-predicates'. - -Decides if paredit should insert a space after/before (if/unless -ENDP) DELIM." - (if (eq major-mode 'nrepl-repl-mode) - (save-excursion - (backward-char) - (if (and (or (char-equal delim ?\() - (char-equal delim ?\") - (char-equal delim ?{)) - (not endp)) - (if (char-equal (char-after) ?#) - (and (not (bobp)) - (or (char-equal ?w (char-syntax (char-before))) - (char-equal ?_ (char-syntax (char-before))))) - t) - t)) - t)) - -(defvar nrepl-repl-mode-hook nil - "Hook executed when entering `nrepl-repl-mode'.") - -(defvar nrepl-repl-mode-syntax-table - (copy-syntax-table clojure-mode-syntax-table)) - -(defvar nrepl-repl-mode-map - (let ((map (make-sparse-keymap))) - (set-keymap-parent map clojure-mode-map) - (define-key map (kbd "M-.") 'nrepl-jump) - (define-key map (kbd "M-,") 'nrepl-jump-back) - (define-key map (kbd "RET") 'nrepl-return) - (define-key map (kbd "TAB") 'nrepl-tab) - (define-key map (kbd "C-") 'nrepl-closing-return) - (define-key map (kbd "C-j") 'nrepl-newline-and-indent) - (define-key map (kbd "C-c C-d") 'nrepl-doc) - (define-key map (kbd "C-c C-s") 'nrepl-src) - (define-key map (kbd "C-c C-o") 'nrepl-clear-output) - (define-key map (kbd "C-c M-o") 'nrepl-clear-buffer) - (define-key map (kbd "C-c C-u") 'nrepl-kill-input) - (define-key map (kbd "C-a") 'nrepl-bol) - (define-key map (kbd "C-S-a") 'nrepl-bol-mark) - (define-key map [home] 'nrepl-bol) - (define-key map [S-home] 'nrepl-bol-mark) - (define-key map (kbd "C-") 'nrepl-backward-input) - (define-key map (kbd "C-") 'nrepl-forward-input) - (define-key map (kbd "M-p") 'nrepl-previous-input) - (define-key map (kbd "M-n") 'nrepl-next-input) - (define-key map (kbd "M-r") 'nrepl-previous-matching-input) - (define-key map (kbd "M-s") 'nrepl-next-matching-input) - (define-key map (kbd "C-c C-n") 'nrepl-next-prompt) - (define-key map (kbd "C-c C-p") 'nrepl-previous-prompt) - (define-key map (kbd "C-c C-b") 'nrepl-interrupt) - (define-key map (kbd "C-c C-c") 'nrepl-interrupt) - (define-key map (kbd "C-c C-j") 'nrepl-javadoc) - (define-key map (kbd "C-c C-m") 'nrepl-macroexpand-1) - (define-key map (kbd "C-c M-m") 'nrepl-macroexpand-all) - (define-key map (kbd "C-c C-z") 'nrepl-switch-to-last-clojure-buffer) - (define-key map (kbd "C-c M-s") 'nrepl-selector) - (define-key map (kbd "C-c M-r") 'nrepl-rotate-connection) - (define-key map (kbd "C-c M-d") 'nrepl-display-current-connection-info) - (define-key map (kbd "C-c C-q") 'nrepl-quit) - map)) - -(define-derived-mode nrepl-repl-mode fundamental-mode "nREPL/r" - "Major mode for nREPL interactions. - -\\{nrepl-repl-mode-map}" - (setq-local lisp-indent-function 'clojure-indent-function) - (setq-local indent-line-function 'lisp-indent-line) - (make-local-variable 'completion-at-point-functions) - (add-to-list 'completion-at-point-functions - 'nrepl-complete-at-point) - (set-syntax-table nrepl-repl-mode-syntax-table) - (nrepl-turn-on-eldoc-mode) - (if (fboundp 'hack-dir-local-variables-non-file-buffer) - (hack-dir-local-variables-non-file-buffer)) - (when nrepl-history-file - (nrepl-history-load nrepl-history-file) - (add-hook 'kill-buffer-hook 'nrepl-history-just-save t t) - (add-hook 'kill-emacs-hook 'nrepl-history-just-save)) - (add-hook 'paredit-mode-hook - (lambda () - (when (>= paredit-version 21) - (define-key nrepl-repl-mode-map "{" 'paredit-open-curly) - (define-key nrepl-repl-mode-map "}" 'paredit-close-curly) - (add-to-list 'paredit-space-for-delimiter-predicates - 'nrepl-space-for-delimiter-p))))) - -(easy-menu-define nrepl-repl-mode-menu nrepl-repl-mode-map - "Menu for nREPL mode" - '("nREPL" - ["Jump" nrepl-jump] - ["Jump back" nrepl-jump-back] - "--" - ["Complete symbol" complete-symbol] - "--" - ["Display documentation" nrepl-doc] - ["Display source" nrepl-src] - ["Display JavaDoc" nrepl-javadoc] - "--" - ["Toggle pretty printing of results" nrepl-toggle-pretty-printing] - ["Clear output" nrepl-clear-output] - ["Clear buffer" nrepl-clear-buffer] - ["Kill input" nrepl-kill-input] - ["Interrupt" nrepl-interrupt] - ["Quit" nrepl-quit] - ["Restart" nrepl-restart] - "--" - ["Version info" nrepl-version])) - -(provide 'nrepl-repl-mode) -;;; nrepl-repl-mode.el ends here diff --git a/nrepl-repl.el b/nrepl-repl.el deleted file mode 100644 index 9657f3bd..00000000 --- a/nrepl-repl.el +++ /dev/null @@ -1,864 +0,0 @@ -;;; nrepl-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 'nrepl-interaction) -(require 'nrepl-version) - -(defgroup nrepl-repl nil - "Interaction with the REPL." - :prefix "nrepl-repl-" - :group 'nrepl) - -(defface nrepl-repl-prompt-face - '((t (:inherit font-lock-keyword-face))) - "Face for the prompt in the REPL buffer." - :group 'nrepl-repl) - -(defface nrepl-repl-output-face - '((t (:inherit font-lock-string-face))) - "Face for output in the REPL buffer." - :group 'nrepl-repl) - -(defface nrepl-repl-input-face - '((t (:bold t))) - "Face for previous input in the REPL buffer." - :group 'nrepl-repl) - -(defface nrepl-repl-result-face - '((t ())) - "Face for the result of an evaluation in the REPL buffer." - :group 'nrepl-repl) - -(defcustom nrepl-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 `nrepl-popup-stacktraces' in REPL buffers." - :type 'boolean - :group 'nrepl-repl) - -(defcustom nrepl-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 'nrepl-repl) - -(defcustom nrepl-repl-use-pretty-printing nil - "Control whether the results in REPL are pretty-printed or not. -The `nrepl-toggle-pretty-printing' command can be used to interactively -change the setting's value." - :type 'boolean - :group 'nrepl-repl) - -(defcustom nrepl-repl-tab-command 'nrepl-indent-and-complete-symbol - "Select the command to be invoked by the TAB key. -The default option is `nrepl-indent-and-complete-symbol'. If -you'd like to use the default Emacs behavior use -`indent-for-tab-command'." - :type 'symbol - :group 'nrepl-repl) - -;;;; REPL buffer local variables -(defvar nrepl-input-start-mark) - -(defvar nrepl-prompt-start-mark) - -(defvar nrepl-old-input-counter 0 - "Counter used to generate unique `nrepl-old-input' properties. -This property value must be unique to avoid having adjacent inputs be -joined together.") - -(defvar nrepl-input-history '() - "History list of strings read from the nREPL buffer.") - -(defvar nrepl-input-history-items-added 0 - "Variable counting the items added in the current session.") - -(defvar nrepl-output-start nil - "Marker for the start of output.") - -(defvar nrepl-output-end nil - "Marker for the end of output.") - -(nrepl-make-variables-buffer-local - 'nrepl-input-start-mark - 'nrepl-prompt-start-mark - 'nrepl-old-input-counter - 'nrepl-input-history - 'nrepl-input-history-items-added - 'nrepl-output-start - 'nrepl-output-end) - -(defun nrepl-tab () - "Invoked on TAB keystrokes in `nrepl-repl-mode' buffers." - (interactive) - (funcall nrepl-repl-tab-command)) - -(defun nrepl-reset-markers () - "Reset all REPL markers." - (dolist (markname '(nrepl-output-start - nrepl-output-end - nrepl-prompt-start-mark - nrepl-input-start-mark)) - (set markname (make-marker)) - (set-marker (symbol-value markname) (point)))) - -(defmacro nrepl-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 'nrepl-propertize-region 'lisp-indent-function 1) - -;;; REPL init -(defun nrepl-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 nrepl-create-repl-buffer (process) - "Create a REPL buffer for PROCESS." - (nrepl-init-repl-buffer - process - (let ((buffer-name (nrepl-repl-buffer-name))) - (if nrepl-repl-pop-to-buffer-on-connect - (pop-to-buffer buffer-name) - (generate-new-buffer buffer-name)) - buffer-name))) - -(defun nrepl-make-repl (process) - "Make a REPL for the connection PROCESS." - (let ((connection-buffer (process-buffer process)) - (repl-buffer (nrepl-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 nrepl-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 nrepl-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." - (nrepl-user-first-name))) - "Scientifically-proven optimal words of hackerish encouragement.") - -(defun nrepl-random-words-of-inspiration () - "Select a random entry from `nrepl-words-of-inspiration'." - (eval (nth (random (length nrepl-words-of-inspiration)) - nrepl-words-of-inspiration))) - -(defun nrepl--banner () - "Generate the welcome REPL buffer banner." - (format "; nrepl.el %s (Clojure %s, nREPL %s)" - (nrepl-version) - (nrepl--clojure-version) - (nrepl--backend-version))) - -(defun nrepl-insert-banner-and-prompt (ns) - "Insert REPL banner and REPL prompt, taking into account NS." - (when (zerop (buffer-size)) - (insert (propertize (nrepl--banner) 'face 'font-lock-comment-face))) - (goto-char (point-max)) - (nrepl-mark-output-start) - (nrepl-mark-input-start) - (nrepl-insert-prompt ns)) - - -(defun nrepl-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 'nrepl-repl-mode) - (nrepl-repl-mode)) - ;; use the same requires by default as clojure.main does - (nrepl-send-string-sync nrepl-repl-requires-sexp) - (nrepl-reset-markers) - (unless noprompt - (nrepl-insert-banner-and-prompt nrepl-buffer-ns)) - (nrepl-remember-clojure-buffer nrepl-current-clojure-buffer) - (current-buffer))) - -(defun nrepl-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") - (nrepl-init-repl-buffer - (get-process buffer) - (get-buffer-create - (nrepl-repl-buffer-name)))))))))) - - -;;; REPL interaction -(defun nrepl-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 nrepl-in-input-area-p () - "Return t if in input area." - (<= nrepl-input-start-mark (point))) - -(defun nrepl-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 nrepl-input-start-mark - (if until-point-p - (point) - (point-max)))) - -(defun nrepl-previous-prompt () - "Move backward to the previous prompt." - (interactive) - (nrepl-find-prompt t)) - -(defun nrepl-next-prompt () - "Move forward to the next prompt." - (interactive) - (nrepl-find-prompt)) - -(defun nrepl-find-prompt (&optional backward) - "Find the next prompt. -If BACKWARD is non-nil look backward." - (let ((origin (point)) - (prop 'nrepl-prompt)) - (while (progn - (nrepl-search-property-change prop backward) - (not (or (nrepl-end-of-proprange-p prop) (bobp) (eobp))))) - (unless (nrepl-end-of-proprange-p prop) - (goto-char origin)))) - -(defun nrepl-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 nrepl-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 nrepl-mark-input-start () - "Mark the input start." - (set-marker nrepl-input-start-mark (point) (current-buffer))) - -(defun nrepl-mark-output-start () - "Mark the output start." - (set-marker nrepl-output-start (point)) - (set-marker nrepl-output-end (point))) - -(defun nrepl-mark-output-end () - "Marke the output end." - (add-text-properties nrepl-output-start nrepl-output-end - '(face nrepl-repl-output-face - rear-nonsticky (face)))) - -;;;;; History - -(defcustom nrepl-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 'nrepl-history-replace, -;; otherwise we reinitialize them. - -(defvar nrepl-input-history-position -1 - "Newer items have smaller indices.") - -(defvar nrepl-history-pattern nil - "The regexp most recently used for finding input history.") - -(defun nrepl-add-to-input-history (string) - "Add STRING to the input history. -Empty strings and duplicates are ignored." - (unless (or (equal string "") - (equal string (car nrepl-input-history))) - (push string nrepl-input-history) - (incf nrepl-input-history-items-added))) - -(defun nrepl-delete-current-input () - "Delete all text after the prompt." - (interactive) - (goto-char (point-max)) - (delete-region nrepl-input-start-mark (point-max))) - -(defun nrepl-replace-input (string) - "Replace the current REPL input with STRING." - (nrepl-delete-current-input) - (insert-and-inherit string)) - -(defun nrepl-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 nrepl-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 nrepl-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 nrepl-history-pattern regexp) - (let* ((min-pos -1) - (max-pos (length nrepl-input-history)) - (pos0 (cond ((nrepl-history-search-in-progress-p) - nrepl-input-history-position) - (t min-pos))) - (pos (nrepl-position-in-history pos0 direction (or regexp ""))) - (msg nil)) - (cond ((and (< min-pos pos) (< pos max-pos)) - (nrepl-replace-input (nth pos nrepl-input-history)) - (setq msg (format "History item: %d" pos))) - ((not nrepl-wrap-history) - (setq msg (cond ((= pos min-pos) "End of history") - ((= pos max-pos) "Beginning of history")))) - (nrepl-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 nrepl-input-history-position pos) - (setq this-command 'nrepl-history-replace))) - -(defun nrepl-history-search-in-progress-p () - "Return t if a current history search is in progress." - (eq last-command 'nrepl-history-replace)) - -(defun nrepl-terminate-history-search () - "Terminate the current history search." - (setq last-command this-command)) - -(defun nrepl-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) - (nrepl-history-replace 'backward (nrepl-history-pattern t))) - -(defun nrepl-next-input () - "Cycle forwards through input history. -See `nrepl-previous-input'." - (interactive) - (nrepl-history-replace 'forward (nrepl-history-pattern t))) - -(defun nrepl-forward-input () - "Cycle forwards through input history." - (interactive) - (nrepl-history-replace 'forward (nrepl-history-pattern))) - -(defun nrepl-backward-input () - "Cycle backwards through input history." - (interactive) - (nrepl-history-replace 'backward (nrepl-history-pattern))) - -(defun nrepl-previous-matching-input (regexp) - "Find the previous input matching REGEXP." - (interactive "sPrevious element matching (regexp): ") - (nrepl-terminate-history-search) - (nrepl-history-replace 'backward regexp)) - -(defun nrepl-next-matching-input (regexp) - "Find then next input matching REGEXP." - (interactive "sNext element matching (regexp): ") - (nrepl-terminate-history-search) - (nrepl-history-replace 'forward regexp)) - -(defun nrepl-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 ((nrepl-history-search-in-progress-p) - nrepl-history-pattern) - (use-current-input - (assert (<= nrepl-input-start-mark (point))) - (let ((str (nrepl-current-input t))) - (cond ((string-match "^[ \n]*$" str) nil) - (t (concat "^" (regexp-quote str)))))) - (t nil))) - -;;; persistent history -(defcustom nrepl-history-size 500 - "The maximum number of items to keep in the REPL history." - :type 'integer - :safe 'integerp - :group 'nrepl-repl-mode) - -(defcustom nrepl-history-file nil - "File to save the persistent REPL history to." - :type 'string - :safe 'stringp - :group 'nrepl-repl-mode) - -(defun nrepl-history-read-filename () - "Ask the user which file to use, defaulting `nrepl-history-file'." - (read-file-name "Use nREPL history file: " - nrepl-history-file)) - -(defun nrepl-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 nrepl-history-load (&optional filename) - "Load history from FILENAME into current session. -FILENAME defaults to the value of `nrepl-history-file' but user -defined filenames can be used to read special history files. - -The value of `nrepl-input-history' is set by this function." - (interactive (list (nrepl-history-read-filename))) - (let ((f (or filename nrepl-history-file))) - ;; TODO: probably need to set nrepl-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 nrepl-input-history (nrepl-history-read f)))) - -(defun nrepl-history-write (filename) - "Write history to FILENAME. -Currently coding system for writing the contents is hardwired to -utf-8-unix." - (let* ((mhist (nrepl-histories-merge nrepl-input-history - nrepl-input-history-items-added - (nrepl-history-read filename))) - ;; newest items are at the beginning of the list, thus 0 - (hist (cl-subseq mhist 0 (min (length mhist) nrepl-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 nrepl-history-save (&optional filename) - "Save the current nREPL input history to FILENAME. -FILENAME defaults to the value of `nrepl-history-file'." - (interactive (list (nrepl-history-read-filename))) - (let* ((file (or filename nrepl-history-file))) - (nrepl-history-write file))) - -(defun nrepl-history-just-save () - "Just save the history to `nrepl-history-file'. -This function is meant to be used in hooks to avoid lambda -constructs." - (nrepl-history-save nrepl-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 nrepl-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 nrepl-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 nrepl-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 nrepl-bol-internal () - "Go to the beginning of line or the prompt." - (cond ((and (>= (point) nrepl-input-start-mark) - (nrepl-same-line-p (point) nrepl-input-start-mark)) - (goto-char nrepl-input-start-mark)) - (t (beginning-of-line 1)))) - -(defun nrepl-bol () - "Go to the beginning of line or the prompt." - (interactive) - (deactivate-mark) - (nrepl-bol-internal)) - -(defun nrepl-bol-mark () - "Set the mark and go to the beginning of line or the prompt." - (interactive) - (unless mark-active - (set-mark (point))) - (nrepl-bol-internal)) - -(defun nrepl-at-prompt-start-p () - "Return t if point is at the start of prompt. -This will not work on non-current prompts." - (= (point) nrepl-input-start-mark)) - -(defun nrepl-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 nrepl-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 'nrepl-save-marker 'lisp-indent-function 1) - -(defun nrepl-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 nrepl-input-start-mark) - (nrepl-save-marker nrepl-output-start - (nrepl-save-marker nrepl-output-end - (unless (bolp) (insert-before-markers "\n")) - (let ((prompt-start (point)) - (prompt (format "%s> " namespace))) - (nrepl-propertize-region - '(face nrepl-repl-prompt-face read-only t intangible t - nrepl-prompt t - rear-nonsticky (nrepl-prompt read-only face intangible)) - (insert-before-markers prompt)) - (set-marker nrepl-prompt-start-mark prompt-start) - prompt-start)))) - -(defun nrepl-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 - (nrepl-save-marker nrepl-output-start - (nrepl-save-marker nrepl-output-end - (goto-char position) - (when (and bol (not (bolp))) (insert-before-markers "\n")) - (nrepl-propertize-region `(face nrepl-repl-output-face - rear-nonsticky (face)) - (insert-before-markers string) - (when (and (= (point) nrepl-prompt-start-mark) - (not (bolp))) - (insert-before-markers "\n") - (set-marker nrepl-output-end (1- (point)))))))) - (nrepl-show-maximum-output))) - -(defun nrepl-emit-interactive-output (string) - "Emit STRING as interactive output." - (with-current-buffer (nrepl-current-repl-buffer) - (let ((pos (1- (nrepl-input-line-beginning-position)))) - (nrepl-emit-output-at-pos (current-buffer) string pos t) - (ansi-color-apply-on-region pos (point-max)) - ))) - -(defun nrepl-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 - (nrepl-emit-output-at-pos buffer string nrepl-input-start-mark bol))) - -(defun nrepl-emit-prompt (buffer) - "Emit the REPL prompt into BUFFER." - (with-current-buffer buffer - (save-excursion - (nrepl-save-marker nrepl-output-start - (nrepl-save-marker nrepl-output-end - (nrepl-insert-prompt nrepl-buffer-ns)))) - (nrepl-show-maximum-output))) - -(defun nrepl-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 - (nrepl-save-marker nrepl-output-start - (nrepl-save-marker nrepl-output-end - (goto-char nrepl-input-start-mark) - (when (and bol (not (bolp))) (insert-before-markers "\n")) - (nrepl-propertize-region `(face nrepl-repl-result-face - rear-nonsticky (face)) - (insert-before-markers string))))) - (nrepl-show-maximum-output))) - - -(defun nrepl-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 nrepl-prompt-start-mark (point-max)) - (insert "\n") - (lisp-indent-line))) - -(defun nrepl-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 nrepl-kill-input () - "Kill all text from the prompt to point." - (interactive) - (cond ((< (marker-position nrepl-input-start-mark) (point)) - (kill-region nrepl-input-start-mark (point))) - ((= (point) (marker-position nrepl-input-start-mark)) - (nrepl-delete-current-input)))) - -(defun nrepl-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 nrepl-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 (nrepl-in-input-area-p) - (error "No input at point")) - (goto-char (point-max)) - (let ((end (point))) ; end of input, without the newline - (nrepl-add-to-input-history (buffer-substring nrepl-input-start-mark end)) - (when newline - (insert "\n") - (nrepl-show-maximum-output)) - (let ((inhibit-modification-hooks t)) - (add-text-properties nrepl-input-start-mark - (point) - `(nrepl-old-input - ,(incf nrepl-old-input-counter)))) - (let ((overlay (make-overlay nrepl-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 'nrepl-repl-input-face))) - (let* ((input (nrepl-current-input)) - (form (if (and (not (string-match "\\`[ \t\r\n]*\\'" input)) nrepl-repl-use-pretty-printing) - (format "(clojure.pprint/pprint %s)" input) input))) - (goto-char (point-max)) - (nrepl-mark-input-start) - (nrepl-mark-output-start) - (nrepl-send-string form (nrepl-handler (current-buffer)) nrepl-buffer-ns))) - -(defun nrepl-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 - (nrepl-send-input)) - ((and (get-text-property (point) 'nrepl-old-input) - (< (point) nrepl-input-start-mark)) - (nrepl-grab-old-input end-of-input) - (nrepl-recenter-if-needed)) - ((nrepl-input-complete-p nrepl-input-start-mark (point-max)) - (nrepl-send-input t)) - (t - (nrepl-newline-and-indent) - (message "[input not complete]")))) - -(defun nrepl-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 nrepl-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 `nrepl-old-input'." - (multiple-value-bind (beg end) (nrepl-property-bounds 'nrepl-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 nrepl-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 nrepl-closing-return () - "Evaluate the current input string after closing all open lists." - (interactive) - (goto-char (point-max)) - (save-restriction - (narrow-to-region nrepl-input-start-mark (point)) - (while (ignore-errors (save-excursion (backward-up-list 1)) t) - (insert ")"))) - (nrepl-return)) - -(defun nrepl-toggle-pretty-printing () - "Toggle pretty-printing in the REPL." - (interactive) - (setq nrepl-repl-use-pretty-printing (not nrepl-repl-use-pretty-printing)) - (message "Pretty printing in nREPL %s." - (if nrepl-repl-use-pretty-printing "enabled" "disabled"))) - -(defvar nrepl-clear-buffer-hook) - -(defun nrepl-clear-buffer () - "Delete the output generated by the Clojure process." - (interactive) - (let ((inhibit-read-only t)) - (delete-region (point-min) nrepl-prompt-start-mark) - (delete-region nrepl-output-start nrepl-output-end) - (when (< (point) nrepl-input-start-mark) - (goto-char nrepl-input-start-mark)) - (recenter t)) - (run-hooks 'nrepl-clear-buffer-hook)) - -(defun nrepl-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)) - (nrepl-clear-buffer) - (switch-to-buffer origin-buffer))) - -(defun nrepl-input-line-beginning-position () - "Return the position of the beginning of input." - (save-excursion - (goto-char nrepl-input-start-mark) - (line-beginning-position))) - -(defun nrepl-clear-output () - "Delete the output inserted since the last input." - (interactive) - (let ((start (save-excursion - (nrepl-previous-prompt) - (ignore-errors (forward-sexp)) - (forward-line) - (point))) - (end (1- (nrepl-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 'nrepl-repl) -;;; nrepl-repl.el ends here diff --git a/nrepl-selector.el b/nrepl-selector.el deleted file mode 100644 index 141a14f7..00000000 --- a/nrepl-selector.el +++ /dev/null @@ -1,137 +0,0 @@ -;;; nrepl-selector.el --- Buffer selection command inspired by SLIME's selector - -;; 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: - -;; Buffer selection command inspired by SLIME's selector. - -;;; Code: - -(defvar nrepl-selector-methods nil - "List of buffer-selection methods for the `nrepl-select' command. -Each element is a list (KEY DESCRIPTION FUNCTION). -DESCRIPTION is a one-line description of what the key selects.") - -(defvar nrepl-selector-other-window nil - "If non-nil use `switch-to-buffer-other-window'.") - -(defun nrepl-selector (&optional other-window) - "Select a new buffer by type, indicated by a single character. -The user is prompted for a single character indicating the method by -which to choose a new buffer. The `?' character describes then -available methods. OTHER-WINDOW provides an optional target. - -See `def-nrepl-selector-method' for defining new methods." - (interactive) - (message "Select [%s]: " - (apply #'string (mapcar #'car nrepl-selector-methods))) - (let* ((nrepl-selector-other-window other-window) - (ch (save-window-excursion - (select-window (minibuffer-window)) - (read-char))) - (method (cl-find ch nrepl-selector-methods :key #'car))) - (cond (method - (funcall (cl-caddr method))) - (t - (message "No method for character: ?\\%c" ch) - (ding) - (sleep-for 1) - (discard-input) - (nrepl-selector))))) - -(defmacro def-nrepl-selector-method (key description &rest body) - "Define a new `nrepl-select' buffer selection method. - -KEY is the key the user will enter to choose this method. - -DESCRIPTION is a one-line sentence describing how the method -selects a buffer. - -BODY is a series of forms which are evaluated when the selector -is chosen. The returned buffer is selected with -`switch-to-buffer'." - (let ((method `(lambda () - (let ((buffer (progn ,@body))) - (cond ((not (get-buffer buffer)) - (message "No such buffer: %S" buffer) - (ding)) - ((get-buffer-window buffer) - (select-window (get-buffer-window buffer))) - (nrepl-selector-other-window - (switch-to-buffer-other-window buffer)) - (t - (switch-to-buffer buffer))))))) - `(setq nrepl-selector-methods - (cl-sort (cons (list ,key ,description ,method) - (cl-remove ,key nrepl-selector-methods :key #'car)) - #'< :key #'car)))) - -(def-nrepl-selector-method ?? "Selector help buffer." - (ignore-errors (kill-buffer "*Select Help*")) - (with-current-buffer (get-buffer-create "*Select Help*") - (insert "Select Methods:\n\n") - (loop for (key line nil) in nrepl-selector-methods - do (insert (format "%c:\t%s\n" key line))) - (goto-char (point-min)) - (help-mode) - (display-buffer (current-buffer) t)) - (nrepl-selector) - (current-buffer)) - -(pushnew (list ?4 "Select in other window" (lambda () (nrepl-selector t))) - nrepl-selector-methods :key #'car) - -(def-nrepl-selector-method ?c - "most recently visited clojure-mode buffer." - (nrepl-recently-visited-buffer 'clojure-mode)) - -(def-nrepl-selector-method ?e - "most recently visited emacs-lisp-mode buffer." - (nrepl-recently-visited-buffer 'emacs-lisp-mode)) - -(def-nrepl-selector-method ?q "Abort." - (top-level)) - -(def-nrepl-selector-method ?r - "Current *nrepl* buffer." - (nrepl-find-or-create-repl-buffer)) - -(def-nrepl-selector-method ?n - "NREPL connections buffer." - (nrepl-connection-browser) - nrepl--connection-browser-buffer-name) - -(def-nrepl-selector-method ?v - "*nrepl-events* buffer." - nrepl-event-buffer-name) - -(def-nrepl-selector-method ?s - "Cycle to the next Clojure connection." - (nrepl-rotate-connections) - (nrepl-find-or-create-repl-buffer)) - -(provide 'nrepl-selector) -;;; nrepl-selector.el ends here diff --git a/nrepl-version.el b/nrepl-version.el deleted file mode 100644 index 447420d1..00000000 --- a/nrepl-version.el +++ /dev/null @@ -1,77 +0,0 @@ -;;; nrepl-version.el --- Version information - -;; 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: - -;; Version information. - -;;; Code: - -(require 'pkg-info) - -;;; Version information -(defun nrepl-library-version () - "Get the version in the nrepl library header." - ;; (-when-let (version (pkg-info-defining-library-version 'nrepl)) - ;; (pkg-info-format-version version))) - "0.3.0-snapshot") - -(defun nrepl-package-version () - "Get the package version of nrepl. - -This is the version number of the installed nrepl package." - (-when-let (version (pkg-info-package-version 'nrepl)) - (pkg-info-format-version version))) - -(defun nrepl-version (&optional show-version) - "Get the nrepl version as string. - -If called interactively or if SHOW-VERSION is non-nil, show the -version in the echo area and the messages buffer. - -The returned string includes both, the version from package.el -and the library version, if both a present and different. - -If the version number could not be determined, signal an error, -if called interactively, or if SHOW-VERSION is non-nil, otherwise -just return nil." - (interactive (list (not (or executing-kbd-macro noninteractive)))) - (let* ((lib-version (nrepl-library-version)) - (pkg-version (nrepl-package-version)) - (version (cond - ((and lib-version pkg-version - (not (string= lib-version pkg-version))) - (format "%s (package: %s)" lib-version pkg-version)) - ((or pkg-version lib-version) - (format "%s" (or pkg-version lib-version)))))) - (when show-version - (unless version - (error "Could not find out nrepl version")) - (message "nrepl version: %s" version)) - version)) - -(provide 'nrepl-version) -;;; nrepl-version.el ends here diff --git a/nrepl.el b/nrepl.el deleted file mode 100644 index bb177377..00000000 --- a/nrepl.el +++ /dev/null @@ -1,70 +0,0 @@ -;;; nrepl.el --- Client for Clojure nREPL - -;; 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 -;; URL: http://www.github.com/clojure-emacs/nrepl.el -;; Version: 0.3.0-cvs -;; Keywords: languages, clojure, nrepl -;; Package-Requires: ((clojure-mode "2.0.0") (cl-lib "0.3") (dash "2.1.0") (pkg-info "0.1")) - -;; 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: - -;; Provides an elisp client to connect to Clojure nREPL servers. - -;;; Installation: - -;; Available as a package in marmalade-repo.org and melpa.milkbox.net. - -;; (add-to-list 'package-archives -;; '("marmalade" . "http://marmalade-repo.org/packages/")) -;; -;; or -;; -;; (add-to-list 'package-archives -;; '("melpa" . "http://melpa.milkbox.net/packages/") t) -;; -;; M-x package-install nrepl - -;;; Usage: - -;; M-x nrepl-jack-in - -;;; Code: - -;; needed for make test -(eval-when-compile - (add-to-list 'load-path default-directory)) - -(require 'nrepl-client) -(require 'nrepl-version) -(require 'nrepl-interaction) -(require 'nrepl-eldoc) -(require 'nrepl-repl) -(require 'nrepl-repl-mode) -(require 'nrepl-selector) -(require 'nrepl-interaction-mode) -(require 'nrepl-macroexpansion) - -(provide 'nrepl) -;;; nrepl.el ends here diff --git a/test/nrepl-tests.el b/test/nrepl-tests.el index cf4fb642..6b257970 100644 --- a/test/nrepl-tests.el +++ b/test/nrepl-tests.el @@ -1,4 +1,4 @@ -;;; nrepl-tests.el +;;; cider-tests.el ;; Copyright © 2012-2013 Tim King @@ -29,7 +29,7 @@ ;;; Code: (require 'ert) -(require 'nrepl) +(require 'cider) (require 'noflet) (ert-deftest test-nrepl-decode-string () @@ -124,93 +124,93 @@ "d2:id2:422:ns4:user7:session36:3f586403-ed47-4e4d-b8db-70522054f9715:value5:\"←\"ed2:id2:427:session36:3f586403-ed47-4e4d-b8db-70522054f9716:statusl4:doneee")))) ;;;; generic -(ert-deftest test-nrepl-connection-buffer-name () +(ert-deftest test-cider-connection-buffer-name () (let ((nrepl-hide-special-buffers nil)) (should (equal (nrepl-connection-buffer-name) "*nrepl-connection*"))) (let ((nrepl-hide-special-buffers t)) (should (equal (nrepl-connection-buffer-name) " *nrepl-connection*")))) -(ert-deftest test-nrepl-server-buffer-name () +(ert-deftest test-cider-server-buffer-name () (let ((nrepl-hide-special-buffers nil)) (should (equal (nrepl-server-buffer-name) "*nrepl-server*"))) (let ((nrepl-hide-special-buffers t)) (should (equal (nrepl-server-buffer-name) " *nrepl-server*")))) -(ert-deftest test-nrepl--banner () - (noflet ((nrepl-version () "0.2.0") - (nrepl--clojure-version () "1.5.1") - (nrepl--backend-version () "0.2.1")) - (should (equal (nrepl--banner) "; nrepl.el 0.2.0 (Clojure 1.5.1, nREPL 0.2.1)")))) +(ert-deftest test-cider--banner () + (noflet ((cider-version () "0.2.0") + (cider--clojure-version () "1.5.1") + (cider--backend-version () "0.2.1")) + (should (equal (cider--banner) "; CIDER 0.2.0 (Clojure 1.5.1, nREPL 0.2.1)")))) -(ert-deftest test-nrepl-extract-error-info-14 () +(ert-deftest test-cider-extract-error-info-14 () (let ((message "CompilerException java.lang.RuntimeException: Unable to resolve symbol: dummy in this context, compiling:(/some/test/file/core.clj:31)")) - (let ((info (nrepl-extract-error-info nrepl-compilation-regexp message))) + (let ((info (cider-extract-error-info cider-compilation-regexp message))) (should (string= (nth 0 info) "/some/test/file/core.clj")) (should (= (nth 1 info) 31)) (should (equal (nth 2 info) nil)) - (should (equal (nth 3 info) 'nrepl-error-highlight-face))))) + (should (equal (nth 3 info) 'cider-error-highlight-face))))) -(ert-deftest test-nrepl-extract-error-info-14-no-file () +(ert-deftest test-cider-extract-error-info-14-no-file () (let ((message "CompilerException java.lang.RuntimeException: Unable to resolve symbol: dummy in this context, compiling:(NO_SOURCE_PATH:31)")) - (let ((info (nrepl-extract-error-info nrepl-compilation-regexp message))) + (let ((info (cider-extract-error-info cider-compilation-regexp message))) (should (equal (nth 0 info) nil)) (should (= (nth 1 info) 31)) (should (equal (nth 2 info) nil)) - (should (equal (nth 3 info) 'nrepl-error-highlight-face))))) + (should (equal (nth 3 info) 'cider-error-highlight-face))))) -(ert-deftest test-nrepl-extract-warning-info-14 () +(ert-deftest test-cider-extract-warning-info-14 () (let ((message "Reflection warning, /some/othertest/file/core.clj:24 - reference to field getCanonicalPath can't be resolved. ")) - (let ((info (nrepl-extract-error-info nrepl-compilation-regexp message))) + (let ((info (cider-extract-error-info cider-compilation-regexp message))) (should (string= (nth 0 info) "/some/othertest/file/core.clj")) (should (= (nth 1 info) 24)) (should (equal (nth 2 info) nil)) - (should (equal (nth 3 info) 'nrepl-warning-highlight-face))))) + (should (equal (nth 3 info) 'cider-warning-highlight-face))))) -(ert-deftest test-nrepl-extract-warning-info-14-no-file () +(ert-deftest test-cider-extract-warning-info-14-no-file () (let ((message "Reflection warning, NO_SOURCE_PATH:24 - reference to field getCanonicalPath can't be resolved. ")) - (let ((info (nrepl-extract-error-info nrepl-compilation-regexp message))) + (let ((info (cider-extract-error-info cider-compilation-regexp message))) (should (equal (nth 0 info) nil)) (should (= (nth 1 info) 24)) (should (equal (nth 2 info) nil)) - (should (equal (nth 3 info) 'nrepl-warning-highlight-face))))) + (should (equal (nth 3 info) 'cider-warning-highlight-face))))) -(ert-deftest test-nrepl-extract-error-info-15 () +(ert-deftest test-cider-extract-error-info-15 () (let ((message "CompilerException java.lang.RuntimeException: Unable to resolve symbol: dummy in this context, compiling:(/some/test/file/core.clj:31:3)")) - (let ((info (nrepl-extract-error-info nrepl-compilation-regexp message))) + (let ((info (cider-extract-error-info cider-compilation-regexp message))) (should (string= (nth 0 info) "/some/test/file/core.clj")) (should (= (nth 1 info) 31)) (should (= (nth 2 info) 3)) - (should (equal (nth 3 info) 'nrepl-error-highlight-face))))) + (should (equal (nth 3 info) 'cider-error-highlight-face))))) -(ert-deftest test-nrepl-extract-error-info-15-no-file () +(ert-deftest test-cider-extract-error-info-15-no-file () (let ((message "CompilerException java.lang.RuntimeException: Unable to resolve symbol: dummy in this context, compiling:(NO_SOURCE_PATH:31:3)")) - (let ((info (nrepl-extract-error-info nrepl-compilation-regexp message))) + (let ((info (cider-extract-error-info cider-compilation-regexp message))) (should (equal (nth 0 info) nil)) (should (= (nth 1 info) 31)) (should (= (nth 2 info) 3)) - (should (equal (nth 3 info) 'nrepl-error-highlight-face))))) + (should (equal (nth 3 info) 'cider-error-highlight-face))))) -(ert-deftest test-nrepl-extract-warning-info-15 () +(ert-deftest test-cider-extract-warning-info-15 () (let ((message "Reflection warning, /some/othertest/file/core.clj:24:43 - reference to field getCanonicalPath can't be resolved. ")) - (let ((info (nrepl-extract-error-info nrepl-compilation-regexp message))) + (let ((info (cider-extract-error-info cider-compilation-regexp message))) (should (string= (nth 0 info) "/some/othertest/file/core.clj")) (should (= (nth 1 info) 24)) (should (= (nth 2 info) 43)) - (should (equal (nth 3 info) 'nrepl-warning-highlight-face))))) + (should (equal (nth 3 info) 'cider-warning-highlight-face))))) -(ert-deftest test-nrepl-extract-warning-info-15-no-file () +(ert-deftest test-cider-extract-warning-info-15-no-file () (let ((message "Reflection warning, NO_SOURCE_PATH:24:43 - reference to field getCanonicalPath can't be resolved. ")) - (let ((info (nrepl-extract-error-info nrepl-compilation-regexp message))) + (let ((info (cider-extract-error-info cider-compilation-regexp message))) (should (equal (nth 0 info) nil)) (should (= (nth 1 info) 24)) (should (= (nth 2 info) 43)) - (should (equal (nth 3 info) 'nrepl-warning-highlight-face))))) + (should (equal (nth 3 info) 'cider-warning-highlight-face))))) -(defmacro nrepl-test-with-buffers (buffer-names &rest body) +(defmacro cider-test-with-buffers (buffer-names &rest body) (lexical-let ((create (lambda (b) (list b `(generate-new-buffer " *temp*"))))) `(lexical-let (,@(mapcar create buffer-names)) (unwind-protect @@ -219,7 +219,7 @@ (ert-deftest test-nrepl-make-repl-connection-default () (lexical-let ((connections (nrepl-connection-buffers))) - (nrepl-test-with-buffers + (cider-test-with-buffers (a b) (should (get-buffer a)) (should (get-buffer b)) @@ -236,7 +236,7 @@ (ert-deftest test-nrepl-connection-buffers () (lexical-let ((connections (nrepl-connection-buffers))) - (nrepl-test-with-buffers + (cider-test-with-buffers (a b) (nrepl-make-repl-connection-default a) (nrepl-make-repl-connection-default b) @@ -246,42 +246,42 @@ (nrepl-connection-buffers))) (should (equal (buffer-name b) (nrepl-current-connection-buffer)))))) -(ert-deftest test-nrepl-rotate-connecton-buffer () +(ert-deftest test-cider-rotate-connecton-buffer () (noflet ((nrepl--connection-info (connection-buffer-name))) - (nrepl-test-with-buffers + (cider-test-with-buffers (a b c) (let ((nrepl-connection-list (list (buffer-name a) (buffer-name b) (buffer-name c)))) (should (equal (buffer-name a) (nrepl-current-connection-buffer))) - (nrepl-rotate-connection) + (cider-rotate-connection) (should (equal (buffer-name b) (nrepl-current-connection-buffer))) - (nrepl-rotate-connection) + (cider-rotate-connection) (should (equal (buffer-name c) (nrepl-current-connection-buffer))) - (nrepl-rotate-connection) + (cider-rotate-connection) (should (equal (buffer-name a) (nrepl-current-connection-buffer))))))) -(ert-deftest test-nrepl--current-connection-info () +(ert-deftest test-cider--current-connection-info () (with-temp-buffer - (noflet ((nrepl--clojure-version () "1.5.1") - (nrepl--backend-version () "0.2.1")) + (noflet ((cider--clojure-version () "1.5.1") + (cider--backend-version () "0.2.1")) (set (make-local-variable 'nrepl-endpoint) '("localhost" 4005)) (set (make-local-variable 'nrepl-project-dir) "proj") (set (make-local-variable 'nrepl-buffer-ns) "somens") - (should (string= (nrepl--connection-info (buffer-name (current-buffer))) + (should (string= (cider--connection-info (buffer-name (current-buffer))) "Active nrepl connection: proj:somens, localhost:4005 (Clojure 1.5.1, nREPL 0.2.1)"))))) -(ert-deftest test-nrepl-current-connection-info-no-project () +(ert-deftest test-cider-current-connection-info-no-project () (with-temp-buffer - (noflet ((nrepl--clojure-version () "1.5.1") - (nrepl--backend-version () "0.2.1")) + (noflet ((cider--clojure-version () "1.5.1") + (cider--backend-version () "0.2.1")) (set (make-local-variable 'nrepl-endpoint) '("localhost" 4005)) (set (make-local-variable 'nrepl-buffer-ns) "somens") - (should (string= (nrepl--connection-info (buffer-name (current-buffer))) + (should (string= (cider--connection-info (buffer-name (current-buffer))) "Active nrepl connection: :somens, localhost:4005 (Clojure 1.5.1, nREPL 0.2.1)"))))) -(ert-deftest test-nrepl-close () +(ert-deftest test-cider-close () (lexical-let ((connections (nrepl-connection-buffers))) - (nrepl-test-with-buffers + (cider-test-with-buffers (a b) (nrepl-make-repl-connection-default a) (nrepl-make-repl-connection-default b) @@ -294,14 +294,14 @@ ;;; connection browser -(ert-deftest test-nrepl-connections-buffer () +(ert-deftest test-cider-connections-buffer () (with-temp-buffer (lexical-let ((b1 (current-buffer))) - (set (make-local-variable 'nrepl-endpoint) '("localhost" 4005)) + (set (make-local-variable 'cider-endpoint) '("localhost" 4005)) (set (make-local-variable 'nrepl-project-dir) "proj") (with-temp-buffer (lexical-let ((b2 (current-buffer))) - (set (make-local-variable 'nrepl-endpoint) '("123.123.123.123" 4006)) + (set (make-local-variable 'cider-endpoint) '("123.123.123.123" 4006)) (let ((nrepl-connection-list (list (buffer-name b1) (buffer-name b2)))) (nrepl-connection-browser) @@ -312,7 +312,7 @@ 123.123.123.123 4006 \n\n" (buffer-string))) (goto-char 80) ; somewhere in the second connection listed - (nrepl-connections-make-default) + (cider-connections-make-default) (should (equal (buffer-name b2) (first nrepl-connection-list))) (should (equal " Host Port Project @@ -320,7 +320,7 @@ * 123.123.123.123 4006 \n\n" (buffer-string))) (goto-char 80) ; somewhere in the second connection listed - (nrepl-connections-close-connection) + (cider-connections-close-connection) (should (equal (list (buffer-name b1)) nrepl-connection-list)) (should (equal " Host Port Project @@ -331,27 +331,27 @@ (with-current-buffer b1 (set (make-local-variable 'nrepl-repl-buffer) b3)) (with-current-buffer "*nrepl-connections*" - (nrepl-connections-goto-connection) + (cider-connections-goto-connection) (should (equal b3 (current-buffer)))))) (kill-buffer "*nrepl-connections*")))))))) ;; selector -(defun nrepl-invoke-selector-method-by-key (ch) - (lexical-let ((method (find ch nrepl-selector-methods :key #'car))) +(defun cider-invoke-selector-method-by-key (ch) + (lexical-let ((method (find ch cider-selector-methods :key #'car))) (funcall (third method)))) -(ert-deftest test-nrepl-selector-n () +(ert-deftest test-cider-selector-n () (with-temp-buffer (lexical-let ((b1 (current-buffer))) - (set (make-local-variable 'nrepl-endpoint) '("123.123.123.123" 4006)) + (set (make-local-variable 'cider-endpoint) '("123.123.123.123" 4006)) (let ((nrepl-connection-list (list (buffer-name b1)))) (nrepl-connection-browser) (with-temp-buffer ;; switch to another buffer - (nrepl-invoke-selector-method-by-key ?n) + (cider-invoke-selector-method-by-key ?n) (should (equal (current-buffer) (get-buffer nrepl--connection-browser-buffer-name)))))))) -(ert-deftest test-nrepl-selector-c () +(ert-deftest test-cider-selector-c () (with-temp-buffer (rename-buffer "*testfile*.clj") (lexical-let ((b1 (current-buffer))) @@ -361,12 +361,12 @@ (setq major-mode 'emacs-lisp-mode) (with-temp-buffer (should (not (equal (current-buffer) b1))) - (nrepl-invoke-selector-method-by-key ?e) + (cider-invoke-selector-method-by-key ?e) (should (not (equal (current-buffer) b1))) - (nrepl-invoke-selector-method-by-key ?c) + (cider-invoke-selector-method-by-key ?c) (should (equal (current-buffer) b1))))))) -(ert-deftest test-nrepl-selector-e () +(ert-deftest test-cider-selector-e () (with-temp-buffer (rename-buffer "*testfile*.el") (lexical-let ((b1 (current-buffer))) @@ -376,18 +376,18 @@ (setq major-mode 'clojure-mode) (with-temp-buffer (should (not (equal (current-buffer) b1))) - (nrepl-invoke-selector-method-by-key ?c) + (cider-invoke-selector-method-by-key ?c) (should (not (equal (current-buffer) b1))) - (nrepl-invoke-selector-method-by-key ?e) + (cider-invoke-selector-method-by-key ?e) (should (equal (current-buffer) b1))))))) -(ert-deftest test-nrepl-selector-v () +(ert-deftest test-cider-selector-v () (with-temp-buffer (rename-buffer "*nrepl-events*") (lexical-let ((b1 (current-buffer))) (with-temp-buffer (should (not (equal (current-buffer) b1))) - (nrepl-invoke-selector-method-by-key ?v) + (cider-invoke-selector-method-by-key ?v) (should (equal (current-buffer) b1)))))) (ert-deftest test-nrepl-buffer-name () @@ -436,53 +436,53 @@ (ert-deftest test-nrepl-buffer-name-two-buffers-same-project () (with-temp-buffer (set (make-local-variable 'nrepl-project-dir) "proj") - (let* ((nrepl-new-buffer (nrepl-buffer-name "*buff-name%s*"))) - (get-buffer-create nrepl-new-buffer) + (let* ((cider-new-buffer (nrepl-buffer-name "*buff-name%s*"))) + (get-buffer-create cider-new-buffer) (should - (equal nrepl-new-buffer "*buff-name proj*")) + (equal cider-new-buffer "*buff-name proj*")) (with-temp-buffer (set (make-local-variable 'nrepl-project-dir) "proj") (should (equal (nrepl-buffer-name "*buff-name%s*") "*buff-name proj*<2>")) - (kill-buffer nrepl-new-buffer))))) + (kill-buffer cider-new-buffer))))) (ert-deftest test-nrepl-buffer-name-duplicate-proj-port () (with-temp-buffer (set (make-local-variable 'nrepl-buffer-name-show-port) t) (set (make-local-variable 'nrepl-project-dir) "proj") (set (make-local-variable 'nrepl-endpoint) '("localhost" 4009)) - (let* ((nrepl-new-buffer (nrepl-buffer-name "*buff-name%s*"))) - (get-buffer-create nrepl-new-buffer) + (let* ((cider-new-buffer (nrepl-buffer-name "*buff-name%s*"))) + (get-buffer-create cider-new-buffer) (should - (equal nrepl-new-buffer "*buff-name proj:4009*")) + (equal cider-new-buffer "*buff-name proj:4009*")) (with-temp-buffer (set (make-local-variable 'nrepl-buffer-name-show-port) t) (set (make-local-variable 'nrepl-project-dir) "proj") (set (make-local-variable 'nrepl-endpoint) '("localhost" 4009)) (should (equal (nrepl-buffer-name "*buff-name%s*") "*buff-name proj:4009*<2>")) - (kill-buffer nrepl-new-buffer))))) + (kill-buffer cider-new-buffer))))) -(ert-deftest test-nrepl-clojure-buffer-name () +(ert-deftest test-cider-clojure-buffer-name () (with-temp-buffer (lexical-let ((b1 (current-buffer))) (let ((nrepl-connection-list (list (buffer-name b1)))) (should - (equal (nrepl-repl-buffer-name) "*nrepl*")))))) + (equal (cider-repl-buffer-name) "*cider*")))))) -(ert-deftest test-nrepl-clojure-buffer-name-w/project () +(ert-deftest test-cider-clojure-buffer-name-w/project () (with-temp-buffer (lexical-let ((b1 (current-buffer))) (let ((nrepl-connection-list (list (buffer-name b1))) (nrepl-project-dir "/a/test/directory/project")) (should - (equal (nrepl-repl-buffer-name) "*nrepl project*")))))) + (equal (cider-repl-buffer-name) "*cider project*")))))) -(ert-deftest test-nrepl--find-rest-args-position () - (should (= (nrepl--find-rest-args-position [fmt & arg]) 1)) - (should (equal (nrepl--find-rest-args-position [fmt arg]) nil))) +(ert-deftest test-cider--find-rest-args-position () + (should (= (cider--find-rest-args-position [fmt & arg]) 1)) + (should (equal (cider--find-rest-args-position [fmt arg]) nil))) -(ert-deftest test-nrepl-switch-to-relevant-repl-buffer () +(ert-deftest test-cider-switch-to-relevant-repl-buffer () (noflet ((nrepl-project-directory-for (dontcare) nrepl-project-dir)) (let* ((b1 (generate-new-buffer "temp")) @@ -494,32 +494,32 @@ (nrepl-connection-list (list (buffer-name b1) (buffer-name b2) (buffer-name b3)))) - (with-current-buffer b1 ;; nrepl-jack-in 1 + (with-current-buffer b1 ;; cider-jack-in 1 (set (make-local-variable 'nrepl-endpoint) '("localhost" 4005)) (set (make-local-variable 'nrepl-project-dir) "proj1") (set (make-local-variable 'nrepl-repl-buffer) b4)) - (with-current-buffer b2 ;; nrepl-jack-in 2 + (with-current-buffer b2 ;; cider-jack-in 2 (set (make-local-variable 'nrepl-endpoint) '("localhost" 4006)) (set (make-local-variable 'nrepl-project-dir) "proj2") (set (make-local-variable 'nrepl-repl-buffer) b5)) - (with-current-buffer b3 ;; nrepl-connect - no relevant buffer + (with-current-buffer b3 ;; cider-connect - no relevant buffer (set (make-local-variable 'nrepl-endpoint) '("123.123.123.123" 4009)) (set (make-local-variable 'nrepl-repl-buffer) b6)) (with-current-buffer b1 - (nrepl-switch-to-relevant-repl-buffer '()) + (cider-switch-to-relevant-repl-buffer '()) (should (equal b4 (current-buffer))) (should (equal (list (buffer-name b1) (buffer-name b2) (buffer-name b3)) nrepl-connection-list))) (with-current-buffer b2 - (nrepl-switch-to-relevant-repl-buffer '()) + (cider-switch-to-relevant-repl-buffer '()) (should (equal b5 (current-buffer))) (should (equal (list (buffer-name b2) (buffer-name b1) (buffer-name b3)) nrepl-connection-list))) (with-current-buffer b3 - (nrepl-switch-to-relevant-repl-buffer '()) + (cider-switch-to-relevant-repl-buffer '()) (should (equal b5 (current-buffer))) ;; didn't switch to anything (should (equal (list (buffer-name b2) (buffer-name b1) (buffer-name b3)) nrepl-connection-list))) @@ -528,7 +528,7 @@ (buffer-name b2) (buffer-name b1)))) (with-current-buffer b1 - (nrepl-switch-to-relevant-repl-buffer '()) + (cider-switch-to-relevant-repl-buffer '()) (should (equal b4 (current-buffer))) (should (equal (list (buffer-name b1) (buffer-name b3) (buffer-name b2)) nrepl-connection-list)))) diff --git a/test/run-tests b/test/run-tests index c923b3ea..937051a8 100755 --- a/test/run-tests +++ b/test/run-tests @@ -1,16 +1,16 @@ #!/usr/bin/env emacs --script (let ((current-directory (file-name-directory load-file-name))) - (setq nrepl-test-path (expand-file-name "." current-directory)) - (setq nrepl-root-path (expand-file-name ".." current-directory))) + (setq cider-test-path (expand-file-name "." current-directory)) + (setq cider-root-path (expand-file-name ".." current-directory))) -(add-to-list 'load-path nrepl-root-path) -(add-to-list 'load-path nrepl-test-path) +(add-to-list 'load-path cider-root-path) +(add-to-list 'load-path cider-test-path) -(require 'nrepl) +(require 'cider) (require 'cl) -(dolist (test-file (or argv (directory-files nrepl-test-path t "-tests.el$"))) +(dolist (test-file (or argv (directory-files cider-test-path t "-tests.el$"))) (load test-file nil t)) ;; run tests -- cgit v1.2.3