1 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
3 ;; eproject-config.el --- project workspaces for emacs --- UI part
5 ;; Copyright (C) 2008-2010 grischka
7 ;; Author: grischka -- grischka@users.sourceforge.net
8 ;; Created: 24 Jan 2008
11 ;; This program is free software, released under the GNU General
12 ;; Public License (GPL, version 2). For details see:
14 ;; http://www.fsf.org/licenses/gpl.html
16 ;; This program is distributed in the hope that it will be useful,
17 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
18 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
19 ;; General Public License for more details.
21 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
24 (defvar prj-buffer nil
)
26 (defvar prj-browse-map nil
)
28 (defvar prj-hilight-bar nil
)
29 (defvar prj-hilight-bar-2 nil
)
31 (defvar prj-edit-mode nil
)
35 (defvar prj-active-group nil
)
36 (defvar prj-group-top nil
)
37 (defvar prj-group-left nil
)
38 (defvar prj-group-tab nil
)
44 (defvar prj-qs-face nil
)
45 (defvar prj-qs-str nil
)
46 (defvar prj-qs-len nil
)
47 (defvar prj-qs-pos nil
)
57 (declare-function prj-setconfig
"eproject")
58 (declare-function prj-getconfig
"eproject")
59 (declare-function prj-setup-all
"eproject")
60 (declare-function prj-remove-file
"eproject")
61 (declare-function caddr
"eproject")
63 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
66 `(plist-get ,(car e
) ',(cdr e
))
69 `(plist-put ,(car e
) ',(cdr e
) ,v
)
71 (defmacro p-call
(e &rest args
)
72 `(funcall (plist-get ,(car e
) ',(cdr e
)) ,@args
)
75 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
76 ;; Show/Hide the *eproject* buffer
79 (defun eproject-setup ()
80 "Show the configuration buffer."
82 (let ((map (make-keymap)))
84 (substitute-key-definition
92 ("\t" . prj-next-button
)
93 ([tab] . prj-next-button)
94 ("\e\t" . prj-prev-button)
95 ([S-tab] . prj-prev-button)
96 ([backtab] . prj-prev-button)
98 ([left] . prj-move-left)
99 ([right] . prj-move-right)
100 ([backspace] . prj-qsearch)
101 ([delete] . prj-qsearch)
102 ([127] . prj-qsearch)
103 ([return] . prj-enter)
105 ([32] . eproject-edit)
106 ([escape] . eproject-setup-quit)
108 ([down-mouse-1] . prj-mouse)
109 ([down-mouse-2] . prj-mouse)
110 ([mouse-1] . prj-mouse)
111 ([mouse-2] . prj-mouse)
113 ([drag-mouse-1] . ignore)
115 (define-key map (car k) (cdr k))
118 (cond ((buffer-live-p prj-buffer)
119 (switch-to-buffer prj-buffer)
123 (add-hook 'post-command-hook 'prj-post-command-hook)
126 (setq prj-buffer (get-buffer-create "*eproject*"))
127 (switch-to-buffer prj-buffer)
130 (setq prj-browse-map map)
132 (unless prj-edit-mode
138 (defun eproject-setup-quit ()
139 "Kill the configuration buffer."
141 (let ((alive (buffer-live-p prj-buffer)))
142 (cond ((and alive prj-edit-mode)
143 (bury-buffer prj-buffer)
147 (kill-buffer prj-buffer)
149 (remove-hook 'post-command-hook 'prj-post-command-hook)
150 (setq prj-buffer nil)
153 (defun eproject-setup-toggle ()
154 "Show/hide the project configuration browser."
156 (if (prj-config-active)
157 (eproject-setup-quit)
161 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
164 (defun eproject-edit ()
166 (if (eq 'u (car prj-active-group)) (emacs-lisp-mode))
167 (let ((map (make-sparse-keymap)))
168 (define-key map [escape] 'eproject-edit-quit)
169 (setq prj-edit-mode t)
175 (defun eproject-edit-quit ()
177 (if (eq 'u (car prj-active-group)) (fundamental-mode))
179 (use-local-map prj-browse-map)
180 (setq prj-edit-mode nil)
181 (setq cursor-type nil)
186 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
188 (defun prj-config-active ()
189 (eq (current-buffer) prj-buffer)
192 (defun prj-save-window-pos ()
193 (p-set (prj-active-group . :pos)
196 (- (line-number-at-pos) prj-group-top)
199 (defun prj-config-reset ()
200 (dolist (s prj-groups)
201 (p-set (s . :pos) (list 1 0))
203 (setq prj-active-group (car prj-groups))
206 (defun prj-config-init ()
217 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
218 ;; Read back the configuration after edits
220 (defun prj-config-parse ()
221 (when (and (prj-config-active) prj-edit-mode)
222 (with-current-buffer prj-buffer
224 (let ((s (p-get (prj-active-group . :scan))) l r e)
225 (prj-goto-line prj-group-top)
226 (if (eq 'u (car prj-active-group))
227 (setq l (read (concat
229 (buffer-substring-no-properties (point) (point-max))
232 (while (< (point) (point-max))
233 (setq e (line-end-position))
235 (if (and s (posix-search-forward (car s) e t))
237 (and (re-search-forward "^ *\\(.*[^ :]\\)[ :]*$" e t)
238 (list (match-string-no-properties 1))
240 (if r (setq l (cons r l)))
243 (setq l (nreverse l))
245 (p-call (prj-active-group . :parse) l)
248 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
249 ;; The project config window
251 ;; (makunbound 'prj-groups) (makunbound 'prj-links)
253 (defvar prj-groups `(
257 :comment "All projects on a list"
261 :print ,(lambda (a p)
262 (prj-link (car a) nil a)
263 (prj-link-2 nil p (cadr a))
264 (and (caddr a) (prj-link-2 nil p (caddr a)))
266 :scan ("^ *\\([^ :]+\\) *: *\\([^ ]+\\) *\\( +: *\\([^ ]+\\)\\)? *$" .
268 (let ((a (match-string-no-properties 1))
269 (b (match-string-no-properties 2))
270 (c (match-string-no-properties 4))
272 (cons a (cons b (and c (list c))))
277 (error "Error: Project directory empty: %s" (car a))
280 (when (string-equal (cadr a) (cadr prj-current))
282 (prj-setconfig "project-name" (car a))
286 :menu (add remove open close)
291 :comment "The files that belong to the project"
294 :exec eproject-visitfile
295 :print ,(lambda (a p)
296 (prj-link (car a) nil a)
302 (setcdr l (cdr (assoc (car l) prj-files)))
304 (dolist (a prj-files)
305 (if (setq b (assoc (car a) s))
306 (if (eq a prj-curfile) (setq prj-curfile b))
311 :menu (add-file remove-file visit-file)
316 :comment "Configurable tools and keyboard shortcuts"
320 :print ,(lambda (a p)
321 (prj-link (car a) nil a)
323 (unless prj-edit-mode
324 (insert-char 32 (- (- prj-group-tab 12) (- (point) p)))
326 (insert " (" (caddr a) ")")
328 (prj-link-2 nil p (cadr a))
330 :scan ("^ *\\([^(:]*[^(: ]\\) *\\(([^ ):]+)\\)?\\( *: *\\(.*[^ ]\\)?\\)? *$" .
332 (let ((a (match-string-no-properties 1))
333 (b (match-string-no-properties 2))
334 (c (match-string-no-properties 4))
336 (list a c (and b (substring b 1 -1)))
346 :comment "Project options"
350 :print ,(lambda (a p)
351 (prj-link-2 (car a) p (cdr a))
353 :scan ("^ *\\([^ :]+\\) *: *\\(.*[^ ]\\)? *$" .
355 (list (match-string-no-properties 1)
356 (match-string-no-properties 2)
359 (dolist (l s) (setcdr l (cadr l)))
360 (let ((prj-config s) n)
361 (setq n (prj-getconfig "project-name"))
362 (unless (> (length n) 0)
363 (error "Error: Project name empty.")
366 (setcar prj-current n)
375 ;;; :title "Functions"
376 ;;; :comment "ELisP Utitlities"
378 ;;; :list prj-functions
379 ;;; :exec eproject-edit
380 ;;; :print ,(lambda (a p)
381 ;;; (pp a (current-buffer))
383 ;;; :parse ,(lambda (s)
384 ;;; (prj-set-functions s)
394 (add "Add" "Add new or existing project to the list"
397 (remove "Remove" "Remove a project from the the list"
400 (open "Open" "Open a Project"
403 (close "Close" "Close the current project"
408 (add-file "Add" "Add a file to the project"
411 (remove-file "Remove" "Remove file from project"
414 (dired "Dired" "Browse project directory - Use 'a' in dired to add file(s) to the project"
417 (visit-file "Visit" "Visit this file"
422 (edit "Edit" "Edit this list (spacebar)"
425 (quit-edit "Quit" "Quit edit mode (escape)"
428 (revert "Revert" "Revert all configuration to last saved state"
431 (save "Save" "Save the configuration now"
436 (help "Help" "View the 'eproject' documentation."
439 (quit "Quit" "Quit configuration area"
444 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
447 (defun prj-config-print ()
448 (when (prj-config-active)
449 (let (x f a n title l p (inhibit-read-only t) active)
451 (setq buffer-read-only nil)
452 (buffer-disable-undo)
455 (setq prj-group-left (if prj-edit-mode 0 1))
456 (setq prj-group-tab (+ 26 prj-group-left))
459 (setq prj-active-group (car prj-groups))
463 (dolist (s prj-groups)
464 (setq f (eq s active))
465 (when (or f (and prj-current (null prj-edit-mode)))
466 (setq title (p-get (s . :title)))
471 (prj-make-hilite-bar 'prj-hilight-bar-2 p (point))
474 (prj-link title (p-get (s . :comment)) s t)
479 (dolist (s prj-links)
480 (prj-define-shortcut nil (cadr s) 'ignore)
482 (dolist (s prj-groups)
483 (prj-define-shortcut nil (symbol-name (car s)) 'prj-key-set-group)
486 (dolist (id (if prj-edit-mode '(revert save quit-edit) '(edit help quit)))
492 (insert " " (car prj-current) " ")
495 (unless prj-edit-mode
496 (dolist (id (p-get (active . :menu)))
504 (add-text-properties (point-min) (point)
505 '(read-only t intangible t front-sticky t rear-nonsticky t))
508 (setq prj-group-top (line-number-at-pos))
511 (p-get (active . :print))
512 (eval (p-get (active . :list)))
516 (setq p (p-get (active . :pos)))
517 (set-window-start (get-buffer-window prj-buffer) (car p))
518 (prj-goto-line (+ prj-group-top (cadr p)))
520 (forward-char prj-group-left)
522 (unless (pos-visible-in-window-p)
523 (recenter (/ (window-height) 5))
525 (set-buffer-modified-p nil)
528 (setq cursor-type 'box)
532 (setq buffer-read-only t)
533 (setq cursor-type nil)
538 (defun prj-print-items (fn items tab)
540 (when (stringp (car a))
541 (unless (and (string-match "^ *#" (car a)) (null prj-edit-mode))
544 (funcall fn a (- (point) tab))
548 (defun prj-link (text help &optional fn top)
549 (if (and prj-edit-mode (null help))
551 (let ((p (point)) (f (if top 'link)))
562 (add-text-properties p (1+ p) '(face (:foreground "blue" :underline t)))
566 (defun prj-link-2 (a p b)
568 (insert-char 32 (- prj-group-tab 1 (- (point) p)))
569 (if b (insert " : " b) (insert " :"))
572 (defun prj-link-3 (id f)
573 (let ((a (assq id prj-links)))
575 (prj-link (cadr a) (caddr a) a f)
576 (prj-define-shortcut nil (cadr a) (nth 3 a))
579 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
580 ;; Project selection and configuration
582 (defun prj-action (b)
583 (let ((a (button-get b 'class)))
584 (cond ((memq a prj-links)
585 (command-execute (nth 3 a))
588 (setq prj-active-group a)
592 (p-call (prj-active-group . :exec) a)
595 (defun prj-key-set-group ()
597 (let ((c (intern (char-to-string (logand last-input-event 255)))) s)
598 (when (setq s (assoc c prj-groups))
599 (setq prj-active-group s)
603 (defun prj-define-shortcut (map s fn)
604 (let ((c (logior (aref s 0) 32)))
606 (or map (current-local-map))
607 (read (format "\"\\M-%c\"" c))
611 (defun prj-config-get-result (id)
612 (and (prj-config-active)
613 (eq id (car prj-active-group))
614 (nth (cadr (p-get (prj-active-group . :pos)))
615 (eval (p-get (prj-active-group . :list)))
618 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
619 ;; Tab between buttons and move files up/down
621 (defun prj-next-button ()
627 (defun prj-prev-button ()
633 (defun prj-move-left ()
638 (defun prj-move-right ()
643 (defun prj-move-to (d &optional cycle)
645 (dolist (s prj-groups)
646 (if (eq s prj-active-group)
651 (unless prj-current (setq n 1))
653 (if (< x 0) (setq x (1- n)) (if (>= x n) (setq x 0)))
654 (setq x (max 0 (min (1- n) x)))
656 (setq prj-active-group (nth x prj-groups))
663 (and (setq b (button-at (point)))
664 (setq a (button-get b 'action))
670 ;;(message "LC: %s" (prin1-to-string last-input-event))
671 (let ((i last-input-event) p b a x y tp)
673 (select-window (car (cadr i)))
674 (setq p (nth 5 (cadr i)))
675 (setq tp (nth 6 (cadr i)))
676 (setq y (+ (cdr tp) (line-number-at-pos (window-start))))
677 (setq x (+ (car tp) 1))
678 (if (>= y prj-group-top)
681 (and (memq (car i) '(mouse-1 mouse-2))
682 (setq b (button-at p))
683 (setq a (button-get b 'action))
687 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
688 ;; A hook to maintain the selection bar
690 (defun prj-post-command-hook ()
696 (defun prj-set-hilite-bar ()
697 (unless prj-edit-mode
698 ;;(message "LC: %s" (prin1-to-string (cons this-command last-input-event)))
700 (setq m (length (eval (p-get (prj-active-group . :list)))))
701 (setq p (line-number-at-pos))
702 (setq n (max prj-group-top
703 (min (line-number-at-pos)
704 (1- (+ prj-group-top m))
708 (set-window-start nil (point-min))
712 (forward-char prj-group-left)
713 (setq e (line-end-position))
714 (when (< (setq c (+ a prj-group-tab)) e)
716 (if (re-search-forward " *:" e t)
717 (setq e (1- (match-end 0)))
719 (while (= (char-after) 32)
722 (prj-make-hilite-bar 'prj-hilight-bar (point) e)
723 (prj-save-window-pos)
726 (defun prj-make-hilite-bar (s a e)
728 (if (and (boundp s) (setq b (eval s)))
731 (set s (make-overlay a e))
732 'face '(:background "grey90" :foreground "blue")
736 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
739 (defun prj-qsearch ()
742 (cond ((member last-command-event '(backspace 127))
743 (substring prj-qs-str 0 (max 0 (1- (length prj-qs-str))))
745 ((eq last-command-event 'delete)
749 (concat prj-qs-str (char-to-string last-command-event))
754 (defun prj-qs-clear ()
756 (delete-overlay prj-qs-face)
758 (setq prj-qs-face nil)
759 (setq prj-qs-pos nil)
764 (defun prj-qs-find (s f p)
766 (let (r fn beg end start limit)
768 "^[[:space:]]*\\([^[:space:]]*[/\\]\\)?\\("
770 "\\)[^/\\[:space:]]*\\([[:space:]]\\|$\\)"
773 (prj-goto-line prj-group-top)
775 (setq end (point-max))
776 (goto-char (max p beg))
779 (setq fn 're-search-forward
783 (setq fn 're-search-backward
790 (beginning-of-line (max 1 (1+ f)))
791 (cond ((funcall fn s limit t)
792 (throw 'loop (match-beginning 2))
801 (defun prj-qs-next (f)
812 (if (setq n (prj-qs-find s f (or p (point))))
814 (setq s (substring s 0 l))
816 (message "Quick search: %s" s)
823 (setq prj-qs-face (make-overlay p (+ p l)))
824 (overlay-put prj-qs-face 'face '(:background "white" :box "black"))
830 (when (setq e (read-key-sequence nil))
831 (setq e (listify-key-sequence e))
832 (setq unread-command-events (nconc e unread-command-events))
833 (unless (lookup-key prj-browse-map (vconcat e) t)
838 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
839 ;; eproject-config.el ends here