|
38 | 38 |
|
39 | 39 | (defvar inferior-fsharp-program
|
40 | 40 | (if fsharp-ac-using-mono
|
41 |
| - "fsharpi --readline-" |
| 41 | + "fsharpi" |
42 | 42 | (concat "\"" (fsharp-mode--executable-find "fsi.exe") "\" --fsi-server-input-codepage:65001"))
|
43 | 43 | "*Program name for invoking an inferior fsharp from Emacs.")
|
44 | 44 |
|
|
48 | 48 | (defvar inferior-fsharp-mode-map
|
49 | 49 | (let ((map (copy-keymap comint-mode-map)))
|
50 | 50 | (define-key map [M-return] 'fsharp-comint-send)
|
| 51 | + (define-key map (kbd "<tab>") 'inferior-fsharp-get-completion) |
51 | 52 | map))
|
52 | 53 |
|
53 | 54 | ;; Augment fsharp mode, so you can process fsharp code in the source files.
|
@@ -108,6 +109,132 @@ be sent from another buffer in fsharp mode.
|
108 | 109 | (inferior-fsharp-mode))
|
109 | 110 | (display-buffer inferior-fsharp-buffer-name))))
|
110 | 111 |
|
| 112 | + |
| 113 | +;; the first value returned from our inferior f# process that appears to be a |
| 114 | +;; completion. our filters can end up receiving multiple results that would match |
| 115 | +;; any reasonable regexps, doing this prevents clobbering our match with |
| 116 | +;; confusing-looking values. |
| 117 | +;; |
| 118 | +;; this will hopefully go away as we figure out how to get full completion results |
| 119 | +;; from fsi without the sort of awkward automation we're doing here, but on the |
| 120 | +;; chance that it doesn't we might consider making this buffer-local in the case |
| 121 | +;; that people want to use multiple inferior fsharp buffers in the future. |
| 122 | +(defvar inf-fsharp-completion-match nil) |
| 123 | + |
| 124 | +;; the completion functions below are almost directly ripped from `comint', in |
| 125 | +;; particular the `comint-redirect' functions. since `comint' pretty much |
| 126 | +;; assumes we're line- based, and we cant easily (as far as i know) extend fsi |
| 127 | +;; at runtime to let us retrieve full completion info the way that the |
| 128 | +;; `python-shell-completion-native' functions do, we need to do some extra stuff |
| 129 | +;; to send <tab> and handle deleting input that comint doesn't know has already |
| 130 | +;; been sent to fsi |
| 131 | + |
| 132 | +(defun inf-fsharp-redirect-filter (process input-string) |
| 133 | + (with-current-buffer (process-buffer process) |
| 134 | + (unless inf-fsharp-completion-match |
| 135 | + ;; this if-cascasde doesn't work if we convert it to a cond clause ?? |
| 136 | + (if (and input-string |
| 137 | + (string-match comint-redirect-finished-regexp input-string)) |
| 138 | + (setq inf-fsharp-completion-match input-string) |
| 139 | + |
| 140 | + ;; for some reason, we appear to get the results from fsi fontified |
| 141 | + ;; already in `comint-redirect-previous-input-string' without having |
| 142 | + ;; them pass through this function as `input-string' even though this |
| 143 | + ;; function (or comint-redirect-filter when we were using that directly) |
| 144 | + ;; is the only place we've been able to find that modifies the variable. |
| 145 | + ;; |
| 146 | + ;; looks like a race-condition or multithreading issue but not sure. |
| 147 | + ;; either way, we need to check here to make sure we don't miss our match |
| 148 | + (if (and comint-redirect-previous-input-string |
| 149 | + (string-match comint-redirect-finished-regexp |
| 150 | + (concat comint-redirect-previous-input-string input-string))) |
| 151 | + (setq inf-fsharp-completion-match |
| 152 | + (concat comint-redirect-previous-input-string input-string))))) |
| 153 | + |
| 154 | + (setq comint-redirect-previous-input-string input-string) |
| 155 | + |
| 156 | + (if inf-fsharp-completion-match |
| 157 | + (let ((del-string (make-string (length inf-fsharp-completion-match) ?\b))) |
| 158 | + ;; fsi thinks we should have completed string that hasn't been sent in |
| 159 | + ;; the input buffer, but we will actually send later after inserting |
| 160 | + ;; the fsi-completed string into our repl buffer, so we need to delete |
| 161 | + ;; the match from fsi's input buffer to avoid sending nonsense strings. |
| 162 | + (process-send-string process del-string) |
| 163 | + |
| 164 | + ;; we need to make sure our deletion command goes through before we |
| 165 | + ;; exit this func and remove our current output-filter otherwise we'll |
| 166 | + ;; end up with the output from fsi confirming our backspaces in our |
| 167 | + ;; repl buffer, i.e, if we had "string" we'd see "strin stri str st s |
| 168 | + ;; ". we'd like to find a better way than sleeping to do this, but |
| 169 | + ;; there's not really a way for emacs to know that a process is done |
| 170 | + ;; sending it input as opposed to just not sending it yet.... |
| 171 | + (sleep-for 1) |
| 172 | + |
| 173 | + (save-excursion |
| 174 | + (set-buffer comint-redirect-output-buffer) |
| 175 | + (erase-buffer) |
| 176 | + (goto-char (point-min)) |
| 177 | + (insert (ansi-color-filter-apply inf-fsharp-completion-match))) |
| 178 | + |
| 179 | + (comint-redirect-cleanup) |
| 180 | + (run-hooks 'comint-redirect-hook))))) |
| 181 | + |
| 182 | +(defun inf-fsharp-redirect-get-completion-from-process (input output-buffer process) |
| 183 | + (let* ((process-buffer (if (processp process) |
| 184 | + (process-buffer process) |
| 185 | + process)) |
| 186 | + (proc (get-buffer-process process-buffer))) |
| 187 | + |
| 188 | + (with-current-buffer process-buffer |
| 189 | + (comint-redirect-setup |
| 190 | + output-buffer |
| 191 | + (current-buffer) |
| 192 | + (concat input "\\(.+\\)") |
| 193 | + nil) |
| 194 | + |
| 195 | + (set-process-filter proc #'inf-fsharp-redirect-filter) |
| 196 | + (process-send-string (current-buffer) (concat input "\t"))))) |
| 197 | + |
| 198 | +(defun inf-fsharp-get-completion-from-process (process to-complete) |
| 199 | + (let ((output-buffer " *inf-fsharp-completion*")) |
| 200 | + (with-current-buffer (get-buffer-create output-buffer) |
| 201 | + (erase-buffer) |
| 202 | + (inf-fsharp-redirect-get-completion-from-process to-complete output-buffer process) |
| 203 | + |
| 204 | + (set-buffer (process-buffer process)) |
| 205 | + (while (and (null comint-redirect-completed) |
| 206 | + (accept-process-output process))) |
| 207 | + |
| 208 | + (set-buffer output-buffer) |
| 209 | + (buffer-substring-no-properties (point-min) (point-max))))) |
| 210 | + |
| 211 | +(defun inferior-fsharp-get-completion () |
| 212 | + (interactive) |
| 213 | + (let* ((inf-proc (get-process inferior-fsharp-buffer-subname)) |
| 214 | + (orig-filter (process-filter inf-proc))) |
| 215 | + |
| 216 | + ;; reset our global completion match marker every time we start a completion |
| 217 | + ;; search so we don't accidentally use old complete data. |
| 218 | + (setq inf-fsharp-completion-match nil) |
| 219 | + |
| 220 | + (with-current-buffer (process-buffer inf-proc) |
| 221 | + (let* ((pos (marker-position (cdr comint-last-prompt))) |
| 222 | + (input (buffer-substring-no-properties pos (point)))) |
| 223 | + |
| 224 | + ;; we get the whole of our input back from fsi in the response to our |
| 225 | + ;; <tab> completion request, so remove the initial repl input here and |
| 226 | + ;; replace it with that response. |
| 227 | + (delete-backward-char (length input)) |
| 228 | + |
| 229 | + (insert (inf-fsharp-get-completion-from-process inf-proc input)))) |
| 230 | + |
| 231 | + ;; we'd prefer to reset this filter closer in the file to where we replace |
| 232 | + ;; it, but we ran into some issues with setting it too early. try to fix |
| 233 | + ;; this up when we figure out a nicer way of doing this completion stuff |
| 234 | + ;; overall. |
| 235 | + (set-process-filter inf-proc orig-filter))) |
| 236 | + |
| 237 | + |
111 | 238 | ;;;###autoload
|
112 | 239 | (defun run-fsharp (&optional cmd)
|
113 | 240 | "Run an inferior fsharp process.
|
|
0 commit comments