diff --git a/History.txt b/History.txt
index 9732142..1dfc083 100644
--- a/History.txt
+++ b/History.txt
@@ -4,6 +4,18 @@
=== New features
* company mode completion can be case insensitive
+ * added eclim-run-configuartion which runs a run configuration in the
+ compilation buffer.
+ * added eclim-java-generate-getter which generates a getter method
+ for the symbol at point.
+ * added eclim-java-generate-setter which generates a setter method
+ for the symbol at point.
+ * added eclim-java-refactor-move-class which allows moving a top level
+ class or interface from one package to another.
+ * added eclim-project-setting-set which can assign an Eclim project
+ setting.
+ * Fixed some compiler warnings. Not all compiler warnings were fixed
+ since those changes could not be tested.
== 0.3
diff --git a/README.md b/README.md
index fc9ba26..dfcd285 100644
--- a/README.md
+++ b/README.md
@@ -1,13 +1,13 @@
Development has moved to https://github.com/emacs-eclim/emacs-eclim
[![License GPL 3][badge-license]](http://www.gnu.org/licenses/gpl-3.0.txt)
-[![Build Status](https://travis-ci.org/senny/emacs-eclim.svg?branch=master)](https://travis-ci.org/senny/emacs-eclim)
+[![Build Status](https://travis-ci.org/emacs-eclim/emacs-eclim.svg?branch=master)](https://travis-ci.org/emacs-eclim/emacs-eclim)
[![MELPA](http://melpa.org/packages/emacs-eclim-badge.svg)](http://melpa.org/#/emacs-eclim)
[![MELPA Stable](http://stable.melpa.org/packages/emacs-eclim-badge.svg)](http://stable.melpa.org/#/emacs-eclim)
## Overview
-[![Join the chat at https://gitter.im/senny/emacs-eclim](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/senny/emacs-eclim?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
+[![Join the chat at https://gitter.im/emacs-eclim/emacs-eclim](https://badges.gitter.im/emacs-eclim/emacs-eclim.svg)](https://gitter.im/emacs-eclim/emacs-eclim?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
[Eclim](http://eclim.org) is an Eclipse plugin which exposes Eclipse
features through a server interface. When this server is started, the
diff --git a/company-emacs-eclim.el b/company-emacs-eclim.el
index 27c9d9b..8854658 100644
--- a/company-emacs-eclim.el
+++ b/company-emacs-eclim.el
@@ -1,4 +1,4 @@
-;; company-emacs-eclim.el --- an interface to the Eclipse IDE.
+;;; company-emacs-eclim.el --- an interface to the Eclipse IDE.
;;
;; Copyright (C) 2009-2012 Fredrik Appelberg
;; Copyright (C) 2013-2014 Dmitry Gutov
@@ -16,7 +16,7 @@
;; You should have received a copy of the GNU General Public License
;; along with this program. If not, see .
;;
-;;; Description
+;;; Commentary:
;;
;; company-emacs-eclim.el -- company-mode backend that replaces company-eclim
;;
@@ -26,12 +26,15 @@
;;
;; Minimum company-mode version required: 0.7.
+;;; Code:
+
;;* Eclim Company
(require 'eclim)
(require 'eclim-completion)
(require 'eclim-java)
(require 'company)
+(require 'cl-lib)
(defcustom company-emacs-eclim-ignore-case t
"If t, case is ignored in completion matches."
@@ -39,13 +42,14 @@
:type '(choice (const :tag "Yes" t)
(const :tag "No" nil)))
+;;;###autoload
(defun company-emacs-eclim-setup ()
"Convenience function that adds company-emacs-eclim to the list
of available company backends."
(setq company-backends
(cons 'company-emacs-eclim
- (remove-if (lambda (b) (find b '(company-nxml company-eclim)))
- company-backends))))
+ (cl-remove-if (lambda (b) (cl-find b '(company-nxml company-eclim)))
+ company-backends))))
(defun company-emacs-eclim--before-prefix-in-buffer (prefix)
"Search for the text before prefix that may be included as part of completions"
@@ -80,17 +84,22 @@
(mapcar
(lambda (candidate)
(annotate (without-redundant-prefix candidate)))
- (eclim--completion-candidates)))))
+ ;; Company says backend is responsible for filtering prefix case.
+ (if company-emacs-eclim-ignore-case
+ (eclim--completion-candidates)
+ (cl-remove-if-not #'(lambda(str) (string-prefix-p prefix str))
+ (eclim--completion-candidates)))))))
(defun company-emacs-eclim--annotation (candidate)
(let ((str (get-text-property 0 'eclim-meta candidate)))
(when (and str (string-match "(" str))
(substring str (match-beginning 0)))))
+;;;###autoload
(defun company-emacs-eclim (command &optional arg &rest ignored)
"`company-mode' back-end for Eclim completion"
(interactive (list 'interactive))
- (case command
+ (cl-case command
(interactive (company-begin-backend 'company-emacs-eclim))
(prefix (let ((start (and eclim-mode
(eclim--accepted-p (buffer-file-name))
@@ -115,3 +124,4 @@
(eclim--completion-action beg end)))
(provide 'company-emacs-eclim)
+;;; company-emacs-eclim.el ends here
diff --git a/eclim-ant.el b/eclim-ant.el
index c33a867..e563540 100644
--- a/eclim-ant.el
+++ b/eclim-ant.el
@@ -67,7 +67,7 @@ stored. It is used globally for all eclim projects."
(eclim--check-project project)
(mapcar (lambda (line)
(split-string line "|"))
- (eclim--call-process "ant_validate" "-p" project "-f" file)))
+ (eclim--call-process "ant_validate" "-p" project "-f" buildfile)))
(defun eclim/ant-target-list (project buildfile)
(eclim--check-project project)
@@ -89,7 +89,7 @@ buffer. The results are displayed in a dedicated compilation buffer."
(dolist (line (eclim/ant-validate project file))
(insert (eclim--convert-find-result-to-string line))
(newline)))
- (beginning-of-buffer)
+ (goto-char (point-min))
(compilation-mode))
(defun eclim-ant-run (target)
diff --git a/eclim-completion.el b/eclim-completion.el
index 7e7f65e..979ddf1 100644
--- a/eclim-completion.el
+++ b/eclim-completion.el
@@ -50,6 +50,13 @@
(defvar eclim--completion-candidates nil)
+(defvar eclim-insertion-functions nil
+ "Use one of these functons when inserting a completion in
+preference to yasnippet or raw insertion. Each will be called
+with a yas template and should return nil iff it cannot do the
+insertion (e.g. wrong mode). For example, `eclim-completion-insert-empty'
+removes all arguments before inserting.")
+
(defun eclim--complete ()
(setq eclim--is-completing t)
(unwind-protect
@@ -75,6 +82,7 @@
(setq eclim--is-completing nil)))
(defun eclim--completion-candidates-filter (c)
+ "Rejects completion candidate C (non-nil return) in certain situations."
(case major-mode
((xml-mode nxml-mode) (or (search "XML Schema" c)
(search "Namespace" c)))
@@ -142,15 +150,24 @@ buffer."
(defun eclim--completion-yasnippet-convert (completion)
"Convert a completion string to a yasnippet template"
- (apply #' concat
- (loop for c across (replace-regexp-in-string ", " "," completion)
- collect (case c
- (40 "(${")
- (60 "<${")
- (44 "}, ${")
- (41 "})")
- (62 "}>")
- (t (char-to-string c))))))
+ (let ((level 0))
+ (replace-regexp-in-string
+ ;; ORs: 1) avoid empty case; 2) eat spaces sometimes; 3) not when closing.
+ "()\\|[(<,] *\\|[)>]"
+ #'(lambda (m)
+ (let ((c (string-to-char m)) (repl m))
+ (unless (string= m "()")
+ (when (memq c '(?\( ?<)) (incf level))
+ (when (<= level 1) (setq repl (case c
+ (?\( "(${")
+ (?< "<${")
+ (?, "}, ${")
+ (?\) "})")
+ (?> "}>")
+ (t (error "RE/case mismatch")))))
+ (when (memq c '(?\) ?>)) (decf level)))
+ repl))
+ completion)))
(defvar eclim--completion-start)
@@ -161,6 +178,15 @@ buffer."
(case major-mode
((java-mode javascript-mode js-mode ruby-mode groovy-mode php-mode c-mode c++-mode scala-mode)
(progn
+ ;; Allow completion after open bracket. Eclipse/eclim do.
+ (when (or (eq ?\( (char-before))
+ ;; Template? Technically it could be a less-than sign
+ ;; but it's unlikely the user completes there and
+ ;; no particular harm done.
+ (and (eq ?\< (char-before))
+ (memq major-mode
+ '(java-mode c++-mode goovy-mode))))
+ (backward-char 1))
(ignore-errors (beginning-of-thing 'symbol))
;; Completion candidates for annotations don't include '@'.
(when (eq ?@ (char-after))
@@ -183,9 +209,12 @@ buffer."
(package (if (and rest (string-match "\\w+\\(\\.\\w+\\)*" rest)) rest nil))
(template (eclim--completion-yasnippet-convert insertion)))
(delete-region beg end)
- (if (and eclim-use-yasnippet template (featurep 'yasnippet) yas-minor-mode)
+ (unless (loop for f in eclim-insertion-functions thereis
+ (funcall f template))
+ (if (and eclim-use-yasnippet template
+ (featurep 'yasnippet) yas-minor-mode)
(yas/expand-snippet template)
- (insert insertion))
+ (insert insertion)))
(when package
(eclim-java-import
(concat package "." (substring insertion 0 (or (string-match "[<(]" insertion)
@@ -212,12 +241,13 @@ buffer."
(backward-char)))))
(defun eclim--completion-action (beg end)
- (case major-mode
- ('java-mode (eclim--completion-action-java beg end))
- ('groovy-mode (eclim--completion-action-java beg end))
- ((c-mode c++-mode) (eclim--completion-action-java beg end))
- ('nxml-mode (eclim--completion-action-xml beg end))
- (t (eclim--completion-action-default))))
+ (let ((eclim--is-completing t)) ;; an import should not refresh problems
+ (case major-mode
+ ('java-mode (eclim--completion-action-java beg end))
+ ('groovy-mode (eclim--completion-action-java beg end))
+ ((c-mode c++-mode) (eclim--completion-action-java beg end))
+ ('nxml-mode (eclim--completion-action-xml beg end))
+ (t (eclim--completion-action-default)))))
(defun eclim--render-doc (str)
"Performs rudimentary rendering of HTML elements in
@@ -238,4 +268,14 @@ completion candidates list."
(when doc
(eclim--render-doc doc))))
+(defun eclim-completion-insert-empty (template)
+ "Insert a completion erasing arguments, leaving point inside argument list
+or outside if empty. Meant for `eclim-insertion-functions'."
+ (save-match-data
+ (if (not (string-match "${.*}" template))
+ (insert template)
+ (insert (substring template 0 (match-beginning 0)))
+ (save-excursion (insert (substring template (match-end 0))))))
+ t)
+
(provide 'eclim-completion)
diff --git a/eclim-java-run.el b/eclim-java-run.el
index 5626239..3487b6b 100644
--- a/eclim-java-run.el
+++ b/eclim-java-run.el
@@ -146,5 +146,16 @@
classpath
project-dir))))
+(defun eclim-run-configuartion (configuration-name)
+ "Runs the configuration given in CONFIGURATION-NAME in the compilation buffer."
+ (interactive (list (eclim-java-run--ask-which-configuration)))
+ (let* ((configurations (eclim-java-run--load-configurations (eclim-project-name)))
+ (configuration (eclim-java-run--configuration configuration-name configurations))
+ (project-dir (eclim-java-run--project-dir (eclim-project-name)))
+ (classpath (eclim/java-classpath (eclim-project-name)))
+ (default-directory project-dir)
+ (command (eclim-java-run--command configuration (eclim-java-run--java-vm-args classpath))))
+ (compile command)))
+
(provide 'eclim-java-run)
;;; eclim-java-run.el ends here
diff --git a/eclim-java.el b/eclim-java.el
index 1ddd205..89a2b15 100644
--- a/eclim-java.el
+++ b/eclim-java.el
@@ -104,6 +104,11 @@ Java documentation under Android docs, so don't forget to set
"references"))
(defvar eclim--is-completing nil)
+(defvar eclim-java-show-documentation-history nil)
+(defvar eclim--run-class-history nil)
+(defvar-local eclim--run-class-commands nil
+ "Alist of previously ran commands in current buffer.
+See `eclim-run-class'.")
(defun eclim/groovy-src-update (&optional save-others)
"If `eclim-auto-save' is non-nil, save the current java
@@ -142,9 +147,9 @@ in eclim when appropriate."
(replace-regexp-in-string
"[<>(),?]"
(lambda (m) (assoc-default m '(("<" . "((") (">" . "))")
- ("(" . "((") (")" ."))")
- ("," . ")(")
- ("?" . "\\\\?"))))
+ ("(" . "((") (")" ."))")
+ ("," . ")(")
+ ("?" . "\\\\?"))))
str)))))
(defun eclim--java-parse-method-signature (signature)
@@ -174,6 +179,34 @@ declaration has been found. TYPE may be either 'class',
has been found."
(eclim--java-current-type-name "\\(class\\)"))
+(defun eclim--java-generate-bean-properties (project file offset encoding type)
+ "Generates a bean property for the symbol at point. TYPE specifies the property to generate."
+ (eclim--call-process "java_bean_properties"
+ "-p" project
+ "-f" file
+ "-o" (number-to-string offset)
+ "-e" encoding
+ "-r" (cdr (eclim--java-identifier-at-point t))
+ "-t" type)
+ (revert-buffer t t t))
+
+(defun eclim--java-refactor (result)
+ "Processes the resulst of a refactor command. RESULT is the
+ results of invoking eclim/execute-command."
+ (if (stringp result) (error "%s" result))
+ (loop for (from to) in (mapcar (lambda (x) (list (assoc-default 'from x) (assoc-default 'to x))) result)
+ do (when (and from to)
+ (kill-buffer (find-buffer-visiting from))
+ (find-file to)))
+ (save-excursion
+ (loop for file in (mapcar (lambda (x) (assoc-default 'file x)) result)
+ do (when file
+ (let ((buf (get-file-buffer (file-name-nondirectory file))))
+ (when buf
+ (switch-to-buffer buf)
+ (revert-buffer t t t))))))
+ (message "Done"))
+
(defun eclim/java-classpath (project)
(eclim--check-project project)
(eclim--call-process "java_classpath" "-p" project))
@@ -197,11 +230,11 @@ has been found."
(defun eclim-run-java-doc ()
"Run Javadoc on current or all projects."
(interactive)
- (let ((project-list (mapcar 'third (eclim/project-list))))
+ (let ((proj-list (eclim/project-list)))
(if (y-or-n-p "Run Javadoc for all projects?")
- (dolist (project project-list)
- (eclim/execute-command "javadoc" ("-p" project)))
- (eclim/execute-command "javadoc" "-p"))
+ (dotimes (i (length proj-list))
+ (eclim--call-process-no-parse "javadoc" "-p" (rest (assq 'name (elt proj-list i)))))
+ (eclim--call-process-no-parse "javadoc" "-p"))
(message "Javadoc creation finished.")))
(defun eclim-java-format ()
@@ -215,15 +248,23 @@ has been found."
(eclim--project-current-file)
(eclim--byte-offset)
(eclim--current-encoding)))
+ (eclim--java-generate-bean-properties project file offset encoding "gettersetter"))
- (eclim--call-process "java_bean_properties"
- "-p" project
- "-f" file
- "-o" (number-to-string offset)
- "-e" encoding
- "-r" (cdr (eclim--java-identifier-at-point t))
- "-t" "gettersetter")
- (revert-buffer t t t))
+(defun eclim-java-generate-getter (project file offset encoding)
+ "Generates a getter method for the symbol at point."
+ (interactive (list (eclim-project-name)
+ (eclim--project-current-file)
+ (eclim--byte-offset)
+ (eclim--current-encoding)))
+ (eclim--java-generate-bean-properties project file offset encoding "getter"))
+
+(defun eclim-java-generate-setter (project file offset encoding)
+ "Generates a setter method for the symbol at point."
+ (interactive (list (eclim-project-name)
+ (eclim--project-current-file)
+ (eclim--byte-offset)
+ (eclim--current-encoding)))
+ (eclim--java-generate-bean-properties project file offset encoding "setter"))
(defun eclim-java-constructor ()
(interactive)
@@ -251,19 +292,17 @@ has been found."
(n (read-string (concat "Rename " (cdr i) " to: ") (cdr i))))
(eclim/with-results res ("java_refactor_rename" "-p" "-e" "-f" ("-n" n)
("-o" (car i)) ("-l" (length (cdr i))))
- (if (stringp res) (error res))
- (loop for (from to) in (mapcar (lambda (x) (list (assoc-default 'from x) (assoc-default 'to x))) res)
- do (when (and from to)
- (kill-buffer (find-buffer-visiting from))
- (find-file to)))
- (save-excursion
- (loop for file in (mapcar (lambda (x) (assoc-default 'file x)) res)
- do (when file
- (let ((buf (get-file-buffer (file-name-nondirectory file))))
- (when buf
- (switch-to-buffer buf)
- (revert-buffer t t t))))))
- (message "Done"))))
+ (eclim--java-refactor res))))
+
+(defun eclim-java-refactor-move-class ()
+ "Renames the java class. Searches backward in the current buffer
+until a class declaration has been found."
+ (interactive)
+ (let* ((class-name (eclim--java-current-class-name))
+ (package-name (eclim--java-current-package))
+ (n (read-string (concat "Move " class-name " to: ") package-name)))
+ (eclim/with-results res ("java_refactor_move" "-p" "-f" ("-n" n))
+ (eclim--java-refactor res))))
(defun eclim-java-call-hierarchy (project file encoding)
(interactive (list (eclim-project-name)
@@ -419,12 +458,12 @@ imports section of a java source file. This will preserve the
undo history."
(interactive)
(cl-flet ((cut-imports ()
- (beginning-of-buffer)
+ (goto-char (point-min))
(if (re-search-forward "^import" nil t)
(progn
(beginning-of-line)
(let ((beg (point)))
- (end-of-buffer)
+ (goto-char (point-max))
(re-search-backward "^import")
(end-of-line)
(let ((imports (buffer-substring-no-properties beg (point))))
@@ -435,27 +474,29 @@ undo history."
(delete-blank-lines)
(insert "\n\n\n")
(forward-line -2)))))
- (save-excursion
- (clear-visited-file-modtime)
- (cut-imports)
- (widen)
- (insert
- (let ((fname (buffer-file-name)))
- (with-temp-buffer
- (insert-file-contents fname)
- (cut-imports))))
- (not-modified)
- (set-visited-file-modtime))))
+ (let* ((fname (buffer-file-name))
+ (new-imports (with-temp-buffer
+ (insert-file-contents fname)
+ (cut-imports))))
+ (save-excursion
+ (clear-visited-file-modtime)
+ (cut-imports)
+ (widen)
+ (insert new-imports)
+ (not-modified)
+ (set-visited-file-modtime)))))
(defun eclim-java-import (type)
"Adds an import statement for the given type, if one does not
exist already."
- (save-excursion
- (beginning-of-buffer)
+ (unless (save-excursion
+ (goto-char (point-min))
+ (beginning-of-buffer)
+ (re-search-forward (format "^import %s;" type) nil t))
(let ((revert-buffer-function 'eclim-soft-revert-imports))
- (when (not (re-search-forward (format "^import %s;" type) nil t))
- (eclim/execute-command "java_import" "-p" "-f" "-o" "-e" ("-t" type))
- (eclim--problems-update-maybe)))))
+ (eclim/execute-command "java_import" "-p" "-f" "-o" "-e" ("-t" type))
+ (eclim--problems-update-maybe)
+ (message "Imported %s" type))))
(defun eclim-java-import-organize (&optional types)
"Checks the current file for missing imports, removes unused imports and
@@ -470,57 +511,135 @@ sorts import statements. "
(eclim-java-import-organize
(mapcar (lambda (imports) (eclim--completing-read "Import: " (append imports '()))) res)))))))
-(defun format-type (type)
- (cond ((null type) nil)
- ((listp (first type))
- (append (list "<") (rest (mapcan (lambda (type) (append (list ", ") (format-type type))) (first type))) (list ">")
- (format-type (rest type))))
- (t (cons (let ((type-name (symbol-name (first type))))
- (when (string-match "\\(.*\\.\\)?\\(.*\\)" type-name)
- (match-string 2 type-name)))
- (format-type (rest type))))))
+
+(defun eclim--signature-has-keyword (sig java-keyword)
+ "Returns true if a method signature SIG has the keyword JAVA-KEYWORD."
+ ;; \_< is beginning of identifier E.g. don't match do_abstract".
+ (string-match-p (format "\\_<%s\\_>" java-keyword) sig))
+
+
+(defun eclim--colorize-signature (sig)
+ "Minimal colorization for a method signature that we offer for completion,
+so the essential bits stand out from the block of text that ido presents.
+Keep this minimal: more highlighting could easily make things worse not better."
+ (save-match-data
+ (mapc #'(lambda(re-g-f) ;; expecting single match per RE
+ (when (string-match (elt re-g-f 0) sig)
+ (setq sig (replace-match
+ (propertize (match-string (elt re-g-f 1) sig)
+ 'face (elt re-g-f 2))
+ nil nil sig (elt re-g-f 1)))))
+ '(("\\_<\\(class\\|interface\\)\\s +\\([[:alnum:]_]+\\_>\\)"
+ 2 font-lock-type-face)
+ ("\\_<\\([[:alnum:]_]+\\)(" 1 font-lock-function-name-face)
+ ("all [[:digit:]]+ \\w+ methods" 0 font-lock-function-name-face))))
+ sig)
+
(defun eclim-java-implement (&optional name)
- "Lets the user select from a list of methods to
-implemnt/override, then inserts a skeleton for the chosen
-method."
+ "Implement or override methods from parents of the class, prompting the
+user to select with a completing read (even if one, as confirmation). If
+NAME was specified programmatically, filters for that name (strict,
+although only on method name not arguments) and if only one choice
+implement it without prompting. The actual change is done by Eclipse
+and will be close to point although not necessarily at it (e.g. if in a
+sub block)."
(interactive)
- (eclim/with-results response ("java_impl" "-p" "-f" "-o")
- (cl-flet ((join (glue items)
- (cond ((null items) "")
- ((= 1 (length items)) (format "%s" (first items)))
- (t (reduce (lambda (a b) (format "%s%s%s" a glue b)) items))))
- (format-type (type)
- (cond ((null type) nil)
- ((listp (first type))
- (append (list "<") (rest (mapcan (lambda (type) (append (list ", ") (format-type type))) (first type))) (list ">")
- (format-type (rest type))))
- (t (cons (let ((type-name (symbol-name (first type))))
- (when (string-match "\\(.*\\.\\)?\\(.*\\)" type-name)
- (let ((package (match-string 1 type-name))
- (class (match-string 2 type-name)))
- (eclim-java-import (concat package class))
- class)))
- (format-type (rest type)))))))
- (let* ((methods (remove-if-not (lambda (m) (or (null name)
- (string-match name m)))
- (mapcar (lambda (x) (replace-regexp-in-string "[ \n\t]+" " " x))
- (apply 'append
- (mapcar (lambda (x) (append (assoc-default 'methods x) nil))
- (assoc-default 'superTypes response))))))
- (method (if (= 1 (length methods)) (first methods)
- (eclim--completing-read "Signature: " methods)))
- (sig (eclim--java-parse-method-signature method))
- (ret (assoc-default :return sig)))
- (yas/expand-snippet (format "@Override\n%s %s(%s) {$0}"
- (apply #'concat
- (join " " (remove-if-not (lambda (m) (find m '(public protected private void))) (subseq ret 0 (1- (length ret)))))
- " "
- (format-type (remove-if (lambda (m) (find m '(abstract public protected private ))) ret)))
- (assoc-default :name sig)
- (join ", " (loop for arg in (remove-if #'null (assoc-default :arglist sig))
- for i from 0
- collect (format "%s ${arg%s}" (apply #'concat (format-type (assoc-default :type arg))) i)))))))))
+ (eclim/with-results list-response ("java_impl" "-p" "-f" "-o")
+ (let* ((supertypes (assoc-default 'superTypes list-response))
+ ;; "Choices" are lists of user-friendly method names. We want to
+ ;; present interfaces/abstract first, otherwise Object can barge in.
+ (choices nil) (choices-opt nil) (choices-last nil)
+ ;; Maps a choice to a (supertype method1 method2...), needed
+ ;; when we request eclim to implement that method.
+ (choice-data (make-hash-table :test 'equal)))
+ (loop
+ for super-entry across supertypes do
+ (let* ((package (assoc-default 'packageName super-entry))
+ (super-sig (assoc-default 'signature super-entry))
+ ;; Erase type arguments. This looks like "class List".
+ (friendly-super (replace-regexp-in-string "<[^<]*>" "" super-sig))
+ (full-super (concat package "."
+ (replace-regexp-in-string "^\\w+ " ""
+ friendly-super)))
+ (is-interface (eclim--signature-has-keyword
+ super-sig "interface"))
+ (methods (assoc-default 'methods super-entry))
+ (required-methods nil)) ;; Eclim names here
+ (loop
+ for method across methods
+ ;; Skip if specified name doesn't match.
+ if (or (null name)
+ (string-match-p (format "\\_<%s(" (regexp-quote name)) method))
+ do
+ ;; This regexp stuff is how vim (and thus eclim) does it. Nothing
+ ;; fancy. If it breaks, Google eclim/java/impl.vim for changes.
+ (let ((name-for-eclim
+ ;; Remove keywords and return type. \_< begins identifier.
+ (replace-regexp-in-string "^\\s *[^(]*\\(\\_<[[:alnum:]_]+(\\)"
+ "\\1"
+ ;; Remove any and all type parameters.
+ (replace-regexp-in-string "<[^<]*>" "" method)))
+ ;; For the user, we have very different requirements. I like
+ ;; knowing public and abstract, and the return type. I hate
+ ;; packages -- I'm already implementing this class so I know.
+ (friendly-name
+ ;; Packages are non-trivial to find (think Map.Entry) but
+ ;; if we stop at the first capitalized portion we're okay.
+ (replace-regexp-in-string "\\_<[[:lower:]][[:alnum:]_]+\\."
+ "" method))
+ (is-required (or is-interface (eclim--signature-has-keyword
+ method "abstract"))))
+ (let ((choice (format "%s [%s]" friendly-name friendly-super))
+ (data (list full-super name-for-eclim)))
+ ;; This is probably overkill but what if our package erasing
+ ;; resulted in duplicates? Use full name then. As in, really full.
+ (when (gethash choice choice-data)
+ (setq choice (format "%s [%s]" name-for-eclim full-super)))
+ (cond (is-required (push choice choices))
+ ((member full-super '("java.lang.Object")) ; others like it?
+ (push choice choices-last))
+ (t (push choice choices-opt)))
+ (puthash choice (list full-super name-for-eclim) choice-data)
+ (when is-required (push name-for-eclim required-methods)))))
+ ;; Since we don't allow multiple selection like Eclipse / vim, let's
+ ;; provide for the cases that matter. Note that full non-abstract
+ ;; overrides are typically a use case for *delegates*.
+ (when (> (length required-methods) 1) ;; 1 method already there
+ (let ((choice
+ (format ""
+ (length required-methods)
+ (cond (is-interface "missing") (name) (t "abstract"))
+ friendly-super))
+ (data (cons full-super (reverse required-methods))))
+ (push choice choices) ;; I'll not worry about conflict here.
+ (puthash choice data choice-data)))))
+ ;; Keep inital order, except for our tweaks.
+ (setq choices (append (nreverse choices) (reverse choices-opt)
+ (reverse choices-last)))
+ (unless choices
+ (if name (error "No such unimplemented method: %s" name) ;most likely
+ (error "No candidates to implement"))) ;; Rare, given Object ancestor.
+
+ ;; Ask user even if only one choice, for confirmation. Otherwise it's
+ ;; possible to not even notice the change from a bad key combo. Unless
+ ;; we were called programmatically a for specific method.
+ (let ((choice
+ (if (and name (eq 1 (length choices)))
+ (first choices)
+ (funcall eclim-interactive-completion-function
+ "Implement: "
+ (mapcar #'eclim--colorize-signature choices)
+ nil t)))) ; require match
+ (setq choice (substring-no-properties choice)) ; uncolorize
+ (let* ((eclim-data (gethash choice choice-data))
+ (super (car eclim-data)) (methods (cdr eclim-data))
+ (methods-str (json-encode methods)))
+ (eclim/with-results impl-result ("java_impl" "-p" "-f" "-o"
+ ("-s" super) ("-m" methods-str))
+ ;; eclim should give us a smaller list if it did something. But
+ ;; it's probably not worth an error in case this changes.
+ (revert-buffer t t t)))))))
(defun eclim-package-and-class ()
(let ((package-name (eclim--java-current-package))
@@ -528,25 +647,49 @@ method."
(if package-name (concat package-name "." class-name)
class-name)))
-(defun eclim-run-class ()
- "Run the current class."
- (interactive)
+(defun eclim-run-class (&optional editp)
+ "Run the current class.
+If optional EDITP is non-nil, edit the command before running
+it. The following format specs are substituted in the eclim command:
+
+ %p project name
+ %c fully qualified class name
+ %r root directory of the current project
+
+See help string of 'eclim ? java` for available
+arguments. Currently available arguments:
+
+ java -p project [-d] [-c classname] [-w workingdir]
+ [-v vmargs] [-s sysprops] [-e envargs] [-a args]
+"
+ (interactive "P")
(if (not (string= major-mode "java-mode"))
(message "Sorry cannot run current buffer.")
- (compile (concat eclim-executable " -command java -p " (eclim-project-name)
- " -c " (eclim-package-and-class)))))
+ (let* ((class (eclim-package-and-class))
+ (hist-command (and eclim--run-class-commands
+ (assoc class eclim--run-class-commands)))
+ (command (or (cdr hist-command)
+ (concat eclim-executable " -command java -p %p -c %c"))))
+ (when editp
+ (setq command (read-string "Run command: " command 'eclim--run-class-history))
+ (if hist-command
+ (setf (cdr hist-command) command)
+ (add-to-list 'eclim--run-class-commands (cons class command))))
+ (compile (format-spec command `((?p . ,(eclim-project-name))
+ (?c . ,class)
+ (?r . ,(eclim--project-dir))))))))
(defun eclim--java-junit-file (project file offset encoding)
- (concat eclim-executable
- " -command java_junit -p " project
- " -f " file
- " -o " (number-to-string offset)
- " -e " encoding))
+ (concat eclim-executable
+ " -command java_junit -p " project
+ " -f " file
+ " -o " (number-to-string offset)
+ " -e " encoding))
(defun eclim--java-junit-project (project encoding)
- (concat eclim-executable
- " -command java_junit -p " project
- " -e " encoding))
+ (concat eclim-executable
+ " -command java_junit -p " project
+ " -e " encoding))
(defun eclim--buffer-contains-substring (string)
(save-excursion
@@ -595,9 +738,47 @@ much faster than running mvn test -Dtest=TestClass#method."
"-f"
("-l" line)
("-o" offset)
- ("-a" choice)))
+ ("-a" choice))
+ ;; Problem updates can be distracting, but here the user was
+ ;; actively trying to fix one.
+ (eclim--problems-update-maybe))
(message "No automatic corrections found. Sorry")))))
+(defun eclim-java-browse-documentation-at-point (&optional arg)
+ "Browse the documentation of the element at point.
+With the prefix ARG, ask for pattern. Pattern is a shell glob
+pattern, not a regexp. Rely on `browse-url' to open user defined
+browser."
+ (interactive "P")
+ (let ((symbol (if arg
+ (read-string "Glob Pattern: ")
+ (symbol-at-point)))
+ (proj-name (or (eclim-project-name)
+ (error "Not in Eclim project"))))
+ (if symbol
+ (let* ((urls (if arg
+ (eclim/execute-command "java_docsearch"
+ ("-n" proj-name)
+ "-f"
+ ("-p" symbol))
+ (let ((bounds (bounds-of-thing-at-point 'symbol)))
+ (eclim/execute-command "java_docsearch"
+ ("-n" proj-name)
+ "-f"
+ ("-l" (- (cdr bounds) (car bounds)))
+ ("-o" (save-excursion
+ (goto-char (car bounds))
+ (eclim--byte-offset)))))))
+ ;; convert from vector to list
+ (urls (append urls nil)))
+ (if urls
+ (let ((url (if (> (length urls) 1)
+ (eclim--completing-read "Browse: " (append urls nil))
+ (car urls))))
+ (browse-url url))
+ (message "No documentation for '%s' found" symbol)))
+ (message "No element at point"))))
+
(defun eclim-java-show-documentation-for-current-element ()
"Displays the doc comments for the element at the pointers position."
(interactive)
@@ -649,17 +830,17 @@ much faster than running mvn test -Dtest=TestClass#method."
(replace-match text)
(make-text-button (match-beginning 0)
(+ (match-beginning 0) (length text))
+ 'follow-link t
'action 'eclim-java-show-documentation-follow-link
'url href))))
(when add-to-history
(goto-char (point-max))
(insert "\n\n")
- (insert-text-button "back" 'action 'eclim--java-show-documentation-go-back))
+ (insert-text-button "back" 'follow-link t 'action 'eclim--java-show-documentation-go-back))
(goto-char (point-min)))
-
(defun eclim-java-show-documentation-follow-link (link)
(interactive)
(let ((url (button-get link 'url)))
@@ -672,16 +853,16 @@ much faster than running mvn test -Dtest=TestClass#method."
(let* ((doc-root-vars '(eclim-java-documentation-root
eclim-java-android-documentation-root))
(path (replace-regexp-in-string "^[./]+" "" url))
- (fullpath (some (lambda (var)
- (let ((fullpath (concat (symbol-value var)
- "/"
- path)))
- (if (file-exists-p (replace-regexp-in-string
- "#.+"
- ""
- fullpath))
+ (fullpath (cl-some (lambda (var)
+ (let ((fullpath (concat (symbol-value var)
+ "/"
+ path)))
+ (if (file-exists-p (replace-regexp-in-string
+ "#.+"
+ ""
+ fullpath))
fullpath)))
- doc-root-vars)))
+ doc-root-vars)))
(if fullpath
(browse-url (concat "file://" fullpath))
diff --git a/eclim-problems.el b/eclim-problems.el
index b25124a..3acda08 100644
--- a/eclim-problems.el
+++ b/eclim-problems.el
@@ -33,6 +33,17 @@
:type '(choice (const :tag "Off" nil)
(const :tag "On" t)))
+(defcustom eclim-problems-suppress-highlights nil
+ "When set, error and warning highlights are disabled in source files,
+although counts are printed and they remain navigable. This is
+designed to be made buffer-local (by user, not eclim) most of the
+time, but it also works globally."
+ :group 'eclim-problems
+ :type '(choice (const :tag "Allow" nil)
+ (const :tag "Suppress" t)
+ (sexp :tag "Suppress when"
+ :value (lambda() 'for-example buffer-read-only))))
+
(defface eclim-problems-highlight-error-face
'((t (:underline "red")))
"Face used for highlighting errors in code"
@@ -68,6 +79,7 @@
(define-key eclim-mode-map (kbd "C-c C-e o") 'eclim-problems-open)
(defvar eclim--problems-list nil)
+(defvar eclim--problems-refreshing nil) ;; Set to true while refreshing probs.
(defvar eclim--problems-filter nil) ;; nil -> all problems, w -> warnings, e -> errors
(defvar eclim--problems-filefilter nil) ;; should filter by file name
@@ -151,17 +163,27 @@
(overlay-put highlight 'category 'eclim-problem)
(overlay-put highlight 'kbd-help (assoc-default 'message problem))))))
-(defun eclim--problems-clear-highlights ()
+
+(defun eclim-problems-clear-highlights ()
+ "Clears all eclim problem highlights in the current buffer. This is temporary
+until the next refresh."
+ (interactive)
(remove-overlays nil nil 'category 'eclim-problem))
+
(defun eclim-problems-highlight ()
+ "Inserts the currently active problem highlights in the current buffer,
+if `eclim-problems-suppress-highlights' allows it."
(interactive)
(when (eclim--accepted-p (buffer-file-name))
(save-restriction
(widen)
- (eclim--problems-clear-highlights)
- (loop for problem across (remove-if-not (lambda (p) (string= (assoc-default 'filename p) (buffer-file-name))) eclim--problems-list)
- do (eclim--problems-insert-highlight problem)))))
+ (eclim-problems-clear-highlights)
+ (unless (if (functionp eclim-problems-suppress-highlights)
+ (funcall eclim-problems-suppress-highlights)
+ eclim-problems-suppress-highlights)
+ (loop for problem across (cl-remove-if-not (lambda (p) (string= (assoc-default 'filename p) (buffer-file-name))) eclim--problems-list)
+ do (eclim--problems-insert-highlight problem))))))
(defadvice find-file (after eclim-problems-highlight-on-find-file activate)
(eclim-problems-highlight))
@@ -186,7 +208,7 @@
(widen)
(let ((line (line-number-at-pos))
(col (current-column)))
- (or (find-if (lambda (p) (and (string= (assoc-default 'filename p) (file-truename buffer-file-name))
+ (or (cl-find-if (lambda (p) (and (string= (assoc-default 'filename p) (file-truename buffer-file-name))
(= (assoc-default 'line p) line)))
eclim--problems-list)
(error "No problem on this line")))))))
@@ -201,26 +223,35 @@
(eclim--problem-goto-pos p)))
(defun eclim-problems-correct ()
+ "Pops up a suggestion for the current correction. This can be
+invoked in either the problems buffer or a source code buffer."
(interactive)
(let ((p (eclim--problems-get-current-problem)))
- (if (not (string-match "\\.\\(groovy\\|java\\)$" (cdr (assoc 'filename p))))
- (error "Not a Java or Groovy file. Corrections are currently supported only for Java or Groovy")
+ (unless (string-match "\\.\\(groovy\\|java\\)$" (cdr (assoc 'filename p)))
+ (error "Not a Java or Groovy file. Corrections are currently supported only for Java or Groovy"))
+ (if (eq major-mode 'eclim-problems-mode)
+ (let ((p-buffer (find-file-other-window (assoc-default 'filename p))))
+ (with-selected-window (get-buffer-window p-buffer t)
+ ;; Intentionally DON'T save excursion. Often times we need edits.
+ (eclim--problem-goto-pos p)
+ (eclim-java-correct (cdr (assoc 'line p)) (eclim--byte-offset))))
+ ;; source code buffer
(eclim-java-correct (cdr (assoc 'line p)) (eclim--byte-offset)))))
(defmacro eclim--with-problems-list (problems &rest body)
(declare (indent defun))
"Utility macro to refresh the problem list and do operations on
it asynchronously."
- (let ((res (gensym)))
+ (let ((res (cl-gensym)))
`(when eclim--problems-project
- (when (not (minibuffer-window-active-p (minibuffer-window)))
- (message "refreshing... %s " (current-buffer)))
+ (setq eclim--problems-refreshing t)
(eclim/with-results-async ,res ("problems" ("-p" eclim--problems-project) (when (string= "e" eclim--problems-filter) '("-e" "true")))
(loop for problem across ,res
do (let ((filecell (assq 'filename problem)))
(when filecell (setcdr filecell (file-truename (cdr filecell))))))
(setq eclim--problems-list ,res)
(let ((,problems ,res))
+ (setq eclim--problems-refreshing nil)
,@body)))))
(defun eclim-problems-buffer-refresh ()
@@ -232,11 +263,11 @@ it asynchronously."
(if (string= "e" eclim--problems-filter)
(message "Eclim reports %d errors." (length problems))
(message "Eclim reports %d errors, %d warnings."
- (length (remove-if-not (lambda (p) (not (eq t (assoc-default 'warning p)))) problems))
- (length (remove-if-not (lambda (p) (eq t (assoc-default 'warning p))) problems)))))))
+ (length (cl-remove-if-not (lambda (p) (not (eq t (assoc-default 'warning p)))) problems))
+ (length (cl-remove-if-not (lambda (p) (eq t (assoc-default 'warning p))) problems)))))))
(defun eclim--problems-cleanup-filename (filename)
- (let ((x (file-name-nondirectory (assoc-default 'filename problem))))
+ (let ((x (file-name-nondirectory filename)))
(if eclim-problems-show-file-extension x (file-name-sans-extension x))))
(defun eclim--problems-filecol-size ()
@@ -261,7 +292,7 @@ it asynchronously."
"Draw the problem list on screen."
(let ((buf (get-buffer "*eclim: problems*")))
(when buf
- (save-excursion
+ (with-current-buffer
(set-buffer buf)
(eclim--problems-update-filter-description)
(save-excursion
@@ -308,11 +339,11 @@ COMPILATION-SKIP-THRESHOLD, implement this feature."
(defun eclim--filter-problems (type-filter file-filter file problems)
(let ((type-filterp (eclim--choose-type-filter type-filter))
(file-filterp (eclim--choose-file-filter file-filter file)))
- (remove-if-not (lambda (x) (and (funcall type-filterp x) (funcall file-filterp x))) problems)))
+ (cl-remove-if-not (lambda (x) (and (funcall type-filterp x) (funcall file-filterp x))) problems)))
(defun eclim--insert-problem (problem filecol-size)
(let* ((filecol-format-string (concat "%-" (number-to-string filecol-size) "s"))
- (problem-new-line-pos (position ?\n (assoc-default 'message problem)))
+ (problem-new-line-pos (cl-position ?\n (assoc-default 'message problem)))
(problem-message
(if problem-new-line-pos
(concat (substring (assoc-default 'message problem)
@@ -439,27 +470,57 @@ is convenient as it lets the user navigate between errors using
`next-error' (\\[next-error])."
(interactive)
(lexical-let ((filecol-size (eclim--problems-filecol-size))
- (project-directory (concat (eclim--project-dir buffer-file-name) "/"))
- (compil-buffer (get-buffer-create eclim--problems-compilation-buffer-name)))
+ (project-directory (concat (eclim--project-dir) "/"))
+ (compil-buffer (get-buffer-create eclim--problems-compilation-buffer-name))
+ (project-name (eclim-project-name))) ; To store it in buffer.
+
+ (with-current-buffer compil-buffer
+ (setq default-directory project-directory)
+ (setq mode-line-process
+ (concat ": " (propertize "refreshing"
+ 'face 'compilation-mode-line-run))))
+ ;; Remember that the part below is asynchronous. This can be tricky.
(eclim--with-problems-list problems
- (with-current-buffer compil-buffer
- (setq default-directory project-directory)
- (setq buffer-read-only nil)
- (erase-buffer)
- (insert (concat "-*- mode: compilation; default-directory: "
- project-directory
- " -*-\n\n"))
- (let ((errors 0) (warnings 0))
- (loop for problem across (eclim--problems-filtered)
- do (eclim--insert-problem-compilation problem filecol-size project-directory)
- (cond ((assoc-default 'warning problem)
- (setq warnings (1+ warnings)))
- (t
- (setq errors (1+ errors)))))
- (insert (format "\nCompilation results: %d errors and %d warnings."
- errors warnings)))
- (compilation-mode))
- (display-buffer compil-buffer 'other-window))))
+ (let (saved-user-pos)
+ (with-current-buffer compil-buffer
+ (buffer-disable-undo)
+ (setq buffer-read-only nil)
+ (setq saved-user-pos (point))
+ (erase-buffer)
+ (let ((errors 0) (warnings 0))
+ (loop for problem across (eclim--problems-filtered) do
+ (eclim--insert-problem-compilation
+ problem filecol-size project-directory)
+ (if (eq t (assoc-default 'warning problem)) ; :json-false, WTH
+ (setq warnings (1+ warnings))
+ (setq errors (1+ errors))))
+ (let ((msg (format
+ "Compilation results: %d errors, %d warnings [%s].\n"
+ errors warnings (current-time-string))))
+ (insert "\n" msg)
+ (goto-char (point-min))
+ (insert msg "\n"))
+ (compilation-mode)
+ ;; The above killed local variables, so recover our lexical-lets
+ (setq default-directory project-directory)
+ (setq eclim--project-name project-name)
+ ;; Remap the very dangerous "g" command :) A make -k in some of
+ ;; my projects would throw Eclipse off-balance by cleaning .classes.
+ ;; May look funky, but it's safe.
+ (local-set-key "g" 'eclim-problems-compilation-buffer)
+
+ (setq mode-line-process
+ (concat ": "
+ (propertize (format "%d/%d" errors warnings)
+ 'face (when (> errors 0)
+ 'compilation-mode-line-fail))))))
+ ;; Sometimes, buffer was already current. Note outside with-current-buf.
+ (unless (eq compil-buffer (current-buffer))
+ (display-buffer compil-buffer 'other-window))
+ (with-selected-window (get-buffer-window compil-buffer t)
+ (when (< saved-user-pos (point-max))
+ (goto-char saved-user-pos)))))))
+
(defun eclim--insert-problem-compilation (problem filecol-size project-directory)
(let ((filename (first (split-string (assoc-default 'filename problem) project-directory t)))
@@ -477,11 +538,46 @@ is convenient as it lets the user navigate between errors using
(length
(eclim--filter-problems "w" t (buffer-file-name (current-buffer)) eclim--problems-list)))
+(defun eclim-problems-next-same-file (&optional up)
+ "Moves to the next problem in the current file, with wraparound. If UP
+or prefix arg, moves to previous instead; see `eclim-problems-prev-same-file'."
+ (interactive "P")
+ ;; This seems pretty inefficient, but it's fast enough. Would be even
+ ;; more inefficient if we didn't assume problems were sorted.
+ (let ((problems-file
+ (eclim--filter-problems nil t (buffer-file-name (current-buffer))
+ eclim--problems-list))
+ (pass-line (line-number-at-pos))
+ (pass-col (+ (current-column) (if up 0 1)))
+ (first-passed nil) (last-not-passed nil))
+ (when (= 0 (length problems-file)) (error "No problems in this file"))
+ (loop for p across problems-file until first-passed do
+ (let ((line (assoc-default 'line p))
+ (col (assoc-default 'column p)))
+ (if (or (> line pass-line)
+ (and (= line pass-line) (> col pass-col)))
+ (setq first-passed p)
+ (setq last-not-passed p))))
+ (eclim--problem-goto-pos
+ (or
+ (if up last-not-passed first-passed)
+ (when up (message "Moved past first error, continuing to last")
+ (elt problems-file (- (length problems-file) 1))) ; Ugh, vector
+ (progn (message "Moved past last error, continuing to first")
+ (elt problems-file 0))))))
+
+(defun eclim-problems-prev-same-file ()
+ "Moves to the previous problem in the same file, with wraparound."
+ (interactive)
+ (eclim-problems-next-same-file t))
+
+
(defun eclim-problems-modeline-string ()
"Returns modeline string with additional info about
problems for current file"
- (concat (format " : %s/%s"
+ (concat (format ": %s/%s"
(eclim--count-current-errors)
- (eclim--count-current-warnings))))
+ (eclim--count-current-warnings))
+ (when eclim--problems-refreshing "*")))
(provide 'eclim-problems)
diff --git a/eclim-project.el b/eclim-project.el
index 326191f..8bfee18 100644
--- a/eclim-project.el
+++ b/eclim-project.el
@@ -1,4 +1,4 @@
-;; eclim-project.el --- an interface to the Eclipse IDE.
+; eclim-project.el --- an interface to the Eclipse IDE.
;;
;; Copyright (C) 2009 Yves Senn
;;
@@ -98,7 +98,8 @@
(erase-buffer)
(loop for project across (eclim/project-list)
do (eclim--insert-project project))
- (goto-line line-number))))
+ (goto-char (point-min))
+ (forward-line (1- line-number)))))
(defun eclim--insert-project (project)
(insert (format " | %-6s | %-30s | %s\n"
@@ -125,7 +126,7 @@
(interactive)
(let ((marked-projects '()))
(save-excursion
- (beginning-of-buffer)
+ (goto-char (point-min))
(while (re-search-forward "*" nil t)
(push (eclim--project-current-line) marked-projects)))
marked-projects))
@@ -185,6 +186,10 @@
;; TODO: make the output useable
(eclim--call-process "project_setting" "-p" project "-s" setting))
+(defun eclim/project-setting-set (project setting value)
+ (eclim--check-project project)
+ (eclim--call-process "project_setting" "-p" project "-s" setting "-v" (concat "[\"" value "\"]")))
+
(defun eclim/project-nature-add (project nature)
(eclim--check-project project)
(eclim--check-nature nature)
@@ -218,6 +223,20 @@
(eclim--check-project project)
(eclim--call-process "project_rename" "-p" project "-n" new-name))
+(defun eclim--ask-which-project-setting ()
+ (completing-read "Which project setting do you wish to set? "
+ (--map (cdr (assoc 'name it))
+ (eclim/project-settings (eclim-project-name)))
+ nil t))
+
+(defun eclim-project-setting-set (setting)
+ "Assigns the Eclim project setting given in SETTING."
+ (interactive (list (eclim--ask-which-project-setting)))
+ (let* ((project (eclim-project-name))
+ (prev-value (eclim/project-setting project setting))
+ (value (read-string (concat "value " prev-value ": "))))
+ (eclim/project-setting-set project setting value)))
+
(defun eclim/project-classpath (&optional delimiter)
"return project classpath for the current buffer."
(eclim/execute-command "java_classpath" "-p" ("-d" delimiter)))
@@ -242,12 +261,13 @@
(eclim--project-nature-read)))
;;android project is need the vars target,package,application
(if (string-equal nature "android")
- (progn (setq target (read-string "Target: "))
- (setq package (read-string "Package: "))
- (setq application (read-string "Application: "))
- (message (eclim/project-create path nature name target package application)))
- (message (eclim/project-create path nature name))
- (eclim--project-buffer-refresh)))
+ (progn
+ (let ((target (read-string "Target: "))
+ (package (read-string "Package: "))
+ (application (read-string "Application: ")))
+ (message (eclim/project-create path nature name target package application))))
+ (message (eclim/project-create path nature name))
+ (eclim--project-buffer-refresh)))
(defun eclim-project-import (folder)
(interactive "DProject Directory: ")
@@ -317,7 +337,7 @@
(defun eclim-project-mark-all ()
(interactive)
(save-excursion
- (beginning-of-buffer)
+ (goto-char (point-min))
(loop do (eclim--project-insert-mark-current 'dired-mark)
until (not (forward-line 1)))))
@@ -329,7 +349,7 @@
(defun eclim-project-unmark-all ()
(interactive)
(save-excursion
- (beginning-of-buffer)
+ (goto-char (point-min))
(loop do (eclim--project-remove-mark-current)
until (not (forward-line 1)))))
@@ -337,7 +357,7 @@
(interactive (list (eclim--project-read t)))
(ido-find-file-in-dir
(assoc-default 'path
- (find project (eclim/project-list)
+ (cl-find project (eclim/project-list)
:key (lambda (e) (assoc-default 'name e))
:test #'string=))))
@@ -390,7 +410,7 @@
(use-local-map eclim-project-mode-map)
(cd "~") ;; setting a defualt directoy avoids some problems with tramp
(eclim--project-buffer-refresh)
- (beginning-of-buffer)
+ (goto-char (point-min))
(run-mode-hooks 'eclim-project-mode-hook))
(defalias 'eclim-manage-projects 'eclim-project-mode)
diff --git a/eclim-scala.el b/eclim-scala.el
new file mode 100644
index 0000000..eb33c46
--- /dev/null
+++ b/eclim-scala.el
@@ -0,0 +1,47 @@
+;; eclim-scala.el --- an interface to the Eclipse IDE.
+;;
+;; Copyright (C) 2009 Yves Senn
+;;
+;; 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 .
+;;
+;;; Contributors
+;;
+;; - Shuai Lin
+;;
+;;; Conventions
+;;
+;; Conventions used in this file: Name internal variables and functions
+;; "eclim--", and name eclim command invocations
+;; "eclim/command-name", like eclim/project-list.
+
+;;* Eclim Scala
+
+(require 'eclim-java)
+
+(defun eclim-scala-find-declaration ()
+ "Find and display the declaration of the scala identifier at point."
+ (interactive)
+ (let ((i (eclim--java-identifier-at-point t)))
+ (eclim/with-results hits
+ (
+ "scala_search"
+ "-n"
+ "-f"
+ ("-o" (car i))
+ ("-l" (length (cdr i)))
+ ("-e" "utf-8")
+ )
+ (eclim--find-display-results (cdr i) hits t))))
+
+(provide 'eclim-scala)
diff --git a/eclim.el b/eclim.el
index 76ea3a5..4391767 100644
--- a/eclim.el
+++ b/eclim.el
@@ -120,7 +120,8 @@ in the current workspace."
("utf-8-unix" . "utf-8")
("utf-8-emacs-unix" . "utf-8")))
-(defvar eclim--compressed-urls-regexp "^\\(\\(?:jar\\|file\\|zip\\)://\\)")
+(defvar eclim--compressed-urls-regexp
+ "^\\(\\(?:jar\\|file\\|zip\\):\\(?:file:\\)?//\\)")
(defvar eclim--compressed-file-path-replacement-regexp "\\\\")
(defvar eclim--compressed-file-path-removal-regexp "^/")
@@ -143,7 +144,7 @@ operation, and the rest are flags/values to be passed on to
eclimd."
(when (not eclim-executable)
(error "Eclim installation not found. Please set eclim-executable."))
- (reduce (lambda (a b) (format "%s %s" a b))
+ (cl-reduce (lambda (a b) (format "%s %s" a b))
(append (list eclim-executable "-command" (first args))
(loop for a = (rest args) then (rest (rest a))
for arg = (first a)
@@ -177,13 +178,17 @@ where is the corresponding java name for this encoding." e e)))
(error (match-string 1 result)))
(t (error result)))))))
+(defun eclim--call-process-no-parse (&rest args)
+ "Calls eclim with the supplied arguments but does not attempt to parse the result. "
+ (let ((cmd (eclim--make-command args)))
+ (when eclim-print-debug-messages (message "Executing: %s" cmd))
+ (shell-command-to-string cmd)))
+
(defun eclim--call-process (&rest args)
"Calls eclim with the supplied arguments. Consider using
`eclim/execute-command' instead, as it has argument expansion,
error checking, and some other niceties.."
- (let ((cmd (eclim--make-command args)))
- (when eclim-print-debug-messages (message "Executing: %s" cmd))
- (eclim--parse-result (shell-command-to-string cmd))))
+ (eclim--parse-result (apply 'eclim--call-process-no-parse args)))
(defvar eclim--currently-running-async-calls nil)
@@ -193,7 +198,7 @@ asynchronously. CALLBACK is a function that accepts a list of
strings and will be called on completion."
(lexical-let ((handler callback)
(cmd (eclim--make-command args)))
- (when (not (find cmd eclim--currently-running-async-calls :test #'string=))
+ (when (not (cl-find cmd eclim--currently-running-async-calls :test #'string=))
(lexical-let ((buf (get-buffer-create (generate-new-buffer-name "*eclim-async*"))))
(when eclim-print-debug-messages
(message "Executing: %s" cmd)
@@ -203,25 +208,25 @@ strings and will be called on completion."
(let ((sentinel (lambda (process signal)
(unwind-protect
(save-excursion
- (setq eclim--currently-running-async-calls (remove-if (lambda (x) (string= cmd x)) eclim--currently-running-async-calls))
+ (setq eclim--currently-running-async-calls (cl-remove-if (lambda (x) (string= cmd x)) eclim--currently-running-async-calls))
(set-buffer (process-buffer process))
(funcall handler (eclim--parse-result (buffer-substring 1 (point-max)))))
(kill-buffer buf)))))
(set-process-sentinel proc sentinel)))))))
-(setq eclim--default-args
- '(("-n" . (eclim-project-name))
- ("-p" . (or (eclim-project-name) (error "Could not find eclipse project for %s" (buffer-name (current-buffer)))))
- ("-e" . (eclim--current-encoding))
- ("-f" . (eclim--project-current-file))
- ("-o" . (eclim--byte-offset))
- ("-s" . "project")))
+(defvar eclim--default-args
+ '(("-n" . (eclim-project-name))
+ ("-p" . (or (eclim-project-name) (error "Could not find eclipse project for %s" (buffer-name (current-buffer)))))
+ ("-e" . (eclim--current-encoding))
+ ("-f" . (eclim--project-current-file))
+ ("-o" . (eclim--byte-offset))
+ ("-s" . "project")))
(defun eclim--args-contains (args flags)
"Check if an (unexpanded) ARGS list contains any of the
specified FLAGS."
(loop for f in flags
- return (find f args :test #'string= :key (lambda (a) (if (listp a) (car a) a)))))
+ return (cl-find f args :test #'string= :key (lambda (a) (if (listp a) (car a) a)))))
(defun eclim--expand-args (args)
"Takes a list of command-line arguments with which to call the
@@ -341,12 +346,13 @@ argument PROJECTNAME is given, return that project's root directory."
(defun eclim-project-name (&optional filename)
"Returns this file's project name. If the optional argument
FILENAME is given, return that file's project name instead."
- (labels ((get-project-name (file)
+ (cl-labels ((get-project-name (file)
(eclim/execute-command "project_by_resource" ("-f" file))))
(if filename
(get-project-name filename)
(or eclim--project-name
- (and buffer-file-name (setq eclim--project-name (get-project-name buffer-file-name)))))))
+ (and buffer-file-name (setq eclim--project-name (get-project-name buffer-file-name)))
+ (and buffer-file-name (gethash buffer-file-name eclim-projects-for-archive-file))))))
(defun eclim--find-file (path-to-file)
(if (not (string-match-p "!" path-to-file))
@@ -356,7 +362,7 @@ FILENAME is given, return that file's project name instead."
(archive-name (replace-regexp-in-string eclim--compressed-urls-regexp "" (first parts)))
(file-name (second parts)))
(find-file-other-window archive-name)
- (beginning-of-buffer)
+ (goto-char (point-min))
(re-search-forward (replace-regexp-in-string
eclim--compressed-file-path-removal-regexp ""
(regexp-quote (replace-regexp-in-string
@@ -364,12 +370,26 @@ FILENAME is given, return that file's project name instead."
"/" file-name))))
(let ((old-buffer (current-buffer)))
(archive-extract)
- (beginning-of-buffer)
+ (goto-char (point-min))
(kill-buffer old-buffer)))))
+(defvar eclim-projects-for-archive-file (make-hash-table :test 'equal))
+(defun eclim-java-archive-file (file)
+ (let ((eclim-auto-save nil))
+ (eclim/with-results tmp-file ("archive_read" ("-f" file))
+ ;; archive file's project should be same as current context.
+ (setf (gethash tmp-file eclim-projects-for-archive-file) (eclim-project-name))
+ tmp-file)))
+
(defun eclim--find-display-results (pattern results &optional open-single-file)
- (let ((results (remove-if (lambda (result) (string-match (rx bol (or "jar" "zip") ":") (assoc-default 'filename result))) results)))
- (if (and (= 1 (length results)) open-single-file) (eclim--visit-declaration (elt results 0))
+ (let ((results
+ (loop for result across results
+ for file = (cdr (assoc 'filename result))
+ if (string-match (rx bol (or "jar" "zip") ":") file)
+ do (setf (cdr (assoc 'filename result)) (eclim-java-archive-file file))
+ finally (return results))))
+ (if (and (= 1 (length results)) open-single-file)
+ (eclim--visit-declaration (elt results 0))
(pop-to-buffer (get-buffer-create "*eclim: find"))
(let ((buffer-read-only nil))
(erase-buffer)
@@ -383,19 +403,30 @@ FILENAME is given, return that file's project name instead."
(grep-mode)))))
(defun eclim--format-find-result (line &optional directory)
- (let ((converted-directory (replace-regexp-in-string "\\\\" "/" (assoc-default 'filename line))))
- (format "%s:%d:%d:%s\n"
- (if converted-directory
- (replace-regexp-in-string (concat (regexp-quote directory) "/?") "" converted-directory)
- converted-directory)
- (assoc-default 'line line)
- (assoc-default 'column line)
- (assoc-default 'message line))))
+ (let* ((converted-directory (replace-regexp-in-string "\\\\" "/" (assoc-default 'filename line)))
+ (parts (split-string converted-directory "!"))
+ (filename (replace-regexp-in-string
+ eclim--compressed-urls-regexp "" (first parts)))
+ (filename-in-dir (if directory
+ (replace-regexp-in-string (concat (regexp-quote directory) "/?")
+ "" filename)
+ filename)))
+ (if (cdr parts)
+ ;; Just put the jar path, since there's no easy way to instruct
+ ;; compile-mode to go into an archive. Better than nothing.
+ ;; TODO: revisit when an archive file-handler shows up somewhere.
+ (format "%s:1: %s\n" filename-in-dir (assoc-default 'message line))
+ (format "%s:%d:%d:%s\n"
+ filename-in-dir
+ (assoc-default 'line line)
+ (assoc-default 'column line)
+ (assoc-default 'message line)))))
(defun eclim--visit-declaration (line)
(ring-insert find-tag-marker-ring (point-marker))
(eclim--find-file (assoc-default 'filename line))
- (goto-line (assoc-default 'line line))
+ (goto-char (point-min))
+ (forward-line (1- (assoc-default 'line line)))
(move-to-column (1- (assoc-default 'column line))))
(defun eclim--string-strip (content)
@@ -404,7 +435,9 @@ FILENAME is given, return that file's project name instead."
(defun eclim--project-current-file ()
(or eclim--project-current-file
(setq eclim--project-current-file
- (eclim/execute-command "project_link_resource" ("-f" buffer-file-name)))))
+ (eclim/execute-command "project_link_resource" ("-f" buffer-file-name)))
+ ;; command archive_read will extract archive file to /tmp directory, which is out of current project directory.
+ (and buffer-file-name (gethash buffer-file-name eclim-projects-for-archive-file) buffer-file-name)))
(defun eclim--byte-offset (&optional text)
;; TODO: restricted the ugly newline counting to dos buffers => remove it all the way later
@@ -435,6 +468,26 @@ FILENAME is given, return that file's project name instead."
hits))
t)))
+(defun eclim-find-file-path-strict (filename &optional project directory)
+ "Locates a file (basename) in Eclipse. If PROJECT is a string,
+searches only that project; if nil, the project of the current
+file. If t, searches all Eclipse projects. If DIRECTORY is
+specified, returns only files that are under that
+directory. Returns a list of matching absolute paths; possibly
+empty. This can be used to help resolve exception stack traces,
+for example."
+ (let* ((results (apply #'eclim--call-process "locate_file"
+ "-p" (regexp-quote filename)
+ (if (eq project t)
+ (list "-s" "workspace")
+ (list "-s" "project" "-n"
+ (or project (eclim-project-name))))))
+ (paths (mapcar #'(lambda(hit) (assoc-default 'path hit)) results)))
+ (if directory
+ (cl-remove-if-not #'(lambda (f) (file-in-directory-p f directory)) paths)
+ paths)))
+
+
;;;###autoload
(defun eclim/workspace-dir ()
(eclim--call-process "workspace_dir"))
@@ -470,11 +523,11 @@ FILENAME is given, return that file's project name instead."
(remove-hook 'after-save-hook 'eclim--after-save-hook 't)))
(defcustom eclim-accepted-file-regexps
- '("\\.java" "\\.js" "\\.xml" "\\.rb" "\\.groovy" "\\.php" "\\.c" "\\.cc" "\\.h" "\\.scala")
+ '("\\.java$" "\\.js$" "\\.xml$" "\\.rb$" "\\.groovy$" "\\.php$" "\\.c$" "\\.cc$" "\\.h$" "\\.scala$")
"List of regular expressions that are matched against filenames
to decide if eclim should be automatically started on a
particular file. By default all files part of a project managed
-by eclim can be accepted (see `eclim--accepted-filename' for more
+by eclim can be accepted (see `eclim--accepted-filename-p' for more
information). It is nevertheless possible to restrict eclim to
some files by changing this variable. For example, a value
of (\"\\\\.java\\\\'\" \"build\\\\.xml\\\\'\") can be used to restrict
@@ -485,7 +538,7 @@ the use of eclim to java and ant files."
(defun eclim--accepted-filename-p (filename)
"Return t if and only one of the regular expressions in
`eclim-accepted-file-regexps' matches FILENAME."
- (if (member-if
+ (if (cl-member-if
(lambda (regexp) (string-match regexp filename))
eclim-accepted-file-regexps)
t))
@@ -529,13 +582,16 @@ the use of eclim to java and ant files."
;;;###autoload
(define-globalized-minor-mode global-eclim-mode eclim-mode
(lambda ()
- (if (and buffer-file-name
- (eclim--accepted-p buffer-file-name)
- (eclim--project-dir))
- (eclim-mode 1))))
+ ;; Errors here can REALLY MESS UP AN EMACS SESSION. Can't emphasize enough.
+ (ignore-errors
+ (if (and buffer-file-name
+ (eclim--accepted-p buffer-file-name)
+ (eclim--project-dir))
+ (eclim-mode 1)))))
(require 'eclim-project)
(require 'eclim-java)
+(require 'eclim-scala)
(require 'eclim-ant)
(require 'eclim-maven)
(require 'eclim-problems)
@@ -544,6 +600,6 @@ the use of eclim to java and ant files."
(defun eclim-modeline-string ()
(when eclim-mode
- (concat " Eclim " (eclim-problems-modeline-string))))
+ (concat " Eclim" (eclim-problems-modeline-string))))
(provide 'eclim)
diff --git a/eclimd.el b/eclimd.el
index 824e208..8fb4387 100644
--- a/eclimd.el
+++ b/eclimd.el
@@ -64,6 +64,8 @@ You can freeze emacs until eclimd is ready to accept commands with this variable
(defvar eclimd-process nil
"The active eclimd process")
+(defvar eclimd-port nil)
+
(defconst eclimd-process-buffer-name "eclimd")
(defun eclimd--executable-path ()
diff --git a/emacs-eclim-pkg.el b/emacs-eclim-pkg.el
index 206c071..07be54c 100644
--- a/emacs-eclim-pkg.el
+++ b/emacs-eclim-pkg.el
@@ -3,4 +3,5 @@
'((dash "2.11.0")
(json "1.2")
(popup "0.5.2")
- (s "1.9.0")))
+ (s "1.9.0")
+ (cl-lib "0.5")))
diff --git a/tests/completion-tests.el b/tests/completion-tests.el
new file mode 100644
index 0000000..1626e8a
--- /dev/null
+++ b/tests/completion-tests.el
@@ -0,0 +1,31 @@
+(ert-deftest completion-yasnippet-convert ()
+ ;; Nested params should *not* be nested templates.
+ (should (equal (eclim--completion-yasnippet-convert
+ "addAll(Collection super Object> c, T... elements)")
+ "addAll(${Collection super Object> c}, ${T... elements})"))
+ ;; Corner case: no argument.
+ (should (equal (eclim--completion-yasnippet-convert "toString()")
+ "toString()"))
+
+ ;; Basic cases.
+ (should (equal (eclim--completion-yasnippet-convert
+ "printf(Locale l, String format, Object... args)")
+ "printf(${Locale l}, ${String format}, ${Object... args})"))
+ (should (equal (eclim--completion-yasnippet-convert "HashMap")
+ "HashMap<${K}, ${V}>"))
+
+ )
+
+(ert-deftest completion-insert-empty-usable ()
+ (let ((eclim-insertion-functions '(eclim-completion-insert-empty)))
+ (cl-letf (((symbol-function 'eclim-java-import) #'ignore))
+ (with-temp-buffer
+ (insert "method(String arg1, List arg2) - some.Class")
+ (eclim--completion-action-java (line-beginning-position) (point))
+ (should (equal (thing-at-point 'line) "method()"))
+ (should (looking-at ")"))
+ (erase-buffer)
+ (insert "method2()")
+ (should (equal (thing-at-point 'line) "method2()"))
+ (should (eolp))
+ ))))