; pop up the tooltip under the text ; partially complete as much as possible (if (string-match "XEmacs" emacs-version) (defun replace-regexp-in-string (regexp newtext string) (replace-in-string string regexp newtext))) (unless (fboundp 'looking-back) ; Exists in Emacs 22 (defun looking-back (regexp &optional limit greedy) ; Copied from Emacs 22 "Return non-nil if text before point matches regular expression REGEXP. Like `looking-at' except matches before point, and is slower. LIMIT if non-nil speeds up the search by specifying a minimum starting position, to avoid checking matches that would start before LIMIT. If GREEDY is non-nil, extend the match backwards as far as possible, stopping when a single additional previous character cannot be part of a match for REGEXP." (let ((start (point)) (pos (save-excursion (and (re-search-backward (concat "\\(?:" regexp "\\)\\=") limit t) (point))))) (if (and greedy pos) (save-restriction (narrow-to-region (point-min) start) (while (and (> pos (point-min)) (save-excursion (goto-char pos) (backward-char 1) (looking-at (concat "\\(?:" regexp "\\)\\'")))) (setq pos (1- pos))) (save-excursion (goto-char pos) (looking-at (concat "\\(?:" regexp "\\)\\'"))))) (not (null pos))))) (unless (fboundp 'tooltip-show) (defun tooltip-show (tip) (print tip))) (unless (fboundp 'line-number-at-pos) ; Exists in Emacs 22. (defun line-number-at-pos (&optional pos) "Buffer line number at position POS. Current line number if POS is nil. Counting starts at (point-min), so any narrowing restriction applies." (1+ (count-lines (point-min) (save-excursion (when pos (goto-char pos)) (forward-line 0) (point)))))) (defun fold (f x li) "Recursively applies (f x i) where i is the ith element in the list li. For example, (fold f x '(1 2)) returns (f (f x 1) 2)" (let ((li2 li) (ele) (x2 x)) (while (setq ele (pop li2)) (setq x2 (funcall f x2 ele))) x2)) (defun filter (g li) (fold (lambda (acc x) (if (funcall g x) (cons x acc) acc)) nil li)) (defun caml-format-packages (packages) (mapconcat 'identity packages ",")) ;(caml-format-packages '("pcre" "netstring" "ocamldap")) (defun caml-format-paths (paths) (fold '(lambda (acc p) (cons "-I" (cons p acc))) () paths)) ;(caml-format-paths '("/home/eric" "/opt/godi/lib/ocaml/pkg-lib/pcre" "foo")) ; state and configuration variables (defvar caml-completion-buf "*caml-cmigrep*") (defvar caml-packages nil) (defvar caml-includes nil) (defvar caml-default-dir nil) (defvar caml-always-show-completions-buf t) (make-variable-buffer-local 'caml-default-dir) (set-default 'caml-default-dir nil) (defconst search-type-value "-v") (defconst search-type-record-label "-r") (defconst search-type-module "-m") (defconst search-type-constructor "-c") (defconst search-type-variant "-p") (defun caml-clear-completion-buf () (save-excursion (set-buffer caml-completion-buf) (delete-region (point-min) (point-max)))) (defun strip-props (s) (set-text-properties 0 (length s) nil s) s) (defun open-modules () "parse the file to determine the list of modules open, and return the list unqualified" (save-excursion (save-match-data (goto-char (point-min)) (let ((modules "")) (while (re-search-forward "open +\\([A-Z][a-zA-Z0-9'._]*\\)" nil t) (if (equal modules "") (setq modules (strip-props (match-string 1))) (setq modules (concat modules "," (strip-props (match-string 1)))))) modules)))) (defun caml-search (search-type value &rest module-exps) "search for a value starting with [value] in [module-exp], in the directories specified by [packages] and [includes] and with the current working directory of cmigrep set to [dir]. placing the results in the *caml-cmigrep* buffer" (let ((process-connection-type nil) ; Use a pipe for communication (default-directory (if caml-default-dir caml-default-dir default-directory)) ; Set CWD of cmigrep to dir (args (if value (append (list search-type value) module-exps) (cons search-type module-exps))) (open (open-modules))) (and caml-packages (let ((packages (caml-format-packages caml-packages))) (push packages args) (push "-package" args))) (and caml-includes (let ((includes (caml-format-paths caml-includes))) (setq args (append includes args)))) (and (not (equal open "")) (progn (push open args) (push "-open" args))) (and (get-buffer caml-completion-buf) (caml-clear-completion-buf)) (apply 'call-process (append (list "cmigrep" nil caml-completion-buf nil) args)))) (defun condense-spaces (s) "condense long strings of white space into a single space" (replace-regexp-in-string "[[:space:]]+" " " s)) (defun strip (s) (replace-regexp-in-string "[[:space:]]+$" "" (replace-regexp-in-string "^[[:space:]]+" "" s))) (defun extract-value-name () (save-match-data (if (re-search-forward "[a-z]") (let ((start (progn (backward-char) (point)))) (if (re-search-forward ":") (progn (backward-char) (strip (buffer-substring start (point))))))))) (defun extract-value-type () (interactive) (save-match-data (let ((start (point))) (if (re-search-forward "=\\|(\\*" (point-at-eol) t) (progn (backward-char 2) (strip (buffer-substring start (point)))) (progn (goto-char (point-at-eol)) (strip (buffer-substring start (point)))))))) (defun extract-value-module () (save-match-data (let ((start (point))) (if (search-forward "(*" (point-at-eol) t) (if (re-search-forward "[[:space:]]*\\([A-Za-z0-9_'.]*\\)" (point-at-eol) t) (match-string 1) (error "invalid module comment")) nil)))) (defun caml-parse-value-completion () (save-match-data (if (re-search-forward "val\\|external") (let* ((value-name (extract-value-name)) (value-type (extract-value-type)) (value-module (extract-value-module))) (if value-module (list value-name (condense-spaces (concat value-type " from " value-module))) (list value-name value-type))) (error "invalid value completion")))) (defun caml-extract-value-completion (line) (set-buffer caml-completion-buf) (goto-line line) ; goto the line that our completion is on (beginning-of-line) ; goto the beginning (caml-parse-value-completion)) (defun caml-extract-module-completion (line) (save-match-data (set-buffer caml-completion-buf) (goto-line line) (beginning-of-line) (if (looking-at "\\([A-Z][a-zA-Z0-9._']*\\)") (match-string 1) (error "cannot read completion")))) (defun caml-parse-record-label () (or (search-forward "mutable" (point-at-eol) t) ; skip the "mutable" keyword (goto-char (point-at-bol))) (let* ((field-name (extract-value-name)) (field-type (extract-value-type)) (field-module (extract-value-module))) (if field-module (list field-name (condense-spaces (concat field-type " from " field-module))) (list field-name field-type)))) (defun caml-extract-record-label (line) (set-buffer caml-completion-buf) (goto-line line) (beginning-of-line) (caml-parse-record-label)) (defun extract-constructor-name () (save-match-data (let ((start (point))) (if (search-forward " of " (point-at-eol) t) (progn (backward-char 4) (strip (buffer-substring start (point)))) (progn (goto-char (point-at-bol)) (if (search-forward "(*" (point-at-eol) t) (progn (backward-char 2) (strip (buffer-substring start (point)))) (progn (goto-char (point-at-eol)) (strip (buffer-substring start (point)))))))))) (defun caml-extract-constructor-completion (line) (set-buffer caml-completion-buf) (goto-line line) (beginning-of-line) (let* ((constructor-name (extract-constructor-name)) (constructor-type (extract-value-type)) (constructor-module (extract-value-module)) (hint constructor-type)) (and constructor-module (setq hint (concat hint " from " constructor-module))) (list constructor-name hint))) ; (caml-extract-value-completion 1) (defun caml-extract-completions (completion-parser) (save-match-data (save-excursion (set-buffer caml-completion-buf) (goto-char (point-min)) (let ((beg (line-number-at-pos (point-min))) (end (line-number-at-pos (point-max))) completions) (while (> end (line-number-at-pos (point))) (let ((completion (funcall completion-parser (line-number-at-pos (point))))) (setq completions (cons completion completions)) (forward-line))) completions)))) (defun caml-format-value-match (value) (if value (concat "^" value ".*") ".*")) (defun caml-format-module-exp (module-match) (if module-match (substring module-match 0 (- (length module-match) 1)) (error "no module matched"))) ; (caml-format-module-exp "Unix.LargeFile.") (defun strip-colon (type) "given a type expression in the form ': foo -> bar', this function will strip the ':', just a small cosmetic thing. It actually just strips any colon and following white space" (save-match-data (if (string-match ":[[:space:]]*" type) (replace-match "" nil nil type nil) type))) ; (strip-colon-from-type ": foo -> bar") (defun caml-show-completions (completions) (with-output-to-temp-buffer "*Completions*" (display-completion-list completions) 0)) (defun caml-show-unique-completion (completion) (if caml-always-show-completions-buf (caml-show-completions (list completion)) (tooltip-show completion))) (defun caml-perform-completion (unformatted-value completions) (save-match-data (if completions (if (> (length completions) 1) (caml-show-completions completions) (let* ((completion (car completions)) (value-name (if (listp completion) (car completion) completion)) (value-type (if (listp completion) (car (cdr completion)) nil))) (if unformatted-value (let* ((beg (length unformatted-value)) (end (length value-name)) (value-substr (substring value-name beg end))) (insert value-substr) (if value-type (caml-show-unique-completion (strip-colon value-type))) (length value-substr)) (progn (insert value-name) (if value-type (caml-show-unique-completion (strip-colon value-type))) (length value-name)))))))) (defun deref-module (x) (let* ((local (concat "let +module +" x " *= *\\([A-Z][A-Za-z_'0-9.]*\\) +in")) (global (concat "module +" x " *= *\\([A-Z][A-Za-z_'0-9.]*\\)"))) (cond ((re-search-backward local nil t) (deref-module-exp (match-string 1))) ((re-search-backward global nil t) (deref-module-exp (match-string 1))) (t x)))) (defun deref-module-exp (x) (mapconcat 'deref-module (split-string x "\\.") ".")) (defun caml-cmigrep-complete-qualified (parser search-type) (let* ((module-name (match-string 1)) (unformatted-value (match-string 2)) (value (caml-format-value-match unformatted-value)) (module-exp (save-excursion (save-match-data (deref-module-exp (caml-format-module-exp module-name)))))) (if (caml-search search-type value module-exp) (let ((completions (caml-extract-completions parser))) (caml-perform-completion unformatted-value completions)) (error "cmigrep failed")))) (defun caml-cmigrep-complete-unqualified (parser search-type) (let* ((unformatted-value (match-string 1)) (value (caml-format-value-match unformatted-value))) (if (caml-search search-type value) (caml-perform-completion unformatted-value (caml-extract-completions parser)) (error "cmigrep failed")))) (defconst qualified-record-field-lookup "[^a-zA-Z_'][a-z_][a-zA-Z0-9_']*\\.\\(\\(?:[A-Z][A-Za-z_'0-9]*\\.\\)+\\)\\([a-z_][a-zA-Z0-9_']*\\)?") (defconst qualified-value "[^a-zA-Z_'.]\\([A-Z][A-Za-z_'0-9.]*\\.\\)\\([a-z_][A-Za-z0-9_']*\\)?") (defconst qualified-constructor "[^a-zA-Z_'.]\\(\\(?:[A-Z][A-Za-z_'0-9]*\\.\\)+\\)\\([A-Z][A-Za-z_'0-9]*\\)") (defconst unqualified-record-field-lookup "[^a-zA-Z_'][a-z][A-Za-z0-9_']*\\.\\([a-z][A-Za-z0-9_']*\\)?") (defconst unqualified-value "^[^a-zA-Z_']\\([a-z][A-Za-z0-9_']*\\)") (defconst qualified-partial-module "[^a-zA-Z_']\\(\\(?:[A-Z][A-Za-z_'0-9]*\\.\\)+\\)\\([A-Z][A-Za-z_'0-9]*\\)?") (defconst unqualified-partial-module "[^a-zA-Z_']\\([A-Z][A-Za-z_'0-9]*\\)") (defun caml-cmigrep-complete () "complete OCaml based on context" (interactive) (let ((case-fold-search nil) ; make searches case sensitive. I HATE DYNAMIC SCOPE! chars-added) (save-excursion (save-match-data (or caml-default-dir (and (buffer-file-name) (setq caml-default-dir (file-name-directory (buffer-file-name))))) (setq chars-added (cond ((looking-back qualified-record-field-lookup (point-at-bol)) (caml-cmigrep-complete-qualified 'caml-extract-record-label search-type-record-label)) ((looking-back qualified-value (point-at-bol)) (caml-cmigrep-complete-qualified 'caml-extract-value-completion search-type-value)) ((looking-back unqualified-record-field-lookup (point-at-bol)) (caml-cmigrep-complete-unqualified 'caml-extract-record-label search-type-record-label)) ((looking-back unqualified-value (point-at-bol)) (caml-cmigrep-complete-unqualified 'caml-extract-value-completion search-type-value)) ((looking-back qualified-constructor (point-at-bol)) (caml-cmigrep-complete-qualified 'caml-extract-constructor-completion search-type-constructor)) (t (error "requested completion not implemented (yet)")))))) (if chars-added (forward-char chars-added)))) (defun not-empty-string (s) (if (equal s "") nil s)) (defun caml-complete-module () (let* (unformatted-value (module-exp (cond ((looking-back qualified-partial-module (point-at-bol)) (let ((containing-module (not-empty-string (match-string 1))) (partial-module (match-string 2))) (setq unformatted-value partial-module) (list (concat (caml-format-module-exp containing-module) "." partial-module "*")))) ((looking-back unqualified-partial-module (point-at-bol)) (let* ((partial-module (match-string 1)) (partial-module-exp (concat partial-module "*"))) (setq unformatted-value partial-module) (list partial-module-exp))) (t (list "*"))))) (if (apply 'caml-search (cons search-type-module module-exp)) (let ((completions (caml-extract-completions 'caml-extract-module-completion))) (caml-perform-completion unformatted-value completions)) (error "cmigrep failed")))) (defun caml-cmigrep-complete-module () "complete the partial module name before the point" (interactive) (let ((case-fold-search nil) ; make searches case sensitive. I HATE DYNAMIC SCOPE! chars-added) (save-excursion (save-match-data (or caml-default-dir (setq caml-default-dir (file-name-directory (buffer-file-name)))) (setq chars-added (caml-complete-module)))) (if chars-added (forward-char chars-added))))