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 c, T... elements)") + "addAll(${Collection 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)) + ))))