autocomplete.go 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270
  1. package atom
  2. import (
  3. "os"
  4. "sort"
  5. "strings"
  6. "path/filepath"
  7. "kumachan/stdlib"
  8. "kumachan/interpreter/lang/textual/ast"
  9. "kumachan/interpreter/lang/textual/syntax"
  10. "kumachan/interpreter/compiler/loader"
  11. )
  12. var IdentifierRegexp = syntax.GetIdentifierRegexp()
  13. var Keywords = syntax.GetKeywordList()
  14. type AutoCompleteRequest struct {
  15. PrecedingText string `json:"precedingText"`
  16. LocalBindings [] string `json:"localBindings"`
  17. CurrentPath string `json:"currentPath"`
  18. }
  19. type AutoCompleteResponse struct {
  20. Suggestions [] AutoCompleteSuggestion `json:"suggestions"`
  21. }
  22. type AutoCompleteSuggestion struct {
  23. Text string `json:"text"`
  24. Replace string `json:"replacementPrefix"`
  25. Type string `json:"type"`
  26. Display string `json:"displayText,omitempty"`
  27. }
  28. func AutoComplete(req AutoCompleteRequest, ctx LangServerContext) AutoCompleteResponse {
  29. const double_colon = "::"
  30. var get_search_text = func() (string, string) {
  31. var raw_text = req.PrecedingText
  32. var ranges = IdentifierRegexp.FindAllStringIndex(raw_text, -1)
  33. if len(ranges) == 0 {
  34. return "", ""
  35. }
  36. var last = ranges[len(ranges)-1]
  37. var lo = last[0]
  38. var hi = last[1]
  39. if hi != len(raw_text) {
  40. if strings.HasSuffix(raw_text, double_colon) &&
  41. hi == (len(raw_text) - len(double_colon)) {
  42. return "", raw_text[lo:hi]
  43. } else {
  44. return "", ""
  45. }
  46. }
  47. var text = raw_text[lo:hi]
  48. var ldc = len(double_colon)
  49. if len(ranges) >= ldc {
  50. var preceding = ranges[len(ranges)-ldc]
  51. var p_lo = preceding[0]
  52. var p_hi = preceding[1]
  53. if (p_hi + ldc) == lo && raw_text[p_hi:lo] == double_colon {
  54. var text_mod = raw_text[p_lo:p_hi]
  55. return text, text_mod
  56. } else {
  57. return text, ""
  58. }
  59. } else {
  60. return text, ""
  61. }
  62. }
  63. var input, input_mod = get_search_text()
  64. if input == "" && input_mod == "" {
  65. return AutoCompleteResponse {}
  66. }
  67. var quick_check = func(id ast.Identifier) bool {
  68. if !(len(id.Name) > 0) { panic("something went wrong") }
  69. if len(input) > 0 {
  70. var first_char = id.Name[0]
  71. if first_char < 128 &&
  72. first_char != rune(input[0]) &&
  73. (first_char + ('a' - 'A')) != rune(input[0]) {
  74. return false
  75. }
  76. }
  77. return true
  78. }
  79. var suggestions = make([] AutoCompleteSuggestion, 0)
  80. if len(input) > 0 && input_mod == "" {
  81. for _, binding := range req.LocalBindings {
  82. if strings.HasPrefix(binding, input) {
  83. suggestions = append(suggestions, AutoCompleteSuggestion {
  84. Text: binding,
  85. Replace: input,
  86. Type: "variable",
  87. })
  88. }
  89. }
  90. }
  91. var suggested_function_names = make(map[string] bool)
  92. var process_statement func(ast.Statement, string)
  93. process_statement = func(stmt ast.Statement, mod string) {
  94. switch s := stmt.(type) {
  95. case ast.DeclType:
  96. { var def, has_def = s.TypeDef.(ast.VariousTypeDef)
  97. if has_def {
  98. var enum, is_enum = def.TypeDef.(ast.EnumType)
  99. if is_enum {
  100. for _, case_decl := range enum.Cases {
  101. process_statement(case_decl, mod)
  102. }
  103. }}}
  104. if !(input_mod == mod) {
  105. return
  106. }
  107. if !(quick_check(s.Name)) {
  108. return
  109. }
  110. var name = ast.Id2String(s.Name)
  111. var name_lower = strings.ToLower(name)
  112. if strings.HasPrefix(name, input) || strings.HasPrefix(name_lower, input) {
  113. suggestions = append(suggestions, AutoCompleteSuggestion {
  114. Text: name,
  115. Replace: input,
  116. Type: "type",
  117. })
  118. }
  119. case ast.DeclConst:
  120. if (mod != "" && !(s.Public)) {
  121. return
  122. }
  123. if input_mod != mod {
  124. return
  125. }
  126. if !(quick_check(s.Name)) {
  127. return
  128. }
  129. var name = ast.Id2String(s.Name)
  130. var name_lower = strings.ToLower(name)
  131. if strings.HasPrefix(name, input) || strings.HasPrefix(name_lower, input) {
  132. suggestions = append(suggestions, AutoCompleteSuggestion {
  133. Text: name,
  134. Replace: input,
  135. Type: "constant",
  136. })
  137. }
  138. case ast.DeclFunction:
  139. if (mod != "" && !(s.Public)) {
  140. return
  141. }
  142. if (input_mod != "" && input_mod != mod) {
  143. return
  144. }
  145. if !(quick_check(s.Name)) {
  146. return
  147. }
  148. var name = ast.Id2String(s.Name)
  149. if strings.HasPrefix(name, input) {
  150. if !(suggested_function_names[name]) {
  151. suggestions = append(suggestions, AutoCompleteSuggestion {
  152. Text: name,
  153. Replace: input,
  154. Type: "function",
  155. })
  156. suggested_function_names[name] = true
  157. }
  158. }
  159. }
  160. }
  161. if (input_mod == "" && len(input) >= 2) || input_mod != "" {
  162. var dir = filepath.Dir(req.CurrentPath)
  163. var manifest_path = filepath.Join(dir, loader.ManifestFileName)
  164. var mod_path string
  165. var mf, err_mf = os.Open(manifest_path)
  166. if err_mf != nil {
  167. mod_path = req.CurrentPath
  168. } else {
  169. mod_path = dir
  170. _ = mf.Close()
  171. }
  172. var mod, idx, err = loader.LoadEntry(mod_path)
  173. if err != nil { goto keywords }
  174. for _, item := range mod.AST.Statements {
  175. process_statement(item.Statement, "")
  176. }
  177. for mod_prefix, imp := range mod.ImpMap {
  178. for _, item := range imp.AST.Statements {
  179. process_statement(item.Statement, mod_prefix)
  180. }
  181. }
  182. if input_mod == "" {
  183. for mod_prefix, _ := range mod.ImpMap {
  184. if strings.HasPrefix(mod_prefix, input) {
  185. suggestions = append(suggestions, AutoCompleteSuggestion {
  186. Text: mod_prefix,
  187. Replace: input,
  188. Type: "import",
  189. })
  190. }
  191. }
  192. var process_core_statement func(stmt ast.Statement)
  193. process_core_statement = func(stmt ast.Statement) {
  194. switch s := stmt.(type) {
  195. case ast.DeclConst:
  196. if !(s.Public) { return }
  197. var name = ast.Id2String(s.Name)
  198. if strings.HasPrefix(name, input) {
  199. suggestions = append(suggestions, AutoCompleteSuggestion {
  200. Text: name,
  201. Replace: input,
  202. Type: "constant",
  203. })
  204. }
  205. case ast.DeclType:
  206. { var def, has_def = s.TypeDef.(ast.VariousTypeDef)
  207. if has_def {
  208. var enum, is_enum = def.TypeDef.(ast.EnumType)
  209. if is_enum {
  210. for _, case_decl := range enum.Cases {
  211. process_core_statement(case_decl)
  212. }
  213. }}}
  214. var name = ast.Id2String(s.Name)
  215. if strings.HasPrefix(name, input) {
  216. suggestions = append(suggestions, AutoCompleteSuggestion{
  217. Text: name,
  218. Replace: input,
  219. Type: "type",
  220. })
  221. }
  222. }
  223. }
  224. var core_mod = idx[stdlib.Mod_core]
  225. for _, stmt := range core_mod.AST.Statements {
  226. process_core_statement(stmt.Statement)
  227. }
  228. }
  229. }
  230. keywords:
  231. if len(input) > 0 && input_mod == "" {
  232. for _, kw := range Keywords {
  233. if len(kw) > 1 && ('a' <= kw[0] && kw[0] <= 'z') &&
  234. strings.HasPrefix(kw, input) {
  235. suggestions = append(suggestions, AutoCompleteSuggestion {
  236. Text: kw,
  237. Replace: input,
  238. Type: "keyword",
  239. })
  240. }
  241. }
  242. }
  243. sort.SliceStable(suggestions, func(i, j int) bool {
  244. var a = suggestions[i]
  245. var b = suggestions[j]
  246. if a.Type == b.Type {
  247. return a.Text < b.Text
  248. } else if a.Type == "keyword" {
  249. return false
  250. } else if b.Type == "keyword" {
  251. return true
  252. } else if a.Type == "function" {
  253. return false
  254. } else if b.Type == "function" {
  255. return true
  256. } else {
  257. return a.Text < b.Text
  258. }
  259. })
  260. return AutoCompleteResponse { Suggestions: suggestions }
  261. }