1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442
|
; 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))))
|