Skip to content

Commit 1a8d270

Browse files
committed
First attempt at tab completion for inferior fsharp
This is a start at implementing tab completion for inferior fsharp buffers through comint-redirect style process filters. The completion process is as follows: When inferior-fsharp-get-completion is triggered, we copy the input at the inferior fsharp prompt and clear the input. We then set up redirection of the inferior processes output via comint-redirect-setup, and set up a proess filter to check the output for our completion. Once we get output that looks like a completion, we grab it, send (length completion) backspaces to the inferior process to clear it's input buffer, remove our process filter, undo our redirection setup, return our completion back to the function that requested it, and insert it in the inferior fsharp prompt line. This is pretty awkward feeling due to needing to manage our buffer's prompt and fsi's input buffer seperately, hopefully that's not entirely unavoidable. Right now, this lacks a few things needed as a bare minimum to be acceptable, imo: + it currently assumes a completion will always be returned (easy to fix) + it needs to call (sleep-for) to ensure our output filters aren't deactivated before all the output from fsi has been received + it will only ever give the first completion -- the way fsi does it is to return subsequent members of completions on subsequent tab presses, we don't have a mechanism for handling this yet. The first point can probably be fixed just by looking for a bell in output or using a timeout, but the second two will require a more fleshed out solution for process/buffer communication. Need to read python-shell-completion-native and probably some other inferior-repl modes completion implementations for guidance. We also need to figure out how to write tests for this and write them before going much further here.
1 parent a1e16c6 commit 1a8d270

File tree

1 file changed

+128
-1
lines changed

1 file changed

+128
-1
lines changed

Diff for: inf-fsharp-mode.el

+128-1
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838

3939
(defvar inferior-fsharp-program
4040
(if fsharp-ac-using-mono
41-
"fsharpi --readline-"
41+
"fsharpi"
4242
(concat "\"" (fsharp-mode--executable-find "fsi.exe") "\" --fsi-server-input-codepage:65001"))
4343
"*Program name for invoking an inferior fsharp from Emacs.")
4444

@@ -48,6 +48,7 @@
4848
(defvar inferior-fsharp-mode-map
4949
(let ((map (copy-keymap comint-mode-map)))
5050
(define-key map [M-return] 'fsharp-comint-send)
51+
(define-key map (kbd "<tab>") 'inferior-fsharp-get-completion)
5152
map))
5253

5354
;; 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.
108109
(inferior-fsharp-mode))
109110
(display-buffer inferior-fsharp-buffer-name))))
110111

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+
111238
;;;###autoload
112239
(defun run-fsharp (&optional cmd)
113240
"Run an inferior fsharp process.

0 commit comments

Comments
 (0)