editor-completion.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355
  1. "use strict";
  2. const tools = require("../../tools/tools");
  3. const fs = require("fs");
  4. exports.IMPORT_COMMAND = 'resolveImport';
  5. class ImportCompletion
  6. {
  7. // 触发提示字符
  8. constructor(parent)
  9. {
  10. this.parent = parent;
  11. this.parent = parent;
  12. this.triggerCharacters = ['/','.'];
  13. this.all_sym_sugges = []
  14. // 编辑代码提示 配置
  15. this.comp_cfg_map = {};
  16. this.comp_cfg = [
  17. // {
  18. // label: "forEach", //显示的名称,‘奖金’
  19. // insertText: "forEach((v,k)=>{})", //插入的值,‘100’
  20. // kind: 0, //提示的图标
  21. // detail: "遍历" //描述,‘我的常量
  22. // }
  23. ];
  24. this.command = {
  25. title: 'AI: Autocomplete',
  26. id: exports.IMPORT_COMMAND,
  27. arguments: [0]
  28. }
  29. }
  30. onLoad(editor){
  31. // Register the resolveImport
  32. this.editor = editor;
  33. this.callbackTimeouts = {}
  34. this.editor._commandService.addCommand(exports.IMPORT_COMMAND,(_,filePath,type)=>{
  35. this.parent.setTimeoutById(()=>{
  36. delete this.callbackTimeouts[filePath];
  37. },600,'ImportCompletion'+filePath);
  38. // 临时解决会调用两次的bug
  39. if(!this.callbackTimeouts[filePath]){
  40. this.handleFixImport(filePath,type);
  41. }
  42. this.callbackTimeouts[filePath] = 1;
  43. });
  44. }
  45. // 添加自定义代码输入提示, 例子: this.addCustomCompleters(["words","cc.Label"])
  46. addCustomCompleters(words) {
  47. words.forEach((v) => {
  48. this.addCustomCompleter(v);
  49. });
  50. }
  51. // 添加自定义代码提示,例子: this.addCustomCompleter("forEach","forEach((v,k)=>{})","遍历数组")
  52. addCustomCompleter(word, value, meta, kind, isCover = false) {
  53. if(word.length <2 || !isNaN(Number(word[0])) ) return;
  54. // 覆盖信息
  55. if (isCover && this.comp_cfg_map[word]) {
  56. let list = this.comp_cfg_map[word];
  57. list.label = word;
  58. list.insertText = (value || word);
  59. list.detail = meta;
  60. list.kind = kind != null ? kind : this.parent.monaco.languages.CompletionItemKind.Text;
  61. return list;
  62. }else{
  63. if (!this.comp_cfg_map[word] || isCover) {
  64. this.comp_cfg_map[word] = {
  65. label: word,
  66. insertText: (value || word),
  67. kind: kind != null ? kind : this.parent.monaco.languages.CompletionItemKind.Text,
  68. insertTextRules: this.parent.monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
  69. // preselect:true,
  70. detail: meta || ''
  71. };
  72. this.comp_cfg.push(this.comp_cfg_map[word]);
  73. return this.comp_cfg_map[word];
  74. }
  75. }
  76. }
  77. provideCompletionItems(model,position,context,token)
  78. {
  79. let suggestions = [];
  80. if(model != this.editor.getModel()){
  81. return {suggestions};
  82. }
  83. let m_path = model.uri.toString();
  84. let text = model.getLineContent(position.lineNumber);
  85. // 1.文件路径提示
  86. this.getPathItems(model,position,text,suggestions);
  87. if(suggestions.length){
  88. return {suggestions,incomplete:false};
  89. }
  90. if(this.all_sym_sugges.length == 0) this.upAllSymSuggests();
  91. var p = new Promise( (resolve, reject )=>
  92. {
  93. let offset = model.getOffsetAt(position);
  94. // 2.isHasSym 检测是否存在精准的内置代码提示
  95. this.parent.tsWr.hasCompletionsAtPosition(m_path,offset).then((isHasSym)=>
  96. {
  97. !isHasSym ? this.getCommonItems(model,text,suggestions,isHasSym) : 0;
  98. let retSuggesFunc = (isUseGlobal)=>
  99. {
  100. isUseGlobal ? suggestions.push.apply(suggestions,this.all_sym_sugges) : 0;
  101. for (let i = 0; i < suggestions.length; i++) {
  102. const v = suggestions[i];
  103. delete v.range;
  104. delete v.sortText;
  105. delete v.preselect;
  106. }
  107. resolve( {suggestions,incomplete:false});
  108. }
  109. // 3.全部代码文件的代码提示合集
  110. let isJs = this.parent.fileMgr.getUriInfo(m_path).extname == '.js'
  111. let enable = isJs && this.parent.cfg.enabledJsGlobalSugges || !isJs && this.parent.cfg.enabledTsGlobalSugges
  112. let wordInfo = model.getWordAtPosition(position);
  113. // 文本头部
  114. if(!wordInfo && text.substr(position.column-2,1) != '.' || wordInfo && text.substr(wordInfo.startColumn-2,1) != '.')
  115. {
  116. // 4.使用 Auto import 提示
  117. this.parent.tsWr.getAutoImportSuggests(m_path).then((list)=>{
  118. // for (let i = 0; i < list.length; i++) {
  119. // let item = list[i];
  120. // item.command = this.command;
  121. // }
  122. suggestions.push.apply(suggestions,list)
  123. // 使用全文件模糊代码提示
  124. retSuggesFunc(true && enable);
  125. });
  126. }else{
  127. // 4.存在精准的内置代码提示,不使用模糊代码提示
  128. retSuggesFunc(!isHasSym && enable);
  129. }
  130. })
  131. })
  132. return p;
  133. }
  134. getCommonItems(model,text,suggestions){
  135. let is_has_string = text.indexOf('"') != -1 || text.indexOf("'") !=-1;
  136. for (let i = 0; i < this.comp_cfg.length; i++)
  137. {
  138. const v = this.comp_cfg[i];
  139. if(!is_has_string &&
  140. v.kind == this.parent.monaco.languages.CompletionItemKind.Folder ){ // 只在字符串中提示文件路径
  141. continue;
  142. }
  143. suggestions.push(v)
  144. }
  145. return suggestions;
  146. }
  147. getPathItems(model,position,text,suggestions){
  148. let imports = tools.getImportStringPaths(text);
  149. if(!imports.length) return;
  150. let item ;
  151. for (let i = 0; i < imports.length; i++) {
  152. let col = position.column-1;
  153. // “”范围内路径
  154. if(imports[i].start <= col && imports[i].start+imports[i].length >= col){
  155. item = imports[i];
  156. break;
  157. }
  158. }
  159. if(!item) {
  160. return;
  161. }
  162. // 读取目录
  163. let i = item.path.lastIndexOf('/')
  164. if(i == -1){
  165. return;// 没有相对路径
  166. }
  167. let importFsPath = tools.relativePathTofsPath(model.fsPath,item.path.substr(0,i+1));
  168. if(!tools.isDirectory(importFsPath)){
  169. return;// 没有目录
  170. }
  171. this.getDirItems(importFsPath,suggestions,/(\.ts|\.js)/,false);
  172. }
  173. getDirItems(fsPath,suggestions,useExtnames,isShowExtname){
  174. let files = fs.readdirSync(fsPath);
  175. files.forEach((dirFile, index) =>
  176. {
  177. if(dirFile.indexOf('.') != -1 && (
  178. dirFile == '.DS_Store' ||
  179. dirFile.indexOf(".meta") != -1 ||
  180. useExtnames && !dirFile.match(useExtnames))){
  181. return;
  182. }
  183. let kind = dirFile.indexOf('.') != -1 ? this.parent.monaco.languages.CompletionItemKind.File : this.parent.monaco.languages.CompletionItemKind.Folder;
  184. let extname = tools.getFileExtname(dirFile) || '文件夹';
  185. if(!isShowExtname){
  186. dirFile = tools.getFileName(dirFile);
  187. }
  188. suggestions.push({
  189. label: dirFile,
  190. insertText: dirFile,
  191. kind: kind,
  192. detail: extname,
  193. });
  194. });
  195. }
  196. // 光标选中当前自动补全item时触发动作
  197. // resolveCompletionItem(item, token) {
  198. // console.log('补全',item, token)
  199. // // if(item.isAutoImport){
  200. // // this.handleFixImport();
  201. // // }
  202. // return null;
  203. // }
  204. // 刷新全局模糊代码提示
  205. upAllSymSuggests()
  206. {
  207. if(!this.parent.cfg.enabledJsGlobalSugges && !this.parent.cfg.enabledTsGlobalSugges){
  208. return;
  209. }
  210. // 防止短时间内大量重复调用
  211. this.parent.setTimeoutById(()=>
  212. {
  213. this.parent.tsWr.getAllSuggests().then((suggeList)=>
  214. {
  215. this.all_sym_sugges = suggeList;
  216. });
  217. },50,'upAllSymSuggests')
  218. }
  219. // 使用自动修复功能完成import
  220. handleFixImport(fileName,type)
  221. {
  222. let model = this.editor.getModel();
  223. let position = this.editor.getPosition();
  224. let worldInfo = model.getWordAtPosition(position)
  225. if(!worldInfo){
  226. position.column -= 2;
  227. worldInfo = model.getWordAtPosition(position)
  228. if(!worldInfo){
  229. return;
  230. }
  231. }
  232. let start = model.getOffsetAt({
  233. lineNumber: position.lineNumber,
  234. column: worldInfo.startColumn
  235. });
  236. let end = model.getOffsetAt({
  237. lineNumber: position.lineNumber,
  238. column: worldInfo.endColumn
  239. });
  240. let options = model.getOptions();
  241. let formatOptions = {
  242. ConvertTabsToSpaces: options.insertSpaces,
  243. TabSize: options.tabSize,
  244. IndentSize: options.tabSize,
  245. IndentStyle: 'Smart',
  246. NewLineCharacter: '\n',
  247. InsertSpaceAfterCommaDelimiter: true,
  248. InsertSpaceAfterSemicolonInForStatements: true,
  249. InsertSpaceBeforeAndAfterBinaryOperators: true,
  250. InsertSpaceAfterKeywordsInControlFlowStatements: true,
  251. InsertSpaceAfterFunctionKeywordForAnonymousFunctions: true,
  252. InsertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis: false,
  253. InsertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets: false,
  254. InsertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces: false,
  255. PlaceOpenBraceOnNewLineForControlBlocks: false,
  256. PlaceOpenBraceOnNewLineForFunctions: false
  257. };
  258. let errorCodes = [2304];
  259. this.parent.monaco.tsWr.getCodeFixesAtPosition(model.uri.toString(),start,end,errorCodes,formatOptions)
  260. .then((list)=>{
  261. if(!list || !list.length){
  262. return;
  263. }
  264. // 优先使用指定路径的修复项
  265. let useItem;
  266. for (let i = 0; i < list.length; i++) {
  267. const fixInfo = list[i];
  268. if(fixInfo.modulePath == fileName){
  269. useItem = fixInfo;
  270. break
  271. }else if(fixInfo.isAddExisting){
  272. useItem = fixInfo;
  273. }
  274. }
  275. if(!useItem) useItem = list[0];
  276. this.fixImport(model,useItem.changes)
  277. this.upAllSymSuggests();
  278. if(type == 'showPanel'){
  279. // 刷新提示面板
  280. setTimeout(()=>{
  281. this.editor._commandService.executeCommand('editor.action.triggerSuggest')
  282. },100)
  283. }
  284. })
  285. }
  286. fixImport(model, changes){
  287. let selects = []
  288. let edits = []
  289. for (let i = 0; i < changes.length; i++) {
  290. const item = changes[i];
  291. for (let n = 0; n < item.textChanges.length; n++)
  292. {
  293. const fix = item.textChanges[n];
  294. const range = this.textSpanToRange(model,fix.span)
  295. selects.push(range);
  296. edits.push({
  297. range:range,
  298. text:fix.newText,
  299. forceMoveMarkers:false,
  300. })
  301. }
  302. }
  303. model.pushStackElement();
  304. model.pushEditOperations(selects, edits, () => []);
  305. model.pushStackElement();
  306. }
  307. textSpanToRange(model, span) {
  308. var p1 = model.getPositionAt(span.start);
  309. var p2 = model.getPositionAt(span.start + span.length);
  310. var startLineNumber = p1.lineNumber, startColumn = p1.column;
  311. var endLineNumber = p2.lineNumber, endColumn = p2.column;
  312. return { startLineNumber: startLineNumber, startColumn: startColumn, endLineNumber: endLineNumber, endColumn: endColumn };
  313. }
  314. }
  315. exports.default = ImportCompletion;