firefly-compiler 0.5.35 → 0.5.37

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (225) hide show
  1. package/.hintrc +4 -4
  2. package/.vscode/settings.json +4 -4
  3. package/bin/Release.ff +157 -154
  4. package/bin/firefly.mjs +1 -1
  5. package/compiler/Builder.ff +275 -277
  6. package/compiler/Compiler.ff +234 -233
  7. package/compiler/Dependencies.ff +186 -187
  8. package/compiler/DependencyLock.ff +17 -17
  9. package/compiler/Deriver.ff +23 -31
  10. package/compiler/Dictionaries.ff +1 -1
  11. package/compiler/Inference.ff +43 -20
  12. package/compiler/JsEmitter.ff +1437 -1282
  13. package/compiler/LspHook.ff +202 -202
  14. package/compiler/Main.ff +25 -24
  15. package/compiler/ModuleCache.ff +178 -178
  16. package/compiler/Parser.ff +36 -109
  17. package/compiler/Resolver.ff +5 -8
  18. package/compiler/Substitution.ff +1 -1
  19. package/compiler/Syntax.ff +1 -16
  20. package/compiler/Token.ff +9 -0
  21. package/compiler/Tokenizer.ff +4 -0
  22. package/compiler/Workspace.ff +88 -88
  23. package/core/.firefly/include/package.json +5 -5
  24. package/core/.firefly/package.ff +2 -2
  25. package/core/Any.ff +26 -30
  26. package/core/Array.ff +298 -265
  27. package/core/Atomic.ff +63 -64
  28. package/core/Box.ff +7 -7
  29. package/core/BrowserSystem.ff +40 -40
  30. package/core/Buffer.ff +185 -152
  31. package/core/BuildSystem.ff +156 -148
  32. package/core/Channel.ff +95 -92
  33. package/core/Char.ff +3 -2
  34. package/core/Core.ff +16 -23
  35. package/core/Crypto.ff +94 -96
  36. package/core/Equal.ff +41 -36
  37. package/core/Error.ff +15 -10
  38. package/core/FileHandle.ff +45 -37
  39. package/core/Float.ff +176 -200
  40. package/core/HttpClient.ff +142 -148
  41. package/core/Instant.ff +6 -8
  42. package/core/Int.ff +40 -24
  43. package/core/IntMap.ff +61 -39
  44. package/core/Js.ff +305 -0
  45. package/core/JsSystem.ff +135 -114
  46. package/core/JsValue.ff +303 -159
  47. package/core/Json.ff +423 -443
  48. package/core/List.ff +482 -486
  49. package/core/Lock.ff +108 -144
  50. package/core/Log.ff +25 -14
  51. package/core/NodeSystem.ff +198 -191
  52. package/core/Ordering.ff +160 -161
  53. package/core/Path.ff +377 -409
  54. package/core/Queue.ff +90 -0
  55. package/core/Random.ff +140 -134
  56. package/core/RbMap.ff +216 -216
  57. package/core/Serializable.ff +16 -13
  58. package/core/Show.ff +44 -43
  59. package/core/SourceLocation.ff +68 -68
  60. package/core/Stream.ff +1 -1
  61. package/core/String.ff +224 -202
  62. package/core/StringMap.ff +58 -36
  63. package/core/Task.ff +165 -149
  64. package/experimental/benchmarks/ListGrab.ff +23 -23
  65. package/experimental/benchmarks/ListGrab.java +55 -55
  66. package/experimental/benchmarks/Pyrotek45.ff +30 -30
  67. package/experimental/benchmarks/Pyrotek45.java +64 -64
  68. package/experimental/bidirectional/Bidi.ff +88 -88
  69. package/experimental/lines/Main.ff +40 -0
  70. package/experimental/random/Index.ff +53 -53
  71. package/experimental/random/Process.ff +120 -120
  72. package/experimental/random/RunLength.ff +65 -65
  73. package/experimental/random/Scrape.ff +51 -51
  74. package/experimental/random/Symbols.ff +73 -73
  75. package/experimental/random/Tensor.ff +52 -52
  76. package/experimental/random/Units.ff +36 -36
  77. package/experimental/s3/S3TestAuthorizationHeader.ff +39 -39
  78. package/experimental/s3/S3TestPut.ff +16 -16
  79. package/experimental/tests/TestJson.ff +26 -26
  80. package/firefly.sh +0 -0
  81. package/fireflysite/.firefly/package.ff +4 -4
  82. package/fireflysite/CommunityOverview.ff +20 -20
  83. package/fireflysite/CountingButtonDemo.ff +58 -58
  84. package/fireflysite/DocumentParser.ff +325 -331
  85. package/fireflysite/ExamplesOverview.ff +40 -40
  86. package/fireflysite/FrontPage.ff +344 -344
  87. package/fireflysite/GettingStarted.ff +45 -45
  88. package/fireflysite/Guide.ff +456 -456
  89. package/fireflysite/Main.ff +163 -152
  90. package/fireflysite/MatchingPasswordsDemo.ff +82 -82
  91. package/fireflysite/PackagesOverview.ff +49 -49
  92. package/fireflysite/PostgresqlDemo.ff +34 -34
  93. package/fireflysite/ReferenceAll.ff +18 -18
  94. package/fireflysite/ReferenceIntroduction.ff +11 -11
  95. package/fireflysite/Styles.ff +567 -567
  96. package/fireflysite/Test.ff +121 -62
  97. package/fireflysite/assets/markdown/reference/BaseTypes.md +209 -209
  98. package/fireflysite/assets/markdown/reference/EmittedJavascript.md +65 -65
  99. package/fireflysite/assets/markdown/reference/Exceptions.md +101 -101
  100. package/fireflysite/assets/markdown/reference/FunctionsAndMethods.md +364 -364
  101. package/fireflysite/assets/markdown/reference/JavascriptInterop.md +235 -172
  102. package/fireflysite/assets/markdown/reference/ModulesAndPackages.md +162 -162
  103. package/fireflysite/assets/markdown/reference/OldStructuredConcurrency.md +48 -48
  104. package/fireflysite/assets/markdown/reference/PatternMatching.md +224 -224
  105. package/fireflysite/assets/markdown/reference/StatementsAndExpressions.md +86 -86
  106. package/fireflysite/assets/markdown/reference/StructuredConcurrency.md +99 -99
  107. package/fireflysite/assets/markdown/reference/TraitsAndInstances.md +100 -100
  108. package/fireflysite/assets/markdown/reference/UserDefinedTypes.md +184 -184
  109. package/fireflysite/assets/markdown/scratch/ControlFlow.md +136 -136
  110. package/fireflysite/assets/markdown/scratch/Toc.md +40 -40
  111. package/lsp/.firefly/package.ff +1 -1
  112. package/lsp/CompletionHandler.ff +827 -827
  113. package/lsp/Handler.ff +714 -714
  114. package/lsp/HoverHandler.ff +79 -79
  115. package/lsp/LanguageServer.ff +272 -272
  116. package/lsp/SignatureHelpHandler.ff +55 -55
  117. package/lsp/SymbolHandler.ff +181 -181
  118. package/lsp/TestReferences.ff +17 -17
  119. package/lsp/TestReferencesCase.ff +7 -7
  120. package/lsp/stderr.txt +1 -1
  121. package/lsp/stdout.txt +34 -34
  122. package/lux/.firefly/package.ff +1 -1
  123. package/lux/Css.ff +648 -648
  124. package/lux/CssTest.ff +48 -48
  125. package/lux/Lux.ff +608 -617
  126. package/lux/LuxEvent.ff +79 -116
  127. package/lux/Main.ff +123 -123
  128. package/lux/Main2.ff +143 -143
  129. package/lux/TestDry.ff +28 -28
  130. package/output/js/ff/compiler/Builder.mjs +72 -71
  131. package/output/js/ff/compiler/Compiler.mjs +19 -13
  132. package/output/js/ff/compiler/Dependencies.mjs +8 -7
  133. package/output/js/ff/compiler/DependencyLock.mjs +6 -4
  134. package/output/js/ff/compiler/Deriver.mjs +26 -24
  135. package/output/js/ff/compiler/Dictionaries.mjs +14 -18
  136. package/output/js/ff/compiler/Environment.mjs +6 -4
  137. package/output/js/ff/compiler/Inference.mjs +238 -164
  138. package/output/js/ff/compiler/JsEmitter.mjs +1160 -350
  139. package/output/js/ff/compiler/JsImporter.mjs +20 -18
  140. package/output/js/ff/compiler/LspHook.mjs +12 -10
  141. package/output/js/ff/compiler/Main.mjs +61 -41
  142. package/output/js/ff/compiler/ModuleCache.mjs +10 -8
  143. package/output/js/ff/compiler/Parser.mjs +153 -669
  144. package/output/js/ff/compiler/Patterns.mjs +12 -10
  145. package/output/js/ff/compiler/Resolver.mjs +52 -78
  146. package/output/js/ff/compiler/Substitution.mjs +12 -16
  147. package/output/js/ff/compiler/Syntax.mjs +50 -341
  148. package/output/js/ff/compiler/Token.mjs +126 -4
  149. package/output/js/ff/compiler/Tokenizer.mjs +62 -52
  150. package/output/js/ff/compiler/Unification.mjs +74 -90
  151. package/output/js/ff/compiler/Wildcards.mjs +4 -2
  152. package/output/js/ff/compiler/Workspace.mjs +26 -20
  153. package/output/js/ff/core/Any.mjs +20 -20
  154. package/output/js/ff/core/Array.mjs +268 -175
  155. package/output/js/ff/core/AssetSystem.mjs +8 -6
  156. package/output/js/ff/core/Atomic.mjs +84 -52
  157. package/output/js/ff/core/Bool.mjs +6 -4
  158. package/output/js/ff/core/BrowserSystem.mjs +38 -29
  159. package/output/js/ff/core/Buffer.mjs +285 -133
  160. package/output/js/ff/core/BuildSystem.mjs +36 -56
  161. package/output/js/ff/core/Channel.mjs +250 -97
  162. package/output/js/ff/core/Char.mjs +5 -3
  163. package/output/js/ff/core/Core.mjs +28 -34
  164. package/output/js/ff/core/Crypto.mjs +30 -52
  165. package/output/js/ff/core/Duration.mjs +4 -2
  166. package/output/js/ff/core/Equal.mjs +14 -12
  167. package/output/js/ff/core/Error.mjs +17 -11
  168. package/output/js/ff/core/FileHandle.mjs +76 -38
  169. package/output/js/ff/core/Float.mjs +92 -160
  170. package/output/js/ff/core/HttpClient.mjs +208 -76
  171. package/output/js/ff/core/Instant.mjs +8 -10
  172. package/output/js/ff/core/Int.mjs +36 -26
  173. package/output/js/ff/core/IntMap.mjs +79 -33
  174. package/output/js/ff/core/Js.mjs +751 -0
  175. package/output/js/ff/core/JsSystem.mjs +54 -60
  176. package/output/js/ff/core/JsValue.mjs +294 -143
  177. package/output/js/ff/core/Json.mjs +443 -253
  178. package/output/js/ff/core/List.mjs +262 -214
  179. package/output/js/ff/core/Lock.mjs +156 -125
  180. package/output/js/ff/core/Log.mjs +20 -10
  181. package/output/js/ff/core/Map.mjs +10 -8
  182. package/output/js/ff/core/NodeSystem.mjs +189 -123
  183. package/output/js/ff/core/Nothing.mjs +4 -2
  184. package/output/js/ff/core/Option.mjs +40 -38
  185. package/output/js/ff/core/Ordering.mjs +26 -20
  186. package/output/js/ff/core/Pair.mjs +4 -2
  187. package/output/js/ff/core/Path.mjs +517 -315
  188. package/output/js/ff/core/Queue.mjs +306 -0
  189. package/output/js/ff/core/Random.mjs +141 -77
  190. package/output/js/ff/core/RbMap.mjs +36 -34
  191. package/output/js/ff/core/Serializable.mjs +44 -28
  192. package/output/js/ff/core/Set.mjs +6 -4
  193. package/output/js/ff/core/Show.mjs +8 -6
  194. package/output/js/ff/core/SourceLocation.mjs +4 -2
  195. package/output/js/ff/core/Stream.mjs +30 -50
  196. package/output/js/ff/core/String.mjs +263 -172
  197. package/output/js/ff/core/StringMap.mjs +77 -31
  198. package/output/js/ff/core/Task.mjs +91 -76
  199. package/output/js/ff/core/Try.mjs +20 -18
  200. package/output/js/ff/core/Unit.mjs +4 -2
  201. package/package.json +1 -1
  202. package/postgresql/Pg.ff +53 -59
  203. package/rpc/.firefly/package.ff +1 -1
  204. package/rpc/Rpc.ff +70 -70
  205. package/s3/.firefly/package.ff +1 -1
  206. package/s3/S3.ff +92 -94
  207. package/vscode/LICENSE.txt +21 -21
  208. package/vscode/Prepublish.ff +15 -15
  209. package/vscode/README.md +16 -16
  210. package/vscode/client/package-lock.json +544 -544
  211. package/vscode/client/package.json +22 -22
  212. package/vscode/client/src/extension.ts +104 -104
  213. package/vscode/icons/firefly-icon.svg +10 -10
  214. package/vscode/language-configuration.json +61 -61
  215. package/vscode/package-lock.json +3623 -3623
  216. package/vscode/package.json +1 -1
  217. package/vscode/snippets.json +241 -241
  218. package/vscode/syntaxes/firefly-markdown-injection.json +45 -45
  219. package/webserver/.firefly/include/package.json +5 -5
  220. package/webserver/.firefly/package.ff +2 -2
  221. package/webserver/WebServer.ff +647 -685
  222. package/websocket/.firefly/package.ff +1 -1
  223. package/websocket/WebSocket.ff +100 -131
  224. package/core/UnsafeJs.ff +0 -42
  225. package/output/js/ff/core/UnsafeJs.mjs +0 -191
