-
Notifications
You must be signed in to change notification settings - Fork 35
/
autocomplete.nim
210 lines (182 loc) · 5.88 KB
/
autocomplete.nim
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
## This module communicates with nimsuggest in the background.
## Take a look at the ``suggest`` module for the GUI of the suggest feature.
##
## Uses 'nimsuggest' and not 'nim idetools'
import osproc, streams, os, net, glib2, gtk2, strutils, unicode
import utils
# the compiler never produces endToken:
const
endToken = "EOF\t"
stopToken = "STOP\t"
errorToken = "ERROR\t"
port = 6000.Port
var
commands: Channel[string]
results: Channel[string]
suggestTasks: Channel[string]
commands.open()
results.open()
suggestTasks.open()
proc shutdown(p: Process) =
if not p.running:
echod("[AutoComplete] Process exited.")
else:
echod("[AutoComplete] Process Shutting down.")
p.terminate()
discard p.waitForExit()
p.close()
proc suggestThread() {.thread.} =
let nimBinPath = findExe("nim")
let nimPath = nimBinPath.splitFile.dir.parentDir
var p: Process = nil
var o: Stream = nil
while true:
if not p.isNil:
if not p.running:
p.shutdown()
results.send(endToken)
p = nil
continue
if not o.isNil:
if o.atEnd:
echod("[AutoComplete] Stream is at end")
o.close()
o = nil
else:
let line = o.readLine()
# For some reason on Linux reading from the process
# returns an empty line sometimes.
if line.len == 0: continue
echod("[AutoComplete] Got line from NimSuggest (stdout): ", line.repr)
if unicode.toLower(line).startsWith("error:"):
results.send(errorToken & line)
var tasks = commands.peek()
if tasks > 0 or (o.isNil and p.isNil):
let task = commands.recv()
echod("[AutoComplete] Got command: ", task)
case task
of endToken:
p.shutdown()
results.send(endToken)
p = nil
o = nil
of stopToken:
# Can't do much here right now since we're not async.
# TODO
discard
else:
let projectFile = task
let projectFileNorm = projectFile.replace('\\', '/')
# TODO: Ensure nimPath exists.
echod("[AutoComplete] Work Dir for NimSuggest: ", nimPath)
echod("[AutoComplete] Project file for NimSuggest: ", projectFileNorm)
p = startProcess(findExe("nimsuggest"), nimPath,
["--port:" & $port, projectFileNorm],
options = {poStdErrToStdOut, poUsePath,
poInteractive})
echod("[AutoComplete] NimSuggest started on port ", port)
o = p.outputStream
echod("[AutoComplete] Process thread exiting")
proc processTask(task: string) =
var socket = newSocket()
socket.connect("localhost", port)
echod("[AutoComplete] Socket connected")
socket.send(task & "\c\l")
while true:
var line = ""
socket.readLine(line)
echod("[AutoComplete] Recv line: \"", line, "\"")
if line.len == 0: break
results.send(line)
socket.close()
results.send(stopToken)
proc socketThread() {.thread.} =
while true:
let task = suggestTasks.recv()
echod("[AutoComplete] Got suggest task: ", task)
case task
of endToken:
assert false
of stopToken:
assert false
else:
var success = false
for i in 0 .. 10:
try:
processTask(task)
success = true
break
except OSError:
echod("[AutoComplete] Error sending task. Retrying in 500ms.")
sleep(500)
if not success:
results.send(errorToken & "Couldn't connect to NimSuggest.")
proc newAutoComplete*(): AutoComplete =
result = AutoComplete()
createThread[void](result.thread, suggestThread)
createThread[void](result.sockThread, socketThread)
proc startNimSuggest*(self: AutoComplete, projectFile: string) =
assert(not self.nimSuggestRunning)
commands.send projectFile
self.nimSuggestRunning = true
proc peekSuggestOutput(self: AutoComplete): gboolean {.cdecl.} =
result = true
if not self.taskRunning and results.peek() == 0:
# There is no suggest task running, so end this idle proc.
echod("[AutoComplete] idleproc exiting")
return false
while true:
let (available, msg) = tryRecv[string](results)
if not available:
break
let cmd = msg.split("\t")[0]
echod("[AutoComplete] SuggestOutput: Cmd: ", cmd.repr)
echod("[AutoComplete] SuggestOutput: Full: ", msg.repr)
case cmd & '\t'
of endToken:
self.nimSuggestRunning = false
self.taskRunning = false
self.onSugExit(0)
return results.peek() != 0
of stopToken:
self.taskRunning = false
self.onSugExit(0)
return false
of errorToken:
self.onSugError(msg.split("\t")[1])
else:
discard
self.onSugLine(msg)
if not result:
echod("[AutoComplete] idle exiting")
proc clear(chan: var Channel[string]) =
while true:
let (available, msg) = tryRecv(chan)
if not available: break
echod("[AutoComplete] Skipped: ", msg)
proc startTask*(self: AutoComplete, task: string,
onSugLine: proc (line: string) {.closure.},
onSugExit: proc (exit: int) {.closure.},
onSugError: proc (error: string) {.closure.}) =
## Sends a new task to nimsuggest.
echod("[AutoComplete] Starting new task: ", task)
assert(not self.taskRunning)
assert(self.nimSuggestRunning)
self.taskRunning = true
self.onSugLine = onSugLine
self.onSugExit = onSugExit
self.onSugError = onSugError
# Add a function which will be called when the UI is idle.
discard gIdleAdd(peekSuggestOutput, cast[pointer](self))
echod("[AutoComplete] idleAdd(peekSuggestOutput)")
# Ensure that there is no stale results from old task.
results.clear()
# Send the task
suggestTasks.send(task)
proc isTaskRunning*(self: AutoComplete): bool =
self.taskRunning
proc isNimSuggestRunning*(self: AutoComplete): bool =
self.nimSuggestRunning
proc stopTask*(self: AutoComplete) =
#commands.send(stopToken)
discard