Переглянути джерело

代码提示优化,`import`提示优化,提示自动`import`

mxd 3 роки тому
батько
коміт
343783d855

+ 1 - 0
magic-editor/src/console/src/components/editor/magic-script-editor.vue

@@ -114,6 +114,7 @@ export default {
       theme: store.get('skin') || 'default',
       fontFamily: contants.EDITOR_FONT_FAMILY,
       fontSize: contants.EDITOR_FONT_SIZE,
+      scrollBeyondLastLine: false,
       fontLigatures: true,
       // 自动调整大小
       automaticLayout: true

+ 157 - 67
magic-editor/src/console/src/scripts/editor/completion.js

@@ -5,13 +5,78 @@ import {Parser} from '../parsing/parser.js'
 import * as monaco from 'monaco-editor'
 import RequestParameter from "@/scripts/editor/request-parameter";
 
-const completionImport = (suggestions, position, line, importIndex) => {
-    let len = 0;
-    let start = line.indexOf('"') + 1;
-    if (start === 0) {
-        start = line.indexOf("'") + 1;
-    }
-    if(start === 0){
+const completionImportJavaPackage = (suggestions, keyword, start, position) => {
+    let len = -1
+    let importClass = JavaClass.getImportClass();
+    if (start !== 0 && keyword && (len = importClass.length) > 0) {
+        keyword = keyword.toLowerCase()
+        JavaClass.getDefineModules().filter(module => module.toLowerCase().indexOf(keyword) > -1).forEach(module => suggestions.push({
+            label: module,
+            filterText: module,
+            kind: monaco.languages.CompletionItemKind.Module,
+            detail: module,
+            insertText: module,
+            insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet
+        }))
+        let set = new Set();
+        for (let i = 0; i < len; i++) {
+            let clazz = importClass[i];
+            let index = clazz.toLowerCase().indexOf(keyword);
+            if (index > -1) {
+                let className = clazz.substring(clazz.lastIndexOf('.') + 1);
+                if (index === 0) {
+                    let content = clazz.substring(keyword.length);
+                    let detail = content
+                    if(content.startsWith(".")){
+                        detail = keyword + '.'
+                        content = keyword.substring(keyword.lastIndexOf(".") + 1) + '.'
+                    } else {
+                        if(content.indexOf('.') === -1){
+                            suggestions.push({
+                                sortText: `2${className}`,
+                                label: className,
+                                kind: monaco.languages.CompletionItemKind.Class,
+                                filterText: clazz,
+                                detail: clazz,
+                                insertText: className,
+                                insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
+                            })
+                            continue;
+                        }
+                        let text = content.substring(0, content.indexOf('.') + 1);
+                        detail = keyword + text
+                        content = keyword.substring(keyword.lastIndexOf(".") + 1) + text;
+                    }
+                    if (set.has(content)) {
+                        continue;
+                    }
+                    set.add(content);
+                    suggestions.push({
+                        sortText: `1${content}`,
+                        label: content,
+                        kind: monaco.languages.CompletionItemKind.Folder,
+                        filterText: clazz,
+                        detail: detail.replace(/\.$/, ''),
+                        insertText: content,
+                        insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
+                        command: {
+                            id: 'editor.action.triggerSuggest'
+                        }
+                    })
+                } else if (className.toLowerCase().indexOf(keyword) > -1) {
+                    suggestions.push({
+                        sortText: `2${className}`,
+                        label: className,
+                        kind: monaco.languages.CompletionItemKind.Class,
+                        filterText: className,
+                        detail: clazz,
+                        insertText: clazz,
+                        range: new monaco.Range(position.lineNumber, start + 1, position.lineNumber, position.column)
+                    })
+                }
+            }
+        }
+    } else {
         JavaClass.getDefineModules().forEach(module => suggestions.push({
             label: module,
             filterText: module,
@@ -20,9 +85,20 @@ const completionImport = (suggestions, position, line, importIndex) => {
             insertText: module,
             insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet
         }))
+    }
+}
+
+const completionImport = (suggestions, position, line, importIndex) => {
+    let start = line.indexOf('"') + 1;
+    if (start === 0) {
+        start = line.indexOf("'") + 1;
+    }
+    if(start === 0){
+        line = line.trim().replace('import', '').trim()
+        completionImportJavaPackage(suggestions, line, importIndex + 1 , position)
         return;
     }
-    let text = line.trim().substring(importIndex + 6).trim().replace(/['|"]/g, '');
+    let text = line.substring(importIndex).trim().replace(/['|"]/g, '');
     if(text.startsWith('@')){
         if(text.indexOf(' ')> -1){
             return;
@@ -37,7 +113,7 @@ const completionImport = (suggestions, position, line, importIndex) => {
                 filterText: label,
                 detail: it.name,
                 insertText: label,
-                insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet
+                range: new monaco.Range(position.lineNumber, start + 1, position.lineNumber, position.column)
             })
         })
         finder = JavaClass.getFunctionFinder();
@@ -50,55 +126,39 @@ const completionImport = (suggestions, position, line, importIndex) => {
                 filterText: label,
                 detail: it.name,
                 insertText: label,
-                insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet
+                range: new monaco.Range(position.lineNumber, start + 1, position.lineNumber, position.column)
             })
         })
         return;
     }
-    let keyword = text.toLowerCase();
-    let importClass = JavaClass.getImportClass();
-    if (start !== 0 && keyword && (len = importClass.length) > 0) {
-        let set = new Set();
-        for (let i = 0; i < len; i++) {
-            let clazz = importClass[i];
-            let index = clazz.toLowerCase().indexOf(keyword);
-            if (index > -1) {
-                if ((index = clazz.indexOf('.', index + keyword.length)) > -1) {
-                    let content = clazz.substring(0, index);
-                    content = content.substring(content.lastIndexOf('.') + 1) + '.';
-                    if (set.has(content)) {
-                        continue;
-                    }
-                    set.add(content);
-                    suggestions.push({
-                        sortText: '1',
-                        label: content,
-                        kind: monaco.languages.CompletionItemKind.Folder,
-                        filterText: clazz,
-                        detail: content,
-                        insertText: content,
-                        insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
-                        command: {
-                            id: 'editor.action.triggerSuggest'
-                        }
-                    })
-                } else {
+    completionImportJavaPackage(suggestions, text, start, position)
+}
+const completionFunction = async (suggestions, input, env, best) => {
+    env = env || {}
+    if (best && best.constructor.name === 'VariableAccess') {
+        if(await best.getJavaType(env) === 'java.lang.Object'){
+            let importClass = JavaClass.getImportClass();
+            const keyword = best.variable
+            importClass.forEach(clazz => {
+                let className = clazz.substring(clazz.lastIndexOf('.') + 1);
+                if(className.indexOf(keyword) > -1){
                     suggestions.push({
-                        sortText: '2',
-                        label: clazz.substring(clazz.lastIndexOf('.') + 1),
-                        kind: monaco.languages.CompletionItemKind.Module,
-                        filterText: clazz,
+                        sortText: `${className}`,
+                        label: className,
+                        kind: monaco.languages.CompletionItemKind.Class,
+                        filterText: className,
                         detail: clazz,
-                        insertText: clazz,
-                        range: new monaco.Range(position.lineNumber, start + 1, position.lineNumber, position.column)
+                        insertTextRules: monaco.languages.CompletionItemInsertTextRule.KeepWhitespace,
+                        additionalTextEdits: [{
+                            forceMoveMarkers: true,
+                            text: `import ${clazz}\r\n`,
+                            range: new monaco.Range(1, 0, 1, 0)
+                        }]
                     })
                 }
-            }
+            })
         }
     }
-}
-const completionFunction = (suggestions, input, env) => {
-    env = env || {}
     JavaClass.findFunction().forEach(it => {
         suggestions.push({
             sortText: it.sortText || it.fullName,
@@ -124,8 +184,8 @@ const completionFunction = (suggestions, input, env) => {
             insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet
         })
     })
-    if(count > 2){
-        Array.from(new Set(matches)).filter((it,index) => index + 2 < count && known.indexOf(it) === -1 && vars.indexOf(it) === -1).map(it => {
+    if (count > 2) {
+        Array.from(new Set(matches)).filter((it, index) => index + 2 < count && known.indexOf(it) === -1 && vars.indexOf(it) === -1).map(it => {
             suggestions.push({
                 label: it,
                 filterText: it,
@@ -178,20 +238,18 @@ const completionMethod = async (className, suggestions) => {
             }
             mmap[method.signature] = true;
             let document = [];
+            method.comment && document.push(method.comment)
             for (let j = (method.extension ? 1 : 0); j < method.parameters.length; j++) {
                 let param = method.parameters[j];
-                document.push('`' + param.name + '` ' + (param.comment || param.type));
-                document.push('\r\n')
+                document.push(`\`${param.name}\`:${(param.comment || param.type)}`)
             }
-            method.comment && document.push('\r\n') && document.push(method.comment)
+            document.push(`返回类型:\`${method.returnType}\``)
             suggestions.push({
                 sortText: method.sortText || method.fullName,
                 label: method.fullName,
                 kind: monaco.languages.CompletionItemKind.Method,
                 detail: `${simpleName}.${method.fullName}: ${method.returnType}`,
-                documentation: {
-                    value: document.join('\r\n')
-                },
+                documentation: { value: document.join('\r\n\r\n\r\n') },
                 insertText: method.insertText,
                 insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet
             })
@@ -204,7 +262,7 @@ async function completionScript(suggestions, input) {
         let tokens = tokenizer(input);
         let tokenLen = tokens.length;
         if (tokenLen === 0) {
-            completionFunction(suggestions, input)
+            await completionFunction(suggestions, input)
             return;
         }
         let parser = new Parser(new TokenStream(tokens));
@@ -216,17 +274,31 @@ async function completionScript(suggestions, input) {
             if (astName === 'MemberAccess' || astName === 'MethodCall') {
                 await completionMethod(await best.target.getJavaType(env), suggestions)
             } else {
-                completionFunction(suggestions, input, env)
+                await completionFunction(suggestions, input, env, best)
             }
         } else {
-            completionFunction(suggestions, input, env)
+           await completionFunction(suggestions, input, env)
         }
         return suggestions;
     } catch (e) {
-        // console.log("error")
+        // console.error(e)
     }
 }
 
+const quickSuggestions = [
+    ['bre', 'break;', '跳出循环'],
+    ['con', 'continue;', '继续循环'],
+    ['imp', 'import $1', '导入'],
+    ['if', 'if (${1:condition}){\r\n\t$2\r\n}', '判断'],
+    ['ife', 'if (${1:condition}) {\r\n\t$2\r\n} else { \r\n\t$3\r\n}', '判断'],
+    ['for', 'for (item in ${1:collection}) {\r\n\t$2\r\n}', '循环集合'],
+    ['exit', 'exit ${1:code}, ${2:message};', '退出'],
+    ['info', 'log.info($1);', 'info日志'],
+    ['debug', 'log.debug($1);', 'debug日志'],
+    ['err', 'log.error($1);', 'error日志'],
+    ['ass', 'assert ${1:condition} : ${2:code}, ${3:message}', '校验参数']
+]
+
 const CompletionItemProvider = {
     provideCompletionItems: async function (model, position) {
         let value = model.getValueInRange({
@@ -241,10 +313,28 @@ const CompletionItemProvider = {
             endLineNumber: position.lineNumber,
             endColumn: position.column
         });
-        let suggestions = [];
-        let importIndex;
-        if (line.length > 1 && (importIndex = line.trim().indexOf('import')) === 0) {
-            completionImport(suggestions, position, line, importIndex)
+        let word = model.getWordUntilPosition(position);
+        let range = {
+            startLineNumber: position.lineNumber,
+            endLineNumber: position.lineNumber,
+            startColumn: word.startColumn,
+            endColumn: word.endColumn
+        }
+        let incomplete = false;
+        let suggestions = quickSuggestions.map(item => {
+            return {
+                label: item[0],
+                kind: monaco.languages.CompletionItemKind.Struct,
+                detail: item[2] || item[1],
+                insertText: item[1],
+                filterText: item[0],
+                insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
+                range
+            }
+        });
+        if (line.length > 1 && (line.trim().indexOf('import')) === 0) {
+            completionImport(suggestions, position, line, line.indexOf('import') + 6)
+            incomplete = true;
         } else if (line.endsWith("::")) {
             suggestions = ['int', 'long', 'date', 'string', 'short', 'byte', 'float', 'double', 'json','stringify', 'sql'].map(it => {
                 return {
@@ -258,14 +348,14 @@ const CompletionItemProvider = {
         } else if (value.length > 1) {
             await completionScript(suggestions, value)
         } else {
-            completionFunction(suggestions, value, {
+            await completionFunction(suggestions, value, {
                 ...RequestParameter.environmentFunction(),
                 ...JavaClass.getAutoImportClass(),
                 ...JavaClass.getAutoImportModule()
             })
         }
-        return {suggestions}
+        return { suggestions, incomplete }
     },
-    triggerCharacters: ['.', ":"]
+    triggerCharacters: ['.', ':']
 };
 export default CompletionItemProvider

+ 11 - 1
magic-editor/src/console/src/scripts/parsing/ast.js

@@ -51,6 +51,11 @@ class Literal extends Expression {
         return this.javaType;
     }
 
+    getValue(){
+        const text = this.getSpan().getText()
+        return text.replace(/\\\\/g, '\\').replace(/\\n/g, '\n').replace(/\\r/g, '\r').replace(/\\t/g, '\t').replace(/\\"/g, '"').replace(/\\'/g, "\'")
+    }
+
 }
 
 class MethodCall extends Node {
@@ -180,6 +185,11 @@ class MapOrArrayAccess extends Node {
         this.target = target
         this.keyOrIndex = keyOrIndex
     }
+
+    async getJavaType(env) {
+        const javaType = await this.target.getJavaType(env)
+        return javaType === 'db' ? 'db' : super.getJavaType(env);
+    }
 }
 
 class IfStatement extends Node {
@@ -396,7 +406,7 @@ class Import extends Node {
         }else if(this.module){
             env[this.packageName] = this.packageName
         }else if(this.varName){
-            env[this.varName.getText()] = this.packageName
+            env[this.varName] = this.packageName
         }else {
             let index = this.packageName.lastIndexOf('.');
             if (index > -1) {

+ 40 - 10
magic-editor/src/console/src/scripts/parsing/parser.js

@@ -1,4 +1,4 @@
-import {LiteralToken, ParseException, Span, TokenStream, TokenType} from './index.js'
+import { ParseException, Span, TokenStream, TokenType} from './index.js'
 import tokenizer from './tokenizer.js'
 import JavaClass from '../editor/java-class.js'
 import {
@@ -209,20 +209,50 @@ export class Parser {
         let opening = this.stream.expect("import").getSpan();
         if (this.stream.hasMore()) {
             let expected = this.stream.consume();
-            let varName;
-            let packageName;
-            let module = expected.getTokenType() === TokenType.Identifier
-            if (expected.getTokenType() === TokenType.StringLiteral || module) {
-                if (expected.getTokenType() === TokenType.StringLiteral) {
-                    if (this.stream.match("as", true)) {
-                        varName = this.stream.expect(TokenType.Identifier);
-                        this.checkKeyword(varName.getSpan());
+            let packageName = null;
+            let isStringLiteral = expected.getTokenType() === TokenType.StringLiteral
+            if (isStringLiteral) {
+                packageName = this.createStringLiteral(expected).getValue();
+            } else if (expected.type === TokenType.Identifier) {
+                let startSpan = expected.getSpan();
+                let endSpan = null;
+                packageName = startSpan.getText();
+                while (this.stream.match(TokenType.Period, true)){
+                    isStringLiteral = true;
+                    if(this.stream.match(TokenType.Asterisk, false)){
+                        expected = this.stream.consume()
+                        break;
                     }
+                    expected = this.stream.expect(TokenType.Identifier)
+                }
+                if(isStringLiteral){
+                    endSpan = expected.getSpan();
+                    packageName = new Span(startSpan, endSpan).getText();
                 }
-                return new Import(new Span(opening, expected.getSpan()), expected.getText(), varName, module);
             } else {
                 throw new ParseException("Expected identifier or string, but got stream is " + expected.getTokenType().error, this.stream.getPrev().getSpan());
             }
+
+            let varName = packageName;
+            if (isStringLiteral) {
+                if (this.stream.match("as", true)) {
+                    expected = this.stream.expect(TokenType.Identifier);
+                    this.checkKeyword(expected.getSpan());
+                    varName = expected.getSpan().getText();
+                } else {
+                    let temp = packageName;
+                    if (!temp.startsWith("@")) {
+                        let index = temp.lastIndexOf(".");
+                        if (index != -1) {
+                            temp = temp.substring(index + 1);
+                        }
+                    } else {
+                        throw new ParseException("Expected as", this.stream.getPrev().getSpan());
+                    }
+                    varName = temp;
+                }
+            }
+            return new Import(new Span(opening, expected.getSpan()), packageName, varName, !isStringLiteral);
         }
         throw new ParseException("Expected identifier or string, but got stream is EOF", this.stream.getPrev().getSpan());
     }