commander-s/scheme/completer.scm

210 lines
6.0 KiB
Scheme
Raw Normal View History

;;; #### also used in nuit-engine.scm move to some utils module
(define-syntax with-lock
(syntax-rules ()
((_ lock exp ...)
(begin
(obtain-lock lock)
(let ((val (begin exp ...)))
(release-lock lock)
val)))))
;; completion set for executables in PATH
(define executable-completions-lock (make-lock))
(define executable-completions #f)
(define (get-path-list)
(cond
((getenv "PATH")
=> (lambda (str)
(string-tokenize
str (char-set-difference char-set:full (char-set #\:)))))
(else
'("/usr/bin" "/bin" "/usr/sbin" "/sbin"))))
(define (init-executables-completion-set!)
(spawn
(lambda ()
(with-lock executable-completions-lock
(set! executable-completions
(make-completion-set-for-executables (get-path-list)))))))
;; find the part of the command line the user wants to complete
(define (make-scan-for-completions cmd-selector selector symbol)
(lambda (cmd)
(let lp ((things (cmd-selector cmd)))
(cond
((null? things)
#f)
((to-complete? (selector (car things)))
(list symbol cmd (selector (car things))))
(else
(lp (cdr things)))))))
(define scan-redirections-for-completions
(make-scan-for-completions
command-redirections redirection-dest 'redir-dest))
(define scan-args-for-completions
(make-scan-for-completions
command-args (lambda (v) v) 'arg))
(define (scan-command-for-completions cmd)
(cond
((to-complete? (command-executable cmd))
(list 'command cmd (command-executable cmd)))
((scan-redirections-for-completions cmd)
=> (lambda (v) v))
((scan-args-for-completions cmd)
=> (lambda (v) v))
(else #f)))
(define (scan-command-line-for-completions cmdln)
(cond
((scan-command-for-completions
(command-line-first-cmd cmdln))
=> (lambda (v) v))
(else
(let lp ((lst (command-line-combinator/cmds cmdln)))
(cond
((null? lst) #f)
((scan-command-for-completions (cdar lst))
=> (lambda (v) v))
(else (lp (cdr lst))))))))
;; completion functions for arguments and redirection
(define (find-completions-for-arg cmd to-complete)
2005-08-18 05:23:59 -04:00
(debug-message "find-completions-for-arg " cmd "," to-complete)
(let ((prefix (to-complete-prefix to-complete)))
(if prefix
(complete-files/path (expand-file-name prefix (cwd)))
2005-08-19 10:13:19 -04:00
(complete-files/path ""))))
;; #### no special treatment yet
(define find-completions-for-redir find-completions-for-arg)
;; completion functions for commands
(define (find-completions-for-command cmd to-complete)
2005-08-18 05:23:59 -04:00
(debug-message "find-completions-for-command " cmd "," to-complete)
(let ((prefix (or (to-complete-prefix to-complete) "")))
(cond
((command-contains-path? prefix)
;; #### install error handler
(complete-executables/path
(expand-file-name prefix (cwd))))
(else
(append
(completions-for (command-completions) prefix)
(with-lock executable-completions-lock
(completions-for-executables
executable-completions prefix)))))))
;; some helpers for the implementation of completion functions
(define (command-contains-path? command)
(or (string-contains command "/")
(string-contains command "~")
(string-contains command "..")))
(define (files-in-dir file-filter dir)
(with-cwd dir
(filter-map
(lambda (file)
(and (file-filter file)
(absolute-file-name file dir)))
(directory-files))))
(define (complete-path path)
(let ((dir (file-name-directory path)))
(map (lambda (p)
(if (string-prefix? "/" p)
p
(string-append dir p)))
(glob (string-append path "*")))))
(define (file-exists-and-is-directory? fname)
(call-with-current-continuation
(lambda (esc)
(with-handler
(lambda (c more)
(if (error? c)
(esc #f)
(more)))
(lambda ()
(and (file-exists? fname) (file-directory? fname)))))))
(define (complete-with-filesystem-objects filter partial-name)
(if (file-exists-and-is-directory? partial-name)
(files-in-dir filter partial-name)
(complete-path partial-name)))
(define (complete-executables/path partial-name)
(complete-with-filesystem-objects
(lambda (file)
(call-with-current-continuation
(lambda (esc)
(with-handler
(lambda (c more)
(if (error? c)
(esc #f)
(more)))
(lambda ()
(or (file-executable? file) (file-directory? file)))))))
partial-name))
(define (complete-files/path partial-name)
(debug-message "complete-files/path " partial-name)
(complete-with-filesystem-objects
(lambda (file) #t) partial-name))
;; the main part
2005-08-18 05:23:59 -04:00
(define (find-plugin-completer cmd)
(let ((cmd-name (command-executable cmd)))
(cond
((find (lambda (p)
(string=? (command-plugin-command p) cmd-name))
(command-plugin-list))
=> (lambda (plugin)
(or (command-plugin-completer plugin)
find-completions-for-arg)))
2005-08-18 05:23:59 -04:00
(else find-completions-for-arg))))
(define (find-completer type . args)
(let-optionals args
((cmd #f))
(case type
((command) find-completions-for-command)
((redir-dest) find-completions-for-redir)
((arg) (if cmd
(find-plugin-completer cmd)
find-completions-for-arg))
(else
(error "Unknown completion type" type)))))
(define (complete cmdln cursor-index)
(debug-message "complete " cmdln ", " cursor-index)
(let* ((parsed
(lex/parse-partial-command-line cmdln cursor-index))
(completion-info
(scan-command-line-for-completions parsed)))
(debug-message "complete " completion-info)
(and completion-info
(destructure (((type cmd to-complete) completion-info))
2005-08-18 05:23:59 -04:00
(let ((completions ((find-completer type cmd) cmd to-complete)))
(cond
((= (length completions) 1)
(call-with-values
(lambda ()
(unparse-command-line
parsed (lambda (to-complete)
(display (car completions)))))
(lambda (completed-line cursor-index)
2005-08-19 10:13:19 -04:00
(list completed-line cursor-index to-complete parsed))))
(else
(list completions cursor-index to-complete parsed))))))))