completion.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361
  1. import JavaClass from './java-class.js'
  2. import tokenizer from '../parsing/tokenizer.js'
  3. import {TokenStream} from '../parsing/index.js'
  4. import {Parser} from '../parsing/parser.js'
  5. import * as monaco from 'monaco-editor'
  6. import RequestParameter from "@/scripts/editor/request-parameter";
  7. const completionImportJavaPackage = (suggestions, keyword, start, position) => {
  8. let len = -1
  9. let importClass = JavaClass.getImportClass();
  10. if (start !== 0 && keyword && (len = importClass.length) > 0) {
  11. keyword = keyword.toLowerCase()
  12. JavaClass.getDefineModules().filter(module => module.toLowerCase().indexOf(keyword) > -1).forEach(module => suggestions.push({
  13. label: module,
  14. filterText: module,
  15. kind: monaco.languages.CompletionItemKind.Module,
  16. detail: module,
  17. insertText: module,
  18. insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet
  19. }))
  20. let set = new Set();
  21. for (let i = 0; i < len; i++) {
  22. let clazz = importClass[i];
  23. let index = clazz.toLowerCase().indexOf(keyword);
  24. if (index > -1) {
  25. let className = clazz.substring(clazz.lastIndexOf('.') + 1);
  26. if (index === 0) {
  27. let content = clazz.substring(keyword.length);
  28. let detail = content
  29. if(content.startsWith(".")){
  30. detail = keyword + '.'
  31. content = keyword.substring(keyword.lastIndexOf(".") + 1) + '.'
  32. } else {
  33. if(content.indexOf('.') === -1){
  34. suggestions.push({
  35. sortText: `2${className}`,
  36. label: className,
  37. kind: monaco.languages.CompletionItemKind.Class,
  38. filterText: clazz,
  39. detail: clazz,
  40. insertText: className,
  41. insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
  42. })
  43. continue;
  44. }
  45. let text = content.substring(0, content.indexOf('.') + 1);
  46. detail = keyword + text
  47. content = keyword.substring(keyword.lastIndexOf(".") + 1) + text;
  48. }
  49. if (set.has(content)) {
  50. continue;
  51. }
  52. set.add(content);
  53. suggestions.push({
  54. sortText: `1${content}`,
  55. label: content,
  56. kind: monaco.languages.CompletionItemKind.Folder,
  57. filterText: clazz,
  58. detail: detail.replace(/\.$/, ''),
  59. insertText: content,
  60. insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
  61. command: {
  62. id: 'editor.action.triggerSuggest'
  63. }
  64. })
  65. } else if (className.toLowerCase().indexOf(keyword) > -1) {
  66. suggestions.push({
  67. sortText: `2${className}`,
  68. label: className,
  69. kind: monaco.languages.CompletionItemKind.Class,
  70. filterText: className,
  71. detail: clazz,
  72. insertText: clazz,
  73. range: new monaco.Range(position.lineNumber, start + 1, position.lineNumber, position.column)
  74. })
  75. }
  76. }
  77. }
  78. } else {
  79. JavaClass.getDefineModules().forEach(module => suggestions.push({
  80. label: module,
  81. filterText: module,
  82. kind: monaco.languages.CompletionItemKind.Module,
  83. detail: module,
  84. insertText: module,
  85. insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet
  86. }))
  87. }
  88. }
  89. const completionImport = (suggestions, position, line, importIndex) => {
  90. let start = line.indexOf('"') + 1;
  91. if (start === 0) {
  92. start = line.indexOf("'") + 1;
  93. }
  94. if(start === 0){
  95. line = line.trim().replace('import', '').trim()
  96. completionImportJavaPackage(suggestions, line, importIndex + 1 , position)
  97. return;
  98. }
  99. let text = line.substring(importIndex).trim().replace(/['|"]/g, '');
  100. if(text.startsWith('@')){
  101. if(text.indexOf(' ')> -1){
  102. return;
  103. }
  104. let finder = JavaClass.getApiFinder();
  105. (finder && finder() || []).forEach(it => {
  106. let label = '@' + it.method + ':' + it.path
  107. suggestions.push({
  108. sortText: label,
  109. label: label,
  110. kind: monaco.languages.CompletionItemKind.Reference,
  111. filterText: label,
  112. detail: it.name,
  113. insertText: label,
  114. range: new monaco.Range(position.lineNumber, start + 1, position.lineNumber, position.column)
  115. })
  116. })
  117. finder = JavaClass.getFunctionFinder();
  118. (finder && finder() || []).forEach(it => {
  119. let label = '@' + it.path
  120. suggestions.push({
  121. sortText: label,
  122. label: label,
  123. kind: monaco.languages.CompletionItemKind.Reference,
  124. filterText: label,
  125. detail: it.name,
  126. insertText: label,
  127. range: new monaco.Range(position.lineNumber, start + 1, position.lineNumber, position.column)
  128. })
  129. })
  130. return;
  131. }
  132. completionImportJavaPackage(suggestions, text, start, position)
  133. }
  134. const completionFunction = async (suggestions, input, env, best) => {
  135. env = env || {}
  136. if (best && best.constructor.name === 'VariableAccess') {
  137. if(await best.getJavaType(env) === 'java.lang.Object'){
  138. let importClass = JavaClass.getImportClass();
  139. const keyword = best.variable
  140. importClass.forEach(clazz => {
  141. let className = clazz.substring(clazz.lastIndexOf('.') + 1);
  142. if(className.indexOf(keyword) > -1){
  143. suggestions.push({
  144. sortText: `${className}`,
  145. label: className,
  146. kind: monaco.languages.CompletionItemKind.Class,
  147. filterText: className,
  148. detail: clazz,
  149. insertTextRules: monaco.languages.CompletionItemInsertTextRule.KeepWhitespace,
  150. additionalTextEdits: [{
  151. forceMoveMarkers: true,
  152. text: `import ${clazz}\r\n`,
  153. range: new monaco.Range(1, 0, 1, 0)
  154. }]
  155. })
  156. }
  157. })
  158. }
  159. }
  160. JavaClass.findFunction().forEach(it => {
  161. suggestions.push({
  162. sortText: it.sortText || it.fullName,
  163. label: it.fullName,
  164. filterText: it.name,
  165. kind: monaco.languages.CompletionItemKind.Method,
  166. detail: it.comment,
  167. insertText: it.insertText,
  168. insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet
  169. })
  170. })
  171. let known = suggestions.map(it => it.detail);
  172. let matches = input.match(/[a-zA-Z_$]+/ig) || [];
  173. let count = matches.length;
  174. let vars = Object.keys(env);
  175. vars.forEach(key => {
  176. suggestions.push({
  177. label: key,
  178. filterText: key,
  179. kind: monaco.languages.CompletionItemKind.Variable,
  180. detail: env[key],
  181. insertText: key,
  182. insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet
  183. })
  184. })
  185. if (count > 2) {
  186. Array.from(new Set(matches)).filter((it, index) => index + 2 < count && known.indexOf(it) === -1 && vars.indexOf(it) === -1).map(it => {
  187. suggestions.push({
  188. label: it,
  189. filterText: it,
  190. kind: monaco.languages.CompletionItemKind.Text,
  191. detail: it,
  192. insertText: it,
  193. insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet
  194. })
  195. })
  196. }
  197. };
  198. const completionMethod = async (className, suggestions) => {
  199. let clazz = await JavaClass.loadClass(className)
  200. let index = className.lastIndexOf('.')
  201. let simpleName = index > 0 ? className.substring(index + 1) : className
  202. let enums = JavaClass.findEnums(clazz);
  203. if (enums) {
  204. for (let j = 0; j < enums.length; j++) {
  205. let value = enums[j];
  206. suggestions.push({
  207. label: value,
  208. kind: monaco.languages.CompletionItemKind.Enum,
  209. detail: value + ":" + value,
  210. insertText: value,
  211. sortText: ' ~~~' + value
  212. })
  213. }
  214. }
  215. let attributes = JavaClass.findAttributes(clazz);
  216. if (attributes) {
  217. for (let j = 0; j < attributes.length; j++) {
  218. let attribute = attributes[j];
  219. suggestions.push({
  220. label: attribute.name,
  221. kind: monaco.languages.CompletionItemKind.Field,
  222. detail: attribute.comment || (attribute.type + ":" + attribute.name),
  223. insertText: attribute.name,
  224. sortText: ' ~~' + attribute.name
  225. })
  226. }
  227. }
  228. let methods = JavaClass.findMethods(clazz);
  229. if (methods) {
  230. let mmap = {};
  231. for (let j = 0; j < methods.length; j++) {
  232. let method = methods[j];
  233. if (mmap[method.signature]) {
  234. continue;
  235. }
  236. mmap[method.signature] = true;
  237. let document = [];
  238. method.comment && document.push(method.comment)
  239. for (let j = (method.extension ? 1 : 0); j < method.parameters.length; j++) {
  240. let param = method.parameters[j];
  241. document.push(`\`${param.name}\`:${(param.comment || param.type)}`)
  242. }
  243. document.push(`返回类型:\`${method.returnType}\``)
  244. suggestions.push({
  245. sortText: method.sortText || method.fullName,
  246. label: method.fullName,
  247. kind: monaco.languages.CompletionItemKind.Method,
  248. detail: `${simpleName}.${method.fullName}: ${method.returnType}`,
  249. documentation: { value: document.join('\r\n\r\n\r\n') },
  250. insertText: method.insertText,
  251. insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet
  252. })
  253. }
  254. }
  255. }
  256. async function completionScript(suggestions, input) {
  257. try {
  258. let tokens = tokenizer(input);
  259. let tokenLen = tokens.length;
  260. if (tokenLen === 0) {
  261. await completionFunction(suggestions, input)
  262. return;
  263. }
  264. let parser = new Parser(new TokenStream(tokens));
  265. const { best, env } = await parser.parseBest(input.length - 1, env);
  266. if(input.endsWith(".")){
  267. await completionMethod(await best.getJavaType(env), suggestions)
  268. } else if(best) {
  269. let astName = best.constructor.name;
  270. if (astName === 'MemberAccess' || astName === 'MethodCall') {
  271. await completionMethod(await best.target.getJavaType(env), suggestions)
  272. } else {
  273. await completionFunction(suggestions, input, env, best)
  274. }
  275. } else {
  276. await completionFunction(suggestions, input, env)
  277. }
  278. return suggestions;
  279. } catch (e) {
  280. // console.error(e)
  281. }
  282. }
  283. const quickSuggestions = [
  284. ['bre', 'break;', '跳出循环'],
  285. ['con', 'continue;', '继续循环'],
  286. ['imp', 'import $1', '导入'],
  287. ['if', 'if (${1:condition}){\r\n\t$2\r\n}', '判断'],
  288. ['ife', 'if (${1:condition}) {\r\n\t$2\r\n} else { \r\n\t$3\r\n}', '判断'],
  289. ['for', 'for (item in ${1:collection}) {\r\n\t$2\r\n}', '循环集合'],
  290. ['exit', 'exit ${1:code}, ${2:message};', '退出'],
  291. ['info', 'log.info($1);', 'info日志'],
  292. ['debug', 'log.debug($1);', 'debug日志'],
  293. ['err', 'log.error($1);', 'error日志'],
  294. ['ass', 'assert ${1:condition} : ${2:code}, ${3:message}', '校验参数']
  295. ]
  296. const CompletionItemProvider = {
  297. provideCompletionItems: async function (model, position) {
  298. let value = model.getValueInRange({
  299. startLineNumber: 1,
  300. startColumn: 1,
  301. endLineNumber: position.lineNumber,
  302. endColumn: position.column
  303. });
  304. let line = model.getValueInRange({
  305. startLineNumber: position.lineNumber,
  306. startColumn: 1,
  307. endLineNumber: position.lineNumber,
  308. endColumn: position.column
  309. });
  310. let word = model.getWordUntilPosition(position);
  311. let range = {
  312. startLineNumber: position.lineNumber,
  313. endLineNumber: position.lineNumber,
  314. startColumn: word.startColumn,
  315. endColumn: word.endColumn
  316. }
  317. let incomplete = false;
  318. let suggestions = quickSuggestions.map(item => {
  319. return {
  320. label: item[0],
  321. kind: monaco.languages.CompletionItemKind.Struct,
  322. detail: item[2] || item[1],
  323. insertText: item[1],
  324. filterText: item[0],
  325. insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
  326. range
  327. }
  328. });
  329. if (line.length > 1 && (line.trim().indexOf('import')) === 0) {
  330. completionImport(suggestions, position, line, line.indexOf('import') + 6)
  331. incomplete = true;
  332. } else if (line.endsWith("::")) {
  333. suggestions = ['int', 'long', 'date', 'string', 'short', 'byte', 'float', 'double', 'json','stringify', 'sql'].map(it => {
  334. return {
  335. label: it,
  336. detail: `转换为${it === 'stringify' ? 'json字符串': it === 'sql' ? 'sql参数类型': it}`,
  337. insertText: it,
  338. kind: monaco.languages.CompletionItemKind.TypeParameter,
  339. insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet
  340. }
  341. })
  342. } else if (value.length > 1) {
  343. await completionScript(suggestions, value)
  344. } else {
  345. await completionFunction(suggestions, value, {
  346. ...RequestParameter.environmentFunction(),
  347. ...JavaClass.getAutoImportClass(),
  348. ...JavaClass.getAutoImportModule()
  349. })
  350. }
  351. return { suggestions, incomplete }
  352. },
  353. triggerCharacters: ['.', ':']
  354. };
  355. export default CompletionItemProvider