├── README.md └── multi-compile.el /README.md: -------------------------------------------------------------------------------- 1 | [![MELPA](https://melpa.org/packages/multi-compile-badge.svg)](https://melpa.org/#/multi-compile) 2 | 3 | # multi-compile 4 | Multi target interface to compile. 5 | 6 | ## Screenshot 7 | 8 | ![multi-target](https://cloud.githubusercontent.com/assets/1151286/10209424/de607546-67e3-11e5-8cb0-f50d390e823b.png) 9 | 10 | ## Installation 11 | 12 | You can install `multi-compile.el` from [MELPA](https://melpa.org/) with `package.el` 13 | 14 | ``` 15 | M-x package-install RET multi-compile RET 16 | ``` 17 | 18 | Or drop `multi-compile.el` and [`dash.el`](https://github.com/magnars/dash.el) into your load path. 19 | 20 | ## Common settings format: 21 | ```lisp 22 | (setq multi-compile-alist '( 23 | (Trigger1 . (("menu item 1" . "command 1") 24 | ("menu item 2" . "command 2") 25 | ("menu item 3" . "command 3"))) 26 | 27 | (Trigger2 . (("menu item 4" "command 4" (expression returns a directory for the compilation)) 28 | ("menu item 5" "command 5" (expression returns a directory for the compilation)))) 29 | ... 30 | )) 31 | ``` 32 | 33 | In addition to the common string command, there is also an extended syntax that can be used for long or complicated commands. 34 | If the command consists of a list, the elements of the list are expected to be strings and will be joined with a space. 35 | This is a convenience syntax that may be desirable to avoid extremely long command strings. 36 | For instance, if the command consists of long paths or many options, it may be desirable that each path or option exist on its own line. 37 | This extended command syntax is supported both with and without the optional compilation directory expression. 38 | 39 | ```lisp 40 | (setq multi-compile-alist '( 41 | (Trigger1 . (("menu item 1" . "command 1") 42 | ("menu item 2" ("application 2" 43 | "option 1" 44 | "option 2" 45 | ...)) 46 | ("menu item 3" . "command 3"))) 47 | 48 | (Trigger2 . (("menu item 4" "command 4" (expression returns a directory for the compilation)) 49 | ("menu item 5" ("application 5" 50 | "option 1" 51 | "option 2" 52 | ...) 53 | (expression returns a directory for the compilation)))) 54 | ... 55 | )) 56 | ``` 57 | 58 | ### Triggers: 59 | 60 | - Can be major-mode: 61 | 62 | Example adds 3 a menu item for rust-mode: 63 | ```lisp 64 | (setq multi-compile-alist '( 65 | (rust-mode . (("rust-debug" . "cargo run") 66 | ("rust-release" . "cargo run --release") 67 | ("rust-test" . "cargo test"))) 68 | ... 69 | )) 70 | ``` 71 | 72 | - Can be file/buffer name pattern: 73 | 74 | Adds menu item "print-filename" for filename ends "txt": 75 | ```lisp 76 | (setq multi-compile-alist '( 77 | ("\\.txt\\'" . (("print-filename" . "echo %file-name"))) 78 | ... 79 | )) 80 | ``` 81 | menu item "print-hello" for scratch buffer: 82 | ```lisp 83 | (setq multi-compile-alist '( 84 | ("*scratch*" . (("print-hello" . "echo 'hello'"))) 85 | ... 86 | )) 87 | ``` 88 | adds "item-for-all" for any file or buffer: 89 | ```lisp 90 | (setq multi-compile-alist '( 91 | ("\\.*" . (("item-for-all" . "echo 'I am item for all'"))) 92 | ... 93 | )) 94 | ``` 95 | 96 | - Can by expression returns t or nil: 97 | 98 | adds "item-for-home" for any file in "/home/" directory: 99 | ```lisp 100 | (defun string/starts-with (string prefix) 101 | "Return t if STRING starts with prefix." 102 | (and (stringp string) (string-match (rx-to-string `(: bos ,prefix) t) string))) 103 | 104 | (setq multi-compile-alist '( 105 | ((string/starts-with buffer-file-name "/home/") . (("item-for-home" . "echo %file-name"))) 106 | ... 107 | )) 108 | ``` 109 | ### Commands: 110 | String with command for "compile" package. 111 | In a compilation commands, you can use the templates: 112 | 113 | - "%path" - "/tmp/prj/file.rs" (full path) 114 | - "%dir" - "/tmp/prj/" 115 | - "%file-name" - "file.rs" 116 | - "%file-sans" - "file" 117 | - "%file-ext" - "rs" 118 | - "%make-dir" - (Look up the directory hierarchy from current file for a directory containing "Makefile") - "/tmp/" 119 | 120 | ### Advanced Commands: 121 | In their basic form, template variables correspond to expressions that are evaluated and their results substituted into the compile command. 122 | These basic template variables exist in the expansion list contained in `multi-compile-template`, but can be customized as desired. 123 | This means they can be replaced or additional template variables added to the default set. 124 | In addition to expressions, template variables can also correspond to prompts and functions. 125 | To distinguish between these different types of template variables, the following terms will be used when distinction is important: 126 | - Expression template variable 127 | - Prompt template variable 128 | - Function template variable 129 | 130 | There are two different types of prompts supported, a string prompt and a choice prompt. 131 | Prompts utilize a descriptive prompt string to display to the user as well as allowing for either free-form input 132 | (i.e., string prompt) or selecting from a list of possible choices (i.e., choice prompt). 133 | Prompt template variables may appear in the command string multiple times, but the user will only be prompted once, and each 134 | instance of the prompt template variable will be replaced with the user input. 135 | 136 | In addition, function template variables are also supported which can be used to perform arbitrary transformation of the text. 137 | They can take zero or more arguments and can further manipulate the text of the compile command. 138 | It may not be obvious, but template variable strings are actually regular expressions. 139 | In their basic form, no regular expression syntax is needed and the literal characters of the variable are matched. 140 | However, when used to represent functions, sub-expressions of matches of the function template variable are interpreted as the parameters for the function. 141 | Furthermore, function template variables can be nested so that they can be stacked and applied as desired. 142 | These represent advanced features of the template variable structure which can support building of complex compilation commands when necessary. 143 | 144 | The following are examples which demonstrate the advanced prompt template variables and function template variables capability. 145 | These can easily be experimented with to gain further understanding on how to apply them for your own needs. 146 | These examples are structured so that they can be placed into the \*scratch\* buffer and evaluated and then `M-x multi-compile-run` can be executed without affecting your global settings. 147 | Once you become familiar with the syntax and power of these advanced capabilities, you can easily incorporate them into your own configuration. 148 | 149 | The more advanced and complicated the interaction of the function template variables become, the easier it is to start having problems due to order of expansion. 150 | These examples use '[' and ']' to surround the function parameters, but this is only shown for convention and a different approach could be used. 151 | Furthermore, the sub-expressions which are defined for the function parameters don't allow a '[' to exist in the sub-expression and also use the non-greedy (i.e., '?') operator. 152 | This is to make sure that inner nested functions are expanded prior to outer functions (by preventing a match when unexpanded functions exist). 153 | Function expansion is attempted after all expression template variables and prompt template variables have been expanded. 154 | The reason for this is that expressions and prompts can be expanded in a single pass, however functions may require multiple passes to fully expand, due to potential nesting. 155 | 156 | To help in making sure the function template variables receive the type of parameters that you expect, it is suggested to perform checks on the input parameters. 157 | This is not required, but can be helpful to isolate problems of expansion order when initially incorporating this functionality. 158 | The following examples use `cl-assert` to perform the input validation to make sure the functions are receiving the data that was expected. 159 | 160 | The way in which the type of a template variable is determined is based on the content in `multi-compile-template`. 161 | The content of the `multi-compile-template` corresponds to an association list. 162 | The template variable string representation corresponds to the key of the association list. 163 | The associated value of the association list is used to determine the type of the template variable. 164 | - Prompt template variable 165 | - If the associated value is a string, the variable is interpreted as a string prompt variant of the prompt template variable. 166 | The string is used as the prompt displayed to the user for entering of the free-form text. 167 | - If the associated value is a cons cell with the `car` of the cons cell being a string, then the variable is interpreted as a choice prompt variant of the prompt template variable. 168 | The string is used as the prompt displayed to the user for choosing from the set of values. 169 | The `cdr` of the cons cell is used to represent the list of possible values to choose from. 170 | The configured completion system (as determined by `multi-compile-completion-system`) will be used to prompt the user to make a selection. 171 | - Function template variable 172 | - If the associated value is a function, the variable is interpreted as a function template variable. 173 | It is expected that the supplied function parameters align with the sub-expressions found in the template variable string. 174 | - Expression template variable 175 | - If the associated value does not match any of the previous types, it is evaluated as an expression. 176 | 177 | ```lisp 178 | (setq-local multi-compile-alist 179 | '(("*scratch*" . (("Favorite Color" . "echo 'I like %favorite-color too, in fact %uc[%favorite-color] is also my favorite!'") 180 | ("Choose Color" . "echo 'That's too bad, I don't like %uc[%choose-color], I like %uc[%other-color[%other-color[%choose-color]]] instead!'") 181 | ("Add Numbers" . "echo '%enter-first-number + %enter-second-number = %add[%enter-first-number,%enter-second-number]'") 182 | )))) 183 | 184 | (setq-local multi-compile-template 185 | '(;; string prompt example to enter free-form text 186 | ("%favorite-color" . "What's your favorite color?: ") 187 | ;; choice prompt example to select from a set of options 188 | ("%choose-color" . ("Choose one of these colors: " . ("Red" "Green" "Blue"))) 189 | ;; function example to uppercase the matched sub-expression 190 | ("%uc\\[\\([^\\[]+?\\)]" . upcase) 191 | ;; function example to perform a computation with the matched sub-expression 192 | ("%other-color\\[\\([^\\[]+?\\)]" . (lambda (color) 193 | (cl-assert (member color '("Red" "Green" "Blue"))) 194 | (cond ((string= color "Red") "Green") 195 | ((string= color "Green") "Blue") 196 | ((string= color "Blue") "Red")))) 197 | ;; function example utilizing two parameters 198 | ("%enter-first-number" . "Enter first number: ") 199 | ("%enter-second-number" . "Enter second number: ") 200 | ("%add\\[\\(.+?\\),\\(.+?\\)]". (lambda (first second) 201 | (cl-assert (string-match "^[0-9]+\\(\\.[0-9]+\\)?\\'" first)) 202 | (cl-assert (string-match "^[0-9]+\\(\\.[0-9]+\\)?\\'" second)) 203 | (number-to-string (+ (string-to-number first) 204 | (string-to-number second))))))) 205 | ``` 206 | 207 | ### Compilation directory: 208 | Default compilation directory is a system variable default-directory. 209 | If you want to change it, add an expression that returns the correct directory. 210 | 211 | For example, add a menu to compile go project under git: 212 | ```lisp 213 | (setq multi-compile-alist '( 214 | (go-mode . (("go-build" "go build -v" 215 | (locate-dominating-file buffer-file-name ".git")) 216 | ("go-build-and-run" "go build -v && echo 'build finish' && eval ./${PWD##*/}" 217 | (multi-compile-locate-file-dir ".git")))) 218 | ... 219 | )) 220 | ``` 221 | Where: 222 | 223 | - "${PWD##*/}" returns current directory name. 224 | - (locate-dominating-file buffer-file-name ".git") - look up the directory hierarchy from current file for a directory containing directory ".git" 225 | - (multi-compile-locate-file-dir ".git") this is equivalent to (locate-dominating-file buffer-file-name ".git") 226 | 227 | ## Usage 228 | 229 | - M-x multi-compile-run 230 | 231 | ## Links 232 | 233 | [In Russian](http://reangdblog.blogspot.com/2015/10/emacs-multi-compile.html) 234 | 235 | [In Russian (Part 2)](http://reangdblog.blogspot.com/2016/02/emacs-multi-compile.html) 236 | -------------------------------------------------------------------------------- /multi-compile.el: -------------------------------------------------------------------------------- 1 | ;;; multi-compile.el --- Multi target interface to compile. -*- lexical-binding: t; -*- 2 | ;; 3 | ;; Copyright (C) 2015-2016 Kvashnin Vladimir 4 | ;; 5 | ;; Author: Kvashnin Vladimir 6 | ;; Created: 2015-10-01 7 | ;; Version: 0.6.0 8 | ;; Package-Version: 20160215.1219 9 | ;; Keywords: tools compile build 10 | ;; URL: https://github.com/ReanGD/emacs-multi-compile 11 | ;; Package-Requires: ((emacs "24.4") (dash "2.12.1")) 12 | ;; 13 | ;; This file is not part of GNU Emacs. 14 | ;; 15 | ;; This program is free software; you can redistribute it and/or modify 16 | ;; it under the terms of the GNU General Public License as published by 17 | ;; the Free Software Foundation; either version 3, or (at your option) 18 | ;; any later version. 19 | ;; 20 | ;; This program is distributed in the hope that it will be useful, 21 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 22 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 23 | ;; GNU General Public License for more details. 24 | ;; 25 | ;; You should have received a copy of the GNU General Public License 26 | ;; along with GNU Emacs; see the file COPYING. If not, write to the 27 | ;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 28 | ;; Boston, MA 02110-1301, USA. 29 | ;; 30 | ;;; Commentary: 31 | ;; "Multi-compile" is multi target interface to "compile" command. 32 | ;; 33 | ;; Setup 34 | ;; ---- 35 | ;; M-x package-install multi-compile 36 | ;; 37 | ;; Configure 38 | ;; ---- 39 | ;; 40 | ;; Sample config for Rustlang: 41 | ;; ;;; init.el --- user init file 42 | ;; (require 'multi-compile) 43 | ;; (setq multi-compile-alist '( 44 | ;; (rust-mode . (("rust-debug" . "cargo run") 45 | ;; ("rust-release" . "cargo run --release") 46 | ;; ("rust-test" . "cargo test"))) 47 | ;; )) 48 | ;; 49 | ;; In a compilation commands, you can use the templates: 50 | ;; - "%path" - "/tmp/prj/file.rs" 51 | ;; - "%dir" - "/tmp/prj/" 52 | ;; - "%file-name" - "file.rs" 53 | ;; - "%file-sans" - "file" 54 | ;; - "%file-ext" - "rs" 55 | ;; - "%make-dir" - (Look up the directory hierarchy from current file for a directory containing "Makefile") - "/tmp/" 56 | ;; 57 | ;; for example, add a make compilation (with a template "make-dir"): 58 | ;; (setq multi-compile-alist '( 59 | ;; (c++-mode . (("cpp-run" . "make --no-print-directory -C %make-dir"))) 60 | ;; (rust-mode . (("rust-debug" . "cargo run") 61 | ;; ("rust-release" . "cargo run --release") 62 | ;; ("rust-test" . "cargo test"))) 63 | ;; )) 64 | ;; 65 | ;; You can use filename pattern: 66 | ;; (setq multi-compile-alist '( 67 | ;; ("\\.txt\\'" . (("text-filename" . "echo %file-name"))))) 68 | ;; 69 | ;; Or add a pattern for all files: 70 | ;; (setq multi-compile-alist '( 71 | ;; ("\\.*" . (("any-file-command" . "echo %file-name"))))) 72 | ;; 73 | ;; You can use different backends for the menu: 74 | ;; (setq multi-compile-completion-system 'auto) 75 | ;; or 76 | ;; (setq multi-compile-completion-system 'ido) 77 | ;; or 78 | ;; (setq multi-compile-completion-system 'helm) 79 | ;; or 80 | ;; (setq multi-compile-completion-system 'ivy) 81 | ;; or 82 | ;; (setq multi-compile-completion-system 'default) 83 | ;; 84 | ;; Usage 85 | ;; ---- 86 | ;; - Open *.rs file 87 | ;; - M-x multi-compile-run 88 | ;; 89 | ;; For a detailed introduction see: 90 | ;; https://github.com/ReanGD/emacs-multi-compile/blob/master/README.md 91 | ;; 92 | ;;; Code: 93 | (require 'dash) 94 | (require 'compile) 95 | 96 | (eval-when-compile 97 | ;; cl-loop 98 | (require 'cl-lib) 99 | ;; string-join 100 | (require 'subr-x)) 101 | 102 | (defgroup multi-compile nil 103 | "Multi target interface to `compile'." 104 | :link '(url-link "https://github.com/ReanGD/multi-compile") 105 | :prefix "multi-compile-" 106 | :group 'processes) 107 | 108 | (defcustom multi-compile-alist 109 | '( 110 | (rust-mode . (("rust-debug" . "cargo run") 111 | ("rust-release" . "cargo run --release") 112 | ("rust-test" . "cargo test"))) 113 | (c++-mode . (("cpp-run" . "make --no-print-directory -C %make-dir"))) 114 | ) 115 | "Alist of filename patterns vs corresponding format control strings." 116 | :type '(repeat 117 | (cons 118 | (choice :tag "Key" 119 | (regexp :tag "Filename or buffer pattern") 120 | (function :tag "Major-mode") 121 | (sexp :tag "Expression") 122 | ) 123 | (repeat :tag "Settings" 124 | (choice :tag "Type" 125 | (cons :tag "Default compilation directory" 126 | (string :tag "Menu item") 127 | (string :tag "Command")) 128 | (list :tag "Set compilation directory" 129 | (string :tag "Menu item") 130 | (string :tag "Command") 131 | (sexp :tag "Expression returns a compilation root")) 132 | )))) 133 | :group 'multi-compile) 134 | 135 | (defcustom multi-compile-default-directory-function #'ignore 136 | "A function whose result can set the default-directory for a compile target." 137 | :type 'function 138 | :group 'multi-compile) 139 | 140 | (defcustom multi-compile-template 141 | '( 142 | ("%path" . (buffer-file-name)) 143 | ("%dir" . (file-name-directory (buffer-file-name))) 144 | ("%file-name" . (file-name-nondirectory (buffer-file-name))) 145 | ("%file-sans" . (file-name-sans-extension (file-name-nondirectory (buffer-file-name)))) 146 | ("%file-ext" . (file-name-extension (file-name-nondirectory (buffer-file-name)))) 147 | ("%make-dir" . (locate-dominating-file (buffer-file-name) "Makefile")) 148 | ) 149 | "Default expansion list." 150 | :type '(alist :key-type string :value-type sexp) 151 | :group 'multi-compile) 152 | 153 | (defcustom multi-compile-completion-system 'auto 154 | "The completion system to be used by multi-compile." 155 | :type '(radio 156 | (const :tag "Auto-detect" auto) 157 | (const :tag "Ido" ido) 158 | (const :tag "Ivy" ivy) 159 | (const :tag "Helm" helm) 160 | (const :tag "Default" default) 161 | (function :tag "Custom function")) 162 | :group 'multi-compile) 163 | 164 | (defcustom multi-compile-history '() 165 | "Operations history ." 166 | :type 'list 167 | :group 'multi-compile) 168 | 169 | (defcustom multi-compile-history-length 50 170 | "The maximum size of the history." 171 | :type 'integer 172 | :group 'multi-compile) 173 | 174 | (defcustom multi-compile-history-file 175 | (expand-file-name "multi-compile.cache" user-emacs-directory) 176 | "The name of multi-compile cache file." 177 | :type 'string 178 | :group 'multi-compile) 179 | 180 | (defun multi-compile--add-to-history (item) 181 | "Add ITEM to history and save history to file." 182 | (setq multi-compile-history 183 | (-take multi-compile-history-length 184 | (cons item 185 | (-remove-item item multi-compile-history)))) 186 | (when (file-writable-p multi-compile-history-file) 187 | (with-temp-file multi-compile-history-file 188 | (insert (let (print-length) (prin1-to-string multi-compile-history))))) 189 | item) 190 | 191 | (defun multi-compile--load-hostory () 192 | "Load history from file." 193 | (with-demoted-errors 194 | "Error during file deserialization: %S" 195 | (when (file-exists-p multi-compile-history-file) 196 | (with-temp-buffer 197 | (insert-file-contents multi-compile-history-file) 198 | (setq multi-compile-history (read (buffer-string))))))) 199 | 200 | (defun multi-compile--choose (prompt choices) 201 | "Ask user to select one of the CHOICES using PROMPT." 202 | (pcase (if (eq multi-compile-completion-system 'auto) 203 | (cond 204 | ((bound-and-true-p ido-mode) 'ido) 205 | ((bound-and-true-p ivy-mode) 'ivy) 206 | ((bound-and-true-p helm-mode) 'helm) 207 | (t 'default)) 208 | multi-compile-completion-system) 209 | ('ido (ido-completing-read prompt choices)) 210 | ('ivy (if (and (fboundp 'ivy-read) 211 | (fboundp 'ivy-thing-at-point)) 212 | (ivy-read prompt choices 213 | :preselect (ivy-thing-at-point)) 214 | (user-error "Please install ivy"))) 215 | ('helm (if (fboundp 'helm-comp-read) 216 | (helm-comp-read prompt choices 217 | :candidates-in-buffer t 218 | :must-match 'confirm) 219 | (user-error "Please install helm"))) 220 | ('default (completing-read prompt choices)) 221 | (_ (funcall multi-compile-completion-system prompt choices)))) 222 | 223 | (defun multi-compile--fill-template (format-string) 224 | "Apply multi-compile-template to FORMAT-STRING." 225 | (dolist (template multi-compile-template) 226 | (let* ((variable (car template)) 227 | (action (cdr template)) 228 | (action-type 229 | (cond 230 | ((stringp action) 'string-prompt) 231 | ((stringp (car-safe action)) 'choice-prompt) 232 | ((functionp action) 'function) 233 | (t 'expression))) 234 | (expanded) 235 | (new-text)) 236 | (unless (eq action-type 'function) 237 | (while (string-match variable format-string) 238 | ;; Only perform the variable expansion once for user prompts 239 | ;; and then replace subsequent occurrences with the expansion. 240 | ;; This avoids prompting the user multiple times for the same 241 | ;; data if the expansion variable exists multiple times in the 242 | ;; format string. 243 | ;; 244 | ;; Expressions are expanded each time they are encountered since 245 | ;; it is possible that the evaluation of expressions could have 246 | ;; side-effects which could effect the result (such as the use 247 | ;; of `random'). 248 | (when (or (not expanded) 249 | (eq action-type 'expression)) 250 | (setq expanded t) 251 | (setq new-text 252 | (save-match-data 253 | (pcase action-type 254 | ('string-prompt (read-string action)) 255 | ('choice-prompt (multi-compile--choose (car action) (cdr action))) 256 | ('expression (eval action)))))) 257 | (setq format-string 258 | (replace-match 259 | (if new-text new-text 260 | (concat "not-found-" (substring variable 1))) 261 | t nil format-string)))))) 262 | 263 | ;; Perform all of the function expansions after all other variable type expansions have 264 | ;; occurred. This is done separately since functions can be nested and thus we may need to 265 | ;; repeatedly expand functions from the innermost to the outermost. The expectation 266 | ;; is that the regex for the sub-expressions of an outer function will be defined in such 267 | ;; a way as to prevent matching when an unexpanded function exists within that outer function 268 | ;; (e.g., if brackets are used to delimit function parameters, don't allow a bracket in a 269 | ;; sub-expression). 270 | ;; 271 | ;; We keep repeating the loop looking for additional functions which match, so that after 272 | ;; an inner function has been expanded, the outer function would subsequently match in the 273 | ;; next iteration of the loop and then be expanded. We keep repeating this as long as at 274 | ;; least one expansion occurs in a loop iteration. 275 | (cl-loop 276 | with function-expanded 277 | do 278 | (setq function-expanded nil) 279 | (dolist (template multi-compile-template) 280 | (let* ((variable (car template)) 281 | (action (cdr template)) 282 | (new-text)) 283 | (when (functionp action) 284 | (while (string-match variable format-string) 285 | (setq function-expanded t) 286 | (setq new-text 287 | (save-match-data 288 | (let ((subexpression-count (- (/ (length (match-data)) 2) 1))) 289 | (apply action 290 | (cl-loop 291 | for i from 1 to subexpression-count 292 | collect (match-string-no-properties i format-string)))))) 293 | ;; Make sure applied function returned a string for insertion 294 | (unless (stringp new-text) 295 | (user-error "Applied function did not return a string")) 296 | (setq format-string (replace-match new-text t nil format-string)))))) 297 | until (not function-expanded)) 298 | 299 | format-string) 300 | 301 | (defun multi-compile--check-mode (mode-pattern filename) 302 | "Check that the MODE-PATTERN and the FILENAME match." 303 | (or 304 | (and (symbolp mode-pattern) 305 | (eq mode-pattern major-mode)) 306 | (and (listp mode-pattern) 307 | (eval-expression mode-pattern)) 308 | (and (stringp mode-pattern) 309 | (string-match mode-pattern filename)))) 310 | 311 | (defun multi-compile--fill-command-list (filename) 312 | "Fill command list from settings for the FILENAME." 313 | (let ((command-list '())) 314 | (dolist (mode-item multi-compile-alist) 315 | (if (multi-compile--check-mode (car mode-item) filename) 316 | (setq command-list 317 | (append command-list (cdr mode-item))) 318 | ) 319 | ) 320 | command-list)) 321 | 322 | (defun multi-compile--choice-compile-command (compile-list) 323 | "Choice compile command from COMPILE-LIST." 324 | (if (= 1 (length compile-list)) 325 | (cdar compile-list) 326 | (let* ((prompt "action: ") 327 | (keys (mapcar #'car compile-list)) 328 | (choices (-union (-intersection multi-compile-history keys) keys))) 329 | (cdr 330 | (assoc 331 | (multi-compile--add-to-history (multi-compile--choose prompt choices)) 332 | compile-list))))) 333 | 334 | (defun multi-compile--get-command-template () 335 | "Get command pattern selected by the user." 336 | (let ((filename (if (stringp buffer-file-name) buffer-file-name (buffer-name)))) 337 | (if (not filename) 338 | (read-string "Compile command: ") 339 | (let ((command-list (multi-compile--fill-command-list filename))) 340 | (if command-list 341 | (multi-compile--choice-compile-command command-list) 342 | (read-string "Compile command: ")))))) 343 | 344 | ;;;###autoload 345 | (defun multi-compile-locate-file-dir (name) 346 | "Look up the directory hierarchy from current file for a directory containing file NAME." 347 | (locate-dominating-file (buffer-file-name) name)) 348 | 349 | ;;;###autoload 350 | (defun multi-compile-run () 351 | "Choice target and start compile." 352 | (interactive) 353 | (let* ((template (multi-compile--get-command-template)) 354 | (command (or (car-safe template) template)) 355 | ;; The command may be either a string or a list of strings to be joined by a space. 356 | ;; The canonical form is just a string where any joining has already been performed. 357 | (canonical-command (string-join (-flatten command) " ")) 358 | (default-directory (or (and (listp template) 359 | (> (length template) 1) 360 | (eval-expression (cadr template))) 361 | (and multi-compile-default-directory-function 362 | (funcall multi-compile-default-directory-function)) 363 | default-directory))) 364 | (compile 365 | (multi-compile--fill-template canonical-command)))) 366 | 367 | (multi-compile--load-hostory) 368 | 369 | (provide 'multi-compile) 370 | 371 | ;;; multi-compile.el ends here 372 | --------------------------------------------------------------------------------