Rename 'project-buffer-dont-cleanup-empty-projects into 'project-buffer-cleanup-empty...
[project-buffer-mode.git] / project-buffer-mode.el
blobd128e9b12570d901000d1286f739e1df71b036e7
1 ;;; project-buffer-mode.el --- Generic mode to browse project file
2 ;;
3 ;; Author: Cedric Lallain <kandjar76@hotmail.com>
4 ;; Version: 1.20
5 ;; Keywords: project mode buffer viewer generic
6 ;; Description: Generic mode to handle projects.
7 ;; Tested with: GNU Emacs 22.x and GNU Emacs 23.x
8 ;;
9 ;; This file is *NOT* part of GNU Emacs.
11 ;; This program is free software; you can redistribute it and/or modify
12 ;; it under the terms of the GNU General Public License as published by
13 ;; the Free Software Foundation; either version 2 of the License, or
14 ;; (at your option) any later version.
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
19 ;; GNU General Public License for more details.
21 ;; You should have received a copy of the GNU General Public License
22 ;; along with this program; if not, write to the Free Software
23 ;; Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
26 ;;; Summary:
29 ;; project-buffer-mode is a generic mode to handle projects. It
30 ;; provides an easy and intuitive way to interact with the multiple
31 ;; projects with in one buffer.
34 ;; Two extensions already exist for this mode:
35 ;; - fsproject - which creates a project based on the file system
36 ;; - sln-mode - which parses a sln files and create a project representing it
39 ;; Key features:
40 ;; - find file based on regular expression
41 ;; - four different view mode
42 ;; - advance 'search in files' system
43 ;; - notion of master project to launch build/clean/run/debug and update.
44 ;; - intuitive key bindings (at least I hope)
45 ;; - full save/load of a project including hooks and local configuration.
46 ;; - mouse support to expand/collapse a folder or open a file
49 ;;; Commentary:
52 ;; project-buffer-mode provides a generic way to handle multiple
53 ;; projects in a buffer.
55 ;; A Project is defined by:
56 ;; - its name
57 ;; - its main file (Makefile, Jam, Scons...)
58 ;; - a build configuration list (Debug, Release, ...)
59 ;; - a platform list (Win32, PocketPC, Linux...)
60 ;; - and obviously a list of files.
63 ;; QUICK FIND FILE USING REGEXP:
65 ;; Through a hierarchical view, the project-buffer mode provides an
66 ;; very easy and intuitive way to search for a particular files (key:
67 ;; '/', then 'n' or 'p' to go to the next or previous matching
68 ;; result). Note: press 'q' to cancel the research.
70 ;; Opening the current is a simple as pressing <enter> or
71 ;; 'o' to open it in another window.
72 ;; Press 'f' if you want to open all marked files.
75 ;; FOUR DIFFERENT VIEW-MODE:
77 ;; Four different view-modes are currently supported:
78 ;; - folder-view (<default>)
79 ;; - flat-view
80 ;; - folder-hidden-view
81 ;; - marked-view
83 ;; It's possible to switch between them using 'c v'.
85 ;; The first three modes show the project with their associated files:
86 ;; - folder-view shows a tree-view of files.
87 ;; - flat-view shows the list of the files prefixed by their folder
88 ;; - folder-hidden-view shows the list of just the file names, next
89 ;; to it, it displays the real path for each of them.
91 ;; The final view mode named marked-view shows only the list of marked
92 ;; files, prefixed by their project and folders.
95 ;; MARKING FILE MATCHING A REGEXP:
97 ;; Files can be marked/unmarked individually, but you can also easily
98 ;; mark all files whose names are matching a regular expression ('/'
99 ;; then 'm').
100 ;; Note: using the mark/unmark command in front of a folder of a
101 ;; project results in marking every files which belong to this folder
102 ;; or this project.
105 ;; ADVANCE SEARCH IN FILES SYSTEM:
107 ;; The search in files functionality comes with three different behaviors:
108 ;; - Narrow the marked files (<default>)
109 ;; - All files
110 ;; - Current project
112 ;; Before talking about the "Narrow the marked files" behavior which
113 ;; is the default one; let's quickly go throught the others two:
115 ;; - If the search behavior is set to "All files", the search-in-files
116 ;; command ('s') will do a search-regexp in files for each unmarked
117 ;; files (all projects) and mark the ones which contain the regexp.
119 ;; - If the search behavior is set to "Current Project" the
120 ;; search-in-files will do search-regexp in files for each unmarked
121 ;; file contained in the current project. The current project being
122 ;; defined by the position of the cursor. Again, each matching files
123 ;; will be marked.
125 ;; Note: it is possible to have the search-regexp in file unmarking
126 ;; the files instead by using the prefix argument (C-u).
128 ;; Finally in case the search behavior is set to "Narrow the marked
129 ;; files": if no files are actually marked, it will behave the same
130 ;; way as the "All files" behavior. In case some files are marked, it
131 ;; will only perform the "search-regexp in files" in the marked files,
132 ;; unmarking the ones which don't contain the regular expression.
134 ;; This provide an easy way to narrow/refine some research.
136 ;; The search behavior can be either customized or locally change
137 ;; (pressing 'c s')
139 ;; Note: in case a search-in-files mark or unmark some files; the view
140 ;; mode will automatically be switched to marked-view. This behavior
141 ;; can be disabled.
144 ;; MASTER PROJECT / BUILD CONFIGURATION / PLATFORM:
146 ;; The master project, build configuration and platform can be easily
147 ;; changed using respectively: 'c t' 'c b' 'c p' Using the capital
148 ;; letter ('c T' 'c B' and 'c P') will prompt the user for the new
149 ;; value.
151 ;; This value allows to take quick actions for the master project:
152 ;; build/clean/run/debug/update (keys: 'B' 'C' 'R' 'D' 'G')
155 ;; KEY BINDINGS:
157 ;; Shortkey in the project-buffer-mode:
158 ;; + -> collapse/expand folder/project (cursor has to be on a folder/project)
159 ;; m -> mark the 'matching regexp' filename or the current file
160 ;; u -> unmark file
161 ;; t -> toggle marked files
162 ;; M -> mark all
163 ;; U -> unmark all
164 ;; f -> open marked files
165 ;; q -> cancel file search or bury project-buffer
166 ;; ? -> show brief help!!
167 ;; / -> search file name matching regexp
168 ;; n -> next file matching regexp
169 ;; p -> prev file matching regexp
170 ;; v -> view current file in view-mode
171 ;; o -> find file at current pos in other window
172 ;; s -> (un)mark files containing regexp...
173 ;; <TAB> -> collapse/expand folder/project (work if the cursor is on a file)
174 ;; <RET> -> open file at cursor pos
175 ;; <DEL> -> Delete the current node or the marked files
176 ;; <BCK> -> go to parent
177 ;; <SPC> -> next line
178 ;; S-<SPC> -> prev line
179 ;; C-<DWN> -> move to the next folder/project
180 ;; C-<UP> -> move to the previous folder/project
181 ;; C-<LFT> -> expand if collapsed move to the first folder; move inside if expanded
182 ;; C-<RGT> -> move up if folded collapsed; collapse if in front of folder ; move to the folded if in front of a file
183 ;; c s -> Toggle search mode
184 ;; c v -> Toggle view mode (flat / flat with the folder hidden / folder / marked files view)
185 ;; c b -> switch to the next build configuration
186 ;; c m -> switch the master project to be the current project
187 ;; c p -> switch to the next platform
188 ;; c B -> prompt to change build configuration
189 ;; c M -> prompt for the master project (project to build)
190 ;; c P -> prompt to change platform
191 ;; B -> launch build
192 ;; C -> launch clean
193 ;; D -> launch run/with debugger
194 ;; R -> launch run/without debugger
195 ;; G -> launch the update command (useful to regenerate some makefile/vcproj... from cmake for example); can also be consider a user command.
196 ;; 1 -> Switch to folder-view mode
197 ;; 2 -> Switch to flat-view mode
198 ;; 3 -> Switch to folder-hidden-view mode
199 ;; 4 -> Switch to marked-view mode
202 ;; Future improvement:
203 ;; T -> touch marked files (need a variable to make sure touch is always available)
204 ;; h -> find corresponding header/source (need regexps to match one and the other such as: source/header = ( "\.c\(pp\)?" . "\.h\(pp\)?" ) )
205 ;; d -> show/hide project dependencies
206 ;; b -> compile/buils marked files
212 ;;; Raw mode:
215 ;; As it was mentioned earlier, project-buffer-mode is just an abstract
216 ;; project manager. Even if some extensions already exist, you may
217 ;; want to be able to handle you own project system.
219 ;; Here is a sample code which shows how to create a new project:
221 ;; (defun test-projbuff()
222 ;; (interactive)
223 ;; (let ((buffer (generate-new-buffer "test-project-buffer"))) ; Creation of a buffer for the project
224 ;; (display-buffer buffer) ; We want to switch to this buffer right away.
225 ;; (with-current-buffer buffer
226 ;; (cd "~/temp") ; It's always better to set the root directory if it's known.
227 ;; (project-buffer-mode) ; Initialize the project buffer mode
229 ;; (project-buffer-insert "test1" 'project "test1/Makefile" "test1") ; Create an insert a project node called 'test1' (note: it's recommended to have project and node being the same)
230 ;; (project-buffer-insert "src/gfr.cpp" 'file "~/temp/test1/gfr.cpp" "test1") ; Add "~/tenp/gfr.cpp" to the project 'test1' it's project path will be: "src/gfr.cpp"
231 ;; (project-buffer-insert "src/abc.cpp" 'file "~/temp/test1/abc.cpp" "test1") ; Add "~/tenp/abc.cpp" to the project 'test1' it's project path will be: "src/abc.cpp"
233 ;; (project-buffer-insert "test2" 'project "test2/Makefile" "test2") ; Creation of a second project namded "test2"
234 ;; (project-buffer-insert "header/yyy.h" 'file "~/temp/test2/zzz.h" "test2") ; Add some file to this project; note that the project path and the physical file name can be completely different
235 ;; (project-buffer-insert "src/roo.c" 'file "~/temp/test2/roo.c" "test2") ; the file name research will be based on the project-path and not on the physical file name
236 ;; (project-buffer-insert "script.awk" 'file "~/temp/test2/script.awk" "test2") ;
237 ;; )))
240 ;; List of user functions available to handle your own project:
241 ;; - `project-buffer-mode' which initialize the project-buffer mode
242 ;; - `project-buffer-insert' to insert a file or project to the view
243 ;; - `project-buffer-delete-file' to remove a file
244 ;; - `project-buffer-delete-folder' to remove a folder and all its files
245 ;; - `project-buffer-delete-project' to remove a project and all its files
246 ;; - `project-buffer-set-project-platforms' to set the platform configuration for a particular project
247 ;; - `project-buffer-set-build-configurations' to set the build configurations for a particular project
248 ;; - `project-buffer-raw-save' to save a project into a file
249 ;; - `project-buffer-raw-load' to load a project from a file
250 ;; - `project-buffer-set-project-user-data' to set user data to a project node
251 ;; - `project-buffer-get-project-user-data' to get user data from a project node
252 ;; - `project-buffer-set-file-user-data' to set user data to a file node
253 ;; - `project-buffer-get-file-user-data' to get user data from a file node
254 ;; - `project-buffer-get-current-project-name' to get the nane of the current project the cursor is on
255 ;; - `project-buffer-get-current-file-data' to get data about the current file the cursor is on; nil if it's on a folder or a project
256 ;; - `project-buffer-exists-p' to check if a node exists (file or folder) inside a project
257 ;; - `project-buffer-project-exists-p' to check if a project exists
258 ;; - `project-buffer-get-file-path' to get the path of a file of the project
259 ;; - `project-buffer-get-current-node-type' to get the type of the current node (including folder)
260 ;; - `project-buffer-get-current-node-name' to get the name of the current node (including folder)
261 ;; - `project-buffer-get-marked-node-list' to get the list of marked files
263 ;; If you need to have some local variables to be saved; register them in `project-buffer-locals-to-save'.
264 ;; The same way, if there is need to save extra hooks: register them in `project-buffer-hooks-to-save'.
267 ;;; Todo:
270 ;; - show project dependencies
271 ;; e.g: [+] ProjName1 <deps: ProjName3, ProjName2>
272 ;; - add collapsed all / expand all commands
273 ;; - grayed out exclude from build files??
274 ;; - different color for files referenced in the proj but don't exist?
275 ;; - provide a touch marked files command
276 ;; - provide a compile/build marked files command
277 ;; - add a command to easily find the corresponding header/source for the current file (or specified file)
278 ;; - add 'g' to refresh the display
279 ;; - disable project which doesn't have the current selected platform/build-configuration in their list ???
284 ;;; History:
287 ;; v1.00: First public release.
288 ;; v1.10: Added mouse support and save/load.
289 ;; - Enable click on folder/project to expand/collapse them.
290 ;; - Enable click on filename to open them.
291 ;; - Added global command to load/save/write/revert a project buffer.
292 ;; - Added new hook: `project-buffer-post-find-file-hook'.
293 ;; - Added possibilty to attach user data to each nodes.
294 ;; v1.11: Bugs fixed
295 ;; - project-buffer-find-node-up was return nil in view-mode other than folder-view
296 ;; - file-exist-p has been renamed to file-exists-p
297 ;; - minor visibility bug when a files get added to the project if the view-mode is different from folder-view
298 ;; v1.12: New action added
299 ;; - 'update' to allow some project generation from cmake or other build system
300 ;; v1.20: Add new commands:
301 ;; - Delete current node
302 ;; - Delete marked files
303 ;; - Delete current node or marked file if in front of one (bound to <DEL>
304 ;; Add the following user functions:
305 ;; - `project-buffer-get-current-project-name' to get the project name the cursor is on
306 ;; - `project-buffer-get-current-file-data' to get data about the file the cursor is on
307 ;; - `project-buffer-get-file-path' to get the file path
308 ;; - `project-buffer-get-current-node-type' to get the type of the current node (including folder)
309 ;; - `project-buffer-get-current-node-name' to get the name of the current node (including folder)
310 ;; - `project-buffer-delete-folder' to remove a folder and all its files
311 ;; - `project-buffer-exists-p' to check if a node exists (file or folder) inside a project
312 ;; - `project-buffer-project-exists-p' to check if a project exists
313 ;; v1.21: Remap the update action to G; to remove the key conflict with the 'unmark all' command.
314 ;; Added the following user function:
315 ;; - `project-buffer-get-marked-node-list' to get the list of marked files
318 (require 'cl)
319 (require 'ewoc)
323 ;;; Code:
328 ;; Group definition:
332 (defgroup project-buffer nil
333 "A special mode to manage projects."
338 ;; Constants:
341 (defconst project-buffer-mode-version "1.20"
342 "Version numbers of this version of `project-buffer-mode'.")
346 ;; Customizable variables:
350 (defcustom project-buffer-new-project-collapsed t
351 "Newly added project will be collapsed by default."
352 :type 'boolean
353 :group 'project-buffer)
356 (defcustom project-buffer-search-in-files-mode 'narrow-marked-files
357 "Variable defining the current search-in-files mode.
358 The different search mode set to 'narrow-marked-files it will
359 search in the selected marked files, removing the one failing the
360 research, set to 'all-files it will launch the search on all
361 files in the projects, 'current-project will only search with the
362 current project Note: if no files are marked while using
363 narrow-marked-files, the search will occur in all files in the
364 project."
365 :type '(choice (const :tag "Narrow the marked files" narrow-marked-files)
366 (const :tag "All files" all-files)
367 (const :tag "Current project" current-project))
368 :group 'project-buffer)
371 (defcustom project-buffer-autoswitch-marked-view-mode t
372 "If set to t, the view-mode will automatically be switched to
373 the marked-view mode after performing a search-in-files (unless
374 no files got marked/unmarked)."
375 :type 'boolean
376 :group 'project-buffer)
379 (defcustom project-buffer-confirm-function 'yes-or-no-p
380 "Confirmation function called before clean and node deletion."
381 :type '(radio function
382 (function-item yes-or-no-p)
383 (function-item y-or-n-p))
384 :group 'project-buffer)
386 (defcustom project-buffer-cleanup-empty-projects nil
387 "When set, deleting the last file of a project will result in
388 deleting the project itself."
389 :type 'boolean
390 :group 'project-buffer)
395 ;; Font
399 (defface project-buffer-project-face
400 '((((class color) (background light)) (:foreground "red"))
401 (((class color) (background dark)) (:foreground "salmon")))
402 "Project buffer mode face used to highlight project nodes."
403 :group 'project-buffer)
405 (defface project-buffer-master-project-face
406 '((default (:inherit project-buffer-project-face :bold t)))
407 "Master project buffer mode face used to highlight project nodes."
408 :group 'project-buffer)
410 (defface project-buffer-folder-face
411 '((((class color) (background light)) (:foreground "purple"))
412 (((class color) (background dark)) (:foreground "cyan")))
413 "Project buffer mode face used to highlight folder nodes."
414 :group 'project-buffer)
416 (defface project-buffer-file-face
417 '((((class color) (background light)) (:foreground "black"))
418 (((class color) (background dark)) (:foreground "white")))
419 "Project buffer mode face used to highlight file nodes."
420 :group 'project-buffer)
422 (defface project-buffer-project-button-face
423 '((((class color) (background light)) (:foreground "gray50"))
424 (((class color) (background dark)) (:foreground "gray50")))
425 "Project buffer mode face used highligh [ and ] in front of the project name."
426 :group 'project-buffer)
428 (defface project-buffer-indent-face
429 '((((class color) (background light)) (:foreground "gray50"))
430 (((class color) (background dark)) (:foreground "gray50")))
431 "Project buffer mode face used to highlight indent characters."
432 :group 'project-buffer)
434 (defface project-buffer-mark-face
435 '((((class color) (background light)) (:foreground "red"))
436 (((class color) (background dark)) (:foreground "tomato")))
437 "Project buffer mode face used highligh marks."
438 :group 'project-buffer)
440 (defface project-buffer-filename-face
441 '((((class color) (background light)) (:foreground "gray50"))
442 (((class color) (background dark)) (:foreground "gray50")))
443 "Project buffer mode face used highligh file names."
444 :group 'project-buffer)
446 (defface project-buffer-matching-file-face
447 '((default (:inherit project-buffer-file-face :bold t)))
448 "Project buffer mode face used matching file."
449 :group 'project-buffer)
454 ;; User hook:
458 (defcustom project-buffer-mode-hook nil
459 "Post `project-buffer-mode' initialization hook."
460 :type 'hook
461 :group 'project-buffer)
464 (defcustom project-buffer-action-hook nil
465 "Hook to perform the actions (build, clean, run...)
467 The function should follow the prototype:
468 (lambda (action project-name project-path platform configuration)
469 Where ACTION represents the action to apply to the project,
470 it may be: 'build 'clean 'run 'debug 'update,
471 PROJECT-NAME is the name of the master project,
472 PROJECT-PATH is the file path of the project
473 PLATFORM is the name of the selected platform,
474 and CONFIGURATION correspond to the selected build configuration."
475 :type 'hook
476 :group 'project-buffer)
479 (defcustom project-buffer-post-load-hook nil
480 "Hook to run after performing `project-buffer-raw-load'.
482 Register functions here to keep the customization after reloading the project.")
485 (defcustom project-buffer-post-find-file-hook nil
486 "Hook to run after performing `project-buffer-find-file' or
487 `project-buffer-find-file-other-window'.
489 The function should follow the prototype:
490 (lambda (project-buffer file-buffer))
491 Where PROJECT-BUFFER is the buffer of the project, and
492 FILE-BUFFER is the buffer of the file.")
496 ;; Buffer local variables:
500 (defvar project-buffer-status nil)
501 (defvar project-buffer-view-mode nil)
502 (defvar project-buffer-cache-project nil)
503 (defvar project-buffer-cache-subdirectory nil)
504 (defvar project-buffer-projects-list nil)
505 (defvar project-buffer-master-project nil)
506 (defvar project-buffer-platforms-list nil)
507 (defvar project-buffer-current-platform nil)
508 (defvar project-buffer-build-configurations-list nil)
509 (defvar project-buffer-current-build-configuration nil)
510 (defvar project-buffer-file-name nil)
511 (defvar project-buffer-locals-to-save nil)
512 (defvar project-buffer-hooks-to-save nil)
517 ;; History:
521 (defvar project-buffer-regexp-history nil
522 "History list of regular expressions used in project-buffer commands.")
527 ;; Data type:
531 ;; Structure to store data attached to each ewoc-node.
532 ;; Each node represents either a file or a project or a folder indide the project"
533 (defstruct (project-buffer-node
534 (:copier nil)
535 (:constructor project-buffer-create-node (name type filename project &optional hidden))
536 (:conc-name project-buffer-node->))
537 name ;; string displayed to represent the file (usually the file.ext)
538 type ;; project? file? folder?
540 marked ;; is the file marked?
541 hidden ;; hidden files (currently: = project/folder close)
542 collapsed ;; is the folder/project collapsed or not?
543 project-collapsed ;; t if the project the file belong to is collapsed
545 matched ;; the file matches the regexp search
547 filename ;; full path to the filename
548 project ;; name of the project the file belongs to
549 parent ;; parent node (parent folder or project or nil)
551 platform-list ;; list of the platform available for the project (valid in project node only)
552 build-configurations-list ;; list of build configuration avalailable for the project (valid in project node only)
554 user-data ;; user data could be set (mainly useful to store something per project)
560 ;; Key Bindings:
564 ;; Define the key mapping for the spu mode:
565 (defvar project-buffer-mode-map
566 (let ((project-buffer-mode-map (make-keymap)))
567 (define-key project-buffer-mode-map [?+] 'project-buffer-toggle-expand-collapse)
568 (define-key project-buffer-mode-map [?\t] 'project-buffer-toggle-expand-collapse-even-on-file)
569 (define-key project-buffer-mode-map [?m] 'project-buffer-mark-matched-files-or-current-file)
570 (define-key project-buffer-mode-map [?u] 'project-buffer-unmark-matched-files-or-current-file)
571 (define-key project-buffer-mode-map [?M] 'project-buffer-mark-all)
573 (define-key project-buffer-mode-map [?U] 'project-buffer-unmark-all)
574 (define-key project-buffer-mode-map [?t] 'project-buffer-toggle-all-marks)
575 (define-key project-buffer-mode-map [?f] 'project-buffer-find-marked-files)
576 (define-key project-buffer-mode-map [?/] 'project-buffer-search-forward-regexp)
577 (define-key project-buffer-mode-map [?n] 'project-buffer-goto-next-match)
579 (define-key project-buffer-mode-map [?p] 'project-buffer-goto-prev-match)
580 (define-key project-buffer-mode-map [?v] 'project-buffer-view-file)
581 (define-key project-buffer-mode-map [?c ?s] 'project-buffer-toggle-search-mode)
582 (define-key project-buffer-mode-map [?c ?v] 'project-buffer-toggle-view-mode)
583 (define-key project-buffer-mode-map [?c ?b] 'project-buffer-next-build-configuration)
584 (define-key project-buffer-mode-map [?c ?p] 'project-buffer-next-platform)
585 (define-key project-buffer-mode-map [?c ?m] 'project-buffer-select-current-as-master-project)
586 (define-key project-buffer-mode-map [?c ?B] 'project-buffer-choose-build-configuration)
587 (define-key project-buffer-mode-map [?c ?P] 'project-buffer-choose-platform)
588 (define-key project-buffer-mode-map [?c ?M] 'project-buffer-choose-master-project)
589 (define-key project-buffer-mode-map [backspace] 'project-buffer-goto-dir-up)
591 (define-key project-buffer-mode-map [?\ ] 'project-buffer-next-file)
592 (define-key project-buffer-mode-map [(shift ?\ )] 'project-buffer-prev-file)
593 (define-key project-buffer-mode-map [return] 'project-buffer-node-find-file)
594 (define-key project-buffer-mode-map [mouse-1] 'project-buffer-mouse-find-file)
595 (define-key project-buffer-mode-map [?o] 'project-buffer-node-find-file-other-window)
596 (define-key project-buffer-mode-map [(control left)] 'project-buffer-goto-dir-up-or-collapsed)
598 (define-key project-buffer-mode-map [(control right)] 'project-buffer-next-file-or-expand)
599 (define-key project-buffer-mode-map [(control up)] 'project-buffer-go-to-previous-folder-or-project)
600 (define-key project-buffer-mode-map [(control down)] 'project-buffer-go-to-next-folder-or-project)
601 (define-key project-buffer-mode-map [??] 'project-buffer-help)
602 (define-key project-buffer-mode-map [?q] 'project-buffer-quit)
604 (define-key project-buffer-mode-map [?B] 'project-buffer-perform-build-action)
605 (define-key project-buffer-mode-map [?C] 'project-buffer-perform-clean-action)
606 (define-key project-buffer-mode-map [?R] 'project-buffer-perform-run-action)
607 (define-key project-buffer-mode-map [?D] 'project-buffer-perform-debug-action)
608 (define-key project-buffer-mode-map [?G] 'project-buffer-perform-update-action)
609 (define-key project-buffer-mode-map [?s] 'project-buffer-mark-files-containing-regexp)
611 (define-key project-buffer-mode-map [?1] 'project-buffer-set-folder-view-mode)
612 (define-key project-buffer-mode-map [?2] 'project-buffer-set-flat-view-mode)
613 (define-key project-buffer-mode-map [?3] 'project-buffer-set-folder-hidden-view-mode)
614 (define-key project-buffer-mode-map [?4] 'project-buffer-set-marked-view-mode)
616 (define-key project-buffer-mode-map [delete] 'project-buffer-delete-current-node-or-marked-files)
618 project-buffer-mode-map))
622 ;; Internal Utility Functions:
626 (defun project-buffer-erase-all(status)
627 "Erase all nodes from the buffer."
628 (let ((node (ewoc-nth status 0)))
629 (while node
630 (project-buffer-delete-project-node status (project-buffer-node->name (ewoc-data node)) node)
631 (setq node (ewoc-nth status 0)))
632 (setq project-buffer-cache-project nil)
633 (setq project-buffer-cache-subdirectory nil)
634 (setq project-buffer-projects-list nil)
635 (setq project-buffer-master-project nil)
636 (setq project-buffer-platforms-list nil)
637 (setq project-buffer-current-platform nil)
638 (setq project-buffer-build-configurations-list nil)
639 (setq project-buffer-current-build-configuration nil)
640 (project-buffer-refresh-ewoc-hf status)
644 (defun project-buffer-mark-matching-file(status regexp)
645 "Check each file name and mark the files matching the regular expression REGEXP"
646 (let ((node (ewoc-nth status 0)))
647 (while node
648 (let* ((node-data (ewoc-data node))
649 (node-type (project-buffer-node->type node-data))
650 (node-name (project-buffer-node->name node-data))
651 (file (file-name-nondirectory node-name)))
652 (when (string-match regexp file)
653 (let ((parent (project-buffer-find-node-up status node)))
654 (while (and parent
655 (not (eq (project-buffer-node->type (ewoc-data parent)) 'project))
656 (not (project-buffer-node->matched (ewoc-data parent))))
657 (setf (project-buffer-node->matched (ewoc-data parent)) t)
658 (ewoc-invalidate status parent)
659 (setq parent (project-buffer-find-node-up status parent))
661 (setf (project-buffer-node->matched node-data) t)
662 (ewoc-invalidate status node)
664 (setq node (ewoc-next status node)))))
667 (defun project-buffer-read-regexp(prompt)
668 "Read a regular expression from the minibuffer."
669 (read-from-minibuffer prompt nil nil nil 'project-buffer-regexp-history))
672 (defun project-buffer-clear-matched-mark(status)
673 "Clear 'matched' flag"
674 (let (result)
675 (ewoc-map (lambda (node)
676 (when (project-buffer-node->matched node)
677 (setf (project-buffer-node->matched node) nil)
678 (setq result t)))
679 status)
680 result))
683 (defun project-buffer-get-marked-nodes(status)
684 "Return the list of marked node or the current node if none are marked"
685 (or (ewoc-collect status (lambda (node) (project-buffer-node->marked node)))
686 (list (ewoc-data (ewoc-locate status)))))
689 (defun project-buffer-convert-name-for-display(node-data)
690 "Convert the node name into the displayed string depending on the project-buffer-view-mode."
691 (let* ((node-name (project-buffer-node->name node-data))
692 (file-color (if (project-buffer-node->matched node-data) 'project-buffer-matching-file-face 'project-buffer-file-face))
693 (node-color (if (eq (project-buffer-node->type node-data) 'file) file-color 'project-buffer-folder-face))
694 (file-help (concat "mouse-1: find file other window: " (project-buffer-node->filename node-data)))
695 (folder-help (concat "mouse-1: "
696 (if (project-buffer-node->collapsed node-data) "expand" "collapse")
697 " folder " node-name ".")))
698 (cond ((eq project-buffer-view-mode 'flat-view)
699 (concat (propertize " `- " 'face 'project-buffer-indent-face)
700 (and (file-name-directory node-name)
701 (propertize (file-name-directory node-name) 'face 'project-buffer-folder-face))
702 (propertize (file-name-nondirectory node-name)
703 'face file-color
704 'mouse-face 'highlight
705 'help-echo file-help)))
706 ((eq project-buffer-view-mode 'folder-hidden-view)
707 (concat (propertize " `- " 'face 'project-buffer-indent-face)
708 (propertize (file-name-nondirectory node-name)
709 'face file-color
710 'mouse-face 'highlight
711 'help-echo file-help)))
712 ((eq project-buffer-view-mode 'folder-view)
713 (let ((dir-list (split-string node-name "/"))
714 (str (if (eq (project-buffer-node->type node-data) 'file)
715 " `- "
716 (concat " `"
717 (propertize (if (project-buffer-node->collapsed node-data) "+" "-")
718 'mouse-face 'highlight
719 'help-echo folder-help)
720 " ")))
721 (cur 1))
722 (while (< cur (length dir-list))
723 (setq str (concat " | " str)
724 cur (1+ cur)))
725 (concat (propertize str 'face 'project-buffer-indent-face)
726 (if (eq (project-buffer-node->type node-data) 'file)
727 (propertize (file-name-nondirectory node-name)
728 'face node-color
729 'mouse-face 'highlight
730 'help-echo file-help)
731 (propertize (file-name-nondirectory node-name)
732 'face node-color
733 'mouse-face 'highlight
734 'help-echo folder-help))
736 ((eq project-buffer-view-mode 'marked-view)
737 (concat (propertize " - " 'face 'project-buffer-indent-face)
738 (and (file-name-directory node-name)
739 (propertize (file-name-directory node-name) 'face 'project-buffer-folder-face))
740 (propertize (file-name-nondirectory node-name)
741 'face file-color
742 'mouse-face 'highlight
743 'help-echo file-help
745 (t (format "Unknown view mode: %S" project-buffer-view-mode) ))))
748 (defun project-buffer-prettyprint(node)
749 "Pretty-printer function"
750 (let ((node-collapsed (project-buffer-node->collapsed node))
751 (node-name (project-buffer-node->name node))
752 (node-marked (project-buffer-node->marked node))
753 (node-type (project-buffer-node->type node))
754 (node-hidden (project-buffer-node->hidden node))
755 (node-matching (project-buffer-node->matched node))
756 (node-prjcol (project-buffer-node->project-collapsed node))
757 (node-project (project-buffer-node->project node))
758 (project-help (concat "mouse-1: "
759 (if (project-buffer-node->collapsed node) "expand" "collapse")
760 " project "
761 (project-buffer-node->name node))))
762 (if (eq project-buffer-view-mode 'marked-view)
763 (when (and (eq node-type 'file)
764 (or node-marked node-matching))
765 (insert (concat " "
766 (if node-marked (propertize "*" 'face 'project-buffer-mark-face) " ")
768 (propertize (if (> (length node-project) 16)
769 (substring node-project 0 16)
770 node-project)
771 'face 'project-buffer-project-face)))
772 (indent-to-column 19)
773 (insert (concat (project-buffer-convert-name-for-display node)
774 "\n")))
775 (when (or (and (eq project-buffer-view-mode 'folder-view)
776 (or (not node-hidden)
777 node-matching))
778 (and (not (eq project-buffer-view-mode 'folder-view))
779 (not (eq node-type 'folder))
780 (or (not node-prjcol)
781 node-matching))
782 (eq node-type 'project))
783 (insert (concat " "
784 (if node-marked (propertize "*" 'face 'project-buffer-mark-face)" ")
786 (cond ((not (eq node-type 'project)) " ")
787 (node-collapsed (propertize "[+]"
788 'face 'project-buffer-project-button-face
789 'mouse-face 'highlight
790 'help-echo project-help))
791 (t (propertize "[-]"
792 'face 'project-buffer-project-button-face
793 'mouse-face 'highlight
794 'help-echo project-help)))
796 (or (and (eq node-type 'project)
797 (propertize node-name
798 'face (or (and project-buffer-master-project
799 (string= node-name (car project-buffer-master-project))
800 'project-buffer-master-project-face)
801 'project-buffer-project-face)
802 'mouse-face 'highlight
803 'help-echo project-help))
804 (project-buffer-convert-name-for-display node))))
805 (when (and (eq project-buffer-view-mode 'folder-hidden-view)
806 (project-buffer-node->filename node)
807 (eq (project-buffer-node->type node) 'file))
808 (indent-to-column 40)
809 (insert (concat " " (propertize (project-buffer-node->filename node)
810 'face 'project-buffer-filename-face))))
811 (insert "\n"))
815 (defun project-buffer-refresh-ewoc-hf(status)
816 "Refresh ewoc header/footer"
817 (ewoc-set-hf status
818 (concat (format "Project view mode: %s\n" project-buffer-view-mode)
819 (format "Platform: %s\n" (or project-buffer-current-platform "N/A"))
820 (format "Build configuration: %s\n" (or project-buffer-current-build-configuration "N/A"))
821 (format "Search mode: %s\n" project-buffer-search-in-files-mode)
822 "\n\n") ""))
825 (defun project-buffer-extract-folder(name type)
826 "Return the folder associated to the node's NAME of the type TYPE.
827 Return nil if TYPE is project."
828 (cond ((eq type 'folder) name)
829 ((eq type 'project) nil)
830 (t (let ((dirname (file-name-directory name)))
831 (and dirname (substring dirname 0 -1))))))
834 (defun project-buffer-directory-lessp(dir1 dir2 type2)
835 "Return t if DIR1 is less than (DIR2,TYPE2)."
836 (let* ((list1 (and dir1 (split-string dir1 "/")))
837 (list2 (and dir2 (split-string dir2 "/")))
838 (cnt 0))
839 (if (and list1 list2)
840 (progn (while (and (< cnt (length list1))
841 (< cnt (length list2))
842 (string= (nth cnt list1) (nth cnt list2)))
843 (setq cnt (1+ cnt)))
844 (if (and (< cnt (length list1))
845 (< cnt (length list2)))
846 (string-lessp (nth cnt list1) (nth cnt list2))
847 (and (eq type2 'file)
848 (< cnt (length list1)))
850 (null list2))))
853 (defun project-buffer-parent-of-p(child parent)
854 "Check if CHILD is a child of the directory PARENT."
855 (let* ((clist (and child (split-string child "/")))
856 (plist (and parent (split-string parent "/")))
857 (cont t)
858 res)
859 (while (and clist plist cont)
860 (let ((cname (pop clist))
861 (pname (pop plist)))
862 (setq cont (string-equal cname pname))))
863 (and cont (null plist))))
866 (defun project-buffer-find-node-up(status node)
867 "Return the directory or project in which the node belong.
868 This may change depending on the view mode."
869 (if (eq project-buffer-view-mode 'folder-view)
870 (project-buffer-node->parent (ewoc-data node))
871 (let ((parent (project-buffer-node->parent (ewoc-data node))))
872 (when parent
873 (while (not (eq (project-buffer-node->type (ewoc-data parent)) 'project))
874 (setq parent (project-buffer-node->parent (ewoc-data parent))))
875 parent))))
878 (defun project-buffer-search-project-node(status project-name)
879 "Return the node of the project node named PROJECT-NAME or nil if absent"
880 (if (string-equal (car project-buffer-cache-project) project-name)
881 (cdr project-buffer-cache-project)
882 (let ((node (ewoc-nth status 0)))
883 (while (and node
884 (or (not (eq (project-buffer-node->type (ewoc-data node)) 'project))
885 (not (string-equal (project-buffer-node->name (ewoc-data node)) project-name))))
886 (setq node (ewoc-next status node)))
887 node)))
890 (defun project-buffer-set-project-platforms-data(status project platform-list)
891 "Attached the list of platform contained in PLATFORM-LIST to the project named PROJECT."
892 (let ((node (project-buffer-search-project-node status project)))
893 ;; Now, if the project has been found:
894 (when node
895 (setf (project-buffer-node->platform-list (ewoc-data node)) platform-list)
896 ;; also:
897 (while platform-list
898 (add-to-list 'project-buffer-platforms-list (pop platform-list) t))
899 (unless project-buffer-current-platform
900 (setq project-buffer-current-platform (car project-buffer-platforms-list)))))
901 (project-buffer-refresh-ewoc-hf status))
904 (defun project-buffer-set-project-build-configurations-data(status project build-configuration-list)
905 "Attached the list build configurations in BUILD-CONFIGURATION-LIST to the project named PROJECT."
906 (let ((node (project-buffer-search-project-node status project)))
907 ;; Now, if the project has been found:
908 (when node
909 (setf (project-buffer-node->build-configurations-list (ewoc-data node)) build-configuration-list)
910 ;; also:
911 (while build-configuration-list
912 (add-to-list 'project-buffer-build-configurations-list (pop build-configuration-list) t))
913 (unless project-buffer-current-build-configuration
914 (setq project-buffer-current-build-configuration (car project-buffer-build-configurations-list)))))
915 (project-buffer-refresh-ewoc-hf status))
918 (defun project-buffer-insert-node(status data)
919 "Insert a file in alphabetic order in it's project/directory."
920 (let ((node (ewoc-nth status 0))
921 (folder-data (project-buffer-extract-folder (project-buffer-node->name data) (project-buffer-node->type data)))
922 (name-data (file-name-nondirectory (project-buffer-node->name data)))
923 (type-data (project-buffer-node->type data))
924 (proj-data (project-buffer-node->project data))
925 (node-data nil)
926 (here nil)
927 (proj-found nil)
928 (folder nil)
929 (hidden-flag nil)
930 (skip nil)
931 (proj-root-node nil)
932 (folder-node nil)
933 (parent-node nil)
935 (when (eq type-data 'folder)
936 (error "Not supported -- in particular project-buffer-directory-lessp may returns a incorrect value"))
939 ;; Cache check:
940 (when project-buffer-cache-project
941 (cond
942 ;; cache-project < current-project -> we can start the search from here (at least).
943 ((string-lessp (car project-buffer-cache-project) proj-data)
944 (setq node (cdr project-buffer-cache-project)
945 project-buffer-cache-subdirectory nil))
947 ;; cache-project == current-project -> check the folders...
948 ((string-equal (car project-buffer-cache-project) proj-data)
949 ;; cache-subdir < current-subdir -> we can start from here.
950 ;; cache-subdir = current-subdir -> good starting point
951 (if (and project-buffer-cache-subdirectory
952 folder-data
953 (or (string-equal (car project-buffer-cache-subdirectory) folder-data)
954 (project-buffer-directory-lessp (car project-buffer-cache-subdirectory) folder-data 'folder)))
955 (setq node (cdr project-buffer-cache-subdirectory)
956 proj-root-node (cdr project-buffer-cache-project)
957 proj-found t)
958 (setq node (cdr project-buffer-cache-project)
959 project-buffer-cache-subdirectory nil)))
960 ;; other wise: cache miss...
962 (setq project-buffer-cache-project nil
963 project-buffer-cache-subdirectory nil))))
965 ;; Search where to insert the node:
966 (while (and node (not here) (not skip))
967 (setq node-data (ewoc-data node))
969 (cond
970 ;; data.project < node.project -> insert here...
971 ((string-lessp proj-data (project-buffer-node->project node-data))
972 (if (eq (project-buffer-node->type data) 'project)
973 (setq here node)
974 (setq here (and proj-found node)
975 skip (not proj-found))))
977 ;; node.project == data.project -> check folder/file name
978 ((string-equal proj-data (project-buffer-node->project node-data))
979 (if (eq (project-buffer-node->type data) 'project)
980 ;; If we're trying to add the project when the project already exist... we'll skip it.
981 (setq skip t)
982 ;; Otherwise:
983 (let* ((folder-db (project-buffer-extract-folder (project-buffer-node->name node-data) (project-buffer-node->type node-data)))
984 (name-db (file-name-nondirectory (project-buffer-node->name node-data)))
985 (type-db (project-buffer-node->type node-data)))
986 ;; Are we're on the project line???
987 (if (eq type-db 'project)
988 (setq proj-root-node node)
989 (if (and folder-db folder-data)
990 ;; Both the current node and the new one have a directory
991 (progn (when (and (eq type-db 'folder)
992 (project-buffer-parent-of-p (project-buffer-node->name data) folder-db))
993 (setq folder-node node))
994 (cond ((project-buffer-directory-lessp folder-data folder-db type-db)
995 (setq here node))
997 ((string-equal folder-data folder-db)
998 (when (eq type-db 'folder)
999 (setq folder-node node))
1000 (setq folder folder-data)
1001 (if (eq type-data 'folder)
1002 (setq skip t)
1003 (unless (eq type-db 'folder)
1004 (when (string-lessp name-data name-db)
1005 (setq here node)))))
1007 (t (setq folder folder-db))))
1008 ;; Either:
1009 ;; - the current node has no folder, meaning:
1010 ;; -> either the new node has a directory in which case we'll add it here.
1011 ;; -> or we'll search for the right place to add it.
1012 ;; - the current node has a folder, meaning:
1013 ;; -> the new one has no folder, therefore, we need to carry on until we reach the no-folder area.
1014 (unless folder-db
1015 (if folder-data
1016 (setq here node)
1017 (when (string-lessp name-data name-db)
1018 (setq here node)))))))
1019 (setq proj-found t))
1022 ;; Carry on...
1023 (setq node (ewoc-next status node)))
1025 ;; Insert before here...
1026 (when (not skip)
1028 ;; Here we can set the parent folder:
1029 (if folder-node
1030 (setf (project-buffer-node->parent data) folder-node)
1031 (setf (project-buffer-node->parent data) proj-root-node))
1033 ;; Once the node added we will need to check if it should be hidden or not.
1034 ;; At first, if it's a file, it will be hidden to not have any glitch in the displayed buffer
1035 (if (eq type-data 'project)
1036 (progn (setf (project-buffer-node->project-collapsed data) project-buffer-new-project-collapsed)
1037 (setf (project-buffer-node->collapsed data) project-buffer-new-project-collapsed)
1038 (add-to-list 'project-buffer-projects-list name-data)
1039 (unless project-buffer-master-project
1040 (setq project-buffer-master-project (cons name-data nil)))) ; to prevent blinking
1041 (progn (setf (project-buffer-node->hidden data) t)
1042 (setf (project-buffer-node->project-collapsed data) (project-buffer-node->project-collapsed (ewoc-data (project-buffer-node->parent data))))
1043 (unless proj-root-node
1044 (error "Project '%s' not found" proj-data))))
1046 (if here
1047 (setq node (ewoc-enter-before status here data))
1048 (setq node (ewoc-enter-last status data)))
1050 (when (eq type-data 'project)
1051 (unless (cdr project-buffer-master-project)
1052 (setq project-buffer-master-project (cons name-data node)))
1053 (setq proj-root-node node))
1057 ;; If it's not a project type, search up in all possible parent to see if the node is supposed to be visible or not
1058 (unless (eq type-data 'project)
1059 (let* ((shown t)
1060 (parent (project-buffer-find-node-up status node)))
1061 (setf (project-buffer-node->project-collapsed data) (project-buffer-node->project-collapsed (ewoc-data parent)))
1062 (setq shown (not (and parent (project-buffer-node->collapsed (ewoc-data parent)))))
1063 (while (and parent
1064 shown
1065 (not (eq (project-buffer-node->type (ewoc-data parent)) 'project)))
1066 (setq parent (project-buffer-find-node-up status parent))
1067 (setq shown (not (and parent (project-buffer-node->collapsed (ewoc-data parent)))))
1069 (setq hidden-flag (not shown)))
1070 (unless hidden-flag
1071 (setf (project-buffer-node->hidden data) nil)
1072 (ewoc-invalidate status node)))
1074 ;; In case some folder needed to be created:
1075 (when folder-data
1076 (let* ((db-list (and folder (split-string folder "/")))
1077 (curr-list (split-string folder-data "/"))
1078 (cnt 0))
1079 (while (and (< cnt (length curr-list))
1080 (< cnt (length db-list))
1081 (string= (nth cnt db-list) (nth cnt curr-list)))
1082 (setq cnt (1+ cnt)))
1083 ;; Add the extra folder:
1084 (if (< cnt (length curr-list))
1085 (let ((ndx 0)
1086 (str nil))
1087 (while (< ndx cnt)
1088 (setq str (or (and str (concat str "/" (nth ndx curr-list)))
1089 (nth ndx curr-list)))
1090 (setq ndx (1+ ndx)))
1091 (while (< ndx (length curr-list))
1092 (setq str (or (and str (concat str "/" (nth ndx curr-list)))
1093 (nth ndx curr-list)))
1095 (setq parent-node (or folder-node proj-root-node))
1096 (let ((new-data (project-buffer-create-node str 'folder folder proj-data hidden-flag)))
1097 (setf (project-buffer-node->project-collapsed new-data) (project-buffer-node->project-collapsed data))
1098 (setq folder-node (ewoc-enter-before status node new-data)))
1099 (setf (project-buffer-node->parent (ewoc-data folder-node)) parent-node)
1101 (setf (project-buffer-node->parent data) folder-node)
1102 (setq ndx (1+ ndx)))))
1106 ;; Save the project root node:
1107 ;; - to speed up the next insert (we stop looking for the project if it's the same one)
1108 (setq project-buffer-cache-project (cons proj-data proj-root-node))
1109 (setq project-buffer-cache-subdirectory (and folder-node
1110 (cons folder-data folder-node)))
1114 (defun project-buffer-delete-node(status node &optional dont-delete-project)
1115 "Delete a specific node.
1116 Also cleanup with empty folder/project resulting of the deletion."
1117 (let ((parent-node (project-buffer-node->parent (ewoc-data node)))
1118 (project (project-buffer-node->project (ewoc-data node)))
1119 (inhibit-read-only t))
1120 ;; Delete the found node:
1121 (ewoc-delete status node)
1123 ;; Now it's time to check the parent node the file belong to:
1124 (while parent-node
1125 (let ((next-node (ewoc-next status parent-node))
1126 (parent-data (ewoc-data parent-node)))
1127 (if (and next-node
1128 (eq (project-buffer-node->parent (ewoc-data next-node)) parent-node))
1129 (setq parent-node nil)
1130 (let ((new-parent-node (and (not (eq (project-buffer-node->type parent-data) 'project))
1131 (project-buffer-node->parent parent-data))))
1132 (if (not new-parent-node)
1133 (unless dont-delete-project
1134 (project-buffer-delete-project-node status project parent-node))
1135 (ewoc-delete status parent-node))
1136 (setq parent-node new-parent-node))
1141 (defun project-buffer-delete-file-node(status name project &optional dont-delete-project)
1142 "Delete the node named NAME which belongs to PROJECT.
1143 Empty folder node will also be cleared up."
1144 (let* ((node (project-buffer-search-node status name project)))
1145 (when node
1146 (project-buffer-delete-node status node dont-delete-project))
1150 (defun project-buffer-delete-folder-node(status folder-node &optional dont-delete-project)
1151 "Delete the folder FOLDER-NODE and all it's files.
1152 Empty parent folder node will also be cleared up."
1153 (let* ((folder (and folder-node (project-buffer-node->name (ewoc-data folder-node)))))
1154 (when folder
1155 ;; First, let delete the content of the folder:
1156 (let ((inhibit-read-only t))
1157 (save-excursion
1158 (let* ((node (ewoc-next status folder-node))
1159 (node-data (and node (ewoc-data node)))
1160 next-node)
1161 (while (and node
1162 (not (eq (project-buffer-node->type node-data) 'project))
1163 (project-buffer-parent-of-p (project-buffer-node->name node-data) folder))
1164 (setq next-node (ewoc-next status node))
1165 (ewoc-delete status node)
1166 (setq node next-node
1167 node-data (and node (ewoc-data node)))))))
1168 ;; Now let's delete the node:
1169 (project-buffer-delete-node status folder-node dont-delete-project)
1173 (defun project-buffer-delete-project-node(status proj-name proj-node)
1174 "Delete the project node PROJ-NODE.
1175 Each files/folder under the project will also be deleted."
1176 (when proj-node
1177 (let ((proj-data (ewoc-data proj-node))
1178 (prev-node (ewoc-prev status proj-node))
1179 (curr-node proj-node))
1180 ;; Let's start by removing the project from the project list:
1181 (setq project-buffer-projects-list (remove proj-name project-buffer-projects-list))
1183 ;; Check the cache:
1184 (when (string-equal (car project-buffer-cache-project) proj-name)
1185 (setq project-buffer-cache-project nil)
1186 (setq project-buffer-cache-subdirectory nil))
1188 ;; Delete the nodes:
1189 (let ((inhibit-read-only t))
1190 (while (and curr-node
1191 (string-equal (project-buffer-node->project (ewoc-data curr-node)) proj-name))
1192 (let ((next-node (ewoc-next status curr-node)))
1193 (ewoc-delete status curr-node)
1194 (setq curr-node next-node)
1197 ;; Now: the master project may need to be readjusted
1198 (when (string-equal proj-name (car project-buffer-master-project))
1199 (if curr-node
1200 ;; By default the next project become the new master one:
1201 (progn (setq project-buffer-master-project (cons (project-buffer-node->project (ewoc-data curr-node)) curr-node))
1202 (ewoc-invalidate status curr-node))
1203 ;; Otherwise: if the previous node is invalid, it's project will become the new master one:
1204 (if prev-node
1205 (let ((prev-parent (project-buffer-node->parent (ewoc-data prev-node))))
1206 (while (not (eq (project-buffer-node->type (ewoc-data prev-parent)) 'project))
1207 (setq prev-parent (project-buffer-node->parent (ewoc-data prev-parent))))
1208 (setq project-buffer-master-project (cons (project-buffer-node->project (ewoc-data prev-parent)) prev-parent))
1209 (ewoc-invalidate status prev-parent))
1210 (setq project-buffer-master-project nil))))
1214 (defun project-buffer-refresh-all-items(status)
1215 "Refresh all ewoc item from the buffer."
1216 (ewoc-map (lambda (info) t) status)) ; (ewoc-refresh status) doesn't work properly.
1219 (defun project-buffer-perform-action-hook(action)
1220 "Call the user hook to perform ACTION."
1221 (run-hook-with-args 'project-buffer-action-hook
1222 action
1223 (car project-buffer-master-project)
1224 (project-buffer-node->filename (ewoc-data (cdr project-buffer-master-project)))
1225 project-buffer-current-platform
1226 project-buffer-current-build-configuration))
1229 (defun project-buffer-search-and-mark-files(status regexp project marked-flag)
1230 "Search REGEXP in with all files if PROJECT is nil or in each file of the specified PROJECT.
1231 If REGEXP is found, the marked-flag field associated to the file get set to MARKED-FLAG
1232 The function returns the number of files whose marked-flag field changed"
1233 (let ((count 0))
1234 (ewoc-map (lambda (node)
1235 (when (and (eq (project-buffer-node->type node) 'file) ; check only files
1236 (or (not project) ; ( if a project is specified,
1237 (string-equal (project-buffer-node->project node) project)) ; make sure it matches the node's project )
1238 (not (eq (project-buffer-node->marked node) marked-flag))) ; which aren't already (un)marked (based on request)
1239 ;; Check if the file contain the regexp:
1240 (let ((filename (project-buffer-node->filename node)))
1241 (when (and filename
1242 (file-readable-p filename)
1243 (let ((fbuf (get-file-buffer filename)))
1244 (message "Project '%s' -- Searching in '%s'" (project-buffer-node->project node) (project-buffer-node->name node))
1245 (if fbuf
1246 (with-current-buffer fbuf
1247 (save-excursion
1248 (goto-char (point-min))
1249 (re-search-forward regexp nil t)))
1250 (with-temp-buffer
1251 (insert-file-contents filename)
1252 (goto-char (point-min))
1253 (re-search-forward regexp nil t)))))
1254 (setf (project-buffer-node->marked node) marked-flag)
1255 (setq count (1+ count))
1256 t )))) ; to force the update of the display.
1257 status)
1258 count))
1261 (defun project-buffer-refine-mark-files(status regexp marked-flag)
1262 "Search REGEXP in with all marked files.
1263 If REGEXP is found, the marked-flag field associated to the file get set to MARKED-FLAG
1264 The function returns the number of files whose marked-flag field changed
1265 Note: if no files are marked, the search will occur in all existing files of the project"
1266 (let ((count 0)
1267 marked-file-found)
1268 (ewoc-map (lambda (node)
1269 (when (and (eq (project-buffer-node->type node) 'file) ; check only files
1270 (project-buffer-node->marked node)) ; which are already marked
1271 (setq marked-file-found t)
1272 ;; Check if the file contain the regexp:
1273 (let ((filename (project-buffer-node->filename node)))
1274 (when (and filename
1275 (file-readable-p filename)
1276 (let ((found (let ((fbuf (get-file-buffer filename)))
1277 (message "Project '%s' -- Searching in '%s'" (project-buffer-node->project node) (project-buffer-node->name node))
1278 (if fbuf
1279 (with-current-buffer fbuf
1280 (save-excursion
1281 (goto-char (point-min))
1282 (re-search-forward regexp nil t)))
1283 (with-temp-buffer
1284 (insert-file-contents filename)
1285 (goto-char (point-min))
1286 (re-search-forward regexp nil t))))))
1287 (or (and found (not marked-flag))
1288 (and (not found) marked-flag))))
1289 (setf (project-buffer-node->marked node) nil)
1290 (setq count (1+ count))
1291 t )))) ; to force the update of the display.
1292 status)
1293 (if marked-file-found
1294 count
1295 ( - 0 (project-buffer-search-and-mark-files status regexp nil marked-flag)))))
1298 (defun project-buffer-set-master-project(status project-name)
1299 "Set PROJECT-NAME to be the new master project."
1300 (let ((old-node (cdr project-buffer-master-project))
1301 (cur-node (project-buffer-search-project-node status project-name)))
1302 (when cur-node
1303 ;; Let's replace the old node by the new one
1304 (setq project-buffer-master-project (cons (project-buffer-node->name (ewoc-data cur-node)) cur-node))
1305 ;; Force the refresh:
1306 (ewoc-invalidate status old-node)
1307 (ewoc-invalidate status cur-node)
1308 (ewoc-goto-node status cur-node))))
1312 (defun project-buffer-raw-print-hooks(hook-symbol hook-list)
1313 "Print a hooks block in the current buffer."
1314 (print (list 'begin 'hook hook-symbol) (current-buffer))
1315 (while hook-list
1316 (let ((hook-item (pop hook-list)))
1317 (print (cond ((booleanp hook-item)
1318 (list 'value hook-item))
1319 ((symbolp hook-item)
1320 (list 'symbol hook-item (abbreviate-file-name (symbol-file hook-item))))
1321 ((functionp hook-item)
1322 (list 'value hook-item))
1323 (t (error "Unknown type found in the hook list")))
1324 (current-buffer))))
1325 (print (list 'end 'hook hook-symbol) (current-buffer)))
1328 (defun project-buffer-raw-print-locals(local-list)
1329 "Print a local block in the current-buffer."
1330 (print (list 'begin 'locals) (current-buffer))
1331 (while local-list
1332 (print (pop local-list) (current-buffer)))
1333 (print (list 'end 'locals) (current-buffer)))
1336 (defun project-buffer-read-header(status data-buffer &optional set-buffer-name set-current-directory)
1337 "Read the header of the saved file from the DATA-BUFFER."
1338 (let ((header-data (read data-buffer)))
1339 (unless (and header-data
1340 (listp header-data)
1341 (eq (car header-data) 'project-buffer-mode))
1342 (error "Not in project-buffer save file"))
1343 ;; The header list is: '(project-buffer-mode version buffer-name directory)
1344 (when set-buffer-name
1345 (rename-buffer (nth 2 header-data) t))
1346 (when set-current-directory
1347 (cd (nth 3 header-data)))
1348 ;; Finally, let's return the version:
1349 (nth 1 header-data)))
1352 (defun project-buffer-read-block-hook(status data-buffer block-header run-mode-hooks)
1353 "Read a project-buffer-hook block; set the local hook and
1354 attempt to load the definition file if a hook function isnt't bound."
1355 ;; block-header should be: '(begin hook hook-symbol)
1356 (unless (and (listp block-header)
1357 (eq (car block-header) 'begin)
1358 (eq (nth 1 block-header) 'hook)
1359 (= (length block-header) 3))
1360 (error "Invalid block-header"))
1361 (let ((hook-symbol (nth 2 block-header)))
1362 (unless (symbolp hook-symbol)
1363 (error "Invalid block-header"))
1364 (if (and (boundp hook-symbol)
1365 (listp (eval hook-symbol)))
1366 ;; If the hook variable exists:
1367 (let ((block-line (read data-buffer)))
1368 (add-to-list 'project-buffer-hooks-to-save hook-symbol)
1369 (while (and block-line
1370 (not (and (listp block-line)
1371 (eq (car block-line) 'end)
1372 (eq (nth 1 block-line) 'hook)
1373 (eq (nth 2 block-line) hook-symbol))))
1374 (if (listp block-line)
1375 (cond ((eq (car block-line) 'symbol)
1376 (let ((func (nth 1 block-line))
1377 (file (nth 2 block-line)))
1378 (add-hook hook-symbol func nil t)
1379 (when (and (not (fboundp func))
1380 (file-exists-p file)
1381 (file-readable-p file))
1382 (load-file file))))
1383 ((eq (car block-line) 'value)
1384 (add-hook hook-symbol (nth 1 block-line) nil t))
1385 (t (error "Unknown hook type: %s!" (car block-line))))
1386 (error "Unknown hook line: %s" block-line))
1387 (setq block-line (read data-buffer)))
1388 ;; Check if it's the mode-hook:
1389 (when (and run-mode-hooks
1390 (eq hook-symbol 'project-buffer-mode-hook))
1391 (run-hooks 'project-buffer-mode-hook))
1393 ;; If the hook variable doesn't exist, we just skip the block:
1394 (project-buffer-skip-block status data-buffer block-header))))
1397 (defun project-buffer-read-block-node-list(status data-buffer block-header)
1398 "Read a project-buffer-node-list block; add each node to the
1399 project-buffer context."
1400 ;; block-header should be: '(begin node-list)
1401 (unless (and (listp block-header)
1402 (eq (car block-header) 'begin)
1403 (eq (nth 1 block-header) 'node-list))
1404 (error "Invalid block-header"))
1405 (let ((block-line (read data-buffer)))
1406 (while (and block-line
1407 (not (and (listp block-line)
1408 (eq (car block-line) 'end)
1409 (eq (nth 1 block-line) 'node-list))))
1410 (if (and (listp block-line)
1411 (= (length block-line) 7))
1412 (let ((name (nth 0 block-line))
1413 (type (nth 1 block-line))
1414 (filename (nth 2 block-line))
1415 (project (nth 3 block-line))
1416 (platform-list (nth 4 block-line))
1417 (build-configurations-list (nth 5 block-line))
1418 (user-data (nth 6 block-line)))
1419 (let ((data (project-buffer-create-node name type filename project)))
1420 (project-buffer-insert-node status data)
1421 (when platform-list
1422 (project-buffer-set-project-platforms-data status project platform-list))
1423 (when build-configurations-list
1424 (project-buffer-set-project-build-configurations-data status project build-configurations-list))
1425 (when user-data
1426 (setf (project-buffer-node->user-data data) user-data))) )
1427 (error "Unknown node-list line: %s" block-line))
1428 (setq block-line (read data-buffer)))))
1431 (defun project-buffer-read-block-locals(status data-buffer block-header)
1432 "Read a project-buffer-locals block; set the local variable of
1433 the buffer with their specified values. Skip non local
1434 variable."
1435 ;; block-header should be: '(begin locals)
1436 (unless (and (listp block-header)
1437 (eq (car block-header) 'begin)
1438 (eq (nth 1 block-header) 'locals))
1439 (error "Invalid block-header"))
1440 (let ((block-line (read data-buffer)))
1441 (while (and block-line
1442 (not (and (listp block-line)
1443 (eq (car block-line) 'end)
1444 (eq (nth 1 block-line) 'locals))))
1445 (if (and (listp block-line)
1446 (symbolp (car block-line)))
1447 (progn (unless (local-variable-p (car block-line))
1448 (make-local-variable (car block-line)))
1449 (set (car block-line) (cdr block-line))
1450 (add-to-list 'project-buffer-locals-to-save (car block-line)))
1451 (error "Unknown local line: %s" block-line))
1452 (setq block-line (read data-buffer)))))
1456 (defun project-buffer-skip-block(status data-buffer block-header)
1457 "Skip project-buffer block."
1458 (unless (and (listp block-header)
1459 (eq (car block-header) 'begin))
1460 (error "Invalid block-header"))
1461 (let ((block-line (read data-buffer))
1462 (block-type (nth 1 block-header)))
1463 (while (and block-line
1464 (not (and (listp block-line)
1465 (eq (car block-line) 'end)
1466 (eq (nth 1 block-line) block-type))))
1467 (setq block-line (read data-buffer)))))
1470 (defun project-buffer-read-line-master-project(status block-header)
1471 "Read the project-buffer-master-project line."
1472 (unless (and (listp block-header)
1473 (eq (car block-header) 'one-line)
1474 (eq (nth 1 block-header) 'master-project))
1475 (error "Invalid block-header"))
1476 (project-buffer-set-master-project status (nth 2 block-header)))
1479 (defun project-buffer-read-block(status data-buffer run-mode-hooks)
1480 "Read and parse the next block from the DATA-BUFFER."
1481 (let ((block-header (read data-buffer))
1482 (goon t))
1483 (if (listp block-header)
1484 (cond ((eq (car block-header) 'begin)
1485 (cond ((eq (nth 1 block-header) 'hook)
1486 (project-buffer-read-block-hook status data-buffer block-header run-mode-hooks))
1487 ((eq (nth 1 block-header) 'node-list)
1488 (project-buffer-read-block-node-list status data-buffer block-header))
1489 ((eq (nth 1 block-header) 'locals)
1490 (project-buffer-read-block-locals status data-buffer block-header))
1492 (project-buffer-skip-block status data-buffer block-header))))
1493 ((eq (car block-header) 'one-line)
1494 (cond ((eq (nth 1 block-header) 'master-project)
1495 (project-buffer-read-line-master-project status block-header)))))
1496 (setq goon (not (and (symbolp block-header) (eq block-header 'eof))))
1498 goon) ;; carry on
1502 (defun project-buffer-set-view-mode(status view-mode)
1503 "Set the view mode to VIEW-MODE."
1504 (unless (eq project-buffer-view-mode view-mode)
1505 (let ((node (ewoc-locate project-buffer-status)))
1506 (setq project-buffer-view-mode view-mode)
1507 (message "View mode set to: %s" project-buffer-view-mode)
1508 (project-buffer-refresh-all-items status)
1509 (project-buffer-refresh-ewoc-hf status)
1510 (ewoc-goto-node status node))))
1513 (defun project-buffer-search-node(status name project)
1514 "Search a node named NAME which belongs to PROJECT."
1515 (let ((node (ewoc-nth status 0))
1516 (folder-data (project-buffer-extract-folder name 'file))
1517 (proj-data project)
1518 (found nil)
1519 (folder-found nil)
1520 (node-data nil))
1522 ;; Cache check: <no cache update>
1523 (when project-buffer-cache-project
1524 (cond
1525 ;; cache-project < current-project -> we can start the search from here (at least).
1526 ((string-lessp (car project-buffer-cache-project) proj-data)
1527 (setq node (cdr project-buffer-cache-project)))
1529 ;; cache-project == current-project -> check the folders...
1530 ((string-equal (car project-buffer-cache-project) proj-data)
1531 ;; cache-subdir < current-subdir -> we can start from here.
1532 ;; cache-subdir = current-subdir -> good starting point
1533 (if (and project-buffer-cache-subdirectory
1534 folder-data
1535 (or (string-equal (car project-buffer-cache-subdirectory) folder-data)
1536 (project-buffer-directory-lessp (car project-buffer-cache-subdirectory) folder-data 'folder)))
1537 (setq node (cdr project-buffer-cache-subdirectory))
1538 (setq node (cdr project-buffer-cache-project))))
1539 ;; other wise: cache miss...
1542 ;; Search the node:
1543 (while (and node (not found))
1544 (setq node-data (ewoc-data node))
1546 (cond
1547 ;; data.project < node.project -> not found...
1548 ((string-lessp proj-data (project-buffer-node->project node-data))
1549 (setq node nil))
1551 ;; node.project == data.project -> check folder/file name
1552 ((string-equal proj-data (project-buffer-node->project node-data))
1553 (let* ((folder-db (project-buffer-extract-folder (project-buffer-node->name node-data) (project-buffer-node->type node-data)))
1554 (type-db (project-buffer-node->type node-data)))
1555 ;; Make sure it's not the project line:
1556 (unless (eq type-db 'project)
1557 (setq found (and (string-equal (project-buffer-node->name node-data) name) node))))))
1559 ;; next node:
1560 (setq node (and node (ewoc-next status node))))
1562 ;; Final result:
1563 found
1568 ;; External functions:
1572 (defun project-buffer-mode (&optional skip-mode-hooks)
1573 "Major mode to view project.
1575 Commands:
1576 \\{project-buffer-mode-map}"
1577 (kill-all-local-variables)
1578 (buffer-disable-undo)
1579 (setq mode-name "project-buffer"
1580 major-mode 'project-buffer-mode
1581 buffer-read-only t)
1582 (use-local-map project-buffer-mode-map)
1583 (let ((buffer-read-only nil))
1584 (erase-buffer)
1585 (let ((status (ewoc-create 'project-buffer-prettyprint "" "" t)))
1586 (make-local-variable 'project-buffer-status)
1587 (make-local-variable 'project-buffer-view-mode)
1588 (make-local-variable 'project-buffer-cache-project)
1589 (make-local-variable 'project-buffer-cache-subdirectory)
1590 (make-local-variable 'project-buffer-platforms-list)
1591 (make-local-variable 'project-buffer-current-platform)
1592 (make-local-variable 'project-buffer-build-configurations-list)
1593 (make-local-variable 'project-buffer-current-build-configuration)
1594 (make-local-variable 'project-buffer-master-project)
1595 (make-local-variable 'project-buffer-projects-list)
1596 (make-local-variable 'project-buffer-file-name)
1597 (make-local-variable 'project-buffer-locals-to-save)
1598 (make-local-variable 'project-buffer-hooks-to-save)
1600 (setq project-buffer-status status)
1601 (setq project-buffer-view-mode 'folder-view)
1602 (setq project-buffer-cache-project nil)
1603 (setq project-buffer-cache-subdirectory nil)
1604 (setq project-buffer-platforms-list nil)
1605 (setq project-buffer-current-platform nil)
1606 (setq project-buffer-build-configurations-list nil)
1607 (setq project-buffer-current-build-configuration nil)
1608 (setq project-buffer-master-project nil)
1609 (setq project-buffer-projects-list nil)
1610 (setq project-buffer-file-name nil)
1611 (setq project-buffer-locals-to-save '(project-buffer-view-mode project-buffer-current-platform project-buffer-current-build-configuration))
1612 (setq project-buffer-hooks-to-save '(project-buffer-mode-hook project-buffer-action-hook project-buffer-post-load-hook project-buffer-post-find-file-hook))
1614 (project-buffer-refresh-ewoc-hf status)
1616 (unless skip-mode-hooks
1617 (run-hooks 'project-buffer-mode-hook))
1621 (defun project-buffer-insert (name type filename project)
1622 "Insert a file in alphabetic order in it's project/directory.
1624 NAME is the name of the file in the project with it's virtual project directory,
1625 both name and directory may be virtual
1626 TYPE type of the node in the project: should be either 'project or 'file
1627 FILENAME should be either a full path to the project's file or a relative path based
1628 on the current directory of the buffer
1629 PROJECT is the name of the project in which to insert the node
1630 note: regarding the project node, it's recommended to have NAME = PROJECT"
1631 (unless project-buffer-status (error "Not in project-buffer buffer"))
1632 (project-buffer-insert-node project-buffer-status
1633 (project-buffer-create-node name type filename project)))
1635 (defun project-buffer-delete-file (name project &optional dont-delete-project)
1636 "Delete the node named NAME which belongs to PROJECT.
1637 Empty folder node will also be cleared up. If no more file
1638 remain in the project; the project will also be deleted unless
1639 DONT-DELETE-PROJECT is set."
1640 (unless project-buffer-status (error "Not in project-buffer buffer"))
1641 (project-buffer-delete-file-node project-buffer-status name project dont-delete-project))
1644 (defun project-buffer-delete-folder (name project &optional dont-delete-project)
1645 "Delete the node named NAME which belongs to PROJECT."
1646 (unless project-buffer-status (error "Not in project-buffer buffer"))
1647 (project-buffer-delete-folder-node project-buffer-status
1648 (project-buffer-search-node project-buffer-status name project)
1649 dont-delete-project))
1652 (defun project-buffer-delete-project (project)
1653 "Delete the project PROJECT.
1654 Each files/folder under the project will also be deleted."
1655 (unless project-buffer-status (error "Not in project-buffer buffer"))
1656 (project-buffer-delete-project-node project-buffer-status
1657 project
1658 (project-buffer-search-project-node project-buffer-status project)))
1661 (defun project-buffer-set-project-platforms (project platform-list)
1662 "Attached the list of platform contained in PLATFORM-LIST to the project named PROJECT."
1663 (unless project-buffer-status (error "Not in project-buffer buffer"))
1664 (project-buffer-set-project-platforms-data project-buffer-status
1665 project
1666 platform-list))
1668 (defun project-buffer-set-project-build-configurations (project build-configuration-list)
1669 "Attached the list build configurations in BUILD-CONFIGURATION-LIST to the project named PROJECT."
1670 (unless project-buffer-status (error "Not in project-buffer buffer"))
1671 (project-buffer-set-project-build-configurations-data project-buffer-status
1672 project
1673 build-configuration-list))
1676 (defun project-buffer-raw-save (filename)
1677 "Save the project data in FILENAME; the project can later be
1678 reloaded through `project-buffer-raw-load' function."
1679 (unless project-buffer-status (error "Not in project-buffer buffer"))
1680 (let* ((status project-buffer-status)
1681 (node (ewoc-nth status 0))
1682 (buf-name (buffer-name))
1683 (buf-dir default-directory)
1684 (project-buffer (current-buffer))
1685 (hooks-list (mapcar (lambda (item) (cons item (and (local-variable-p item) (eval item))))
1686 project-buffer-hooks-to-save))
1687 (locals-list (remove nil
1688 (mapcar (lambda (item) (and (local-variable-p item) (cons item (eval item))))
1689 project-buffer-locals-to-save))))
1690 (with-temp-buffer
1691 ;; First, let's write a quick header:
1692 (print (list 'project-buffer-mode
1693 project-buffer-mode-version
1694 buf-name
1695 buf-dir) (current-buffer))
1696 ;; Save the hooks:
1697 (mapcar (lambda (item) (when (cdr item) (project-buffer-raw-print-hooks (car item) (cdr item))))
1698 hooks-list)
1699 ;; Save the locals:
1700 (project-buffer-raw-print-locals locals-list)
1701 ;; Save each nodes:
1702 (print (list 'begin 'node-list) (current-buffer))
1703 (while node
1704 (let ((data (ewoc-data node)))
1705 (unless (eq (project-buffer-node->type data) 'folder)
1706 (print (list (project-buffer-node->name data)
1707 (project-buffer-node->type data)
1708 (project-buffer-node->filename data)
1709 (project-buffer-node->project data)
1710 (project-buffer-node->platform-list data)
1711 (project-buffer-node->build-configurations-list data)
1712 (project-buffer-node->user-data data))
1713 (current-buffer))))
1714 (setq node (ewoc-next status node)))
1715 (print (list 'end 'node-list) (current-buffer))
1716 ;; Save the master project:
1717 (print (list 'one-line 'master-project (car (buffer-local-value 'project-buffer-master-project project-buffer)))
1718 (current-buffer))
1719 ;; End of file:
1720 (print 'eof (current-buffer))
1721 ;; Finally: write the file.
1722 (write-file filename))))
1725 (defun project-buffer-raw-load (filename &optional set-buffer-name run-mode-hooks)
1726 "Load a project saved by `project-buffer-raw-data'.
1727 This function does not restore the mode and assume the
1728 project-buffer-mode to be set. It doesn't clear the existing
1729 nodes either."
1730 (unless project-buffer-status (error "Not in project-buffer buffer"))
1731 (let ((project-buffer (current-buffer))
1732 (status project-buffer-status))
1733 (with-temp-buffer
1734 (insert-file filename)
1735 (goto-char (point-min))
1736 (let ((data-buffer (current-buffer))
1737 data-version
1738 block-header)
1739 (with-current-buffer project-buffer
1740 (setq data-version (project-buffer-read-header status data-buffer set-buffer-name t))
1741 ;; The rest of the file is defined by blocks:
1742 (while (project-buffer-read-block status data-buffer run-mode-hooks))
1744 (run-hooks 'project-buffer-post-load-hook)
1748 (defun project-buffer-set-file-user-data (name project user-data)
1749 "Attach user data to a node named NAME in the project PROJECT."
1750 (unless project-buffer-status (error "Not in project-buffer buffer"))
1751 (let ((node (project-buffer-search-node project-buffer-status name project)))
1752 (when node
1753 (setf (project-buffer-node->user-data (ewoc-data node)) user-data))))
1756 (defun project-buffer-set-project-user-data (project user-data)
1757 "Attach user data to the project node named PROJECT."
1758 (unless project-buffer-status (error "Not in project-buffer buffer"))
1759 (let ((node (project-buffer-search-project-node project-buffer-status project)))
1760 (when node
1761 (setf (project-buffer-node->user-data (ewoc-data node)) user-data))))
1764 (defun project-buffer-get-file-user-data (name project)
1765 "Retrieve user data to a node named NAME in the project PROJECT."
1766 (unless project-buffer-status (error "Not in project-buffer buffer"))
1767 (let ((node (project-buffer-search-node project-buffer-status name project)))
1768 (when node
1769 (project-buffer-node->user-data (ewoc-data node)))))
1772 (defun project-buffer-get-project-user-data (project)
1773 "Retrieve user data to the project node named PROJECT."
1774 (unless project-buffer-status (error "Not in project-buffer buffer"))
1775 (let ((node (project-buffer-search-project-node project-buffer-status project)))
1776 (when node
1777 (project-buffer-node->user-data (ewoc-data node)))))
1780 (defun project-buffer-get-current-project-name ()
1781 "Retrieve the name of the project the cursor is on."
1782 (unless project-buffer-status (error "Not in project-buffer buffer"))
1783 (let ((node (ewoc-locate project-buffer-status)))
1784 (when node
1785 (project-buffer-node->project (ewoc-data node)))))
1788 (defun project-buffer-get-current-file-data ()
1789 "Retrieve data about the current file the cursor is on.
1790 Return nil if the cursor is not on a file.
1791 If non-nil the return value is a list containing:
1792 '(project-file-name full-path project-name)"
1793 (unless project-buffer-status (error "Not in project-buffer buffer"))
1794 (let* ((node (ewoc-locate project-buffer-status))
1795 (data (and node (ewoc-data node))))
1796 (when (and data (eq (project-buffer-node->type data) 'file))
1797 (list (project-buffer-node->name data)
1798 (project-buffer-node->filename data)
1799 (project-buffer-node->project data)))))
1802 (defun project-buffer-exists-p (name project)
1803 "Return true if a node NAME exists in PROJECT."
1804 (unless project-buffer-status (error "Not in project-buffer buffer"))
1805 (let ((node (project-buffer-search-node project-buffer-status name project)))
1806 (and node t)))
1809 (defun project-buffer-project-exists-p (project)
1810 "Return true if the project PROJECT exists."
1811 (unless project-buffer-status (error "Not in project-buffer buffer"))
1812 (let ((node (project-buffer-search-project-node project-buffer-status project)))
1813 (and node t)))
1816 (defun project-buffer-get-file-path (name project)
1817 "Retrieve the path of the file NAME in PROJECT."
1818 (unless project-buffer-status (error "Not in project-buffer buffer"))
1819 (let ((node (project-buffer-search-node project-buffer-status name project)))
1820 (when node
1821 (project-buffer-node->filename (ewoc-data node)))))
1824 (defun project-buffer-get-current-node-type ()
1825 "Retrieve the type of the current node."
1826 (unless project-buffer-status (error "Not in project-buffer buffer"))
1827 (let ((node (ewoc-locate project-buffer-status)))
1828 (when node
1829 (project-buffer-node->type (ewoc-data node)))))
1832 (defun project-buffer-get-current-node-name ()
1833 "Retrieve the type of the current node."
1834 (unless project-buffer-status (error "Not in project-buffer buffer"))
1835 (let ((node (ewoc-locate project-buffer-status)))
1836 (when node
1837 (project-buffer-node->name (ewoc-data node)))))
1840 (defun project-buffer-get-marked-node-list ()
1841 "Retrieve the list of marked files.
1842 Each node of the returned list are also list as:
1843 '(project-file-name full-path project-name)"
1844 (unless project-buffer-status (error "Not in project-buffer buffer"))
1845 (let* ((status project-buffer-status)
1846 (node (ewoc-nth status 0))
1847 marked-node-list)
1848 (while node
1849 (let ((node-data (ewoc-data node)))
1850 (when (and (eq (project-buffer-node->type node-data) 'file)
1851 (project-buffer-node->marked node-data))
1852 (setq marked-node-list (cons (list (project-buffer-node->name node-data)
1853 (project-buffer-node->filename node-data)
1854 (project-buffer-node->project node-data))
1855 marked-node-list))))
1856 (setq node (ewoc-next status node)))
1857 (reverse marked-node-list)
1863 ;; Interactive commands:
1867 (defun project-buffer-goto-dir-up ()
1868 "Go to the project/folder containing the current file/folder."
1869 (interactive)
1870 (unless project-buffer-status (error "Not in project-buffer buffer"))
1871 (let* ((status project-buffer-status)
1872 (node (ewoc-locate status)))
1873 (setq node (and node (project-buffer-find-node-up status node)))
1874 (when node
1875 (ewoc-goto-node status node))))
1878 (defun project-buffer-goto-dir-up-or-collapsed ()
1879 "Go to the project/folder containing the current file/folder unless the cursor is on a expanded folder/project in which case, it will collapse it."
1880 (interactive)
1881 (unless project-buffer-status (error "Not in project-buffer buffer"))
1882 (let* ((status project-buffer-status)
1883 (node (ewoc-locate status))
1884 (node-data (and node (ewoc-data node))))
1885 (when node
1886 (if (or (eq (project-buffer-node->type node-data) 'file)
1887 (project-buffer-node->collapsed node-data))
1888 (progn (setq node (and node (project-buffer-find-node-up status node)))
1889 (when node (ewoc-goto-node status node)))
1890 (project-buffer-toggle-expand-collapse)
1891 ))))
1894 (defun project-buffer-search-forward-regexp (regexp)
1895 "Search file matching REGEXP."
1896 (interactive "sSearch forward (regexp): ")
1897 (unless project-buffer-status (error "Not in project-buffer buffer"))
1898 (project-buffer-clear-matched-mark project-buffer-status)
1899 (when (and regexp
1900 (> (length regexp) 0))
1901 (let* ((status project-buffer-status)
1902 (node (ewoc-locate status)))
1903 (project-buffer-mark-matching-file project-buffer-status regexp)
1904 ;; goto first match
1905 (while (and node
1906 (or (not (eq (project-buffer-node->type (ewoc-data node)) 'file))
1907 (not (project-buffer-node->matched (ewoc-data node)))))
1908 (setq node (ewoc-next status node)))
1909 ;; if failed: go to the last search instead
1910 (unless node
1911 (setq node (ewoc-locate status))
1912 (while (and node
1913 (or (not (eq (project-buffer-node->type (ewoc-data node)) 'file))
1914 (not (project-buffer-node->matched (ewoc-data node)))))
1915 (setq node (ewoc-prev status node))))
1916 (if node
1917 (ewoc-goto-node status node)
1918 (message "Search failed: %s." regexp)))))
1921 (defun project-buffer-goto-next-match ()
1922 "Go to the next matching."
1923 (interactive)
1924 (unless project-buffer-status (error "Not in project-buffer buffer"))
1925 (let* ((status project-buffer-status)
1926 (node (ewoc-locate status)))
1927 (if node (setq node (ewoc-next status node)))
1928 ;; goto first match
1929 (while (and node
1930 (or (not (eq (project-buffer-node->type (ewoc-data node)) 'file))
1931 (not (project-buffer-node->matched (ewoc-data node)))))
1932 (setq node (ewoc-next status node)))
1933 (if node
1934 (ewoc-goto-node status node)
1935 (message "Failing forward search."))))
1938 (defun project-buffer-goto-prev-match ()
1939 "Go to the previous matching."
1940 (interactive)
1941 (unless project-buffer-status (error "Not in project-buffer buffer"))
1942 (let* ((status project-buffer-status)
1943 (node (ewoc-locate status)))
1944 (if node (setq node (ewoc-prev status node)))
1945 ;; goto first match
1946 (while (and node
1947 (or (not (eq (project-buffer-node->type (ewoc-data node)) 'file))
1948 (not (project-buffer-node->matched (ewoc-data node)))))
1949 (setq node (ewoc-prev status node)))
1950 (if node
1951 (ewoc-goto-node status node)
1952 (message "Failing backward search."))))
1955 (defun project-buffer-quit ()
1956 "Burry project-buffer mode or cancel the research."
1957 (interactive)
1958 (unless project-buffer-status (error "Not in project-buffer buffer"))
1959 (unless (project-buffer-clear-matched-mark project-buffer-status)
1960 (bury-buffer)))
1963 (defun project-buffer-help ()
1964 "Display help for project-buffer mode."
1965 (interactive)
1966 (describe-function 'project-buffer-mode))
1969 (defun project-buffer-next-file (&optional n)
1970 "Move the cursor down N files."
1971 (interactive "p")
1972 (unless project-buffer-status (error "Not in project-buffer buffer"))
1973 (ewoc-goto-next project-buffer-status n))
1976 (defun project-buffer-next-file-or-expand ()
1977 "Go to the project/folder containing the current file/folder unless the cursor is on a expanded folder/project in which case, it will collapse it."
1978 (interactive)
1979 (unless project-buffer-status (error "Not in project-buffer buffer"))
1980 (let* ((status project-buffer-status)
1981 (node (ewoc-locate status))
1982 (node-data (and node (ewoc-data node))))
1983 (when node
1984 (if (or (eq (project-buffer-node->type node-data) 'file)
1985 (not (project-buffer-node->collapsed node-data)))
1986 (ewoc-goto-next status 1)
1987 (project-buffer-toggle-expand-collapse)
1988 ))))
1991 (defun project-buffer-prev-file (&optional n)
1992 "Move the cursor up N files."
1993 (interactive "p")
1994 (unless project-buffer-status (error "Not in project-buffer buffer"))
1995 (ewoc-goto-prev project-buffer-status n))
1998 (defun project-buffer-find-marked-files ()
1999 "Run find-files on the marked files."
2000 (interactive)
2001 (unless project-buffer-status (error "Not in project-buffer buffer"))
2002 (let* ((file-list (project-buffer-get-marked-nodes project-buffer-status))
2003 (cnt 0)
2004 buffer)
2005 (project-buffer-clear-matched-mark project-buffer-status)
2006 (while file-list
2007 (let ((node (pop file-list)))
2008 (when (eq (project-buffer-node->type node) 'file)
2009 (setq buffer (find-file-noselect (project-buffer-node->filename node))
2010 cnt (1+ cnt)))))
2011 (cond ((> cnt 1) (message "Find %i files." cnt))
2012 ((= cnt 1) (display-buffer buffer))
2013 (t (message "No files selected")))))
2016 (defun project-buffer-go-to-previous-project ()
2017 "Go to previous project line."
2018 (interactive)
2019 (unless project-buffer-status (error "Not in project-buffer buffer"))
2020 (let* ((status project-buffer-status)
2021 (node (ewoc-locate project-buffer-status))
2022 (search (ewoc-prev status node)))
2023 (while (and search
2024 (not (eq (project-buffer-node->type (ewoc-data search)) 'project)))
2025 (setq search (ewoc-prev status search)))
2026 (when search
2027 (ewoc-goto-node status search))))
2030 (defun project-buffer-go-to-previous-folder-or-project ()
2031 "If the cursor is on a file, go up to the previous project/folder.
2032 If the cursor is on a folder, search up for the previous project/folder.
2033 If the cursor is on a project, go to previous project."
2034 (interactive)
2035 (unless project-buffer-status (error "Not in project-buffer buffer"))
2036 (let* ((status project-buffer-status)
2037 (node (ewoc-locate project-buffer-status))
2038 (node-data (and node (ewoc-data node))))
2039 (cond ((eq (project-buffer-node->type node-data) 'file)
2040 (project-buffer-goto-dir-up))
2041 ((eq (project-buffer-node->type node-data) 'folder)
2042 (let ((search (ewoc-prev status node)))
2043 (while (and search
2044 (eq (project-buffer-node->type (ewoc-data search)) 'file))
2045 (setq search (ewoc-prev status search)))
2046 (when search
2047 (ewoc-goto-node status search))))
2048 ((eq (project-buffer-node->type node-data) 'project)
2049 (let ((search (ewoc-prev status node)))
2050 (while (and search
2051 (not (eq (project-buffer-node->type (ewoc-data search)) 'project)))
2052 (setq search (ewoc-prev status search)))
2053 (when search
2054 (ewoc-goto-node status search))))
2055 (t (error "Unknown node type! (%S)" (project-buffer-node->type node-data))))))
2058 (defun project-buffer-go-to-next-project ()
2059 "Go to next project line."
2060 (interactive)
2061 (unless project-buffer-status (error "Not in project-buffer buffer"))
2062 (let* ((status project-buffer-status)
2063 (node (ewoc-locate project-buffer-status))
2064 (search (ewoc-next status node)))
2065 (while (and search
2066 (not (eq (project-buffer-node->type (ewoc-data search)) 'project)))
2067 (setq search (ewoc-next status search)))
2068 (when search
2069 (ewoc-goto-node status search))))
2072 (defun project-buffer-go-to-next-folder-or-project ()
2073 "If the cursor is on a file, go down to the next project/folder.
2074 If the cursor is on a folder, search down for the next project/folder.
2075 If the cursor is on a project, go to next project."
2076 (interactive)
2077 (unless project-buffer-status (error "Not in project-buffer buffer"))
2078 (let* ((status project-buffer-status)
2079 (node (ewoc-locate project-buffer-status))
2080 (node-data (and node (ewoc-data node)))
2081 (fold-ok (and node
2082 (not (eq (project-buffer-node->type node-data) 'project))
2083 (eq project-buffer-view-mode 'folder-view)))
2084 (search (and node (ewoc-next status node))))
2086 (while (and search
2087 (not (eq (project-buffer-node->type (ewoc-data search)) 'project))
2088 (not (and fold-ok
2089 (eq (project-buffer-node->type (ewoc-data search)) 'folder))))
2090 (setq search (ewoc-next status search)))
2091 (when search
2092 (ewoc-goto-node status search))))
2095 (defun project-buffer-node-find-file ()
2096 "Find the file the cursor is on."
2097 (interactive)
2098 (unless project-buffer-status (error "Not in project-buffer buffer"))
2099 (let* ((node (ewoc-locate project-buffer-status))
2100 (node-data (ewoc-data node))
2101 (project-buffer (current-buffer)))
2102 (project-buffer-clear-matched-mark project-buffer-status)
2103 (if (eq (project-buffer-node->type node-data) 'file)
2104 (let ((file-buffer (find-file (project-buffer-node->filename node-data))))
2105 (run-hook-with-args 'project-buffer-post-find-file-hook project-buffer file-buffer))
2106 (project-buffer-toggle-expand-collapse))))
2109 (defun project-buffer-mouse-find-file(event)
2110 "Find the file you click on."
2111 (interactive "e")
2112 (save-excursion
2113 (set-buffer (window-buffer (posn-window (event-end event))))
2114 (save-excursion
2115 (goto-char (posn-point (event-end event)))
2116 (if (get-text-property (point) 'mouse-face)
2117 (project-buffer-node-find-file-other-window)))))
2120 (defun project-buffer-node-find-file-other-window ()
2121 "Find the file the cursor is on in another window."
2122 (interactive)
2123 (unless project-buffer-status (error "Not in project-buffer buffer"))
2124 (let* ((node (ewoc-locate project-buffer-status))
2125 (node-data (ewoc-data node))
2126 (project-buffer (current-buffer)))
2127 (project-buffer-clear-matched-mark project-buffer-status)
2128 (if (eq (project-buffer-node->type node-data) 'file)
2129 (let ((file-buffer (find-file-other-window (project-buffer-node->filename node-data))))
2130 (run-hook-with-args 'project-buffer-post-find-file-hook project-buffer file-buffer))
2131 (project-buffer-toggle-expand-collapse))))
2134 (defun project-buffer-mark-file ()
2135 "Mark the file that the cursor is on and move to the next one."
2136 (interactive)
2137 (unless project-buffer-status (error "Not in project-buffer buffer"))
2138 (let* ((node (ewoc-locate project-buffer-status))
2139 (node-data (ewoc-data node))
2140 (status project-buffer-status))
2141 (cond
2142 ;; Mark the current file:
2143 ((eq (project-buffer-node->type node-data) 'file)
2144 (setf (project-buffer-node->marked node-data) t)
2145 (ewoc-invalidate status node)
2146 (ewoc-goto-next status 1))
2147 ;; Or all files which belong to the project:
2148 ((eq (project-buffer-node->type node-data) 'project)
2149 (let ((prj-name (project-buffer-node->name node-data)))
2150 (save-excursion
2151 (setq node (ewoc-next status node)
2152 node-data (and node (ewoc-data node)))
2153 (while (and node (string-equal (project-buffer-node->project node-data) prj-name))
2154 (when (eq (project-buffer-node->type node-data) 'file)
2155 (setf (project-buffer-node->marked node-data) t)
2156 (ewoc-invalidate status node))
2157 (setq node (ewoc-next status node)
2158 node-data (and node (ewoc-data node)))))))
2159 ;; Or finally, all files which are under the current folder:
2160 ((eq (project-buffer-node->type node-data) 'folder)
2161 (let ((folder (project-buffer-node->name node-data)))
2162 (save-excursion
2163 (setq node (ewoc-next status node)
2164 node-data (and node (ewoc-data node)))
2165 (while (and node
2166 (not (eq (project-buffer-node->type node-data) 'project))
2167 (project-buffer-parent-of-p (project-buffer-node->name node-data) folder))
2168 (when (eq (project-buffer-node->type node-data) 'file)
2169 (setf (project-buffer-node->marked node-data) t)
2170 (ewoc-invalidate status node))
2171 (setq node (ewoc-next status node)
2172 node-data (and node (ewoc-data node)))))))
2176 (defun project-buffer-unmark-file ()
2177 "Unmark the file that the cursor is on and move to the next one."
2178 (interactive)
2179 (unless project-buffer-status (error "Not in project-buffer buffer"))
2180 (let* ((node (ewoc-locate project-buffer-status))
2181 (node-data (ewoc-data node))
2182 (status project-buffer-status))
2183 (cond
2184 ;; Mark the current file:
2185 ((eq (project-buffer-node->type node-data) 'file)
2186 (setf (project-buffer-node->marked node-data) nil)
2187 (ewoc-invalidate project-buffer-status node)
2188 (when (eq node (ewoc-locate project-buffer-status))
2189 (ewoc-goto-next project-buffer-status 1)))
2190 ;; Or all files which belong to the project:
2191 ((eq (project-buffer-node->type node-data) 'project)
2192 (let ((prj-name (project-buffer-node->name node-data)))
2193 (save-excursion
2194 (setq node (ewoc-next status node)
2195 node-data (and node (ewoc-data node)))
2196 (while (and node (string-equal (project-buffer-node->project node-data) prj-name))
2197 (when (eq (project-buffer-node->type node-data) 'file)
2198 (setf (project-buffer-node->marked node-data) nil)
2199 (ewoc-invalidate status node))
2200 (setq node (ewoc-next status node)
2201 node-data (and node (ewoc-data node)))))))
2202 ;; Or finally, all files which are under the current folder:
2203 ((eq (project-buffer-node->type node-data) 'folder)
2204 (let ((folder (project-buffer-node->name node-data)))
2205 (save-excursion
2206 (setq node (ewoc-next status node)
2207 node-data (and node (ewoc-data node)))
2208 (while (and node
2209 (not (eq (project-buffer-node->type node-data) 'project))
2210 (project-buffer-parent-of-p (project-buffer-node->name node-data) folder))
2211 (when (eq (project-buffer-node->type node-data) 'file)
2212 (setf (project-buffer-node->marked node-data) nil)
2213 (ewoc-invalidate status node))
2214 (setq node (ewoc-next status node)
2215 node-data (and node (ewoc-data node)))))))
2219 (defun project-buffer-mark-all ()
2220 "Mark all files."
2221 (interactive)
2222 (unless project-buffer-status (error "Not in project-buffer buffer"))
2223 (ewoc-map (lambda (node) (when (and (eq (project-buffer-node->type node) 'file)
2224 (not (project-buffer-node->marked node)))
2225 (setf (project-buffer-node->marked node) t)))
2226 project-buffer-status))
2229 (defun project-buffer-unmark-all ()
2230 "Unmark all files."
2231 (interactive)
2232 (unless project-buffer-status (error "Not in project-buffer buffer"))
2233 (ewoc-map (lambda (node) (when (and (eq (project-buffer-node->type node) 'file)
2234 (project-buffer-node->marked node))
2235 (setf (project-buffer-node->marked node) nil) t))
2236 project-buffer-status))
2239 (defun project-buffer-toggle-all-marks ()
2240 "Toggle all file mark."
2241 (interactive)
2242 (unless project-buffer-status (error "Not in project-buffer buffer"))
2243 (ewoc-map (lambda (node) (when (eq (project-buffer-node->type node) 'file)
2244 (setf (project-buffer-node->marked node) (not (project-buffer-node->marked node))) t))
2245 project-buffer-status))
2248 (defun project-buffer-toggle-expand-collapse-even-on-file ()
2249 "Expand / Collapse project and folder that the cursor is on.
2250 If the cursor is on a file - search up for the nearest folder and collapse it."
2251 (interactive)
2252 (unless project-buffer-status (error "Not in project-buffer buffer"))
2253 (let* ((node (ewoc-locate project-buffer-status))
2254 (node-data (ewoc-data node))
2255 (status project-buffer-status))
2256 (project-buffer-clear-matched-mark status)
2257 (when (eq (project-buffer-node->type node-data) 'file)
2258 (setq node (and node (project-buffer-find-node-up status node)))
2259 (when node (ewoc-goto-node status node)))
2260 (when node
2261 (project-buffer-toggle-expand-collapse))))
2264 (defun project-buffer-toggle-expand-collapse ()
2265 "Expand / Collapse project and folder that the cursor is on.
2266 If the cursor is on a file - nothing will be done."
2267 (interactive)
2268 (unless project-buffer-status (error "Not in project-buffer buffer"))
2269 (let* ((node (ewoc-locate project-buffer-status))
2270 (node-data (ewoc-data node))
2271 (status project-buffer-status)
2272 prj-sel
2273 hidden-flag
2274 project
2275 skip-under
2276 folder)
2277 (project-buffer-clear-matched-mark status)
2278 (unless (eq (project-buffer-node->type node-data) 'file)
2279 (when (eq (project-buffer-node->type node-data) 'folder)
2280 (setq folder (project-buffer-node->name node-data)))
2281 (setf (project-buffer-node->collapsed node-data) (not (project-buffer-node->collapsed node-data)))
2282 (setq hidden-flag (project-buffer-node->collapsed node-data))
2283 (setq prj-sel (eq (project-buffer-node->type node-data) 'project))
2284 (when prj-sel
2285 (setf (project-buffer-node->project-collapsed node-data) hidden-flag))
2286 (ewoc-invalidate status node)
2287 (setq project (project-buffer-node->project node-data)
2288 node (ewoc-next status node))
2289 (while node
2290 (setq node-data (ewoc-data node))
2291 (when skip-under
2292 (unless (project-buffer-parent-of-p (project-buffer-node->name node-data) skip-under)
2293 (setq skip-under nil)))
2294 (if (and (string-equal (project-buffer-node->project node-data) project)
2295 (or (not folder)
2296 (project-buffer-parent-of-p (project-buffer-node->name node-data) folder)))
2297 (progn
2298 (when prj-sel
2299 (setf (project-buffer-node->project-collapsed node-data) hidden-flag)
2300 (ewoc-invalidate status node))
2301 (unless skip-under
2302 (setf (project-buffer-node->hidden node-data) hidden-flag)
2303 (ewoc-invalidate status node)
2304 (if (and (eq (project-buffer-node->type node-data) 'folder)
2305 (project-buffer-node->collapsed node-data)
2306 (not hidden-flag))
2307 (setq skip-under (project-buffer-node->name node-data))))
2308 (setq node (ewoc-next status node)))
2309 (setq node nil))))))
2312 (defun project-buffer-set-folder-view-mode()
2313 "Set the view mode to folder-view."
2314 (interactive)
2315 (unless project-buffer-status (error "Not in project-buffer buffer"))
2316 (project-buffer-set-view-mode project-buffer-status 'folder-view))
2318 (defun project-buffer-set-flat-view-mode()
2319 "Set the view mode to flat-view."
2320 (interactive)
2321 (unless project-buffer-status (error "Not in project-buffer buffer"))
2322 (project-buffer-set-view-mode project-buffer-status 'flat-view))
2324 (defun project-buffer-set-folder-hidden-view-mode()
2325 "Set the view mode to folder-hidden-view."
2326 (interactive)
2327 (unless project-buffer-status (error "Not in project-buffer buffer"))
2328 (project-buffer-set-view-mode project-buffer-status 'folder-hidden-view))
2331 (defun project-buffer-set-marked-view-mode()
2332 "Set the view mode to marked-view."
2333 (interactive)
2334 (unless project-buffer-status (error "Not in project-buffer buffer"))
2335 (project-buffer-set-view-mode project-buffer-status 'marked-view))
2338 (defun project-buffer-toggle-view-mode ()
2339 "Toggle between the different view mode (folder-view / flat-view / folder-hidden-view)."
2340 (interactive)
2341 (unless project-buffer-status (error "Not in project-buffer buffer"))
2342 (let ((node (ewoc-locate project-buffer-status)))
2343 (setq project-buffer-view-mode
2344 (cond ((eq project-buffer-view-mode 'folder-view) 'flat-view)
2345 ((eq project-buffer-view-mode 'flat-view) 'folder-hidden-view)
2346 ((eq project-buffer-view-mode 'folder-hidden-view) 'marked-view)
2347 ((eq project-buffer-view-mode 'marked-view) 'folder-view)
2349 (let ((status project-buffer-status))
2350 (message "View mode set to: %s" project-buffer-view-mode)
2351 (project-buffer-refresh-all-items status)
2352 (project-buffer-refresh-ewoc-hf status)
2353 (ewoc-goto-node status node)
2357 (defun project-buffer-toggle-search-mode()
2358 "Toggle between the different search-in-files mode (narrow-marked-files / all-files / current-project)."
2359 (interactive)
2360 (unless project-buffer-status (error "Not in project-buffer buffer"))
2361 (let ((node (ewoc-locate project-buffer-status)))
2362 (setq project-buffer-search-in-files-mode
2363 (cond ((eq project-buffer-search-in-files-mode 'narrow-marked-files) 'all-files)
2364 ((eq project-buffer-search-in-files-mode 'all-files) 'current-project)
2365 ((eq project-buffer-search-in-files-mode 'current-project) 'narrow-marked-files)))
2366 (let ((status project-buffer-status))
2367 (message "Search mode set to: %s" project-buffer-search-in-files-mode)
2368 (project-buffer-refresh-ewoc-hf status)
2369 (ewoc-goto-node status node)
2373 (defun project-buffer-choose-build-configuration()
2374 "Ask the user for the build configuration using a completion list"
2375 (interactive)
2376 (unless project-buffer-status (error "Not in project-buffer buffer"))
2377 (unless project-buffer-build-configurations-list (error "No build configuration available"))
2378 (if (cdr project-buffer-build-configurations-list)
2379 (let ((new-build-configuration (completing-read "Build-Configuration: " project-buffer-build-configurations-list nil t)))
2380 (when (and new-build-configuration (> (length new-build-configuration) 0))
2381 (setq project-buffer-current-build-configuration new-build-configuration)))
2382 (message "This is the only one build configuration available."))
2383 (project-buffer-refresh-ewoc-hf project-buffer-status))
2386 (defun project-buffer-next-build-configuration ()
2387 "Select next build configuration (rotate through them)."
2388 (interactive)
2389 (unless project-buffer-status (error "Not in project-buffer buffer"))
2390 (unless project-buffer-build-configurations-list (error "No build configuration available"))
2391 (if (cdr project-buffer-build-configurations-list)
2392 (let ((current (member project-buffer-current-build-configuration project-buffer-build-configurations-list)))
2393 (unless current (error "The current build configuration is invalid"))
2394 (setq project-buffer-current-build-configuration (or (and (cdr current) (cadr current))
2395 (car project-buffer-build-configurations-list)))
2396 (message "Build configuration set to: %s" project-buffer-current-build-configuration))
2397 (message "This is the only one build configuration available."))
2398 (project-buffer-refresh-ewoc-hf project-buffer-status))
2401 (defun project-buffer-choose-platform ()
2402 "Ask the user for the platform using a completion list."
2403 (interactive)
2404 (unless project-buffer-status (error "Not in project-buffer buffer"))
2405 (unless project-buffer-platforms-list (error "No build configuration available"))
2406 (if (cdr project-buffer-platforms-list)
2407 (let ((new-platform (completing-read "Platform: " project-buffer-platforms-list nil t)))
2408 (when (and new-platform (> (length new-platform) 0))
2409 (setq project-buffer-current-platform new-platform)))
2410 (message "This is the only one platform available."))
2411 (project-buffer-refresh-ewoc-hf project-buffer-status))
2414 (defun project-buffer-next-platform ()
2415 "Select next platform (rotate through them)."
2416 (interactive)
2417 (unless project-buffer-status (error "Not in project-buffer buffer"))
2418 (unless project-buffer-platforms-list (error "No build configuration available"))
2419 (if (cdr project-buffer-platforms-list)
2420 (let ((current (member project-buffer-current-platform project-buffer-platforms-list)))
2421 (unless current (error "The current build configuration is invalid"))
2422 (setq project-buffer-current-platform (or (and (cdr current) (cadr current))
2423 (car project-buffer-platforms-list)))
2424 (message "Platform set to: %s" project-buffer-current-platform))
2425 (message "This is the only one platform available."))
2426 (project-buffer-refresh-ewoc-hf project-buffer-status))
2429 (defun project-buffer-choose-master-project ()
2430 "Prompt the user for the master project."
2431 (interactive)
2432 (unless project-buffer-status (error "Not in project-buffer buffer"))
2433 (let ((status project-buffer-status)
2434 (proj-name (completing-read "Enter the master project: " project-buffer-projects-list nil t)))
2435 (when (and proj-name
2436 (> (length proj-name) 0))
2437 (project-buffer-set-master-project status proj-name))))
2440 (defun project-buffer-select-current-as-master-project ()
2441 "Make the current project the new master project."
2442 (interactive)
2443 (unless project-buffer-status (error "Not in project-buffer buffer"))
2444 (let ((status project-buffer-status)
2445 (old-node (cdr project-buffer-master-project))
2446 (cur-node (ewoc-locate project-buffer-status)))
2447 ;; Search for the project node:
2448 (while (and cur-node
2449 (not (eq (project-buffer-node->type (ewoc-data cur-node)) 'project)))
2450 (setq cur-node (project-buffer-find-node-up status cur-node)))
2451 ;; Let's replace the old node by the new one
2452 (setq project-buffer-master-project (cons (project-buffer-node->name (ewoc-data cur-node))
2453 cur-node))
2454 ;; Force the refresh:
2455 (ewoc-invalidate status old-node)
2456 (ewoc-invalidate status cur-node)
2457 (ewoc-goto-node status cur-node)))
2460 (defun project-buffer-perform-build-action ()
2461 "Run the user hook to perform the build action."
2462 (interactive)
2463 (unless project-buffer-status (error "Not in project-buffer buffer"))
2464 (project-buffer-perform-action-hook 'build))
2467 (defun project-buffer-perform-clean-action ()
2468 "Run the user hook to perform the build action."
2469 (interactive)
2470 (unless project-buffer-status (error "Not in project-buffer buffer"))
2471 (when (funcall project-buffer-confirm-function "Clean the master project ")
2472 (project-buffer-perform-action-hook 'clean)))
2475 (defun project-buffer-perform-run-action ()
2476 "Run the user hook to perform the build action."
2477 (interactive)
2478 (unless project-buffer-status (error "Not in project-buffer buffer"))
2479 (project-buffer-perform-action-hook 'run))
2482 (defun project-buffer-perform-debug-action ()
2483 "Run the user hook to perform the build action."
2484 (interactive)
2485 (unless project-buffer-status (error "Not in project-buffer buffer"))
2486 (project-buffer-perform-action-hook 'debug))
2488 (defun project-buffer-perform-update-action ()
2489 "Run the user hook to perform the build action."
2490 (interactive)
2491 (unless project-buffer-status (error "Not in project-buffer buffer"))
2492 (project-buffer-perform-action-hook 'update))
2495 (defun project-buffer-mark-files-containing-regexp (regexp &optional unmark)
2496 "Mark all files containing REGEXP -- A prefix argument means to UNMARK the files containing the REGEXP instead."
2497 (interactive
2498 (list (project-buffer-read-regexp (concat (if current-prefix-arg "Unmark" "Mark")
2499 " files containing (regexp): "))
2500 current-prefix-arg))
2501 (unless project-buffer-status (error "Not in project-buffer buffer"))
2502 (let* ((node (ewoc-locate project-buffer-status))
2503 (node-data (ewoc-data node))
2504 (current-project (project-buffer-node->project node-data))
2505 (count (cond ((eq project-buffer-search-in-files-mode 'narrow-marked-files)
2506 (project-buffer-refine-mark-files project-buffer-status regexp (not unmark)))
2507 ((eq project-buffer-search-in-files-mode 'all-files)
2508 (project-buffer-search-and-mark-files project-buffer-status regexp nil (not unmark)))
2509 ((eq project-buffer-search-in-files-mode 'current-project)
2510 (project-buffer-search-and-mark-files project-buffer-status regexp current-project (not unmark))))))
2511 (if (< count 0)
2512 (message "%i files marked." (- 0 count))
2513 (message "%i files %s."
2514 count
2515 (if (or unmark
2516 (eq project-buffer-search-in-files-mode 'narrow-marked-files))
2517 "unmarked" "marked")))
2518 (when project-buffer-autoswitch-marked-view-mode
2519 (unless (= count 0)
2520 (setq project-buffer-view-mode 'marked-view)
2521 (let ((status project-buffer-status))
2522 (project-buffer-refresh-all-items status)
2523 (project-buffer-refresh-ewoc-hf status)
2524 (ewoc-goto-node status node))))
2528 (defun project-buffer-mark-matched-files-or-current-file(force-marked-current)
2529 "Mark the matched files or the current file if no filename research are in progress or if FORCE-MARKED-CURRENT is set."
2530 (interactive "P")
2531 (unless project-buffer-status (error "Not in project-buffer buffer"))
2532 (let (result)
2533 (unless (or force-marked-current
2534 (not (project-buffer-node->matched (ewoc-data (ewoc-locate project-buffer-status)))))
2535 (ewoc-map (lambda (node-data)
2536 (when (and (eq (project-buffer-node->type node-data) 'file)
2537 (project-buffer-node->matched node-data))
2538 (setf (project-buffer-node->marked node-data) t)
2539 (setq result t)))
2540 project-buffer-status))
2541 (unless result
2542 (project-buffer-mark-file))))
2545 (defun project-buffer-unmark-matched-files-or-current-file(force-unmarked-current)
2546 "Unmark the matched files or the current file if no filename research are in progress or if FORCE-UNMARKED-CURRENT is set."
2547 (interactive "P")
2548 (unless project-buffer-status (error "Not in project-buffer buffer"))
2549 (let (result)
2550 (unless (or force-unmarked-current
2551 (not (project-buffer-node->matched (ewoc-data (ewoc-locate project-buffer-status)))))
2552 (ewoc-map (lambda (node-data)
2553 (when (and (eq (project-buffer-node->type node-data) 'file)
2554 (project-buffer-node->matched node-data))
2555 (setf (project-buffer-node->marked node-data) nil)
2556 (setq result t)))
2557 project-buffer-status))
2558 (unless result
2559 (project-buffer-unmark-file))))
2562 (defun project-buffer-view-file ()
2563 "Examine the current file using the view mode."
2564 (interactive)
2565 (unless project-buffer-status (error "Not in project-buffer buffer"))
2566 (let* ((node (ewoc-locate project-buffer-status))
2567 (node-data (ewoc-data node)))
2568 (when (eq (project-buffer-node->type node-data) 'file)
2569 (view-file (project-buffer-node->filename node-data)))))
2572 (defun project-buffer-delete-current-node()
2573 "Delete the current node."
2574 (interactive)
2575 (unless project-buffer-status (error "Not in project-buffer buffer"))
2576 (let ((status project-buffer-status)
2577 (node (ewoc-locate project-buffer-status)))
2578 (when node
2579 (let* ((node-data (ewoc-data node))
2580 (type (project-buffer-node->type node-data))
2581 (name (project-buffer-node->name node-data)))
2582 (when (funcall project-buffer-confirm-function
2583 (concat (format "Delete %s%s " name (if (eq type 'file) "" " and its content"))))
2584 (message "Deleting %s..." name)
2585 (cond ((eq type 'file)
2586 (project-buffer-delete-node status node (not project-buffer-cleanup-empty-projects)))
2587 ((eq type 'folder)
2588 (project-buffer-delete-folder-node status node (not project-buffer-cleanup-empty-projects)))
2589 ((eq type 'project)
2590 (project-buffer-delete-project-node project-buffer-status name node))
2591 (t (error "Unknown data type"))))))))
2594 (defun project-buffer-delete-marked-files()
2595 "Delete the marked files from the buffer."
2596 (interactive)
2597 (unless project-buffer-status (error "Not in project-buffer buffer"))
2598 (let ((status project-buffer-status)
2599 node-list)
2600 (let ((node (ewoc-nth status 0))
2601 node-data)
2602 (while node
2603 (setq node-data (ewoc-data node))
2604 (when (and (eq (project-buffer-node->type node-data) 'file)
2605 (project-buffer-node->marked node-data))
2606 (setq node-list (cons node node-list)))
2607 (setq node (ewoc-next status node))))
2609 (when node-list
2610 (let* ((lgt (length node-list))
2611 (confirm-str (if (> lgt 1)
2612 (format "Delete marked files [%i files] " lgt)
2613 (format "Delete %s " (project-buffer-node->name (ewoc-data (car node-list))))))
2614 (result-str (format "%i deletion%s done" lgt (if (> lgt 1) "s" ""))))
2615 (when (funcall project-buffer-confirm-function confirm-str)
2616 (while node-list
2617 (project-buffer-delete-node status (pop node-list) (not project-buffer-cleanup-empty-projects)))
2618 (message result-str))))))
2621 (defun project-buffer-delete-current-node-or-marked-files()
2622 "Delete either the current node or the marked files.
2623 The decision is based on the current node: if the current node is
2624 marked, the deletion will attempt to delete all marked files;
2625 otherwise only the current node (and potentially it's content)
2626 will get deleted."
2627 (interactive)
2628 (unless project-buffer-status (error "Not in project-buffer buffer"))
2629 (let ((node (ewoc-locate project-buffer-status)))
2630 (when node
2631 (if (and (eq (project-buffer-node->type (ewoc-data node)) 'file)
2632 (project-buffer-node->marked (ewoc-data node)))
2633 (project-buffer-delete-marked-files)
2634 (project-buffer-delete-current-node)))))
2638 ;; Global command:
2642 (defun project-buffer-write-file (filename)
2643 "Save the content of `project-buffer-mode' buffer to FILENAME."
2644 (interactive "FSave project to file: ")
2645 (unless project-buffer-status (error "Not in project-buffer buffer"))
2646 (project-buffer-raw-save filename)
2647 (setq project-buffer-file-name filename))
2650 ;;;###autoload
2651 (defun project-buffer-find-file (filename)
2652 "Create a `project-buffer-mode' buffer based on the content of FILENAME."
2653 (interactive "fFind project: ")
2654 (let ((new-buffer (generate-new-buffer "*project-temp*")))
2655 (with-current-buffer new-buffer
2656 (project-buffer-mode t)
2657 (project-buffer-raw-load filename t t)
2658 (setq project-buffer-file-name filename))
2659 (switch-to-buffer new-buffer)))
2662 (defun project-buffer-save-file ()
2663 "Save the content of the project-buffer-mode buffer into
2664 `project-buffer-file-name'. If `project-buffer-file-name' is
2665 nil; the command will request a file name."
2666 (interactive)
2667 (unless project-buffer-status (error "Not in project-buffer buffer"))
2668 (if project-buffer-file-name
2669 (project-buffer-raw-save project-buffer-file-name)
2670 (call-interactively 'project-buffer-write-file)))
2673 (defun project-buffer-revert ()
2674 "Revert the prvoject-buffer-mode buffer to match the content
2675 from `project-buffer-file-name'."
2676 (interactive)
2677 (unless project-buffer-status (error "Not in project-buffer buffer"))
2678 (unless project-buffer-file-name (error "No file-name attached to this project-buffer"))
2679 (project-buffer-erase-all project-buffer-status)
2680 (project-buffer-raw-load project-buffer-file-name))
2686 (provide 'project-buffer-mode)
2688 ;;; project-buffer-mode.el ends here