;;; factor.el --- Interacting with Factor within emacs ;; ;; Authors: Eduardo Cavazos <wayo.cavazos@gmail.com> ;; Jose A Ortega Ruiz <jao@gnu.org> ;; Keywords: languages ;;; Commentary: ;;; Quick setup: ;; Add these lines to your .emacs file: ;; ;; (load-file "/scratch/repos/Factor/misc/factor.el") ;; (setq factor-binary "/scratch/repos/Factor/factor") ;; (setq factor-image "/scratch/repos/Factor/factor.image") ;; ;; Of course, you'll have to edit the directory paths for your system ;; accordingly. Alternatively, put this file in your load-path and use ;; ;; (require 'factor) ;; ;; instead of load-file. ;; ;; That's all you have to do to "install" factor.el on your ;; system. Whenever you edit a factor file, Emacs will know to switch ;; to Factor mode. ;; ;; For further customization options, ;; M-x customize-group RET factor ;; ;; To start a Factor listener inside Emacs, ;; M-x run-factor ;;; Requirements: (require 'font-lock) (require 'comint) (require 'view) (require 'ring) ;;; Customization: (defgroup factor nil "Factor mode" :group 'languages) (defcustom factor-default-indent-width 4 "Default indentantion width for factor-mode. This value will be used for the local variable `factor-indent-width' in new factor buffers. For existing code, we first check if `factor-indent-width' is set explicitly in a local variable section or line (e.g. '! -*- factor-indent-witdth: 2 -*-'). If that's not the case, `factor-mode' tries to infer its correct value from the existing code in the buffer." :type 'integer :group 'factor) (defcustom factor-binary "~/factor/factor" "Full path to the factor executable to use when starting a listener." :type '(file :must-match t) :group 'factor) (defcustom factor-image "~/factor/factor.image" "Full path to the factor image to use when starting a listener." :type '(file :must-match t) :group 'factor) (defcustom factor-use-doc-window t "When on, use a separate window to display help information. Disable to see that information in the factor-listener comint window." :type 'boolean :group 'factor) (defcustom factor-listener-use-other-window t "Use a window other than the current buffer's when switching to the factor-listener buffer." :type 'boolean :group 'factor) (defcustom factor-listener-window-allow-split t "Allow window splitting when switching to the factor-listener buffer." :type 'boolean :group 'factor) (defcustom factor-help-always-ask t "When enabled, always ask for confirmation in help prompts." :type 'boolean :group 'factor) (defcustom factor-help-use-minibuffer t "When enabled, use the minibuffer for short help messages." :type 'boolean :group 'factor) (defcustom factor-display-compilation-output t "Display the REPL buffer before compiling files." :type 'boolean :group 'factor) (defcustom factor-mode-hook nil "Hook run when entering Factor mode." :type 'hook :group 'factor) (defcustom factor-help-mode-hook nil "Hook run by `factor-help-mode'." :type 'hook :group 'factor) (defgroup factor-faces nil "Faces used in Factor mode" :group 'factor :group 'faces) (defface factor-font-lock-parsing-word (face-default-spec font-lock-keyword-face) "Face for parsing words." :group 'factor-faces) (defface factor-font-lock-declaration (face-default-spec font-lock-keyword-face) "Face for declaration words (inline, parsing ...)." :group 'factor-faces) (defface factor-font-lock-comment (face-default-spec font-lock-comment-face) "Face for comments." :group 'factor-faces) (defface factor-font-lock-string (face-default-spec font-lock-string-face) "Face for strings." :group 'factor-faces) (defface factor-font-lock-stack-effect (face-default-spec font-lock-comment-face) "Face for stack effect specifications." :group 'factor-faces) (defface factor-font-lock-word-definition (face-default-spec font-lock-function-name-face) "Face for word, generic or method being defined." :group 'factor-faces) (defface factor-font-lock-symbol-definition (face-default-spec font-lock-variable-name-face) "Face for name of symbol being defined." :group 'factor-faces) (defface factor-font-lock-vocabulary-name (face-default-spec font-lock-constant-face) "Face for names of vocabularies in USE or USING." :group 'factor-faces) (defface factor-font-lock-type-definition (face-default-spec font-lock-type-face) "Face for type (tuple) names." :group 'factor-faces) (defface factor-font-lock-constructor (face-default-spec font-lock-type-face) "Face for constructors (<foo>)." :group 'factor-faces) (defface factor-font-lock-setter-word (face-default-spec font-lock-function-name-face) "Face for setter words (>>foo)." :group 'factor-faces) (defface factor-font-lock-parsing-word (face-default-spec font-lock-keyword-face) "Face for parsing words." :group 'factor-faces) (defface factor-font-lock-help-mode-headlines '((t (:bold t :weight bold))) "Face for headlines in help buffers." :group 'factor-faces) ;;; Compatibility (when (not (fboundp 'ring-member)) (defun ring-member (ring item) (catch 'found (dotimes (ind (ring-length ring) nil) (when (equal item (ring-ref ring ind)) (throw 'found ind)))))) ;;; Factor mode font lock: (defconst factor--parsing-words '("{" "}" "^:" "^::" ";" "<<" "<PRIVATE" ">>" "BIN:" "BV{" "B{" "C:" "C-STRUCT:" "C-UNION:" "CHAR:" "CS{" "C{" "DEFER:" "ERROR:" "EXCLUDE:" "FORGET:" "GENERIC#" "GENERIC:" "HEX:" "HOOK:" "H{" "IN:" "INSTANCE:" "INTERSECTION:" "M:" "MACRO:" "MACRO::" "MAIN:" "MATH:" "METHOD:" "MIXIN:" "OCT:" "POSTPONE:" "PREDICATE:" "PRIMITIVE:" "PRIVATE>" "PROVIDE:" "REQUIRE:" "REQUIRES:" "SINGLETON:" "SLOT:" "SYMBOL:" "SYMBOLS:" "TUPLE:" "T{" "t\\??" "TYPEDEF:" "UNION:" "USE:" "USING:" "V{" "VARS:" "W{")) (defconst factor--regex-parsing-words-ext (regexp-opt '("B" "call-next-method" "delimiter" "f" "initial:" "read-only") 'words)) (defconst factor--declaration-words '("flushable" "foldable" "inline" "parsing" "recursive")) (defconst factor--regex-declaration-words (regexp-opt factor--declaration-words 'words)) (defsubst factor--regex-second-word (prefixes) (format "^%s +\\([^ \r\n]+\\)" (regexp-opt prefixes t))) (defconst factor--regex-method-definition "^M: +\\([^ ]+\\) +\\([^ ]+\\)") (defconst factor--regex-word-definition (factor--regex-second-word '(":" "::" "GENERIC:"))) (defconst factor--regex-type-definition (factor--regex-second-word '("TUPLE:" "SINGLETON:"))) (defconst factor--regex-parent-type "^TUPLE: +[^ ]+ +< +\\([^ ]+\\)") (defconst factor--regex-constructor "<[^ >]+>") (defconst factor--regex-setter "\\W>>[^ ]+\\b") (defconst factor--regex-symbol-definition (factor--regex-second-word '("SYMBOL:" "VAR:"))) (defconst factor--regex-stack-effect " ( .* )") (defconst factor--regex-using-lines "^USING: +\\(\\([^;]\\|[\n\r\f]\\)*\\);") (defconst factor--regex-use-line "^USE: +\\(.*\\)$") (defconst factor--font-lock-keywords `((,factor--regex-stack-effect . 'factor-font-lock-stack-effect) ("\\(P\\|SBUF\\)\"" 1 'factor-font-lock-parsing-word) ,@(mapcar #'(lambda (w) (cons (concat "\\(^\\| \\)\\(" w "\\)\\($\\| \\)") '(2 'factor-font-lock-parsing-word))) factor--parsing-words) (,factor--regex-parsing-words-ext . 'factor-font-lock-parsing-word) (,factor--regex-declaration-words 1 'factor-font-lock-declaration) (,factor--regex-word-definition 2 'factor-font-lock-word-definition) (,factor--regex-type-definition 2 'factor-font-lock-type-definition) (,factor--regex-method-definition (1 'factor-font-lock-type-definition) (2 'factor-font-lock-word-definition)) (,factor--regex-parent-type 1 'factor-font-lock-type-definition) (,factor--regex-constructor . 'factor-font-lock-constructor) (,factor--regex-setter . 'factor-font-lock-setter-word) (,factor--regex-symbol-definition 2 'factor-font-lock-symbol-definition) (,factor--regex-use-line 1 'factor-font-lock-vocabulary-name)) "Font lock keywords definition for Factor mode.") ;;; Factor mode syntax: (defconst factor--regex-definition-starters (regexp-opt '("VARS" "TUPLE" "MACRO" "MACRO:" "M" ":" ""))) (defconst factor--regex-definition-start (format "^\\(%s:\\) " factor--regex-definition-starters)) (defconst factor--regex-definition-end (format "\\(;\\( +%s\\)*\\)" factor--regex-declaration-words)) (defconst factor--font-lock-syntactic-keywords `(("\\(#!\\)" (1 "<")) (" \\(!\\)" (1 "<")) ("^\\(!\\)" (1 "<")) ("\\(!(\\) .* \\()\\)" (1 "<") (2 ">")))) (defvar factor-mode-syntax-table nil "Syntax table used while in Factor mode.") (if factor-mode-syntax-table () (let ((i 0)) (setq factor-mode-syntax-table (make-syntax-table)) ;; Default is atom-constituent (while (< i 256) (modify-syntax-entry i "_ " factor-mode-syntax-table) (setq i (1+ i))) ;; Word components. (setq i ?0) (while (<= i ?9) (modify-syntax-entry i "w " factor-mode-syntax-table) (setq i (1+ i))) (setq i ?A) (while (<= i ?Z) (modify-syntax-entry i "w " factor-mode-syntax-table) (setq i (1+ i))) (setq i ?a) (while (<= i ?z) (modify-syntax-entry i "w " factor-mode-syntax-table) (setq i (1+ i))) ;; Whitespace (modify-syntax-entry ?\t " " factor-mode-syntax-table) (modify-syntax-entry ?\f " " factor-mode-syntax-table) (modify-syntax-entry ?\r " " factor-mode-syntax-table) (modify-syntax-entry ? " " factor-mode-syntax-table) ;; (end of) Comments (modify-syntax-entry ?\n ">" factor-mode-syntax-table) ;; Parenthesis (modify-syntax-entry ?\[ "(] " factor-mode-syntax-table) (modify-syntax-entry ?\] ")[ " factor-mode-syntax-table) (modify-syntax-entry ?{ "(} " factor-mode-syntax-table) (modify-syntax-entry ?} "){ " factor-mode-syntax-table) (modify-syntax-entry ?\( "()" factor-mode-syntax-table) (modify-syntax-entry ?\) ")(" factor-mode-syntax-table) ;; Strings (modify-syntax-entry ?\" "\"" factor-mode-syntax-table) (modify-syntax-entry ?\\ "/" factor-mode-syntax-table))) ;;; symbol-at-point (defun factor--beginning-of-symbol () "Move point to the beginning of the current symbol." (while (eq (char-before) ?:) (backward-char)) (skip-syntax-backward "w_")) (defun factor--end-of-symbol () "Move point to the end of the current symbol." (skip-syntax-forward "w_") (while (looking-at ":") (forward-char))) (put 'factor-symbol 'end-op 'factor--end-of-symbol) (put 'factor-symbol 'beginning-op 'factor--beginning-of-symbol) (defsubst factor--symbol-at-point () (let ((s (substring-no-properties (thing-at-point 'factor-symbol)))) (and (> (length s) 0) s))) ;;; Factor mode indentation: (make-variable-buffer-local (defvar factor-indent-width factor-default-indent-width "Indentation width in factor buffers. A local variable.")) (defun factor--guess-indent-width () "Chooses an indentation value from existing code." (let ((word-cont "^ +[^ ]") (iw)) (save-excursion (beginning-of-buffer) (while (not iw) (if (not (re-search-forward factor--regex-definition-start nil t)) (setq iw factor-default-indent-width) (forward-line) (when (looking-at word-cont) (setq iw (current-indentation)))))) iw)) (defsubst factor--ppss-brackets-depth () (nth 0 (syntax-ppss))) (defsubst factor--ppss-brackets-start () (nth 1 (syntax-ppss))) (defun factor--ppss-brackets-end () (save-excursion (goto-char (factor--ppss-brackets-start)) (condition-case nil (progn (forward-sexp) (1- (point))) (error -1)))) (defsubst factor--indentation-at (pos) (save-excursion (goto-char pos) (current-indentation))) (defsubst factor--at-first-char-p () (= (- (point) (line-beginning-position)) (current-indentation))) (defconst factor--regex-single-liner (format "^%s" (regexp-opt '("DEFER:" "GENERIC:" "IN:" "PRIVATE>" "<PRIVATE" "SINGLETON:" "SYMBOL:" "USE:" "VAR:")))) (defconst factor--regex-begin-of-def (format "^USING: \\|\\(%s\\)\\|\\(%s .*\\)" factor--regex-definition-start factor--regex-single-liner)) (defconst factor--regex-end-of-def-line (format "^.*%s" factor--regex-definition-end)) (defconst factor--regex-end-of-def (format "\\(%s\\)\\|\\(%s .*\\)" factor--regex-end-of-def-line factor--regex-single-liner)) (defsubst factor--at-begin-of-def () (looking-at factor--regex-begin-of-def)) (defsubst factor--at-end-of-def () (looking-at factor--regex-end-of-def)) (defsubst factor--looking-at-emptiness () (looking-at "^[ \t]*$")) (defun factor--at-setter-line () (save-excursion (beginning-of-line) (if (not (factor--looking-at-emptiness)) (re-search-forward factor--regex-setter (line-end-position) t) (forward-line -1) (or (factor--at-constructor-line) (factor--at-setter-line))))) (defun factor--at-constructor-line () (save-excursion (beginning-of-line) (re-search-forward factor--regex-constructor (line-end-position) t))) (defsubst factor--increased-indentation (&optional i) (+ (or i (current-indentation)) factor-indent-width)) (defsubst factor--decreased-indentation (&optional i) (- (or i (current-indentation)) factor-indent-width)) (defun factor--indent-in-brackets () (save-excursion (beginning-of-line) (when (> (factor--ppss-brackets-depth) 0) (let ((op (factor--ppss-brackets-start)) (cl (factor--ppss-brackets-end)) (ln (line-number-at-pos))) (when (> ln (line-number-at-pos op)) (if (and (> cl 0) (= ln (line-number-at-pos cl))) (factor--indentation-at op) (factor--increased-indentation (factor--indentation-at op)))))))) (defun factor--indent-definition () (save-excursion (beginning-of-line) (when (factor--at-begin-of-def) 0))) (defun factor--indent-setter-line () (when (factor--at-setter-line) (save-excursion (let ((indent (and (factor--at-constructor-line) (current-indentation)))) (while (not (or indent (bobp) (factor--at-begin-of-def) (factor--at-end-of-def))) (if (factor--at-constructor-line) (setq indent (factor--increased-indentation)) (forward-line -1))) indent)))) (defun factor--indent-continuation () (save-excursion (forward-line -1) (while (and (not (bobp)) (factor--looking-at-emptiness)) (forward-line -1)) (if (or (factor--at-end-of-def) (factor--at-setter-line)) (factor--decreased-indentation) (if (and (factor--at-begin-of-def) (not (looking-at factor--regex-using-lines))) (factor--increased-indentation) (current-indentation))))) (defun factor--calculate-indentation () "Calculate Factor indentation for line at point." (or (and (bobp) 0) (factor--indent-definition) (factor--indent-in-brackets) (factor--indent-setter-line) (factor--indent-continuation) 0)) (defun factor--indent-line () "Indent current line as Factor code" (let ((target (factor--calculate-indentation)) (pos (- (point-max) (point)))) (if (= target (current-indentation)) (if (< (current-column) (current-indentation)) (back-to-indentation)) (beginning-of-line) (delete-horizontal-space) (indent-to target) (if (> (- (point-max) pos) (point)) (goto-char (- (point-max) pos)))))) ;; Factor mode: (defvar factor-mode-map (make-sparse-keymap) "Key map used by Factor mode.") (defsubst factor--beginning-of-defun (&optional times) (re-search-backward factor--regex-begin-of-def nil t times)) (defsubst factor--end-of-defun () (re-search-forward factor--regex-end-of-def nil t)) ;;;###autoload (defun factor-mode () "A mode for editing programs written in the Factor programming language. \\{factor-mode-map}" (interactive) (kill-all-local-variables) (use-local-map factor-mode-map) (setq major-mode 'factor-mode) (setq mode-name "Factor") ;; Font locking (set (make-local-variable 'comment-start) "! ") (set (make-local-variable 'parse-sexp-lookup-properties) t) (set (make-local-variable 'font-lock-comment-face) 'factor-font-lock-comment) (set (make-local-variable 'font-lock-string-face) 'factor-font-lock-string) (set (make-local-variable 'font-lock-defaults) `(factor--font-lock-keywords nil nil nil nil (font-lock-syntactic-keywords . ,factor--font-lock-syntactic-keywords))) (set-syntax-table factor-mode-syntax-table) ;; Defun navigation (set (make-local-variable 'beginning-of-defun-function) 'factor--beginning-of-defun) (set (make-local-variable 'end-of-defun-function) 'factor--end-of-defun) (set (make-local-variable 'open-paren-in-column-0-is-defun-start) nil) ;; Indentation (set (make-local-variable 'indent-line-function) 'factor--indent-line) (setq factor-indent-width (factor--guess-indent-width)) (setq indent-tabs-mode nil) ;; ElDoc (set (make-local-variable 'eldoc-documentation-function) 'factor--eldoc) (run-hooks 'factor-mode-hook)) (add-to-list 'auto-mode-alist '("\\.factor\\'" . factor-mode)) ;;; Factor listener mode: ;;;###autoload (define-derived-mode factor-listener-mode comint-mode "Factor Listener" "Major mode for interacting with an inferior Factor listener process. \\{factor-listener-mode-map}" (set (make-local-variable 'comint-prompt-regexp) "^( [^)]+ ) ")) (defvar factor--listener-buffer nil "The buffer in which the Factor listener is running.") (defun factor--listener-start-process () "Start an inferior Factor listener process, using `factor-binary' and `factor-image'." (setq factor--listener-buffer (apply 'make-comint "factor" (expand-file-name factor-binary) nil `("-run=listener" ,(format "-i=%s" (expand-file-name factor-image))))) (with-current-buffer factor--listener-buffer (factor-listener-mode))) (defun factor--listener-process (&optional start) (or (and (buffer-live-p factor--listener-buffer) (get-buffer-process factor--listener-buffer)) (if (not start) (error "No running factor listener. Try M-x run-factor.") (factor--listener-start-process) (factor--listener-process t)))) ;;;###autoload (defalias 'switch-to-factor 'run-factor) ;;;###autoload (defun run-factor (&optional arg) "Show the factor-listener buffer, starting the process if needed." (interactive) (let ((buf (process-buffer (factor--listener-process t))) (pop-up-windows factor-listener-window-allow-split)) (if factor-listener-use-other-window (pop-to-buffer buf) (switch-to-buffer buf)))) (defun factor-telnet-to-port (port) (interactive "nPort: ") (switch-to-buffer (make-comint-in-buffer "factor-telnet" nil (cons "localhost" port)))) (defun factor-telnet () (interactive) (factor-telnet-to-port 9000)) (defun factor-telnet-factory () (interactive) (factor-telnet-to-port 9010)) ;;; Factor listener interaction: (defun factor--listener-send-cmd (cmd) (let ((proc (factor--listener-process))) (when proc (let* ((out (get-buffer-create "*factor messages*")) (beg (with-current-buffer out (goto-char (point-max))))) (comint-redirect-send-command-to-process cmd out proc nil t) (with-current-buffer factor--listener-buffer (while (not comint-redirect-completed) (sleep-for 0 1))) (with-current-buffer out (split-string (buffer-substring-no-properties beg (point-max)) "[\"\f\n\r\v]+" t)))))) ;;;;; Current vocabulary: (make-variable-buffer-local (defvar factor--current-vocab nil "Current vocabulary.")) (defconst factor--regexp-current-vocab "^IN: +\\([^ \r\n\f]+\\)") (defun factor--current-buffer-vocab () (save-excursion (when (or (re-search-backward factor--regexp-current-vocab nil t) (re-search-forward factor--regexp-current-vocab nil t)) (setq factor--current-vocab (match-string-no-properties 1))))) (defun factor--current-listener-vocab () (car (factor--listener-send-cmd "USING: parser ; in get ."))) (defun factor--set-current-listener-vocab (&optional vocab) (factor--listener-send-cmd (format "IN: %s" (or vocab (factor--current-buffer-vocab)))) t) (defmacro factor--with-vocab (vocab &rest body) (let ((current (make-symbol "current"))) `(let ((,current (factor--current-listener-vocab))) (factor--set-current-listener-vocab ,vocab) (prog1 (condition-case nil (progn . ,body) (error nil)) (factor--set-current-listener-vocab ,current))))) (put 'factor--with-vocab 'lisp-indent-function 1) ;;;;; Synchronous interaction: (defsubst factor--listener-vocab-cmds (cmds &optional vocab) (factor--with-vocab vocab (mapcar #'factor--listener-send-cmd cmds))) (defsubst factor--listener-vocab-cmd (cmd &optional vocab) (factor--with-vocab vocab (factor--listener-send-cmd cmd))) ;;;;; Buffer cycling and docs (defconst factor--cycle-endings '(".factor" "-tests.factor" "-docs.factor")) (defconst factor--regex-cycle-endings (format "\\(.*?\\)\\(%s\\)$" (regexp-opt factor--cycle-endings))) (defconst factor--cycle-endings-ring (let ((ring (make-ring (length factor--cycle-endings)))) (dolist (e factor--cycle-endings ring) (ring-insert ring e)))) (defun factor--cycle-next (file) (let* ((match (string-match factor--regex-cycle-endings file)) (base (and match (match-string-no-properties 1 file))) (ending (and match (match-string-no-properties 2 file))) (idx (and ending (ring-member factor--cycle-endings-ring ending))) (gfl (lambda (i) (concat base (ring-ref factor--cycle-endings-ring i))))) (if (not idx) file (let ((l (length factor--cycle-endings)) (i 1) next) (while (and (not next) (< i l)) (when (file-exists-p (funcall gfl (+ idx i))) (setq next (+ idx i))) (setq i (1+ i))) (funcall gfl (or next idx)))))) (defun factor-visit-other-file (&optional file) "Cycle between code, tests and docs factor files." (interactive) (find-file (factor--cycle-next (or file (buffer-file-name))))) ;;;;; Interface: See (defconst factor--regex-error-marker "^Type :help for debugging") (defconst factor--regex-data-stack "^--- Data stack:") (defun factor--prune-ans-strings (ans) (nreverse (catch 'done (let ((res)) (dolist (a ans res) (cond ((string-match factor--regex-stack-effect a) (throw 'done (cons a res))) ((string-match factor--regex-data-stack a) (throw 'done res)) ((string-match factor--regex-error-marker a) (throw 'done nil)) (t (push a res)))))))) (defun factor--see-ans-to-string (ans) (let ((s (mapconcat #'identity (factor--prune-ans-strings ans) " ")) (font-lock-verbose nil)) (and (> (length s) 0) (with-temp-buffer (insert s) (factor-mode) (font-lock-fontify-buffer) (buffer-string))))) (defun factor--see-current-word (&optional word) (let ((word (or word (factor--symbol-at-point)))) (when word (let ((answer (factor--listener-send-cmd (format "\\ %s see" word)))) (and answer (factor--see-ans-to-string answer)))))) (defalias 'factor--eldoc 'factor--see-current-word) (defun factor-see-current-word (&optional word) "Echo in the minibuffer information about word at point." (interactive) (let* ((proc (factor--listener-process)) (word (or word (factor--symbol-at-point))) (msg (factor--see-current-word word))) (if msg (message "%s" msg) (if word (message "No help found for '%s'" word) (message "No word at point"))))) ;;; to fix: (defun factor-run-file () (interactive) (when (and (buffer-modified-p) (y-or-n-p (format "Save file %s? " (buffer-file-name)))) (save-buffer)) (when factor-display-compilation-output (factor-display-output-buffer)) (comint-send-string "*factor*" (format "\"%s\"" (buffer-file-name))) (comint-send-string "*factor*" " run-file\n")) (defun factor-display-output-buffer () (with-current-buffer "*factor*" (goto-char (point-max)) (unless (get-buffer-window (current-buffer) t) (display-buffer (current-buffer) t)))) (defun factor-send-string (str) (let ((n (length (split-string str "\n")))) (save-excursion (set-buffer "*factor*") (goto-char (point-max)) (if (> n 1) (newline)) (insert str) (comint-send-input)))) (defun factor-send-region (start end) (interactive "r") (let ((str (buffer-substring start end)) (n (count-lines start end))) (save-excursion (set-buffer "*factor*") (goto-char (point-max)) (if (> n 1) (newline)) (insert str) (comint-send-input)))) (defun factor-send-definition () (interactive) (factor-send-region (search-backward ":") (search-forward ";"))) (defun factor-edit () (interactive) (comint-send-string "*factor*" "\\ ") (comint-send-string "*factor*" (thing-at-point 'sexp)) (comint-send-string "*factor*" " edit\n")) (defun factor-clear () (interactive) (factor-send-string "clear")) (defun factor-comment-line () (interactive) (beginning-of-line) (insert "! ")) ;;;; Factor help mode: (defvar factor-help-mode-map (make-sparse-keymap) "Keymap for Factor help mode.") (defconst factor--help-headlines (regexp-opt '("Definition" "Examples" "Generic word contract" "Inputs and outputs" "Parent topics:" "See also" "Syntax" "Vocabulary" "Warning" "Word description") t)) (defconst factor--help-headlines-regexp (format "^%s" factor--help-headlines)) (defconst factor--help-font-lock-keywords `((,factor--help-headlines-regexp . 'factor-font-lock-help-mode-headlines) ,@factor--font-lock-keywords)) (defun factor-help-mode () "Major mode for displaying Factor help messages. \\{factor-help-mode-map}" (interactive) (kill-all-local-variables) (use-local-map factor-help-mode-map) (setq mode-name "Factor Help") (setq major-mode 'factor-help-mode) (set (make-local-variable 'font-lock-defaults) '(factor--help-font-lock-keywords t nil nil nil)) (set (make-local-variable 'comint-redirect-subvert-readonly) t) (set (make-local-variable 'comint-redirect-echo-input) nil) (set (make-local-variable 'view-no-disable-on-exit) t) (view-mode) (setq view-exit-action (lambda (buffer) ;; Use `with-current-buffer' to make sure that `bury-buffer' ;; also removes BUFFER from the selected window. (with-current-buffer buffer (bury-buffer)))) (run-mode-hooks 'factor-help-mode-hook)) (defun factor--listener-help-buffer () (with-current-buffer (get-buffer-create "*factor-help*") (let ((inhibit-read-only t)) (erase-buffer)) (factor-help-mode) (current-buffer))) (defvar factor--help-history nil) (defun factor--listener-show-help (&optional see) (let* ((proc (factor--listener-process)) (def (factor--symbol-at-point)) (prompt (format "See%s help on%s: " (if see " short" "") (if def (format " (%s)" def) ""))) (ask (or (not (eq major-mode 'factor-mode)) (not def) factor-help-always-ask)) (cmd (format "\\ %s %s" (if ask (read-string prompt nil 'factor--help-history def) def) (if see "see" "help"))) (hb (factor--listener-help-buffer))) (comint-redirect-send-command-to-process cmd hb proc nil) (pop-to-buffer hb) (beginning-of-buffer hb))) ;;;; Interface: see/help commands (defun factor-see (&optional arg) "See a help summary of symbol at point. By default, the information is shown in the minibuffer. When called with a prefix argument, the information is displayed in a separate help buffer." (interactive "P") (if (if factor-help-use-minibuffer (not arg) arg) (factor-see-current-word) (factor--listener-show-help t))) (defun factor-help () "Show extended help about the symbol at point, using a help buffer." (interactive) (factor--listener-show-help)) (defun factor-refresh-all () "Reload source files and documentation for all loaded vocabularies which have been modified on disk." (interactive) (comint-send-string "*factor*" "refresh-all\n")) ;;; Key bindings: (defun factor--define-key (key cmd &optional both) (let ((ms (list factor-mode-map))) (when both (push factor-help-mode-map ms)) (dolist (m ms) (define-key m (vector '(control ?c) key) cmd) (define-key m (vector '(control ?c) `(control ,key)) cmd)))) (defun factor--define-auto-indent-key (key) (define-key factor-mode-map (vector key) (lambda (n) (interactive "p") (self-insert-command n) (indent-for-tab-command)))) (factor--define-key ?f 'factor-run-file) (factor--define-key ?r 'factor-send-region) (factor--define-key ?d 'factor-send-definition) (factor--define-key ?s 'factor-see t) (factor--define-key ?e 'factor-edit) (factor--define-key ?z 'switch-to-factor t) (factor--define-key ?o 'factor-visit-other-file) (factor--define-key ?c 'comment-region) (factor--define-auto-indent-key ?\]) (factor--define-auto-indent-key ?\}) (define-key factor-mode-map "\C-ch" 'factor-help) (define-key factor-help-mode-map "\C-ch" 'factor-help) (define-key factor-mode-map "\C-m" 'newline-and-indent) (define-key factor-listener-mode-map [f8] 'factor-refresh-all) (provide 'factor) ;;; factor.el ends here