@@ -1,827 +1,827 @@
1
- import LspHook from ff:compiler
2
- import Unification from ff:compiler
3
- import Environment from ff:compiler
4
- import Syntax from ff:compiler
5
- import Handler
6
-
7
- data CompletionInfo(
8
- label: String
9
- extra: String
10
- more: String
11
- snippet: String
12
- member: Bool
13
- type: Type
14
- documentation: String
15
- expectedType: Option[Type]
16
- secondarySort: Int = 5
17
- )
18
-
19
- handleCompletion(lspHook: LspHook, toplevel: Bool, followedByOpenBracket: Bool): Json {
20
- let topLevelCompletions = if(!toplevel) {[]} else {toplevelCompletion(lspHook)}
21
- let patternCompletions = lspHook.results().collectFirst {
22
- | InferPatternHook h =>
23
- let expected = h.unification.substitute(h.expected)
24
- Some(patternCompletion(h.unification, h.environment, expected))
25
- | _ =>
26
- None
27
- }
28
- mutable sawLookupHook = False // Only consider the first lookup
29
- mutable sawParameterOrVariantFieldHook = False // Rewrite the type completions to parameter completions
30
- mutable typeCompletions = []
31
- let completions = patternCompletions.else {lspHook.results().flatMap {
32
- | ResolveTypeHook h =>
33
- typeCompletions = [...typeCompletions, ...typeCompletion(h.types, h.typeGenerics)]
34
- []
35
- | ResolveVariantFieldHook _ =>
36
- sawParameterOrVariantFieldHook = True
37
- []
38
- | InferLambdaStartHook h =>
39
- h.unification.substitute(h.lambdaType).{
40
- | TConstructor(_, "Function$1", [_, TConstructor(_, prefix, _), _]) =>
41
- exhaustiveMatchCompletion(h.environment, prefix + "_", True)
42
- | _ =>
43
- []
44
- }
45
- | InferLookupHook h {!sawLookupHook && !h.symbol.value.qualifiedName.startsWith("_")} =>
46
- sawLookupHook = True
47
- let typePrefix = if(h.symbol.value.qualifiedName.contains("_")) {
48
- h.symbol.value.qualifiedName.reverse().dropWhile {_ != '_'}.reverse()
49
- } else {
50
- ""
51
- }
52
- let expected = h.unification.substitute(h.expected)
53
- let qualifiedByAlias = h.symbol.value.qualifiedName.{n =>
54
- if(n.contains(".") && !n.contains("/") && !n.contains(":")) {n.takeWhile {_ != '.'}}
55
- }
56
- h.selfVariable.{
57
- | Some(selfName) {typePrefix == "" && qualifiedByAlias == None} {
58
- h.environment.symbols.get(selfName) | Some(scheme)
59
- } {
60
- scheme.signature.returnType | TConstructor(_, selfTypeName, _)
61
- } =>
62
- let otherCompletions = completion(h.unification, h.environment, "", None, expected)
63
- let selfCompletions = h.environment.symbols.get(selfName).toList().flatMap {scheme =>
64
- let prefix = selfTypeName + "_"
65
- completion(h.unification, h.environment, prefix, None, expected).filter {
66
- _.label.first().any {_.isAsciiLower()}
67
- }.map {c =>
68
- c.CompletionInfo(label = selfName + "." + c.label, snippet = selfName + "." + c.snippet)
69
- }
70
- }
71
- [...otherCompletions, ...selfCompletions]
72
- | _ =>
73
- completion(h.unification, h.environment, typePrefix, qualifiedByAlias, expected)
74
- }
75
- | InferRecordFieldHook h {h.unification.substitute(h.recordType) | TConstructor(_, n, ts)} =>
76
- let fieldNames = n.split('$').dropFirst(1)
77
- let fieldCompletions = fieldNames.zip(ts).map {| Pair(name, t) =>
78
- let t2 = h.unification.substitute(t)
79
- CompletionInfo(name, "", "", name, True, t2, "(...)." + name + ": " + t2.show([]), Some(h.expected))
80
- }
81
- [...fieldCompletions, ...completion(h.unification, h.environment, n, None, h.expected)]
82
- | InferArgumentHook h {
83
- ![ // Avoids named argument suggestions for operators that become trait method calls, such as ==
84
- "ff:core/Equal.equals", "ff:core/Equal.notEquals"
85
- "ff:core/Ordering.before", "ff:core/Ordering.notBefore"
86
- "ff:core/Ordering.after", "ff:core/Ordering.notAfter"
87
- ].any {_ == h.callName}
88
- } =>
89
- let usedNames = h.arguments.pairs().filter {| Pair(i, a) => i != h.argumentIndex}.collect {_.second.name}
90
- let usedPlaces = h.arguments.takeFirst(h.argumentIndex).filter {_.name.isEmpty()}.size()
91
- let remainingParameters = h.parameters.filter {p => !usedNames.any {_ == p.name}}.dropFirst(usedPlaces)
92
- let preselect = h.isCopy || usedNames.size() != 0
93
- remainingParameters.pairs().map {| Pair(i, p) =>
94
- namedParameterCompletion(p, i, preselect)
95
- }
96
- | InferTermHook h {h.term | ELet _} =>
97
- h.missing.toList().filter {_.first.all {_.isAsciiLetterOrDigit()}}.flatMap {
98
- | Pair(x, Pair(instantiated, None)) =>
99
- missingCompletion(False, False, h.unification, x, instantiated, None)
100
- | _ =>
101
- []
102
- }
103
- | InferSequentialStartHook h {h.term | ESequential(_, before, _)} =>
104
- function lastIsVariable(term: Term): Bool {
105
- | ESequential(_, _, e) => lastIsVariable(e)
106
- | EVariable _ => True
107
- | _ => False
108
- }
109
- if(!lastIsVariable(before)) {[]} else:
110
- h.missing.toList().filter {| Pair(x, _) =>
111
- x.size() != 0 && x.all {_.isAsciiLetterOrDigit()}
112
- }.flatMap {| Pair(x, Pair(instantiated, arguments)) =>
113
- missingCompletion(True, False, h.unification, x, instantiated, arguments)
114
- }
115
- | InferParameterHook h =>
116
- sawParameterOrVariantFieldHook = True
117
- h.missing.toList().filter {| Pair(x, _) =>
118
- x.size() != 0 && x.all {_.isAsciiLetterOrDigit()}
119
- }.flatMap {| Pair(x, Pair(instantiated, arguments)) =>
120
- missingCompletion(False, True, h.unification, x, instantiated, arguments)
121
- }
122
- | InferFunctionDefinitionHook h {!h.definition.signature.member} =>
123
- h.missing.toList().filter {| Pair(x, _) =>
124
- x.size() != 0 && x.all {_.isAsciiLetterOrDigit()}
125
- }.flatMap {| Pair(x, Pair(instantiated, arguments)) =>
126
- arguments.toList().flatMap {as =>
127
- missingCompletion(False, False, h.unification, x, instantiated, Some(as))
128
- }
129
- }
130
- | InferFunctionDefinitionHook h {h.definition.signature.member} =>
131
- h.missing.toList().filter {_.first.contains("_")}.map {
132
- _.mapFirst {_.reverse().takeWhile {_ != '_'}.reverse()}
133
- }.filter {| Pair(x, _) =>
134
- x.size() != 0 && x.all {_.isAsciiLetterOrDigit()}
135
- }.flatMap {| Pair(x, Pair(instantiated, arguments)) =>
136
- arguments.toList().flatMap {as =>
137
- let instantiated2 = instantiated.Instantiated(scheme = instantiated.scheme.Scheme(
138
- signature = instantiated.scheme.signature.Signature(
139
- parameters = instantiated.scheme.signature.parameters.dropFirst()
140
- )
141
- ))
142
- missingCompletion(False, False, h.unification, x, instantiated2, Some(as.dropFirst()))
143
- }
144
- }
145
- | _ =>
146
- []
147
- }}
148
- let fixedTypeCompletions = if(!sawParameterOrVariantFieldHook) {typeCompletions} else {
149
- typeCompletions.map {c => c.CompletionInfo(snippet =
150
- (c.snippet.slice(0, 1).lower() + c.snippet.dropFirst()).takeWhile {_ != '['} + ": " + c.snippet
151
- )}
152
- }
153
- let fixedCompletions = if(!followedByOpenBracket) {completions} else {
154
- completions.map {c =>
155
- if(c.snippet.dropFirst(1).any {c => c != '(' && c != '[' && c != '{'}) {
156
- c.CompletionInfo(snippet = c.snippet.takeWhile {c => c != ' ' && c != '(' && c != '[' && c != '{'})
157
- } else {c}
158
- }
159
- }
160
- completionsToJson([...fixedCompletions, ...fixedTypeCompletions, ...topLevelCompletions])
161
- }
162
-
163
- completionsToJson(completions: List[CompletionInfo]): Json {
164
- Json.object()
165
- .with("isIncomplete", False)
166
- .with("items", completions.distinct().map {| CompletionInfo i =>
167
- let shownType = i.type.show([])
168
- let isAlpha = i.label.first().any {_.isAsciiLetter()}
169
- let isLower = i.label.first().any {_.isAsciiLower()}
170
- let isUnqualified = !i.label.contains(".")
171
- let isMember = isLower && !isUnqualified
172
- let isVariable = isLower && !i.extra.contains("(") && !i.extra.contains("{")
173
- let isDefinition = ["let ", "mutable ", "function "].any {i.snippet.startsWith(_)}
174
- let isParameter = i.snippet.endsWith(" = ")
175
- let sortText = if(isParameter || isDefinition) {0} else {9 - (
176
- isVariable.toInt() + isUnqualified.toInt() +
177
- isAlpha.toInt() + isLower.toInt() + isMember.toInt()
178
- )} + if(isMember) {"1"} else {"0"} + i.secondarySort + i.label.lower()
179
- let preselect = !isDefinition && Pair(i.type, i.expectedType).{
180
- | Pair(TConstructor(_, n1, _), Some(TConstructor(_, n2, _))) => n1 == n2
181
- | _ => False
182
- }
183
- Json.object()
184
- // Namespace or Property or Constructor or EnumMember or Constructor or Method/Function or Field/Variable
185
- .with("kind"
186
- if(shownType == "") {3} else:
187
- if(isParameter) {10} else:
188
- if(i.type.{| TConstructor(_, "type", _) => True | _ => False}) {9} else:
189
- if(i.label.first().any {c => c == '{' || c == '|'}) {22} else:
190
- if(i.label.first().any {_.isAsciiUpper()}) {4} else:
191
- if(i.extra.any {c => c == '(' || c == '{'}) {2} else {5} + if(i.member) {0} else {1}
192
- )
193
- .with("sortText", sortText)
194
- .with("preselect", preselect)
195
- .with("label", i.label)
196
- .with("labelDetails", Json.object()
197
- .with("detail", i.extra + if(shownType == "") {""} else {": " + shownType})
198
- .with("description", i.more)
199
- )
200
- .with("insertText", i.snippet)
201
- .with("insertTextFormat", 2 /* Snippet */)
202
- .with("documentation", Json.object()
203
- .with("kind", "markdown")
204
- .with("value", "```\n" + i.documentation + "\n```")
205
- )
206
- })
207
- }
208
-
209
- typeCompletion(types: Map[String, String], typeGenerics: Map[String, List[String]]): List[CompletionInfo] {
210
- let completions = types.toList().filter {| Pair(n, full) =>
211
- n.all {_.isAsciiLetterOrDigit()}
212
- }.map {| Pair(typeName, full) =>
213
- let generics = typeGenerics.find {k, _ => k == typeName}.toList().flatMap {_.second}
214
- let realGenerics = generics.filter {_ != "Q$"}
215
- let label = typeName
216
- let extra = if(realGenerics.isEmpty()) {""} else {"[" + realGenerics.join(", ") + "]"}
217
- let snippet = typeName + if(realGenerics.isEmpty()) {""} else {"[$0]"}
218
- CompletionInfo(label, extra, "", snippet, False, TConstructor(Location("", 0, 0), "type", []), full, None)
219
- }
220
- completions
221
- }
222
-
223
- completion(
224
- unification: Unification
225
- environment: Environment
226
- prefix: String
227
- qualifiedByAlias: Option[String]
228
- expected: Type
229
- ): List[CompletionInfo] {
230
-
231
- let member = prefix.contains("_")
232
-
233
- let members = Array.new()
234
-
235
- let symbols = if(prefix == "") {
236
- environment.symbols.toList().collect {
237
- | Pair(name, _)@pair {name.all {_.isAsciiLetterOrDigit()}} =>
238
- Some(pair)
239
- | Pair(name, _)@pair {name.startsWith("ff:core/Core.")} =>
240
- Some(pair.Pair(first = name.dropFirst("ff:core/Core.".size())))
241
- | Pair(name, _)@pair {name.startsWith(environment.modulePrefix)} {
242
- name.dropFirst(environment.modulePrefix.size()) | n
243
- } {n.all {_.isAsciiLetterOrDigit()}} =>
244
- Some(pair.Pair(first = n))
245
- | Pair(name, scheme) {name.dropWhile {_ != '/'}.dropFirst() | short} {
246
- short.reverse().takeWhile {_ != '.'}.reverse() | shorter
247
- } {shorter.all {_.isAsciiLetterOrDigit()}} =>
248
- if(shorter.first().any {_.isAsciiUpper()}) {
249
- Some(Pair(shorter, scheme))
250
- } else {
251
- let module = name.dropLast(shorter.size() + 1)
252
- let alias = environment.imports.find {_, i =>
253
- module == i.package.groupName() + "/" + i.directory.map {_ + "/"}.join() + i.file
254
- }
255
- alias.map {| Pair(alias, i) =>
256
- Pair(alias + short.dropFirst(i.file.size()), scheme)
257
- }
258
- }
259
- | _ =>
260
- None
261
- }.toMap()
262
- } else {
263
- members.pushArray(exhaustiveMatchCompletion(environment, prefix, False).toArray())
264
- let shorterPrefix = prefix.dropLast()
265
- let recordFields = shorterPrefix.split('$').dropFirst().toSet()
266
- environment.symbols.each {
267
- | shortName, scheme {shortName.dropWhile {_ != '/'}.dropFirst() | short} {
268
- short.reverse().takeWhile {_ != '.'}.reverse() | shorter
269
- } {shorter.all {_.isAsciiLetterOrDigit()} && shorter.first().any {_.isAsciiUpper()}} =>
270
- scheme.signature.returnType.{
271
- | _ {prefix.startsWith("Record$")} =>
272
- if(scheme.signature.parameters.any {recordFields.contains(_.name)}) {
273
- members.push(makeCompletion(unification, expected, "", shorter, scheme, True, member))
274
- }
275
- | TConstructor(_, name, _) {name.startsWith(shorterPrefix)} =>
276
- if(!scheme.signature.parameters.isEmpty()) {
277
- members.push(makeCompletion(unification, expected, "", shorter, scheme, True, member))
278
- }
279
- | _ =>
280
- }
281
- | _, _ =>
282
- }
283
- environment.symbols
284
- }
285
-
286
- let filteredSymbols = qualifiedByAlias.{
287
- | None => symbols
288
- | Some(alias) =>
289
- let aliasPrefix = alias + "."
290
- symbols.toList().filter {
291
- _.first.startsWith(aliasPrefix)
292
- }.map {
293
- _.mapFirst {_.dropFirst(aliasPrefix.size())}
294
- }.toMap()
295
- }
296
-
297
- filteredSymbols.each {memberName, memberScheme =>
298
- if(memberName.startsWith(prefix)) {
299
- members.push(makeCompletion(unification, expected, prefix, memberName, memberScheme, False, member))
300
- }
301
- }
302
- members.toList()
303
- }
304
-
305
- makeCompletion(
306
- unification: Unification
307
- expected: Type
308
- prefix: String
309
- memberName: String
310
- memberScheme: Scheme
311
- copy: Bool
312
- member: Bool
313
- ): CompletionInfo {
314
- let shortName = memberName.dropFirst(prefix.size())
315
- let unqualifiedName = shortName.reverse().takeWhile {_ != '.'}.reverse()
316
- let upper = unqualifiedName.first().any {_.isAsciiUpper()}
317
- let variantWithoutParameters = upper && memberScheme.signature.parameters.isEmpty()
318
- let realParameters = memberScheme.signature.parameters.dropFirst(if(member && !copy) {1} else {0})
319
- let pair = if(!memberScheme.isVariable && !variantWithoutParameters) {
320
- let trailing = realParameters.pairs().reverse().map {| Pair(index, p) =>
321
- p.valueType.{
322
- | TConstructor(_, name, _) {name.startsWith("Function$")} =>
323
- Some(Pair(
324
- " {...}"
325
- if(index == 0) {" {$0}"} else {" {}"}
326
- ))
327
- | _ =>
328
- None
329
- }
330
- }.toStream().takeWhile {_ != None}.collect {_}.toList().reverse()
331
- let allRequired = realParameters.filter {_.default.isEmpty()}
332
- let required = allRequired.dropLast(trailing.size()).map {_.name}
333
- let optional = if(allRequired.size() != realParameters.size()) {"..."}
334
- Pair(
335
- if(trailing.isEmpty() || !required.isEmpty()) {
336
- "(" + [...required, ...optional.toList()].join(", ") + ")"
337
- } else {
338
- ""
339
- } + trailing.map {_.first}.join()
340
- if(copy) {
341
- "(${1|" + realParameters.map {_.name}.map {f => f + " = "}.join(",") + "|}$0)"
342
- } else {
343
- if(trailing.isEmpty() || !required.isEmpty()) {
344
- if(required.isEmpty()) {"()"} else {"($0)"}
345
- } else {
346
- ""
347
- } + if(trailing.isEmpty()) {""} else {trailing.map {_.second}.join()}
348
- }
349
- )
350
- } else {Pair("", "")}
351
- let returnType = unification.substitute(memberScheme.signature.returnType)
352
- let documentation = if(memberScheme.isVariable || variantWithoutParameters) {
353
- let methodGenerics = memberScheme.signature.generics
354
- let generics = if(member || methodGenerics.isEmpty()) {""} else {"[" + methodGenerics.join(", ") + "]"}
355
- let beforeAfter = memberScheme.signature.parameters.first().map {
356
- Pair(unification.substitute(_.valueType).show([]) + ".", "")
357
- }.else {Pair("", "")}
358
- if(memberScheme.isMutable) {"mutable "} else {""} +
359
- beforeAfter.first +
360
- unqualifiedName + generics +
361
- ": " + returnType.show([]) +
362
- beforeAfter.second
363
- } else {
364
- let selfType = memberScheme.signature.parameters.first().filter {_ => member && !copy}.map {_.valueType}
365
- let generics = selfType.map {
366
- | TConstructor(_, _, gs) =>
367
- // TODO: Needs constraints as well
368
- // TODO: This drops the wrong number of type parameters - more environment needed
369
- let methodGenerics = memberScheme.signature.generics.dropFirst(gs.size() + 1)
370
- if(methodGenerics.isEmpty()) {""} else {"[" + methodGenerics.join(", ") + "]"}
371
- | _ =>
372
- ""
373
- }.else {
374
- let methodGenerics = memberScheme.signature.generics.filter {_ != "Q$"}
375
- if(methodGenerics.isEmpty()) {""} else {"[" + methodGenerics.join(", ") + "]"}
376
- }
377
- let parameters = if(realParameters.isEmpty()) {""} else {
378
- "\n" + realParameters.map {p =>
379
- showCompletionParameter(" ", p)
380
- }.join("\n") + "\n"
381
- }
382
- selfType.map {_.show([]) + "."}.else {""} + unqualifiedName +
383
- generics + "(" + parameters + "): " +
384
- returnType.show([])
385
- }
386
- CompletionInfo(
387
- label = shortName
388
- extra = pair.first
389
- more = ""
390
- snippet = shortName + pair.second
391
- member = member && !copy
392
- type = returnType
393
- documentation = documentation
394
- expectedType = Some(expected)
395
- )
396
- }
397
-
398
- exhaustiveMatchCompletion(environment: Environment, prefix: String, inside: Bool): List[CompletionInfo] {
399
- if(prefix == "ff:core/List.List_") {
400
- let curly = if(inside) {Pair("", "")} else {Pair("{", "}")}
401
- [CompletionInfo(
402
- label = curly.first + "| "
403
- extra = "[] => ... | [first, ...rest] => ..." + curly.second
404
- more = ""
405
- snippet = curly.first + "\n | [] => $0\n | [first, ...rest] =>\n" + curly.second
406
- member = True
407
- type = TConstructor(Location("", 0, 0), "exhaustive match", [])
408
- documentation = "// Exhaustive list match"
409
- expectedType = None
410
- )]
411
- } else:
412
- let shorterPrefix = prefix.dropLast()
413
- let variants = environment.symbols.toList().filter {s =>
414
- !s.first.contains("_") &&
415
- s.first.reverse().takeWhile {_ != '.'}.reverse().first().any {_.isAsciiUpper()} &&
416
- s.second.signature.returnType.{
417
- | TConstructor(_, n, _) => n == shorterPrefix
418
- | _ => False
419
- }
420
- }
421
- if(variants.isEmpty()) {[]} else:
422
- let label = if(inside) {""} else {"{"} +
423
- variants.map {v =>
424
- "| " + v.first.reverse().takeWhile {_ != '.'}.reverse() +
425
- if(v.second.signature.parameters.isEmpty()) {""} else {"(...)"} + " => ..."
426
- }.join(" ") +
427
- if(inside) {""} else {"}"}
428
- let snippetParts = variants.pairs().map {| Pair(index, Pair(name, scheme)) =>
429
- "| " + name.reverse().takeWhile {_ != '.'}.reverse() +
430
- if(scheme.signature.parameters.isEmpty()) {""} else {
431
- "(" + scheme.signature.parameters.map {_.name}.join(", ") + ")"
432
- } +
433
- " => " + if(index == 0) {"$0"} else {""}
434
- }
435
- let snippet =
436
- if(inside) {""} else {"{"} +
437
- if(snippetParts.size() != 1) {
438
- "\n" + snippetParts.map {" " + _}.join("\n") + "\n"
439
- } else {
440
- snippetParts.join(" ")
441
- } +
442
- if(inside) {""} else {"}"}
443
- [CompletionInfo(
444
- label = label.slice(0, 2)
445
- extra = label.dropFirst(2)
446
- more = ""
447
- snippet = snippet
448
- member = True
449
- type = TConstructor(Location("", 0, 0), "exhaustive match", [])
450
- documentation = "// Exhaustive match:\n" + snippetParts.join("\n").replace("$0", "")
451
- expectedType = None
452
- )]
453
- }
454
-
455
- patternCompletion(unification: Unification, environment: Environment, expected: Type): List[CompletionInfo] {
456
- let typeName = unification.substitute(expected).{
457
- | TConstructor(_, name, _) => name
458
- | _ => ""
459
- }
460
- if(typeName == "") {[]} else:
461
- if(typeName == "ff:core/List.List") {
462
- [CompletionInfo("[...]", "", "", "[$0]", False, expected, "// List pattern", Some(expected))]
463
- } else:
464
- if(typeName == "ff:core/String.String") {
465
- [CompletionInfo("\"...\"", "", "", "\"$0\"", False, expected, "// String pattern", Some(expected))]
466
- } else:
467
- if(typeName == "ff:core/Char.Char") {
468
- [CompletionInfo("'...'", "", "", "'$0'", False, expected, "// Char pattern", Some(expected))]
469
- } else:
470
- if(typeName == "ff:core/Int.Int") {
471
- [CompletionInfo("0", "", "", "0", False, expected, "// Int pattern", Some(expected))]
472
- } else:
473
- let variants = do {
474
- environment.symbols.toList().filter {s =>
475
- !s.first.contains("_") &&
476
- s.first.reverse().takeWhile {_ != '.'}.reverse().first().any {_.isAsciiUpper()} &&
477
- s.second.signature.returnType.{
478
- | TConstructor(_, n, _) => n == typeName
479
- | _ => False
480
- }
481
- }
482
- }
483
- let completions = variants.map {| Pair(name, scheme) =>
484
- let shortName = name.reverse().takeWhile {_ != '.'}.reverse()
485
- let extra = if(scheme.signature.parameters.isEmpty()) {""} else {
486
- "(" + scheme.signature.parameters.map {_.name}.join(", ") + ")"
487
- }
488
- let generics = scheme.signature.generics.filter {_ != "Q$"}
489
- let documentation =
490
- shortName +
491
- if(generics.isEmpty()) {""} else {"[" + generics.join(", ") + "]"} +
492
- if(scheme.signature.parameters.isEmpty()) {""} else {
493
- "(\n" + scheme.signature.parameters.map {
494
- showCompletionParameter(" ", _)
495
- }.join("\n") + "\n)"
496
- } + ": " + scheme.signature.returnType.show([])
497
- CompletionInfo(
498
- label = shortName
499
- extra = extra
500
- more = ""
501
- snippet = shortName + if(scheme.signature.parameters.isEmpty()) {""} else {"($0)"}
502
- member = False
503
- type = expected
504
- documentation = documentation
505
- expectedType = Some(expected)
506
- )
507
- }
508
- completions
509
- }
510
-
511
- missingCompletion(
512
- keyword: Bool
513
- isParameter: Bool
514
- unification: Unification
515
- name: String
516
- instantiated: Instantiated
517
- arguments: Option[List[Argument]]
518
- ): List[CompletionInfo] {
519
- function findTermName(term: Term): Option[String] {
520
- | ECall e {e.target | DynamicCall c} {
521
- e.arguments.last() | Some(Argument(_, _, e2))
522
- } {e2 | ELambda(_, Lambda(_, _, cases))} =>
523
- cases.collectFirst {findTermName(_.body)}
524
- | ECall e {e.target | DynamicCall c} => findTermName(c.function)
525
- | ECopy e => Some(e.name.slice(0, 1).lower() + e.name.dropFirst())
526
- | EField e => Some(e.field)
527
- | ELambda e {e.lambda.cases | [MatchCase c]} => findTermName(c.body)
528
- | EList e {e.items | []} => Some("list")
529
- | EList e {e.items | [e1, ...]} => findTermName(e1.first)
530
- | EPipe e => findTermName(e.function)
531
- | ERecord _ => Some("record")
532
- | EVariable e {e.name != "" && e.name.all {_.isAsciiLetterOrDigit()}} => Some(e.name)
533
- | EVariant e => Some(e.name.slice(0, 1).lower() + e.name.dropFirst())
534
- | EVariantIs e => Some(e.name.slice(0, 1).lower() + e.name.dropFirst())
535
- | _ => None
536
- }
537
- function findTypeName(type: Type): String {
538
- | TConstructor(_, name, _) {name.startsWith("Function$")} =>
539
- "lambda"
540
- | TConstructor(_, name, _) =>
541
- let n = name.reverse().takeWhile {_.isAsciiLetterOrDigit()}.reverse()
542
- if(!n.first().all {_.isAsciiLetter()}) {"p"} else {n.slice(0, 1).lower() + n.dropFirst()}
543
- | TVariable(_, index) => "p"
544
- }
545
- let completions = arguments.{
546
- | None {isParameter} =>
547
- let t = unification.substitute(instantiated.scheme.signature.returnType)
548
- let snippet = t.{
549
- | TConstructor _ => name + ": " + t.show([])
550
- | TVariable _ => name + ": "
551
- }
552
- let documentation = "// Add missing parameter\n" + name + ": " + t.show([])
553
- [CompletionInfo(name, "", "", snippet, False, t, documentation, Some(t))]
554
- | Some(as) {isParameter} =>
555
- let noEffect = TConstructor(instantiated.scheme.signature.at, "ff:core/Nothing.Nothing", [])
556
- let t = unification.substitute(TConstructor(
557
- instantiated.scheme.signature.at
558
- "Function$" + instantiated.scheme.signature.parameters.size()
559
- [
560
- noEffect
561
- ...instantiated.scheme.signature.parameters.map {_.valueType}
562
- instantiated.scheme.signature.returnType
563
- ]
564
- ))
565
- let documentation = "// Add missing parameter\n" + name + ": " + t.show([])
566
- [CompletionInfo(name, "", "", name + ": " + t.show([]), False, t, documentation, Some(t))]
567
- | None =>
568
- let t = unification.substitute(instantiated.scheme.signature.returnType)
569
- let snippet = name + " = "
570
- let documentation = "// Define missing variable\n" + name + ": " + t.show([])
571
- [CompletionInfo(snippet, "...", "", snippet, False, t, documentation, Some(t))]
572
- | Some(as) =>
573
- mutable remainingParameters = instantiated.scheme.signature.parameters
574
- mutable usedNames = [].toSet()
575
- function uniqueName(name: String): String {
576
- let alternativeName = name + (usedNames.size() + 1)
577
- if(!usedNames.contains(name)) {
578
- usedNames = usedNames.add(name)
579
- name
580
- } elseIf {!usedNames.contains(alternativeName)} {
581
- usedNames = usedNames.add(alternativeName)
582
- alternativeName
583
- } else {
584
- usedNames = usedNames.add(alternativeName + "_" + (usedNames.size() + 1))
585
- uniqueName(name)
586
- }
587
- }
588
- let parameters = as.collect {a =>
589
- let found = a.name.{
590
- | Some(n) => remainingParameters.find {_.name == n}
591
- | None => remainingParameters.first()
592
- }
593
- found.map {p =>
594
- remainingParameters = remainingParameters.filter {_.name != p.name}
595
- let t = unification.substitute(p.valueType)
596
- let x = a.name.orElse {findTermName(a.value)}.else {findTypeName(t)}
597
- Pair(uniqueName(x), t)
598
- }
599
- }
600
- let returnType = unification.substitute(instantiated.scheme.signature.returnType)
601
- let types = [...parameters.map {_.second}, returnType]
602
- let returnTypeCode = returnType.{| TVariable _ => "" | _ => ": " + returnType.show(types)}
603
- let parameterSnippets = parameters.map {| Pair(x, t) =>
604
- x + ": " + t.show(types)
605
- }
606
- let extra = "(" + parameterSnippets.join(", ") + ") {...}"
607
- let shortSnippet = name + "(" + parameterSnippets.join(", ") + ")" + returnTypeCode
608
- let longSnippet = name + "(\n " + parameterSnippets.join("\n ") + "\n)" + returnTypeCode
609
- let snippet = if(shortSnippet.size() <= 100) {shortSnippet} else {longSnippet}
610
- let documentation = "// Define missing function\n" +
611
- if(shortSnippet.size() < 30) {shortSnippet} else {longSnippet}
612
- [CompletionInfo(name, extra, "", snippet + " {\n $0\n}", False, returnType, documentation, None)]
613
- }
614
- if(keyword && arguments.isEmpty()) {
615
- ["let ", "mutable "].flatMap {k => completions.map {c =>
616
- c.CompletionInfo(label = k + c.label, snippet = k + c.snippet)
617
- }}
618
- } elseIf {keyword} {
619
- ["function "].flatMap {k => completions.map {c =>
620
- c.CompletionInfo(label = k + c.label, snippet = k + c.snippet)
621
- }}
622
- } else {
623
- completions
624
- }
625
- }
626
-
627
- namedParameterCompletion(parameter: Parameter, index: Int, preselect: Bool): CompletionInfo {
628
- CompletionInfo(
629
- label = parameter.name + " = "
630
- extra = "..."
631
- more = ""
632
- snippet = parameter.name + " = "
633
- member = False
634
- type = parameter.valueType
635
- documentation = showCompletionParameter("", parameter)
636
- expectedType = if(preselect) {parameter.valueType}
637
- secondarySort = (1000 + index).clamp(1000, 9999)
638
- )
639
- }
640
-
641
- toplevelCompletion(lspHook: LspHook): List[CompletionInfo] {
642
- [
643
- CompletionInfo(
644
- label = "package",
645
- extra = " some:package:0.0.0",
646
- more = ""
647
- snippet = "package ",
648
- member = False,
649
- type = TConstructor(lspHook.at, "", []),
650
- documentation = "// Declares the group, name and version of this package",
651
- expectedType = None
652
- )
653
- CompletionInfo(
654
- label = "dependency",
655
- extra = " some:package:0.0.0",
656
- more = ""
657
- snippet = "dependency ",
658
- member = False,
659
- type = TConstructor(lspHook.at, "", []),
660
- documentation = "// Declares a dependency on the specified version of the package",
661
- expectedType = None
662
- )
663
- CompletionInfo(
664
- label = "import",
665
- extra = " Module from some:package",
666
- more = ""
667
- snippet = "import ",
668
- member = False,
669
- type = TConstructor(lspHook.at, "", []),
670
- documentation = "// Imports a module from a package",
671
- expectedType = None
672
- )
673
- CompletionInfo(
674
- label = "extend",
675
- extra = " self[T]: SomeType[T] {...}",
676
- more = ""
677
- snippet = "extend ",
678
- member = False,
679
- type = TConstructor(lspHook.at, "", []),
680
- documentation = "// Add methods to a type defined in this module",
681
- expectedType = None
682
- )
683
- CompletionInfo(
684
- label = "data",
685
- extra = " SomeType[T](...) {...}",
686
- more = ""
687
- snippet = "data ",
688
- member = False,
689
- type = TConstructor(lspHook.at, "", []),
690
- documentation = "// Defines an immutable type",
691
- expectedType = None
692
- )
693
- CompletionInfo(
694
- label = "class",
695
- extra = " SomeType[T](...) {...}",
696
- more = ""
697
- snippet = "class ",
698
- member = False,
699
- type = TConstructor(lspHook.at, "", []),
700
- documentation = "// Defines a mutable type",
701
- expectedType = None
702
- )
703
- CompletionInfo(
704
- label = "capability",
705
- extra = " SomeType[T](...) {...}",
706
- more = ""
707
- snippet = "capability ",
708
- member = False,
709
- type = TConstructor(lspHook.at, "", []),
710
- documentation = "// Defines a type with capabilities and functions",
711
- expectedType = None
712
- )
713
- CompletionInfo(
714
- label = "trait",
715
- extra = " T: SomeTrait {...}",
716
- more = ""
717
- snippet = "trait ",
718
- member = False,
719
- type = TConstructor(lspHook.at, "", []),
720
- documentation = "// Defines a trait",
721
- expectedType = None
722
- )
723
- CompletionInfo(
724
- label = "instance",
725
- extra = " SomeType: SomeTrait {...}",
726
- more = ""
727
- snippet = "instance ",
728
- member = False,
729
- type = TConstructor(lspHook.at, "", []),
730
- documentation = "// Defines an instance of a trait",
731
- expectedType = None
732
- )
733
- CompletionInfo(
734
- label = "nodeMain",
735
- extra = "(system: NodeSystem) {...}",
736
- more = ""
737
- snippet = "nodeMain(system: NodeSystem) {\n $0\n}",
738
- member = False,
739
- type = TConstructor(lspHook.at, "", []),
740
- documentation = "// Main function for targetting Node.js",
741
- expectedType = None
742
- )
743
- CompletionInfo(
744
- label = "browserMain",
745
- extra = "(system: BrowserSystem) {...}",
746
- more = ""
747
- snippet = "browserMain(system: BrowserSystem) {\n $0\n}",
748
- member = False,
749
- type = TConstructor(lspHook.at, "", []),
750
- documentation = "// Main function for targetting the browser",
751
- expectedType = None
752
- )
753
- CompletionInfo(
754
- label = "buildMain",
755
- extra = "(system: BuildSystem) {...}",
756
- more = ""
757
- snippet = "buildMain(system: NodeSystem) {\n $0\n}",
758
- member = False,
759
- type = TConstructor(lspHook.at, "", []),
760
- documentation = "// Main function for building assets",
761
- expectedType = None
762
- )
763
- CompletionInfo(
764
- label = "webapp",
765
- extra = " with frontend and backend",
766
- more = ""
767
- snippet = [
768
- "dependency ff:webserver:0.0.0"
769
- "import WebServer from ff:webserver"
770
- ""
771
- "browserMain(system: BrowserSystem): Unit {"
772
- " let message = system.httpClient().get(\"http://localhost:8080/hello\", []) {_.readText()}"
773
- " system.js()->alert(message)"
774
- "}"
775
- ""
776
- "nodeMain(system: NodeSystem): Unit {"
777
- " WebServer.make(system, \"localhost\", 8080).listen {request =>"
778
- " if(request.readPath() == \"/\") {"
779
- " request.writeHeader(\"Content-Type\", \"text/html; charset=UTF-8\")"
780
- " request.writeText(\"<!doctype html>\")"
781
- " request.writeText(\"<script type='module' src='/js/script/script/$TM_FILENAME_BASE.mjs'></script>\")"
782
- " } elseIf {request.readPath() == \"/hello\"} {"
783
- " request.writeHeader(\"Content-Type\", \"text/plain; charset=UTF-8\")"
784
- " request.writeText(\"Hello from server!\")"
785
- " } elseIf {request.readPath().startsWith(\"/js/\") && !request.readPath().contains(\"..\")} {"
786
- " request.writeHeader(\"Content-Type\", \"text/javascript; charset=UTF-8\")"
787
- " request.writeText(system.assets().readText(request.readPath()))"
788
- " } else {"
789
- " request.writeStatus(\"404 Not found\")"
790
- " }"
791
- " }"
792
- "}"
793
- ""
794
- "buildMain(system: BuildSystem) {"
795
- " let browser = system.compileForBrowser(\"$TM_FILENAME_BASE.ff\")"
796
- " let assets = AssetSystem.create().addAssets(\"/js\", browser.assets())"
797
- " system.setAssets(assets)"
798
- "}"
799
- ].join("\n"),
800
- member = False,
801
- type = TConstructor(lspHook.at, "", []),
802
- documentation = "// Example webapp with frontend and backend",
803
- expectedType = None
804
- )
805
- ]
806
- }
807
-
808
- showCompletionParameter(indentation: String, parameter: Parameter): String {
809
- if(parameter.mutable) {indentation + "mutable "} else {indentation} +
810
- parameter.name + ": " + parameter.valueType.show([]) +
811
- parameter.default.map {
812
- | EVariant(_, n, _, None) =>
813
- n.reverse().takeWhile {_.isAsciiLetterOrDigit()}.reverse()
814
- | EVariant(_, n, _, Some([])) =>
815
- n.reverse().takeWhile {_.isAsciiLetterOrDigit()}.reverse()
816
- | EVariant(_, n, _, _) =>
817
- n.reverse().takeWhile {_.isAsciiLetterOrDigit()}.reverse() + "(...)"
818
- | EChar(_, v) => v
819
- | EInt(_, v) => v
820
- | EFloat(_, v) => v
821
- | EString(_, v) => v.replace("```", "'''")
822
- | ELambda(_, _) => "{...}"
823
- | EList(_, _, []) => "[]"
824
- | EList(_, _, _) => "[...]"
825
- | _ => "..."
826
- }.map {" = " + _}.else {""}
827
- }
1
+ import LspHook from ff:compiler
2
+ import Unification from ff:compiler
3
+ import Environment from ff:compiler
4
+ import Syntax from ff:compiler
5
+ import Handler
6
+
7
+ data CompletionInfo(
8
+ label: String
9
+ extra: String
10
+ more: String
11
+ snippet: String
12
+ member: Bool
13
+ type: Type
14
+ documentation: String
15
+ expectedType: Option[Type]
16
+ secondarySort: Int = 5
17
+ )
18
+
19
+ handleCompletion(lspHook: LspHook, toplevel: Bool, followedByOpenBracket: Bool): Json {
20
+ let topLevelCompletions = if(!toplevel) {[]} else {toplevelCompletion(lspHook)}
21
+ let patternCompletions = lspHook.results().collectFirst {
22
+ | InferPatternHook h =>
23
+ let expected = h.unification.substitute(h.expected)
24
+ Some(patternCompletion(h.unification, h.environment, expected))
25
+ | _ =>
26
+ None
27
+ }
28
+ mutable sawLookupHook = False // Only consider the first lookup
29
+ mutable sawParameterOrVariantFieldHook = False // Rewrite the type completions to parameter completions
30
+ mutable typeCompletions = []
31
+ let completions = patternCompletions.else {lspHook.results().flatMap {
32
+ | ResolveTypeHook h =>
33
+ typeCompletions = [...typeCompletions, ...typeCompletion(h.types, h.typeGenerics)]
34
+ []
35
+ | ResolveVariantFieldHook _ =>
36
+ sawParameterOrVariantFieldHook = True
37
+ []
38
+ | InferLambdaStartHook h =>
39
+ h.unification.substitute(h.lambdaType).{
40
+ | TConstructor(_, "Function$1", [_, TConstructor(_, prefix, _), _]) =>
41
+ exhaustiveMatchCompletion(h.environment, prefix + "_", True)
42
+ | _ =>
43
+ []
44
+ }
45
+ | InferLookupHook h {!sawLookupHook && !h.symbol.value.qualifiedName.startsWith("_")} =>
46
+ sawLookupHook = True
47
+ let typePrefix = if(h.symbol.value.qualifiedName.contains("_")) {
48
+ h.symbol.value.qualifiedName.reverse().dropWhile {_ != '_'}.reverse()
49
+ } else {
50
+ ""
51
+ }
52
+ let expected = h.unification.substitute(h.expected)
53
+ let qualifiedByAlias = h.symbol.value.qualifiedName.{n =>
54
+ if(n.contains(".") && !n.contains("/") && !n.contains(":")) {n.takeWhile {_ != '.'}}
55
+ }
56
+ h.selfVariable.{
57
+ | Some(selfName) {typePrefix == "" && qualifiedByAlias == None} {
58
+ h.environment.symbols.get(selfName) | Some(scheme)
59
+ } {
60
+ scheme.signature.returnType | TConstructor(_, selfTypeName, _)
61
+ } =>
62
+ let otherCompletions = completion(h.unification, h.environment, "", None, expected)
63
+ let selfCompletions = h.environment.symbols.get(selfName).toList().flatMap {scheme =>
64
+ let prefix = selfTypeName + "_"
65
+ completion(h.unification, h.environment, prefix, None, expected).filter {
66
+ _.label.first().any {_.isAsciiLower()}
67
+ }.map {c =>
68
+ c.CompletionInfo(label = selfName + "." + c.label, snippet = selfName + "." + c.snippet)
69
+ }
70
+ }
71
+ [...otherCompletions, ...selfCompletions]
72
+ | _ =>
73
+ completion(h.unification, h.environment, typePrefix, qualifiedByAlias, expected)
74
+ }
75
+ | InferRecordFieldHook h {h.unification.substitute(h.recordType) | TConstructor(_, n, ts)} =>
76
+ let fieldNames = n.split('$').dropFirst(1)
77
+ let fieldCompletions = fieldNames.zip(ts).map {| Pair(name, t) =>
78
+ let t2 = h.unification.substitute(t)
79
+ CompletionInfo(name, "", "", name, True, t2, "(...)." + name + ": " + t2.show([]), Some(h.expected))
80
+ }
81
+ [...fieldCompletions, ...completion(h.unification, h.environment, n, None, h.expected)]
82
+ | InferArgumentHook h {
83
+ ![ // Avoids named argument suggestions for operators that become trait method calls, such as ==
84
+ "ff:core/Equal.equals", "ff:core/Equal.notEquals"
85
+ "ff:core/Ordering.before", "ff:core/Ordering.notBefore"
86
+ "ff:core/Ordering.after", "ff:core/Ordering.notAfter"
87
+ ].any {_ == h.callName}
88
+ } =>
89
+ let usedNames = h.arguments.pairs().filter {| Pair(i, a) => i != h.argumentIndex}.collect {_.second.name}
90
+ let usedPlaces = h.arguments.takeFirst(h.argumentIndex).filter {_.name.isEmpty()}.size()
91
+ let remainingParameters = h.parameters.filter {p => !usedNames.any {_ == p.name}}.dropFirst(usedPlaces)
92
+ let preselect = h.isCopy || usedNames.size() != 0
93
+ remainingParameters.pairs().map {| Pair(i, p) =>
94
+ namedParameterCompletion(p, i, preselect)
95
+ }
96
+ | InferTermHook h {h.term | ELet _} =>
97
+ h.missing.toList().filter {_.first.all {_.isAsciiLetterOrDigit()}}.flatMap {
98
+ | Pair(x, Pair(instantiated, None)) =>
99
+ missingCompletion(False, False, h.unification, x, instantiated, None)
100
+ | _ =>
101
+ []
102
+ }
103
+ | InferSequentialStartHook h {h.term | ESequential(_, before, _)} =>
104
+ function lastIsVariable(term: Term): Bool {
105
+ | ESequential(_, _, e) => lastIsVariable(e)
106
+ | EVariable _ => True
107
+ | _ => False
108
+ }
109
+ if(!lastIsVariable(before)) {[]} else:
110
+ h.missing.toList().filter {| Pair(x, _) =>
111
+ x.size() != 0 && x.all {_.isAsciiLetterOrDigit()}
112
+ }.flatMap {| Pair(x, Pair(instantiated, arguments)) =>
113
+ missingCompletion(True, False, h.unification, x, instantiated, arguments)
114
+ }
115
+ | InferParameterHook h =>
116
+ sawParameterOrVariantFieldHook = True
117
+ h.missing.toList().filter {| Pair(x, _) =>
118
+ x.size() != 0 && x.all {_.isAsciiLetterOrDigit()}
119
+ }.flatMap {| Pair(x, Pair(instantiated, arguments)) =>
120
+ missingCompletion(False, True, h.unification, x, instantiated, arguments)
121
+ }
122
+ | InferFunctionDefinitionHook h {!h.definition.signature.member} =>
123
+ h.missing.toList().filter {| Pair(x, _) =>
124
+ x.size() != 0 && x.all {_.isAsciiLetterOrDigit()}
125
+ }.flatMap {| Pair(x, Pair(instantiated, arguments)) =>
126
+ arguments.toList().flatMap {as =>
127
+ missingCompletion(False, False, h.unification, x, instantiated, Some(as))
128
+ }
129
+ }
130
+ | InferFunctionDefinitionHook h {h.definition.signature.member} =>
131
+ h.missing.toList().filter {_.first.contains("_")}.map {
132
+ _.mapFirst {_.reverse().takeWhile {_ != '_'}.reverse()}
133
+ }.filter {| Pair(x, _) =>
134
+ x.size() != 0 && x.all {_.isAsciiLetterOrDigit()}
135
+ }.flatMap {| Pair(x, Pair(instantiated, arguments)) =>
136
+ arguments.toList().flatMap {as =>
137
+ let instantiated2 = instantiated.Instantiated(scheme = instantiated.scheme.Scheme(
138
+ signature = instantiated.scheme.signature.Signature(
139
+ parameters = instantiated.scheme.signature.parameters.dropFirst()
140
+ )
141
+ ))
142
+ missingCompletion(False, False, h.unification, x, instantiated2, Some(as.dropFirst()))
143
+ }
144
+ }
145
+ | _ =>
146
+ []
147
+ }}
148
+ let fixedTypeCompletions = if(!sawParameterOrVariantFieldHook) {typeCompletions} else {
149
+ typeCompletions.map {c => c.CompletionInfo(snippet =
150
+ (c.snippet.slice(0, 1).lower() + c.snippet.dropFirst()).takeWhile {_ != '['} + ": " + c.snippet
151
+ )}
152
+ }
153
+ let fixedCompletions = if(!followedByOpenBracket) {completions} else {
154
+ completions.map {c =>
155
+ if(c.snippet.dropFirst(1).any {c => c != '(' && c != '[' && c != '{'}) {
156
+ c.CompletionInfo(snippet = c.snippet.takeWhile {c => c != ' ' && c != '(' && c != '[' && c != '{'})
157
+ } else {c}
158
+ }
159
+ }
160
+ completionsToJson([...fixedCompletions, ...fixedTypeCompletions, ...topLevelCompletions])
161
+ }
162
+
163
+ completionsToJson(completions: List[CompletionInfo]): Json {
164
+ Json.object()
165
+ .with("isIncomplete", False)
166
+ .with("items", completions.distinct().map {| CompletionInfo i =>
167
+ let shownType = i.type.show([])
168
+ let isAlpha = i.label.first().any {_.isAsciiLetter()}
169
+ let isLower = i.label.first().any {_.isAsciiLower()}
170
+ let isUnqualified = !i.label.contains(".")
171
+ let isMember = isLower && !isUnqualified
172
+ let isVariable = isLower && !i.extra.contains("(") && !i.extra.contains("{")
173
+ let isDefinition = ["let ", "mutable ", "function "].any {i.snippet.startsWith(_)}
174
+ let isParameter = i.snippet.endsWith(" = ")
175
+ let sortText = if(isParameter || isDefinition) {0} else {9 - (
176
+ isVariable.toInt() + isUnqualified.toInt() +
177
+ isAlpha.toInt() + isLower.toInt() + isMember.toInt()
178
+ )} + if(isMember) {"1"} else {"0"} + i.secondarySort + i.label.lower()
179
+ let preselect = !isDefinition && Pair(i.type, i.expectedType).{
180
+ | Pair(TConstructor(_, n1, _), Some(TConstructor(_, n2, _))) => n1 == n2
181
+ | _ => False
182
+ }
183
+ Json.object()
184
+ // Namespace or Property or Constructor or EnumMember or Constructor or Method/Function or Field/Variable
185
+ .with("kind"
186
+ if(shownType == "") {3} else:
187
+ if(isParameter) {10} else:
188
+ if(i.type.{| TConstructor(_, "type", _) => True | _ => False}) {9} else:
189
+ if(i.label.first().any {c => c == '{' || c == '|'}) {22} else:
190
+ if(i.label.first().any {_.isAsciiUpper()}) {4} else:
191
+ if(i.extra.any {c => c == '(' || c == '{'}) {2} else {5} + if(i.member) {0} else {1}
192
+ )
193
+ .with("sortText", sortText)
194
+ .with("preselect", preselect)
195
+ .with("label", i.label)
196
+ .with("labelDetails", Json.object()
197
+ .with("detail", i.extra + if(shownType == "") {""} else {": " + shownType})
198
+ .with("description", i.more)
199
+ )
200
+ .with("insertText", i.snippet)
201
+ .with("insertTextFormat", 2 /* Snippet */)
202
+ .with("documentation", Json.object()
203
+ .with("kind", "markdown")
204
+ .with("value", "```\n" + i.documentation + "\n```")
205
+ )
206
+ })
207
+ }
208
+
209
+ typeCompletion(types: Map[String, String], typeGenerics: Map[String, List[String]]): List[CompletionInfo] {
210
+ let completions = types.toList().filter {| Pair(n, full) =>
211
+ n.all {_.isAsciiLetterOrDigit()}
212
+ }.map {| Pair(typeName, full) =>
213
+ let generics = typeGenerics.find {k, _ => k == typeName}.toList().flatMap {_.second}
214
+ let realGenerics = generics.filter {_ != "Q$"}
215
+ let label = typeName
216
+ let extra = if(realGenerics.isEmpty()) {""} else {"[" + realGenerics.join(", ") + "]"}
217
+ let snippet = typeName + if(realGenerics.isEmpty()) {""} else {"[$0]"}
218
+ CompletionInfo(label, extra, "", snippet, False, TConstructor(Location("", 0, 0), "type", []), full, None)
219
+ }
220
+ completions
221
+ }
222
+
223
+ completion(
224
+ unification: Unification
225
+ environment: Environment
226
+ prefix: String
227
+ qualifiedByAlias: Option[String]
228
+ expected: Type
229
+ ): List[CompletionInfo] {
230
+
231
+ let member = prefix.contains("_")
232
+
233
+ let members = Array.new()
234
+
235
+ let symbols = if(prefix == "") {
236
+ environment.symbols.toList().collect {
237
+ | Pair(name, _)@pair {name.all {_.isAsciiLetterOrDigit()}} =>
238
+ Some(pair)
239
+ | Pair(name, _)@pair {name.startsWith("ff:core/Core.")} =>
240
+ Some(pair.Pair(first = name.dropFirst("ff:core/Core.".size())))
241
+ | Pair(name, _)@pair {name.startsWith(environment.modulePrefix)} {
242
+ name.dropFirst(environment.modulePrefix.size()) | n
243
+ } {n.all {_.isAsciiLetterOrDigit()}} =>
244
+ Some(pair.Pair(first = n))
245
+ | Pair(name, scheme) {name.dropWhile {_ != '/'}.dropFirst() | short} {
246
+ short.reverse().takeWhile {_ != '.'}.reverse() | shorter
247
+ } {shorter.all {_.isAsciiLetterOrDigit()}} =>
248
+ if(shorter.first().any {_.isAsciiUpper()}) {
249
+ Some(Pair(shorter, scheme))
250
+ } else {
251
+ let module = name.dropLast(shorter.size() + 1)
252
+ let alias = environment.imports.find {_, i =>
253
+ module == i.package.groupName() + "/" + i.directory.map {_ + "/"}.join() + i.file
254
+ }
255
+ alias.map {| Pair(alias, i) =>
256
+ Pair(alias + short.dropFirst(i.file.size()), scheme)
257
+ }
258
+ }
259
+ | _ =>
260
+ None
261
+ }.toMap()
262
+ } else {
263
+ members.pushArray(exhaustiveMatchCompletion(environment, prefix, False).toArray())
264
+ let shorterPrefix = prefix.dropLast()
265
+ let recordFields = shorterPrefix.split('$').dropFirst().toSet()
266
+ environment.symbols.each {
267
+ | shortName, scheme {shortName.dropWhile {_ != '/'}.dropFirst() | short} {
268
+ short.reverse().takeWhile {_ != '.'}.reverse() | shorter
269
+ } {shorter.all {_.isAsciiLetterOrDigit()} && shorter.first().any {_.isAsciiUpper()}} =>
270
+ scheme.signature.returnType.{
271
+ | _ {prefix.startsWith("Record$")} =>
272
+ if(scheme.signature.parameters.any {recordFields.contains(_.name)}) {
273
+ members.push(makeCompletion(unification, expected, "", shorter, scheme, True, member))
274
+ }
275
+ | TConstructor(_, name, _) {name.startsWith(shorterPrefix)} =>
276
+ if(!scheme.signature.parameters.isEmpty()) {
277
+ members.push(makeCompletion(unification, expected, "", shorter, scheme, True, member))
278
+ }
279
+ | _ =>
280
+ }
281
+ | _, _ =>
282
+ }
283
+ environment.symbols
284
+ }
285
+
286
+ let filteredSymbols = qualifiedByAlias.{
287
+ | None => symbols
288
+ | Some(alias) =>
289
+ let aliasPrefix = alias + "."
290
+ symbols.toList().filter {
291
+ _.first.startsWith(aliasPrefix)
292
+ }.map {
293
+ _.mapFirst {_.dropFirst(aliasPrefix.size())}
294
+ }.toMap()
295
+ }
296
+
297
+ filteredSymbols.each {memberName, memberScheme =>
298
+ if(memberName.startsWith(prefix)) {
299
+ members.push(makeCompletion(unification, expected, prefix, memberName, memberScheme, False, member))
300
+ }
301
+ }
302
+ members.toList()
303
+ }
304
+
305
+ makeCompletion(
306
+ unification: Unification
307
+ expected: Type
308
+ prefix: String
309
+ memberName: String
310
+ memberScheme: Scheme
311
+ copy: Bool
312
+ member: Bool
313
+ ): CompletionInfo {
314
+ let shortName = memberName.dropFirst(prefix.size())
315
+ let unqualifiedName = shortName.reverse().takeWhile {_ != '.'}.reverse()
316
+ let upper = unqualifiedName.first().any {_.isAsciiUpper()}
317
+ let variantWithoutParameters = upper && memberScheme.signature.parameters.isEmpty()
318
+ let realParameters = memberScheme.signature.parameters.dropFirst(if(member && !copy) {1} else {0})
319
+ let pair = if(!memberScheme.isVariable && !variantWithoutParameters) {
320
+ let trailing = realParameters.pairs().reverse().map {| Pair(index, p) =>
321
+ p.valueType.{
322
+ | TConstructor(_, name, _) {name.startsWith("Function$")} =>
323
+ Some(Pair(
324
+ " {...}"
325
+ if(index == 0) {" {$0}"} else {" {}"}
326
+ ))
327
+ | _ =>
328
+ None
329
+ }
330
+ }.toStream().takeWhile {_ != None}.collect {_}.toList().reverse()
331
+ let allRequired = realParameters.filter {_.default.isEmpty()}
332
+ let required = allRequired.dropLast(trailing.size()).map {_.name}
333
+ let optional = if(allRequired.size() != realParameters.size()) {"..."}
334
+ Pair(
335
+ if(trailing.isEmpty() || !required.isEmpty()) {
336
+ "(" + [...required, ...optional.toList()].join(", ") + ")"
337
+ } else {
338
+ ""
339
+ } + trailing.map {_.first}.join()
340
+ if(copy) {
341
+ "(${1|" + realParameters.map {_.name}.map {f => f + " = "}.join(",") + "|}$0)"
342
+ } else {
343
+ if(trailing.isEmpty() || !required.isEmpty()) {
344
+ if(required.isEmpty()) {"()"} else {"($0)"}
345
+ } else {
346
+ ""
347
+ } + if(trailing.isEmpty()) {""} else {trailing.map {_.second}.join()}
348
+ }
349
+ )
350
+ } else {Pair("", "")}
351
+ let returnType = unification.substitute(memberScheme.signature.returnType)
352
+ let documentation = if(memberScheme.isVariable || variantWithoutParameters) {
353
+ let methodGenerics = memberScheme.signature.generics
354
+ let generics = if(member || methodGenerics.isEmpty()) {""} else {"[" + methodGenerics.join(", ") + "]"}
355
+ let beforeAfter = memberScheme.signature.parameters.first().map {
356
+ Pair(unification.substitute(_.valueType).show([]) + ".", "")
357
+ }.else {Pair("", "")}
358
+ if(memberScheme.isMutable) {"mutable "} else {""} +
359
+ beforeAfter.first +
360
+ unqualifiedName + generics +
361
+ ": " + returnType.show([]) +
362
+ beforeAfter.second
363
+ } else {
364
+ let selfType = memberScheme.signature.parameters.first().filter {_ => member && !copy}.map {_.valueType}
365
+ let generics = selfType.map {
366
+ | TConstructor(_, _, gs) =>
367
+ // TODO: Needs constraints as well
368
+ // TODO: This drops the wrong number of type parameters - more environment needed
369
+ let methodGenerics = memberScheme.signature.generics.dropFirst(gs.size() + 1)
370
+ if(methodGenerics.isEmpty()) {""} else {"[" + methodGenerics.join(", ") + "]"}
371
+ | _ =>
372
+ ""
373
+ }.else {
374
+ let methodGenerics = memberScheme.signature.generics.filter {_ != "Q$"}
375
+ if(methodGenerics.isEmpty()) {""} else {"[" + methodGenerics.join(", ") + "]"}
376
+ }
377
+ let parameters = if(realParameters.isEmpty()) {""} else {
378
+ "\n" + realParameters.map {p =>
379
+ showCompletionParameter(" ", p)
380
+ }.join("\n") + "\n"
381
+ }
382
+ selfType.map {_.show([]) + "."}.else {""} + unqualifiedName +
383
+ generics + "(" + parameters + "): " +
384
+ returnType.show([])
385
+ }
386
+ CompletionInfo(
387
+ label = shortName
388
+ extra = pair.first
389
+ more = ""
390
+ snippet = shortName + pair.second
391
+ member = member && !copy
392
+ type = returnType
393
+ documentation = documentation
394
+ expectedType = Some(expected)
395
+ )
396
+ }
397
+
398
+ exhaustiveMatchCompletion(environment: Environment, prefix: String, inside: Bool): List[CompletionInfo] {
399
+ if(prefix == "ff:core/List.List_") {
400
+ let curly = if(inside) {Pair("", "")} else {Pair("{", "}")}
401
+ [CompletionInfo(
402
+ label = curly.first + "| "
403
+ extra = "[] => ... | [first, ...rest] => ..." + curly.second
404
+ more = ""
405
+ snippet = curly.first + "\n | [] => $0\n | [first, ...rest] =>\n" + curly.second
406
+ member = True
407
+ type = TConstructor(Location("", 0, 0), "exhaustive match", [])
408
+ documentation = "// Exhaustive list match"
409
+ expectedType = None
410
+ )]
411
+ } else:
412
+ let shorterPrefix = prefix.dropLast()
413
+ let variants = environment.symbols.toList().filter {s =>
414
+ !s.first.contains("_") &&
415
+ s.first.reverse().takeWhile {_ != '.'}.reverse().first().any {_.isAsciiUpper()} &&
416
+ s.second.signature.returnType.{
417
+ | TConstructor(_, n, _) => n == shorterPrefix
418
+ | _ => False
419
+ }
420
+ }
421
+ if(variants.isEmpty()) {[]} else:
422
+ let label = if(inside) {""} else {"{"} +
423
+ variants.map {v =>
424
+ "| " + v.first.reverse().takeWhile {_ != '.'}.reverse() +
425
+ if(v.second.signature.parameters.isEmpty()) {""} else {"(...)"} + " => ..."
426
+ }.join(" ") +
427
+ if(inside) {""} else {"}"}
428
+ let snippetParts = variants.pairs().map {| Pair(index, Pair(name, scheme)) =>
429
+ "| " + name.reverse().takeWhile {_ != '.'}.reverse() +
430
+ if(scheme.signature.parameters.isEmpty()) {""} else {
431
+ "(" + scheme.signature.parameters.map {_.name}.join(", ") + ")"
432
+ } +
433
+ " => " + if(index == 0) {"$0"} else {""}
434
+ }
435
+ let snippet =
436
+ if(inside) {""} else {"{"} +
437
+ if(snippetParts.size() != 1) {
438
+ "\n" + snippetParts.map {" " + _}.join("\n") + "\n"
439
+ } else {
440
+ snippetParts.join(" ")
441
+ } +
442
+ if(inside) {""} else {"}"}
443
+ [CompletionInfo(
444
+ label = label.slice(0, 2)
445
+ extra = label.dropFirst(2)
446
+ more = ""
447
+ snippet = snippet
448
+ member = True
449
+ type = TConstructor(Location("", 0, 0), "exhaustive match", [])
450
+ documentation = "// Exhaustive match:\n" + snippetParts.join("\n").replace("$0", "")
451
+ expectedType = None
452
+ )]
453
+ }
454
+
455
+ patternCompletion(unification: Unification, environment: Environment, expected: Type): List[CompletionInfo] {
456
+ let typeName = unification.substitute(expected).{
457
+ | TConstructor(_, name, _) => name
458
+ | _ => ""
459
+ }
460
+ if(typeName == "") {[]} else:
461
+ if(typeName == "ff:core/List.List") {
462
+ [CompletionInfo("[...]", "", "", "[$0]", False, expected, "// List pattern", Some(expected))]
463
+ } else:
464
+ if(typeName == "ff:core/String.String") {
465
+ [CompletionInfo("\"...\"", "", "", "\"$0\"", False, expected, "// String pattern", Some(expected))]
466
+ } else:
467
+ if(typeName == "ff:core/Char.Char") {
468
+ [CompletionInfo("'...'", "", "", "'$0'", False, expected, "// Char pattern", Some(expected))]
469
+ } else:
470
+ if(typeName == "ff:core/Int.Int") {
471
+ [CompletionInfo("0", "", "", "0", False, expected, "// Int pattern", Some(expected))]
472
+ } else:
473
+ let variants = do {
474
+ environment.symbols.toList().filter {s =>
475
+ !s.first.contains("_") &&
476
+ s.first.reverse().takeWhile {_ != '.'}.reverse().first().any {_.isAsciiUpper()} &&
477
+ s.second.signature.returnType.{
478
+ | TConstructor(_, n, _) => n == typeName
479
+ | _ => False
480
+ }
481
+ }
482
+ }
483
+ let completions = variants.map {| Pair(name, scheme) =>
484
+ let shortName = name.reverse().takeWhile {_ != '.'}.reverse()
485
+ let extra = if(scheme.signature.parameters.isEmpty()) {""} else {
486
+ "(" + scheme.signature.parameters.map {_.name}.join(", ") + ")"
487
+ }
488
+ let generics = scheme.signature.generics.filter {_ != "Q$"}
489
+ let documentation =
490
+ shortName +
491
+ if(generics.isEmpty()) {""} else {"[" + generics.join(", ") + "]"} +
492
+ if(scheme.signature.parameters.isEmpty()) {""} else {
493
+ "(\n" + scheme.signature.parameters.map {
494
+ showCompletionParameter(" ", _)
495
+ }.join("\n") + "\n)"
496
+ } + ": " + scheme.signature.returnType.show([])
497
+ CompletionInfo(
498
+ label = shortName
499
+ extra = extra
500
+ more = ""
501
+ snippet = shortName + if(scheme.signature.parameters.isEmpty()) {""} else {"($0)"}
502
+ member = False
503
+ type = expected
504
+ documentation = documentation
505
+ expectedType = Some(expected)
506
+ )
507
+ }
508
+ completions
509
+ }
510
+
511
+ missingCompletion(
512
+ keyword: Bool
513
+ isParameter: Bool
514
+ unification: Unification
515
+ name: String
516
+ instantiated: Instantiated
517
+ arguments: Option[List[Argument]]
518
+ ): List[CompletionInfo] {
519
+ function findTermName(term: Term): Option[String] {
520
+ | ECall e {e.target | DynamicCall c} {
521
+ e.arguments.last() | Some(Argument(_, _, e2))
522
+ } {e2 | ELambda(_, Lambda(_, _, cases))} =>
523
+ cases.collectFirst {findTermName(_.body)}
524
+ | ECall e {e.target | DynamicCall c} => findTermName(c.function)
525
+ | ECopy e => Some(e.name.slice(0, 1).lower() + e.name.dropFirst())
526
+ | EField e => Some(e.field)
527
+ | ELambda e {e.lambda.cases | [MatchCase c]} => findTermName(c.body)
528
+ | EList e {e.items | []} => Some("list")
529
+ | EList e {e.items | [e1, ...]} => findTermName(e1.first)
530
+ | EPipe e => findTermName(e.function)
531
+ | ERecord _ => Some("record")
532
+ | EVariable e {e.name != "" && e.name.all {_.isAsciiLetterOrDigit()}} => Some(e.name)
533
+ | EVariant e => Some(e.name.slice(0, 1).lower() + e.name.dropFirst())
534
+ | EVariantIs e => Some(e.name.slice(0, 1).lower() + e.name.dropFirst())
535
+ | _ => None
536
+ }
537
+ function findTypeName(type: Type): String {
538
+ | TConstructor(_, name, _) {name.startsWith("Function$")} =>
539
+ "lambda"
540
+ | TConstructor(_, name, _) =>
541
+ let n = name.reverse().takeWhile {_.isAsciiLetterOrDigit()}.reverse()
542
+ if(!n.first().all {_.isAsciiLetter()}) {"p"} else {n.slice(0, 1).lower() + n.dropFirst()}
543
+ | TVariable(_, index) => "p"
544
+ }
545
+ let completions = arguments.{
546
+ | None {isParameter} =>
547
+ let t = unification.substitute(instantiated.scheme.signature.returnType)
548
+ let snippet = t.{
549
+ | TConstructor _ => name + ": " + t.show([])
550
+ | TVariable _ => name + ": "
551
+ }
552
+ let documentation = "// Add missing parameter\n" + name + ": " + t.show([])
553
+ [CompletionInfo(name, "", "", snippet, False, t, documentation, Some(t))]
554
+ | Some(as) {isParameter} =>
555
+ let noEffect = TConstructor(instantiated.scheme.signature.at, "ff:core/Nothing.Nothing", [])
556
+ let t = unification.substitute(TConstructor(
557
+ instantiated.scheme.signature.at
558
+ "Function$" + instantiated.scheme.signature.parameters.size()
559
+ [
560
+ noEffect
561
+ ...instantiated.scheme.signature.parameters.map {_.valueType}
562
+ instantiated.scheme.signature.returnType
563
+ ]
564
+ ))
565
+ let documentation = "// Add missing parameter\n" + name + ": " + t.show([])
566
+ [CompletionInfo(name, "", "", name + ": " + t.show([]), False, t, documentation, Some(t))]
567
+ | None =>
568
+ let t = unification.substitute(instantiated.scheme.signature.returnType)
569
+ let snippet = name + " = "
570
+ let documentation = "// Define missing variable\n" + name + ": " + t.show([])
571
+ [CompletionInfo(snippet, "...", "", snippet, False, t, documentation, Some(t))]
572
+ | Some(as) =>
573
+ mutable remainingParameters = instantiated.scheme.signature.parameters
574
+ mutable usedNames = [].toSet()
575
+ function uniqueName(name: String): String {
576
+ let alternativeName = name + (usedNames.size() + 1)
577
+ if(!usedNames.contains(name)) {
578
+ usedNames = usedNames.add(name)
579
+ name
580
+ } elseIf {!usedNames.contains(alternativeName)} {
581
+ usedNames = usedNames.add(alternativeName)
582
+ alternativeName
583
+ } else {
584
+ usedNames = usedNames.add(alternativeName + "_" + (usedNames.size() + 1))
585
+ uniqueName(name)
586
+ }
587
+ }
588
+ let parameters = as.collect {a =>
589
+ let found = a.name.{
590
+ | Some(n) => remainingParameters.find {_.name == n}
591
+ | None => remainingParameters.first()
592
+ }
593
+ found.map {p =>
594
+ remainingParameters = remainingParameters.filter {_.name != p.name}
595
+ let t = unification.substitute(p.valueType)
596
+ let x = a.name.orElse {findTermName(a.value)}.else {findTypeName(t)}
597
+ Pair(uniqueName(x), t)
598
+ }
599
+ }
600
+ let returnType = unification.substitute(instantiated.scheme.signature.returnType)
601
+ let types = [...parameters.map {_.second}, returnType]
602
+ let returnTypeCode = returnType.{| TVariable _ => "" | _ => ": " + returnType.show(types)}
603
+ let parameterSnippets = parameters.map {| Pair(x, t) =>
604
+ x + ": " + t.show(types)
605
+ }
606
+ let extra = "(" + parameterSnippets.join(", ") + ") {...}"
607
+ let shortSnippet = name + "(" + parameterSnippets.join(", ") + ")" + returnTypeCode
608
+ let longSnippet = name + "(\n " + parameterSnippets.join("\n ") + "\n)" + returnTypeCode
609
+ let snippet = if(shortSnippet.size() <= 100) {shortSnippet} else {longSnippet}
610
+ let documentation = "// Define missing function\n" +
611
+ if(shortSnippet.size() < 30) {shortSnippet} else {longSnippet}
612
+ [CompletionInfo(name, extra, "", snippet + " {\n $0\n}", False, returnType, documentation, None)]
613
+ }
614
+ if(keyword && arguments.isEmpty()) {
615
+ ["let ", "mutable "].flatMap {k => completions.map {c =>
616
+ c.CompletionInfo(label = k + c.label, snippet = k + c.snippet)
617
+ }}
618
+ } elseIf {keyword} {
619
+ ["function "].flatMap {k => completions.map {c =>
620
+ c.CompletionInfo(label = k + c.label, snippet = k + c.snippet)
621
+ }}
622
+ } else {
623
+ completions
624
+ }
625
+ }
626
+
627
+ namedParameterCompletion(parameter: Parameter, index: Int, preselect: Bool): CompletionInfo {
628
+ CompletionInfo(
629
+ label = parameter.name + " = "
630
+ extra = "..."
631
+ more = ""
632
+ snippet = parameter.name + " = "
633
+ member = False
634
+ type = parameter.valueType
635
+ documentation = showCompletionParameter("", parameter)
636
+ expectedType = if(preselect) {parameter.valueType}
637
+ secondarySort = (1000 + index).clamp(1000, 9999)
638
+ )
639
+ }
640
+
641
+ toplevelCompletion(lspHook: LspHook): List[CompletionInfo] {
642
+ [
643
+ CompletionInfo(
644
+ label = "package",
645
+ extra = " some:package:0.0.0",
646
+ more = ""
647
+ snippet = "package ",
648
+ member = False,
649
+ type = TConstructor(lspHook.at, "", []),
650
+ documentation = "// Declares the group, name and version of this package",
651
+ expectedType = None
652
+ )
653
+ CompletionInfo(
654
+ label = "dependency",
655
+ extra = " some:package:0.0.0",
656
+ more = ""
657
+ snippet = "dependency ",
658
+ member = False,
659
+ type = TConstructor(lspHook.at, "", []),
660
+ documentation = "// Declares a dependency on the specified version of the package",
661
+ expectedType = None
662
+ )
663
+ CompletionInfo(
664
+ label = "import",
665
+ extra = " Module from some:package",
666
+ more = ""
667
+ snippet = "import ",
668
+ member = False,
669
+ type = TConstructor(lspHook.at, "", []),
670
+ documentation = "// Imports a module from a package",
671
+ expectedType = None
672
+ )
673
+ CompletionInfo(
674
+ label = "extend",
675
+ extra = " self[T]: SomeType[T] {...}",
676
+ more = ""
677
+ snippet = "extend ",
678
+ member = False,
679
+ type = TConstructor(lspHook.at, "", []),
680
+ documentation = "// Add methods to a type defined in this module",
681
+ expectedType = None
682
+ )
683
+ CompletionInfo(
684
+ label = "data",
685
+ extra = " SomeType[T](...) {...}",
686
+ more = ""
687
+ snippet = "data ",
688
+ member = False,
689
+ type = TConstructor(lspHook.at, "", []),
690
+ documentation = "// Defines an immutable type",
691
+ expectedType = None
692
+ )
693
+ CompletionInfo(
694
+ label = "class",
695
+ extra = " SomeType[T](...) {...}",
696
+ more = ""
697
+ snippet = "class ",
698
+ member = False,
699
+ type = TConstructor(lspHook.at, "", []),
700
+ documentation = "// Defines a mutable type",
701
+ expectedType = None
702
+ )
703
+ CompletionInfo(
704
+ label = "capability",
705
+ extra = " SomeType[T](...) {...}",
706
+ more = ""
707
+ snippet = "capability ",
708
+ member = False,
709
+ type = TConstructor(lspHook.at, "", []),
710
+ documentation = "// Defines a type with capabilities and functions",
711
+ expectedType = None
712
+ )
713
+ CompletionInfo(
714
+ label = "trait",
715
+ extra = " T: SomeTrait {...}",
716
+ more = ""
717
+ snippet = "trait ",
718
+ member = False,
719
+ type = TConstructor(lspHook.at, "", []),
720
+ documentation = "// Defines a trait",
721
+ expectedType = None
722
+ )
723
+ CompletionInfo(
724
+ label = "instance",
725
+ extra = " SomeType: SomeTrait {...}",
726
+ more = ""
727
+ snippet = "instance ",
728
+ member = False,
729
+ type = TConstructor(lspHook.at, "", []),
730
+ documentation = "// Defines an instance of a trait",
731
+ expectedType = None
732
+ )
733
+ CompletionInfo(
734
+ label = "nodeMain",
735
+ extra = "(system: NodeSystem) {...}",
736
+ more = ""
737
+ snippet = "nodeMain(system: NodeSystem) {\n $0\n}",
738
+ member = False,
739
+ type = TConstructor(lspHook.at, "", []),
740
+ documentation = "// Main function for targetting Node.js",
741
+ expectedType = None
742
+ )
743
+ CompletionInfo(
744
+ label = "browserMain",
745
+ extra = "(system: BrowserSystem) {...}",
746
+ more = ""
747
+ snippet = "browserMain(system: BrowserSystem) {\n $0\n}",
748
+ member = False,
749
+ type = TConstructor(lspHook.at, "", []),
750
+ documentation = "// Main function for targetting the browser",
751
+ expectedType = None
752
+ )
753
+ CompletionInfo(
754
+ label = "buildMain",
755
+ extra = "(system: BuildSystem) {...}",
756
+ more = ""
757
+ snippet = "buildMain(system: NodeSystem) {\n $0\n}",
758
+ member = False,
759
+ type = TConstructor(lspHook.at, "", []),
760
+ documentation = "// Main function for building assets",
761
+ expectedType = None
762
+ )
763
+ CompletionInfo(
764
+ label = "webapp",
765
+ extra = " with frontend and backend",
766
+ more = ""
767
+ snippet = [
768
+ "dependency ff:webserver:0.0.0"
769
+ "import WebServer from ff:webserver"
770
+ ""
771
+ "browserMain(system: BrowserSystem): Unit {"
772
+ " let message = system.httpClient().get(\"http://localhost:8080/hello\", []) {_.readText()}"
773
+ " system.js()->alert(message)"
774
+ "}"
775
+ ""
776
+ "nodeMain(system: NodeSystem): Unit {"
777
+ " WebServer.make(system, \"localhost\", 8080).listen {request =>"
778
+ " if(request.readPath() == \"/\") {"
779
+ " request.writeHeader(\"Content-Type\", \"text/html; charset=UTF-8\")"
780
+ " request.writeText(\"<!doctype html>\")"
781
+ " request.writeText(\"<script type='module' src='/js/script/script/$TM_FILENAME_BASE.mjs'></script>\")"
782
+ " } elseIf {request.readPath() == \"/hello\"} {"
783
+ " request.writeHeader(\"Content-Type\", \"text/plain; charset=UTF-8\")"
784
+ " request.writeText(\"Hello from server!\")"
785
+ " } elseIf {request.readPath().startsWith(\"/js/\") && !request.readPath().contains(\"..\")} {"
786
+ " request.writeHeader(\"Content-Type\", \"text/javascript; charset=UTF-8\")"
787
+ " request.writeText(system.assets().readText(request.readPath()))"
788
+ " } else {"
789
+ " request.writeStatus(\"404 Not found\")"
790
+ " }"
791
+ " }"
792
+ "}"
793
+ ""
794
+ "buildMain(system: BuildSystem) {"
795
+ " let browser = system.compileForBrowser(\"$TM_FILENAME_BASE.ff\")"
796
+ " let assets = AssetSystem.create().addAssets(\"/js\", browser.assets())"
797
+ " system.setAssets(assets)"
798
+ "}"
799
+ ].join("\n"),
800
+ member = False,
801
+ type = TConstructor(lspHook.at, "", []),
802
+ documentation = "// Example webapp with frontend and backend",
803
+ expectedType = None
804
+ )
805
+ ]
806
+ }
807
+
808
+ showCompletionParameter(indentation: String, parameter: Parameter): String {
809
+ if(parameter.mutable) {indentation + "mutable "} else {indentation} +
810
+ parameter.name + ": " + parameter.valueType.show([]) +
811
+ parameter.default.map {
812
+ | EVariant(_, n, _, None) =>
813
+ n.reverse().takeWhile {_.isAsciiLetterOrDigit()}.reverse()
814
+ | EVariant(_, n, _, Some([])) =>
815
+ n.reverse().takeWhile {_.isAsciiLetterOrDigit()}.reverse()
816
+ | EVariant(_, n, _, _) =>
817
+ n.reverse().takeWhile {_.isAsciiLetterOrDigit()}.reverse() + "(...)"
818
+ | EChar(_, v) => v
819
+ | EInt(_, v) => v
820
+ | EFloat(_, v) => v
821
+ | EString(_, v) => v.replace("```", "'''")
822
+ | ELambda(_, _) => "{...}"
823
+ | EList(_, _, []) => "[]"
824
+ | EList(_, _, _) => "[...]"
825
+ | _ => "..."
826
+ }.map {" = " + _}.else {""}
827
+ }