(define-syntax when (syntax-rules () ((_ ?test ?do-this ...) (if ?test (begin ?do-this ... (values)) (values))))) (define-syntax with-lock (syntax-rules () ((_ lock exp ...) (begin (obtain-lock lock) (let ((val (begin exp ...))) (release-lock lock) val))))) ;; configurable options (define-option 'main 'switch-command-buffer-mode-key key-f7) ;; configurable options (define-option 'main 'switch-command-buffer-mode-key key-f7) ;; configurable options (define-option 'main 'switch-command-buffer-mode-key key-f7) ;; mode of the command buffer (define-option 'main 'initial-command-mode 'command) (define *command-buffer-mode*) (define (command-buffer-in-scheme-mode?) (eq? *command-buffer-mode* 'scheme)) (define (command-buffer-in-command-mode?) (eq? *command-buffer-mode* 'command)) (define (enter-scheme-mode!) (set! *command-buffer-mode* 'scheme)) (define (enter-command-mode!) (set! *command-buffer-mode* 'command)) (define key-control-x 24) (define key-o 111) (define key-tab 9) ;; History (define history-pos 0) (define the-history (make-empty-history)) (define (history) the-history) (define *current-history-item* #f) (define (current-history-item) *current-history-item*) (define-record-type history-entry :history-entry (make-history-entry command args viewer) history-entry? (command history-entry-command) (args history-entry-args) (viewer history-entry-viewer set-history-entry-viewer!)) (define (current-history-entry-selector-maker selector) (lambda () (cond ((current-history-item) => (lambda (entry) (selector (entry-data entry)))) (else #f)))) (define active-command (current-history-entry-selector-maker history-entry-command)) (define active-command-arguments (current-history-entry-selector-maker history-entry-args)) (define current-viewer (current-history-entry-selector-maker history-entry-viewer)) (define (update-current-viewer! new-viewer) (cond ((current-history-item) => (lambda (entry) (if (not (eq? (history-entry-viewer (entry-data entry)) new-viewer)) (append-to-history! (make-history-entry #f '() new-viewer))))) (else (values)))) (define (append-to-history! history-entry) (append-history-item! the-history history-entry) (set! *current-history-item* (history-last-entry the-history))) ;; one step back in the history (define (history-back!) (cond ((and (current-history-item) (history-prev-entry (current-history-item))) => (lambda (prev) (set! *current-history-item* prev))) (else (values)))) ;; one step forward (define (history-forward!) (cond ((and *current-history-item* (history-next-entry *current-history-item*)) => (lambda (next) (set! *current-history-item* next))) (else (values)))) ;;active keyboard-interrupt: ;;after each input this is set to #f. ;;If a keyboard-interrupt occurs this can be checked by looking-up this box (define active-keyboard-interrupt #f) ;;The "user" (who extends the functionality of NUIT) has to inform NUIT ;;about which function is meant to be the receiver, when a certain ;;command is active ;;************************************************************************* ;;Actions ;;start the whole thing (define (nuit) (let ((tty-name (init-tty-debug-output!))) (if tty-name (begin (display "Debug messages will be on ") (display tty-name) (newline)))) (with-inspecting-handler 8888 (lambda (condition) (with-current-output-port* (error-output-port) (lambda () (display "starting remote handler for condition") (display condition) (newline) (display "Please connect to port 8888") (newline) #t))) run)) (define (toggle-buffer-focus) (cond ((focus-on-command-buffer?) (focus-result-buffer!) (refresh-result-window)) (else (focus-command-buffer!) (move-cursor (command-buffer) (result-buffer)) (refresh-command-window)))) (define (current-command-line) (let ((entered (last (buffer-text (command-buffer))))) (if (string=? entered "") #f entered))) (define (replace-current-command-line! text) (set-buffer-text! (command-buffer) (reverse (cons text (cdr (reverse (buffer-text (command-buffer)))))))) (define (toggle-command/scheme-mode) (cond ((command-buffer-in-command-mode?) (enter-scheme-mode!)) ((command-buffer-in-scheme-mode?) (enter-command-mode!))) (paint-command-frame-window) (paint-command-window-contents) (move-cursor (command-buffer) (result-buffer)) (refresh-command-window)) ;; assumes we are in command mode (define (toggle-command/scheme-mode-with-conversion) (cond ((current-command-line) => (lambda (cmdln) (cond ((lex/parse-partial-command-line cmdln #f) => (lambda (parsed) (let ((scheme-str (write-to-string (compile-command-line parsed)))) (replace-current-command-line! scheme-str) (enter-scheme-mode!) (set-buffer-pos-col! (command-buffer) (+ 2 (string-length scheme-str))) (paint-command-frame-window) (paint-command-window-contents) (move-cursor (command-buffer) (result-buffer)) (refresh-command-window)))) (else (values))))) (else (values)))) (define (handle-return-key) (let ((command-line (cadr (reverse (buffer-text (command-buffer)))))) (debug-message "command-line " command-line) (cond ((string=? command-line "") (values)) ((command-buffer-in-scheme-mode?) (eval-command-in-scheme-mode command-line)) ((command-buffer-in-command-mode?) (eval-command-in-command-mode command-line)) (else (error "Cannot handle return key" command-line))))) (define (find-command-plugin command) (or (find (lambda (p) (string=? (command-plugin-command p) command)) (command-plugin-list)) standard-command-plugin)) (define (eval-command-in-command-mode command-line) (with-fatal-error-handler* display-error-and-continue (lambda () (let* ((tokens (split-command-line command-line)) (command (car tokens)) (args (cdr tokens)) (command-plugin (find-command-plugin command)) (viewer (find/init-plugin-for-result (with-inspector-handler (lambda () ((command-plugin-evaluater command-plugin) command args))))) (new-entry (make-history-entry command args viewer))) ;; FIXME, use insert here (append-to-history! new-entry) (signal-result-buffer-object-change) (obtain-lock paint-lock) (paint) (paint-result-window (entry-data (current-history-item))) (refresh-result-window) (release-lock paint-lock))))) (define (display-error-and-continue condition more) (let ((win (app-window-curses-win (result-window)))) (wclear win) (wattron win (A-BOLD)) (mvwaddstr win 0 0 (string-append "I'm sorry " (user-login-name) ", " "I'm afraid I can't do that. " "The following error occured:")) (wattrset win (A-NORMAL)) (let ((string-port (open-output-string))) (display condition string-port) (display " " string-port) (display more) (mvwaddstr win 5 0 (get-output-string string-port))) (refresh-result-window))) (define (process-scheme-command command-line) (receive (command args) (split-scheme-command-line command-line) (let* ((viewer (find/init-plugin-for-result (with-inspector-handler (lambda () (eval-scheme-command command args))))) (new-entry (make-history-entry command args viewer))) (append-to-history! new-entry) (signal-result-buffer-object-change) (obtain-lock paint-lock) (paint-active-command-window) (paint-result-window new-entry) (refresh-result-window) (move-cursor (command-buffer) (result-buffer)) (refresh-command-window) (release-lock paint-lock)))) (define (eval-command-in-scheme-mode command-line) (if (scheme-command-line? command-line) (process-scheme-command command-line) (let* ((viewer (find/init-plugin-for-result (with-inspector-handler (lambda () (eval-string command-line))))) (new-entry (make-history-entry command-line '() viewer))) ;; #### shouldn't we use some kind of insertion here? (append-to-history! new-entry) (signal-result-buffer-object-change) (obtain-lock paint-lock) (paint-active-command-window) (paint-result-window new-entry) (refresh-result-window) (move-cursor (command-buffer) (result-buffer)) (refresh-command-window) (release-lock paint-lock)))) ;; #### crufty, and a very dumb idea (define split-command-line string-tokenize) (define (paste-selection/refresh viewer) (add-string-to-command-buffer (send (current-viewer) 'get-selection-as-text (command-buffer-in-scheme-mode?) (focus-table))) (print-command-buffer (app-window-curses-win (command-window)) (command-buffer)) (move-cursor (command-buffer) (result-buffer)) (refresh-command-window) (refresh-result-window)) (define (paste-focus-object/refresh viewer) (add-string-to-command-buffer (if (command-buffer-in-command-mode?) (send (current-viewer) 'get-selection-as-text (command-buffer-in-scheme-mode?) (focus-table)) (send (current-viewer) 'get-selection-as-ref (focus-table)))) (print-command-buffer (app-window-curses-win (command-window)) (command-buffer)) (move-cursor (command-buffer) (result-buffer)) (refresh-command-window) (refresh-result-window)) ;; #### implement me (define terminal-input-handler (lambda ignore 'terminal-input)) ;; #### implement me (define terminal-output-handler (lambda ignore 'terminal-output)) (define (install-signal-handlers) (for-each (lambda (signal) (set-interrupt-handler signal #f)) (list interrupt/int ;interrupt/quit interrupt/tstp)) (set-interrupt-handler signal/ttin terminal-input-handler) (set-interrupt-handler signal/ttou terminal-output-handler)) (define (enable-tty-output-control! port) (let ((info (copy-tty-info (tty-info port)))) (set-tty-info:local-flags info (bitwise-ior (tty-info:local-flags info) ttyl/ttou-signal)) (set-tty-info/now port info))) (define (process-group-leader?) (= (process-group) (pid))) ;; handle input (define (run) (ignore-signal signal/ttou) (save-initial-tty-info! (current-input-port)) (init-screen) (init-windows!) (read-config-file!) (set! *command-buffer-mode* (config 'main 'initial-command-mode)) (set-evaluation-package! 'nuit-eval) (clear) (if (not (process-group-leader?)) (become-session-leader)) (set-tty-process-group (current-input-port) (pid)) (init-executables-completion-set!) (enable-tty-output-control! (current-output-port)) ;; init joblist (let ((statistics-channel (spawn-joblist-surveillant))) (spawn (lambda () (let lp ((stats (cml-receive statistics-channel))) (debug-message "statistics update " stats) (obtain-lock paint-lock) (paint-command-frame-window) (paint-job-status-list stats) (paint-command-window-contents) (wrefresh (app-window-curses-win (command-frame-window))) (move-cursor (command-buffer) (result-buffer)) (refresh-command-window) (release-lock paint-lock) (lp (cml-receive statistics-channel)))))) (paint) (let loop ((ch (wait-for-input)) (c-x-pressed? #f)) (cond (maybe-modal-window (if (maybe-modal-window ch) (begin (close-modal-window!) (paint) (when (current-history-item) (paint-result-window (entry-data (current-history-item))) (refresh-result-window)))) (loop (wait-for-input) c-x-pressed?)) ;; Ctrl-x -> wait for next input ((= ch key-control-x) (loop (wait-for-input) #t)) ;; tab is pressed, offer completions ((and (focus-on-command-buffer?) (command-buffer-in-command-mode?) (= ch key-tab)) (offer-completions (last (buffer-text (command-buffer)))) (loop (wait-for-input) #f)) ((and (focus-on-command-buffer?) (command-buffer-in-command-mode?) c-x-pressed? (= ch (config 'main 'switch-command-buffer-mode-key))) (toggle-command/scheme-mode-with-conversion) (loop (wait-for-input) #f)) ((= ch (config 'main 'switch-command-buffer-mode-key)) (toggle-command/scheme-mode) (loop (wait-for-input) #f)) ((= ch key-end) (show-shell-screen) (paint) (loop (wait-for-input) #f)) ;; C-x o --- toggle buffer focus ((and c-x-pressed? (= ch key-o)) (toggle-buffer-focus) (loop (wait-for-input) #f)) ;; C-x p --- insert selection ((and c-x-pressed? (current-history-item) (= ch 112)) (paste-selection/refresh (current-viewer)) (loop (wait-for-input) #f)) ;; C-x P --- insert focus object(s) ((and c-x-pressed? (current-history-item) (= ch 80)) (paste-focus-object/refresh (current-viewer)) (loop (wait-for-input) #f)) ((and c-x-pressed? (focus-on-result-buffer?)) (update-current-viewer! (send (current-viewer) 'key-press ch key-control-x)) (loop (wait-for-input) #f)) ;; C-x r --- redo ((and c-x-pressed? (focus-on-command-buffer?) (= ch 114)) (debug-message "Eric should re-implement redo...") (loop (wait-for-input) #f)) ((= ch key-f1) (endwin)) ((= ch key-f2) (paint) (loop (wait-for-input) c-x-pressed?)) ;; forward in result history ((= ch key-npage) (history-forward!) (obtain-lock paint-lock) (when (current-history-item) (signal-result-buffer-object-change) (paint-active-command-window) (paint-result-window (entry-data (current-history-item)))) (refresh-result-window) (release-lock paint-lock) (loop (wait-for-input) c-x-pressed?)) ;; back in result history ((= ch key-ppage) (history-back!) (obtain-lock paint-lock) (when (current-history-item) (signal-result-buffer-object-change) (paint-active-command-window) (paint-result-window (entry-data (current-history-item)))) (refresh-result-window) (release-lock paint-lock) (loop (wait-for-input) c-x-pressed?)) ((and (focus-on-command-buffer?) (= ch 10)) (input (command-buffer) ch) (obtain-lock paint-lock) (werase (app-window-curses-win (command-window))) (print-command-buffer (app-window-curses-win (command-window)) (command-buffer)) (move-cursor (command-buffer) (result-buffer)) (refresh-command-window) (release-lock paint-lock) (handle-return-key) (loop (wait-for-input) c-x-pressed?)) (else (cond ((focus-on-result-buffer?) (when (current-history-item) (update-current-viewer! (send (current-viewer) 'key-press ch c-x-pressed?)) (obtain-lock paint-lock) ;;; only necessary when continueing a background job in fg (if (redisplay-everything?) (begin (paint-result-frame-window) (paint-active-command-window) (unset-redisplay-everything))) (paint-result-window (entry-data (current-history-item))) (move-cursor (command-buffer) (result-buffer)) (refresh-result-window) (release-lock paint-lock)) (loop (wait-for-input) #f)) (else (input (command-buffer) ch) (obtain-lock paint-lock) (werase (app-window-curses-win (command-window))) (print-command-buffer (app-window-curses-win (command-window)) (command-buffer)) (move-cursor (command-buffer) (result-buffer)) (refresh-command-window) (release-lock paint-lock) (loop (wait-for-input) c-x-pressed?))))))) (define (paint-bar-1) (mvwaddstr (app-window-curses-win (bar-1)) 0 1 "Commander S") (wrefresh (app-window-curses-win (bar-1)))) (define (paint-command-buffer-mode-indicator) (let ((mode-string (string-append "[ " (if (command-buffer-in-command-mode?) "Command" "Scheme") " ]"))) (mvwaddstr (app-window-curses-win (command-frame-window)) 0 (- (- (app-window-width (command-frame-window)) (string-length mode-string)) 2) mode-string))) (define (paint-command-frame-window) (box (app-window-curses-win (command-frame-window)) (ascii->char 0) (ascii->char 0)) (paint-command-buffer-mode-indicator) (paint-job-status-list) (wrefresh (app-window-curses-win (command-frame-window)))) (define paint-job-status-list (let ((latest-statistics (initial-job-statistics))) (lambda args (let-optionals args ((statistics latest-statistics)) (let* ((stat-item (lambda (text number) (string-append text (number->string number)))) (stat (string-join (map (lambda (status.count) (case (car status.count) ((running) (stat-item "run:" (cdr status.count))) ((ready) (stat-item "ready:" (cdr status.count))) ((stopped) (stat-item "stop:" (cdr status.count))) ((new-output) (stat-item "out:" (cdr status.count))) ((waiting-for-input) (stat-item "in:" (cdr status.count))))) statistics))) (line (string-append "[ " stat " ]"))) (set! latest-statistics statistics) (mvwaddstr (app-window-curses-win (command-frame-window)) (- (app-window-height (command-frame-window)) 1) (- (- (app-window-width (command-frame-window)) (string-length line)) 2) line)))))) (define (paint-command-window-contents) (set-buffer-num-lines! (command-buffer) (- (app-window-height (command-window)) 2)) (set-buffer-num-cols! (command-buffer) (- (app-window-width (command-window)) 3)) (werase (app-window-curses-win (command-window))) (print-command-buffer (app-window-curses-win (command-window)) (command-buffer))) (define (refresh-command-window) (wrefresh (app-window-curses-win (command-window)))) (define (paint-result-frame-window) (let ((win (app-window-curses-win (result-frame-window)))) (werase win) (box win (ascii->char 0) (ascii->char 0)) (wrefresh win))) (define (paint-result-window entry) (let ((win (app-window-curses-win (result-window)))) (werase win) (send (history-entry-viewer entry) 'paint win (result-buffer) (focus-on-result-buffer?)))) (define (refresh-result-window) (wrefresh (app-window-curses-win (result-window)))) (define (paint-result/command-buffer history-entry) (paint-result-window history-entry) (paint-active-command-window) (scroll-command-buffer) (paint-command-window-contents) (move-cursor (command-buffer) (result-buffer)) (refresh-result-window) (refresh-command-window)) (define (paint) (paint-bar-1) (paint-command-frame-window) (paint-command-window-contents) (paint-active-command-window) (paint-result-frame-window) ;(paint-result-window) (move-cursor (command-buffer) (result-buffer)) (refresh-command-window) (refresh-result-window)) (define (wait-for-input) (noecho) (keypad (app-window-curses-win (bar-1)) #t) (set! active-keyboard-interrupt #f) (let ((ch (wgetch (app-window-curses-win (bar-1))))) (echo) ch)) (define (find/init-plugin-for-result result) (cond ;; #### a hack ((null? result) (make-standard-viewer result (result-buffer))) ((determine-plugin-by-type result) => (lambda (view-plugin) ((view-plugin-constructor view-plugin) result (result-buffer)))) (else (make-standard-viewer result (result-buffer))))) ;;scroll buffer after one command was entered (define (scroll-command-buffer) (set-buffer-pos-line! (command-buffer) (+ (buffer-pos-line (command-buffer)) 1)) (set-buffer-pos-col! (command-buffer) 2)) (define (determine-plugin-by-type result) (find (lambda (r) ((view-plugin-type-predicate r) result)) (view-plugin-list))) ;;Management of the upper buffer ;;add a char to the buffer (define (add-to-command-buffer ch) (let* ((text (buffer-text (command-buffer))) (last-pos (- (length text) 1)) (old-last-el (list-ref text last-pos)) (old-rest (sublist text 0 last-pos)) (before-ch (substring old-last-el 0 (max 0 (- (buffer-pos-col (command-buffer)) 2)))) (after-ch (substring old-last-el (max 0 (- (buffer-pos-col (command-buffer)) 2)) (string-length old-last-el))) (new-last-el (string-append before-ch (string (ascii->char ch)) after-ch))) (set-buffer-text! (command-buffer) (append old-rest (list new-last-el))) (set-buffer-pos-col! (command-buffer) (+ (buffer-pos-col (command-buffer)) 1)))) ;;add a string to the buffer (define (add-string-to-command-buffer string) (let loop ((str string)) (if (string=? str "") (values) (let ((first-ch (string-ref str 0))) (add-to-command-buffer (char->ascii first-ch)) (loop (substring str 1 (string-length str))))))) ;;; FIXME: I guess s48 knows a better way to do this (see ,inspect) (define (maybe-shorten-string string width) (if (> (string-length string) width) (string-append (substring string 0 (- width 3)) "...") string)) (define (paint-active-command-window) (let ((win (app-window-curses-win (active-command-window))) (width (app-window-width (active-command-window)))) (wclear win) (box win (ascii->char 0) (ascii->char 0)) (cond ((current-history-item) => (lambda (entry) (mvwaddstr win 1 2 (maybe-shorten-string (if (history-entry-command (entry-data entry)) (history-entry-command (entry-data entry)) "user interaction") width))))) (wrefresh win))) ;;Cursor ;;move cursor to the corrct position (define (move-cursor command-buffer result-buffer) (cond ((focus-on-command-buffer?) (cursor-right-pos (app-window-curses-win (command-window)) command-buffer)) (else (compute-y-x result-buffer) (wmove (app-window-curses-win (result-window)) (result-buffer-y result-buffer) (result-buffer-x result-buffer)) (wrefresh (app-window-curses-win (result-window)))))) ;;compue pos-x and pos-y (define (compute-y-x result-buffer) (let ((pos-result (result-buffer-line result-buffer)) (pos-result-col (result-buffer-column result-buffer)) (result-lines (result-buffer-num-lines result-buffer))) (if (>= pos-result result-lines) (set-result-buffer-y! result-buffer result-lines) (set-result-buffer-y! result-buffer pos-result)) (set-result-buffer-x! result-buffer pos-result-col))) (define (sublist l pos k) (let ((tmp (list-tail l pos))) (reverse (list-tail (reverse tmp) (- (length tmp) k))))) ;;When NUIT is closed the state has to be restored, in order to let the ;;user start again from scratch (define (restore-state) (set! history '()) (set! history-pos 0) (set! active-keyboard-interrupt #f)) (define (get-param-as-str param-lst) (let loop ((lst param-lst) (str "")) (if (null? lst) str (loop (cdr lst) (string-append str " " (car lst)))))) (define (completions->select-list completions num-lines) (debug-message "possible completions " completions) (make-select-list (map (lambda (s) (make-unmarked-text-element s #f s)) completions) num-lines)) (define (display-completed-line line cursor-pos) (debug-message "display-completed-line " line "," cursor-pos) (set-buffer-pos-col! (command-buffer) cursor-pos) (set-buffer-text! (command-buffer) (append (drop-right (buffer-text (command-buffer)) 1) (list line))) (wclrtoeol (app-window-curses-win (command-window))) (print-command-buffer (app-window-curses-win (command-window)) (command-buffer)) (move-cursor (command-buffer) (result-buffer)) (refresh-command-window)) (define (current-cursor-index) ;; #### No, I will not comment on this. (- (buffer-pos-col (command-buffer)) 2)) (define (offer-completions command) (debug-message "offer-completions '" command "' " (current-cursor-index)) (let ((completion-info (complete command (current-cursor-index)))) (if (not completion-info) (begin ;; #### the completion mechanism was too confused to do anything ;; #### beep or so #f) (destructure (((maybe-completed-line completions cursor-index to-complete cmdln) completion-info)) (if maybe-completed-line ;; #### don't ask about the 2... (display-completed-line maybe-completed-line (+ 2 cursor-index))) (cond ((null? completions) #f) ((list? completions) (set-modal-window! (make-completions-window command completions cmdln to-complete))) (else (error "COMPLETE returned an unexpected value" completions))))))) (define (make-completions-window command completions cmdln to-complete) (define header-line "Select completion") (define header-length (string-length header-line)) (let* ((lines 10) (inner-width (min (apply max header-length (map string-length completions)) (COLS))) (dialog (make-app-window (- (quotient (COLS) 2) (quotient inner-width 2)) 5 (+ 4 inner-width) lines))) (app-window-init-curses-win! dialog) (let* ((dialog-win (app-window-curses-win dialog)) (select-list (completions->select-list completions (- lines 3)))) (define (paint) (werase dialog-win) (box dialog-win (ascii->char 0) (ascii->char 0)) (mvwaddstr dialog-win 0 (+ 1 (quotient (- inner-width header-length) 2)) header-line) (paint-selection-list-at select-list 1 1 dialog-win inner-width #t) (wrefresh dialog-win)) (paint) (lambda (key) (cond ((= key 27) (delete-app-window! dialog) (close-modal-window!) #t) ((= key 10) (let ((completion (select-list-selected-entry select-list))) ;; #### No, I will not comment on this. (call-with-values (lambda () (unparse-command-line cmdln (lambda (to-complete) (display completion)))) (lambda (completed-line new-cursor-pos) (display-completed-line completed-line (+ 2 new-cursor-pos)))) (delete-app-window! dialog) #t)) ((select-list-key? key) (set! select-list (select-list-handle-key-press select-list key)) (paint) #f) (else #f))))))