1

I use one Lisp function to compile the current Java file and another to run it. Both functions use the same functionality to get the package name and the appropriate directory to compile/run from (this functionality was posted as an answer to another question). I would like to modularize the common functionality of these functions, i.e. from (let* to but not including (cd. How can I do this?

As bonus I would like to undo the changing of directory (the cd:ing) as the functions are done to avoid unexpected behaviour such as finding files (C-x C-f) starting in the parten directory. It has been suggested that this can be achieved by unwind-protect.

(add-hook 'java-mode-hook
      (lambda ()

    (defun java-compile-current-file ()
      "Compiles the current file with javac"
      (interactive)
      (let* ((package (save-excursion
                (goto-char (point-min))
                (when (re-search-forward "^\\s *package\\s +\\(.*\\);" (point-max) t)
                  (match-string 1))))
         (directory (file-name-directory (buffer-file-name)))
         sub-dirs)

        (if directory
        (setq directory (file-truename directory))
          (error "Current buffer is not visiting a file"))

        (when package
          (setq sub-dirs (reverse (split-string package "\\.")))

          (while sub-dirs
        (if (string-match (concat "^\\(.*/\\)" (regexp-quote (car sub-dirs)) "/$") directory)
            (setq directory (match-string 1 directory)
              sub-dirs (cdr sub-dirs))
          (error "Package does not match directory structure"))))

        (cd directory)
        (compile
         (concat "javac -Xlint:all " ; Tog bort -Werror från
                    ; argumenten. För
                    ; gnälligt!
             (if package (concat package "/") "")
             (file-name-nondirectory (buffer-file-name))))))

    (local-set-key [(f8)] 'java-compile-current-file)

    ;; https://stackoverflow.com/a/12548762/789593
    (defun java-run-current-file ()
      "Runs the java program the current file corresponds to"
      (interactive)
      (let* ((package (save-excursion
                (goto-char (point-min))
                (when (re-search-forward "^\\s *package\\s +\\(.*\\);" (point-max) t)
                  (match-string 1))))
         (directory (file-name-directory (buffer-file-name)))
         sub-dirs)

        (if directory
        (setq directory (file-truename directory))
          (error "Current buffer is not visiting a file"))

        (when package
          (setq sub-dirs (reverse (split-string package "\\.")))

          (while sub-dirs
        (if (string-match (concat "^\\(.*/\\)" (regexp-quote (car sub-dirs)) "/$") directory)
            (setq directory (match-string 1 directory)
              sub-dirs (cdr sub-dirs))
          (error "Package does not match directory structure"))))

        (cd directory)
        (shell-command
         (concat "java "
             (if package (concat package ".") "")
             (file-name-sans-extension
              (file-name-nondirectory (buffer-file-name)))))))

    (local-set-key [(f7)] 'java-run-current-file)))
Community
  • 1
  • 1
N.N.
  • 8,336
  • 12
  • 54
  • 94
  • 1
    If you're adding your `(defun...)` to java-mode-hook like you do, it means that you're (re-)defining `java-compile-current-file` with the same body every time you visit a .java file. That's probably not what you want. – Thomas Oct 15 '12 at 08:57
  • @Thomas I should rather go with [`eval-after-load`](http://stackoverflow.com/a/2736153/789593) for `defun`s? – N.N. Oct 15 '12 at 09:01
  • @wvxvw I think I have fixed the parenthesis issue now. Thanks for the heads up on the hook and mode-map, would I then write e.g. `(define-key java-mode-map 'java-run-current-file)`? About `compile`, I already use it as you can see if you study the latter part of `java-compile-current-file`. Finally, have you got any good reference for using SCons with Java and Emacs? – N.N. Oct 15 '12 at 14:28
  • @N.N. Using `eval-after-load` is certainly better - but since you've got lots of code already, you would probably put all of that in a separate file and only have a `(load-file...)` call inside `eval-after-load`. Another possibility is to write your own "nn-java-mode" as a [derived mode](http://www.gnu.org/software/emacs/manual/html_node/elisp/Derived-Modes.html) that inherits from `java-mode`. – Thomas Oct 15 '12 at 23:57

1 Answers1

1

Here is a crude, untested, refactoring of the code. It's not particularly elegant or idiomatic, but should at least be enough to get you started.

;; 1. Refactored `java-get-package' into its own `defun'
;; 2. Refactored `java-package-directory' into its own `defun'
;; 3. Broke out `defun's outside of `java-mode-hook'
;; 4. Use (let ((default-directory ...) ...) instead of (cd ...) (...)
;; 5. Broke out keystroke assignments into `my-java-mode-hook'

(defun java-get-package ()
  "Get package definition before point"
  (interactive)
  (save-excursion
    (save-match-data  ;; Added; you want this too
      (goto-char (point-min))
      (when (re-search-forward "^\\s *package\\s +\\(.*\\);"
                               (point-max) t)
        (match-string 1)) )) )

(defun java-package-directory (package)
  "Get package directory of PACKAGE in current buffer"
  (let ((directory (file-name-directory (buffer-file-name)))
        sub-dirs)
    (if directory
        (setq directory (file-truename directory))
      (error "Current buffer is not visiting a file"))
    (save-match-data
      (setq sub-dirs (reverse (split-string package "\\.")))
      (while sub-dirs
        (if (string-match (concat "^\\(.*/\\)"
                                  (regexp-quote (car sub-dirs)) "/$")
                          directory)
            (setq directory (match-string 1 directory)
                  sub-dirs (cdr sub-dirs))
          (error "Package does not match directory structure") ) ) )
    directory) )

(defun java-compile-current-file ()
 "Compiles the current file with javac"
 (interactive)
 (let* ((package (java-get-package))
        (default-directory (java-package-directory package)) )
   (compile
    (concat "javac -Xlint:all " ; Removed too noisy -Werror
        (if package (concat package "/") "")
        (file-name-nondirectory (buffer-file-name)) ) ) ))

(defun java-run-current-file ()
 "Runs the java program the current file corresponds to"
 (interactive)
 (let* ((package (java-get-package))
        (default-directory (java-package-directory package)) )
   (shell-command
    (concat "java "
        (if package (concat package ".") "")
        (file-name-sans-extension
         (file-name-nondirectory (buffer-file-name)) ) )) ))

(defun my-java-mode-hook () "Stuff to run when entering Java mode"
    (local-set-key [(f7)] 'java-run-current-file)
    (local-set-key [(f8)] 'java-compile-current-file) )

(add-hook 'java-mode-hook #'my-java-mode-hook)

The new functions should probably accept a buffer argument so that you can call them on a different buffer than the one you're in, for general usability and adherence to common Emacs conventions. But again, hope this is enough to get you started.

tripleee
  • 175,061
  • 34
  • 275
  • 318
  • The HTML comment I added [is what controls syntax highlighting](http://meta.stackexchange.com/a/75019/163868). The default diff view is bad for showing addition of such comments. See the diff view "Show the markdown source diffs side-by-side" (the third icon in the vertical row) for a better view. – N.N. Oct 16 '12 at 12:12
  • Did substantial additional refactoring; removing some of my comments above, and marking some of yours as obsolete. – tripleee Oct 16 '12 at 12:24
  • `java-package-directory` throws "split-string: Wrong type argument: stringp, nil". Also, maybe your answer could be even better if you used `unwind-protect` to get back to the current directory when the functions are done, otherwise they will e.g. affect `C-x C-f` – N.N. Oct 16 '12 at 18:26
  • The `(let ((default-directory ...) ...)` takes care of temporarily switching to a different directory. I don't see what `unwind-protect` would add here. – tripleee Oct 17 '12 at 07:41
  • I think I have fixed bug in which running a file in a subpackage failed. Here is a fixed [java-run-current-file](http://pastebin.com/nRcg06dG). Feel free to incorporate it in your answer. – N.N. Mar 19 '13 at 15:57