├── .gitignore ├── goland ├── settings.gradle ├── src │ └── main │ │ ├── resources │ │ ├── fileTemplates │ │ │ └── internal │ │ │ │ ├── Proto File.proto.ft │ │ │ │ ├── Api File.api.ft │ │ │ │ ├── Proto Template.proto.ft │ │ │ │ └── Api Template.api.ft │ │ ├── static │ │ │ ├── new.png │ │ │ ├── alert.png │ │ │ ├── goctl.png │ │ │ ├── proto.png │ │ │ ├── json_tag.png │ │ │ ├── psiTree.png │ │ │ ├── service.png │ │ │ ├── api_colorful.png │ │ │ ├── editor-menu.png │ │ │ ├── live_template.gif │ │ │ ├── project_menu.png │ │ │ ├── go_live_template.png │ │ │ └── project_generate_code.png │ │ ├── icons │ │ │ ├── goctl.png │ │ │ └── goctl@2x.png │ │ ├── liveTemplates │ │ │ ├── apiHidden.xml │ │ │ ├── apiTags.xml │ │ │ └── api.xml │ │ └── META-INF │ │ │ └── plugin.xml │ │ └── java │ │ └── cn │ │ └── xiaoheiban │ │ ├── completion │ │ ├── Priority.java │ │ ├── ApiCompletionContributor.java │ │ ├── KeywordCompletionContributor.java │ │ ├── ApiKeywordCompletionProvider.java │ │ ├── ApiCompletionProvider.java │ │ └── ApiProvider.java │ │ ├── icon │ │ └── ApiIcon.java │ │ ├── psi │ │ ├── nodes │ │ │ ├── TagNode.java │ │ │ ├── ServiceNode.java │ │ │ ├── ApiBodyNode.java │ │ │ ├── ImportValueNode.java │ │ │ ├── ReferenceIdNode.java │ │ │ ├── ServiceNameNode.java │ │ │ ├── HandlerValueNode.java │ │ │ ├── HttpRouteNode.java │ │ │ ├── ServiceRouteNode.java │ │ │ ├── NormalField.java │ │ │ ├── AnonymousField.java │ │ │ ├── IPsiNode.java │ │ │ ├── StructNode.java │ │ │ ├── FieldNode.java │ │ │ ├── StructNameNode.java │ │ │ └── ApiRootNode.java │ │ ├── StructReference.java │ │ ├── ApiASTFactory.java │ │ ├── IReference.java │ │ ├── IdentifierPSINode.java │ │ └── ApiFile.java │ │ ├── language │ │ ├── ApiLanguage.java │ │ ├── ApiFileTypeFactory.java │ │ ├── ApiCommenter.java │ │ ├── ApiFileType.java │ │ └── ApiAnnotator.java │ │ ├── util │ │ ├── Stringx.java │ │ ├── FileReload.java │ │ └── Exec.java │ │ ├── contsant │ │ └── Constant.java │ │ ├── highlighting │ │ ├── ApiSyntaxHighlighterFactory.java │ │ ├── ApiColorSettingsPage.java │ │ └── ApiSyntaxHighlighter.java │ │ ├── template │ │ ├── ApiLiveTemplatesProvider.java │ │ ├── ApiEverywhereContextType.java │ │ ├── ApiFieldNameMacro.java │ │ └── ApiLiveTemplateContextType.java │ │ ├── io │ │ └── IO.java │ │ ├── editor │ │ ├── ApiPairedBraceMatcher.java │ │ ├── QuoteHandler.java │ │ └── AutoInsertHandler.java │ │ ├── action │ │ ├── ApiCreateFileAction.java │ │ ├── ProtoCreateFileAction.java │ │ ├── RpcNewAction.java │ │ ├── ApiNewAction.java │ │ ├── ModelAction.java │ │ ├── ApiAction.java │ │ └── RpcAction.java │ │ ├── antlr4 │ │ ├── ApiParser.g4 │ │ └── ApiLexer.g4 │ │ ├── notification │ │ └── Notification.java │ │ ├── ui │ │ └── FileChooseDialog.java │ │ └── parser │ │ └── ApiParserDefinition.java ├── gradle.properties ├── .gitignore ├── CHANGE_LOG.md ├── build.gradle ├── gradlew.bat ├── README.md └── gradlew ├── vscode ├── .vscode │ ├── settings.json │ ├── tasks.json │ └── launch.json ├── .vscodeignore ├── docs │ └── images │ │ ├── info.gif │ │ ├── jump.gif │ │ ├── type.gif │ │ ├── handler.gif │ │ └── service.gif ├── images │ └── go-zero-logo.png ├── tslint.json ├── src │ ├── goctlMode.ts │ ├── goctlDocument.ts │ ├── util.ts │ ├── extension.ts │ ├── test │ │ └── runTest.ts │ ├── goctlFormat.ts │ └── goctlDeclaration.ts ├── .gitignore ├── tsconfig.json ├── README.md ├── language-configuration.json ├── CHANGELOG.md ├── package.json ├── snippets │ └── goctl.json └── syntaxes │ └── goctl.tmLanguage.json ├── LICENSE ├── README.md └── README-en.md /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store -------------------------------------------------------------------------------- /goland/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'goland' 2 | 3 | -------------------------------------------------------------------------------- /vscode/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.insertSpaces": false 3 | } -------------------------------------------------------------------------------- /vscode/.vscodeignore: -------------------------------------------------------------------------------- 1 | .vscode/** 2 | .vscode-test/** 3 | .gitignore 4 | vsc-extension-quickstart.md 5 | -------------------------------------------------------------------------------- /goland/src/main/resources/fileTemplates/internal/Proto File.proto.ft: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package template; -------------------------------------------------------------------------------- /vscode/docs/images/info.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tal-tech/goctl-plugins/HEAD/vscode/docs/images/info.gif -------------------------------------------------------------------------------- /vscode/docs/images/jump.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tal-tech/goctl-plugins/HEAD/vscode/docs/images/jump.gif -------------------------------------------------------------------------------- /vscode/docs/images/type.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tal-tech/goctl-plugins/HEAD/vscode/docs/images/type.gif -------------------------------------------------------------------------------- /vscode/docs/images/handler.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tal-tech/goctl-plugins/HEAD/vscode/docs/images/handler.gif -------------------------------------------------------------------------------- /vscode/docs/images/service.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tal-tech/goctl-plugins/HEAD/vscode/docs/images/service.gif -------------------------------------------------------------------------------- /vscode/images/go-zero-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tal-tech/goctl-plugins/HEAD/vscode/images/go-zero-logo.png -------------------------------------------------------------------------------- /vscode/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "indent": [true, "tabs"], 4 | "semicolon": [true, "always"] 5 | } 6 | } -------------------------------------------------------------------------------- /goland/src/main/resources/static/new.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tal-tech/goctl-plugins/HEAD/goland/src/main/resources/static/new.png -------------------------------------------------------------------------------- /goland/src/main/resources/icons/goctl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tal-tech/goctl-plugins/HEAD/goland/src/main/resources/icons/goctl.png -------------------------------------------------------------------------------- /goland/src/main/resources/static/alert.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tal-tech/goctl-plugins/HEAD/goland/src/main/resources/static/alert.png -------------------------------------------------------------------------------- /goland/src/main/resources/static/goctl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tal-tech/goctl-plugins/HEAD/goland/src/main/resources/static/goctl.png -------------------------------------------------------------------------------- /goland/src/main/resources/static/proto.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tal-tech/goctl-plugins/HEAD/goland/src/main/resources/static/proto.png -------------------------------------------------------------------------------- /goland/src/main/resources/icons/goctl@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tal-tech/goctl-plugins/HEAD/goland/src/main/resources/icons/goctl@2x.png -------------------------------------------------------------------------------- /goland/src/main/resources/static/json_tag.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tal-tech/goctl-plugins/HEAD/goland/src/main/resources/static/json_tag.png -------------------------------------------------------------------------------- /goland/src/main/resources/static/psiTree.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tal-tech/goctl-plugins/HEAD/goland/src/main/resources/static/psiTree.png -------------------------------------------------------------------------------- /goland/src/main/resources/static/service.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tal-tech/goctl-plugins/HEAD/goland/src/main/resources/static/service.png -------------------------------------------------------------------------------- /goland/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx2048m 2 | systemProp.http.proxyHost=127.0.0.1 3 | systemProp.http.proxyPort=1080 4 | antlr4Version = 4.8 -------------------------------------------------------------------------------- /goland/src/main/resources/liveTemplates/apiHidden.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /vscode/src/goctlMode.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | 3 | export const GOCTL: vscode.DocumentFilter = { language: 'goctl', scheme: 'file' }; 4 | -------------------------------------------------------------------------------- /goland/src/main/resources/static/api_colorful.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tal-tech/goctl-plugins/HEAD/goland/src/main/resources/static/api_colorful.png -------------------------------------------------------------------------------- /goland/src/main/resources/static/editor-menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tal-tech/goctl-plugins/HEAD/goland/src/main/resources/static/editor-menu.png -------------------------------------------------------------------------------- /goland/src/main/resources/static/live_template.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tal-tech/goctl-plugins/HEAD/goland/src/main/resources/static/live_template.gif -------------------------------------------------------------------------------- /goland/src/main/resources/static/project_menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tal-tech/goctl-plugins/HEAD/goland/src/main/resources/static/project_menu.png -------------------------------------------------------------------------------- /goland/src/main/resources/static/go_live_template.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tal-tech/goctl-plugins/HEAD/goland/src/main/resources/static/go_live_template.png -------------------------------------------------------------------------------- /goland/src/main/java/cn/xiaoheiban/completion/Priority.java: -------------------------------------------------------------------------------- 1 | package cn.xiaoheiban.completion; 2 | 3 | public interface Priority { 4 | int KEYWORD_PRIORITY = 20; 5 | } 6 | -------------------------------------------------------------------------------- /goland/src/main/resources/static/project_generate_code.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tal-tech/goctl-plugins/HEAD/goland/src/main/resources/static/project_generate_code.png -------------------------------------------------------------------------------- /vscode/src/goctlDocument.ts: -------------------------------------------------------------------------------- 1 | // TODO probe based on code segment 2 | enum DocSegmentType { 3 | Type, 4 | Service, 5 | None 6 | } 7 | 8 | function getDocSegmentType(): DocSegmentType { 9 | 10 | return DocSegmentType.None; 11 | 12 | } -------------------------------------------------------------------------------- /goland/src/main/resources/fileTemplates/internal/Api File.api.ft: -------------------------------------------------------------------------------- 1 | info( 2 | title: "type title here" 3 | desc: "type desc here" 4 | author: "type author here" 5 | email: "type email here" 6 | version: "type version here" 7 | ) 8 | -------------------------------------------------------------------------------- /vscode/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .cache 3 | Thumbs.db 4 | node_modules/ 5 | .build/ 6 | extensions/**/dist/ 7 | out/ 8 | .vscode/* 9 | !.vscode/settings.json 10 | !.vscode/tasks.json 11 | !.vscode/launch.json 12 | !.vscode/extensions.json 13 | .vscode-test 14 | **/*.vsix -------------------------------------------------------------------------------- /vscode/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es6", 5 | "outDir": "out", 6 | "sourceMap": true, 7 | "strict": true, 8 | "rootDir": "src" 9 | }, 10 | "exclude": [ 11 | "node_modules", 12 | ".vscode-test" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /goland/src/main/java/cn/xiaoheiban/icon/ApiIcon.java: -------------------------------------------------------------------------------- 1 | package cn.xiaoheiban.icon; 2 | 3 | import com.intellij.openapi.util.IconLoader; 4 | 5 | import javax.swing.*; 6 | 7 | public class ApiIcon { 8 | public static final Icon FILE = IconLoader.getIcon("/icons/goctl.png"); 9 | 10 | } 11 | -------------------------------------------------------------------------------- /goland/src/main/java/cn/xiaoheiban/psi/nodes/TagNode.java: -------------------------------------------------------------------------------- 1 | package cn.xiaoheiban.psi.nodes; 2 | 3 | import com.intellij.lang.ASTNode; 4 | import org.jetbrains.annotations.NotNull; 5 | 6 | public class TagNode extends IPsiNode { 7 | public TagNode(@NotNull ASTNode node) { 8 | super(node); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /goland/src/main/resources/fileTemplates/internal/Proto Template.proto.ft: -------------------------------------------------------------------------------- 1 | 2 | syntax = "proto3"; 3 | 4 | package stream; 5 | 6 | message StreamReq { 7 | string name = 1; 8 | } 9 | 10 | message StreamResp { 11 | string greet = 1; 12 | } 13 | 14 | service StreamGreeter { 15 | rpc greet(StreamReq) returns (StreamResp); 16 | } -------------------------------------------------------------------------------- /goland/.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .gradle 3 | build/ 4 | src/main/gen 5 | # Ignore Gradle GUI config 6 | gradle-app.setting 7 | 8 | # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) 9 | !gradle-wrapper.jar 10 | 11 | # Cache of project 12 | .gradletasknamecache 13 | *.iml 14 | classes/ 15 | out/ 16 | .DS_Store 17 | -------------------------------------------------------------------------------- /goland/src/main/java/cn/xiaoheiban/psi/nodes/ServiceNode.java: -------------------------------------------------------------------------------- 1 | package cn.xiaoheiban.psi.nodes; 2 | 3 | import com.intellij.lang.ASTNode; 4 | import org.jetbrains.annotations.NotNull; 5 | 6 | public class ServiceNode extends IPsiNode { 7 | public ServiceNode(@NotNull ASTNode node) { 8 | super(node); 9 | } 10 | 11 | } 12 | -------------------------------------------------------------------------------- /goland/src/main/java/cn/xiaoheiban/psi/nodes/ApiBodyNode.java: -------------------------------------------------------------------------------- 1 | package cn.xiaoheiban.psi.nodes; 2 | 3 | import com.intellij.lang.ASTNode; 4 | import org.jetbrains.annotations.NotNull; 5 | 6 | public class ApiBodyNode extends IPsiNode { 7 | 8 | public ApiBodyNode(@NotNull ASTNode node) { 9 | super(node); 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /goland/src/main/java/cn/xiaoheiban/language/ApiLanguage.java: -------------------------------------------------------------------------------- 1 | package cn.xiaoheiban.language; 2 | 3 | import com.intellij.lang.Language; 4 | 5 | public class ApiLanguage extends Language { 6 | 7 | public static final ApiLanguage INSTANCE = new ApiLanguage(); 8 | 9 | private ApiLanguage() { 10 | super("api"); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /goland/src/main/java/cn/xiaoheiban/psi/nodes/ImportValueNode.java: -------------------------------------------------------------------------------- 1 | package cn.xiaoheiban.psi.nodes; 2 | 3 | import com.intellij.lang.ASTNode; 4 | import org.jetbrains.annotations.NotNull; 5 | 6 | public class ImportValueNode extends IPsiNode { 7 | public ImportValueNode(@NotNull ASTNode node) { 8 | super(node); 9 | } 10 | 11 | } 12 | -------------------------------------------------------------------------------- /goland/src/main/java/cn/xiaoheiban/psi/nodes/ReferenceIdNode.java: -------------------------------------------------------------------------------- 1 | package cn.xiaoheiban.psi.nodes; 2 | 3 | import com.intellij.lang.ASTNode; 4 | import org.jetbrains.annotations.NotNull; 5 | 6 | public class ReferenceIdNode extends IPsiNode { 7 | public ReferenceIdNode(@NotNull ASTNode node) { 8 | super(node); 9 | } 10 | 11 | } 12 | -------------------------------------------------------------------------------- /goland/src/main/java/cn/xiaoheiban/psi/nodes/ServiceNameNode.java: -------------------------------------------------------------------------------- 1 | package cn.xiaoheiban.psi.nodes; 2 | 3 | import com.intellij.lang.ASTNode; 4 | import org.jetbrains.annotations.NotNull; 5 | 6 | public class ServiceNameNode extends IPsiNode { 7 | public ServiceNameNode(@NotNull ASTNode node) { 8 | super(node); 9 | } 10 | 11 | } 12 | -------------------------------------------------------------------------------- /goland/src/main/java/cn/xiaoheiban/psi/nodes/HandlerValueNode.java: -------------------------------------------------------------------------------- 1 | package cn.xiaoheiban.psi.nodes; 2 | 3 | import com.intellij.lang.ASTNode; 4 | import org.jetbrains.annotations.NotNull; 5 | 6 | public class HandlerValueNode extends IPsiNode { 7 | public HandlerValueNode(@NotNull ASTNode node) { 8 | super(node); 9 | } 10 | 11 | } 12 | -------------------------------------------------------------------------------- /goland/src/main/java/cn/xiaoheiban/psi/nodes/HttpRouteNode.java: -------------------------------------------------------------------------------- 1 | package cn.xiaoheiban.psi.nodes; 2 | 3 | import com.intellij.lang.ASTNode; 4 | import org.jetbrains.annotations.NotNull; 5 | 6 | public class HttpRouteNode extends IPsiNode { 7 | 8 | public HttpRouteNode(@NotNull ASTNode node) { 9 | super(node); 10 | } 11 | 12 | 13 | } 14 | -------------------------------------------------------------------------------- /goland/src/main/java/cn/xiaoheiban/psi/nodes/ServiceRouteNode.java: -------------------------------------------------------------------------------- 1 | package cn.xiaoheiban.psi.nodes; 2 | 3 | import com.intellij.lang.ASTNode; 4 | import org.jetbrains.annotations.NotNull; 5 | 6 | public class ServiceRouteNode extends IPsiNode { 7 | public ServiceRouteNode(@NotNull ASTNode node) { 8 | super(node); 9 | } 10 | 11 | } 12 | -------------------------------------------------------------------------------- /goland/src/main/java/cn/xiaoheiban/util/Stringx.java: -------------------------------------------------------------------------------- 1 | package cn.xiaoheiban.util; 2 | 3 | public class Stringx { 4 | public static String trimSpace(String in) { 5 | in = in.replace(" ", ""); 6 | in = in.replace("\t", ""); 7 | in = in.replace("\n", ""); 8 | in = in.replace("\f", ""); 9 | in = in.replace("\r", ""); 10 | return in; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /vscode/.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | // See https://go.microsoft.com/fwlink/?LinkId=733558 2 | // for the documentation about the tasks.json format 3 | { 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "type": "npm", 8 | "script": "watch", 9 | "problemMatcher": "$tsc-watch", 10 | "isBackground": true, 11 | "presentation": { 12 | "reveal": "never" 13 | }, 14 | "group": { 15 | "kind": "build", 16 | "isDefault": true 17 | } 18 | } 19 | ] 20 | } -------------------------------------------------------------------------------- /goland/src/main/java/cn/xiaoheiban/contsant/Constant.java: -------------------------------------------------------------------------------- 1 | package cn.xiaoheiban.contsant; 2 | 3 | public interface Constant { 4 | String API_EXTENSION = "api"; 5 | String MODEL_EXTENSION = "sql"; 6 | String RPC_EXTENSION = "proto"; 7 | String PREFIX_SUCCESS="[SUCCESS]:"; 8 | String PREFIX_WARNING="[WARNING]:"; 9 | String PREFIX_ERROR="[ERROR]:"; 10 | String PREFIX_ERROR2="error:"; 11 | String SH="sh"; 12 | String CMD="cmd.exe"; 13 | } 14 | -------------------------------------------------------------------------------- /goland/src/main/java/cn/xiaoheiban/language/ApiFileTypeFactory.java: -------------------------------------------------------------------------------- 1 | package cn.xiaoheiban.language; 2 | 3 | import com.intellij.openapi.fileTypes.FileTypeConsumer; 4 | import com.intellij.openapi.fileTypes.FileTypeFactory; 5 | import org.jetbrains.annotations.NotNull; 6 | 7 | public class ApiFileTypeFactory extends FileTypeFactory { 8 | @Override 9 | public void createFileTypes(@NotNull FileTypeConsumer fileTypeConsumer) { 10 | fileTypeConsumer.consume(ApiFileType.INSTANCE, ApiFileType.FILE_EXTENSION); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /goland/src/main/java/cn/xiaoheiban/psi/StructReference.java: -------------------------------------------------------------------------------- 1 | package cn.xiaoheiban.psi; 2 | 3 | import cn.xiaoheiban.psi.nodes.ApiRootNode; 4 | import com.intellij.psi.PsiElement; 5 | import org.jetbrains.annotations.NotNull; 6 | 7 | public class StructReference extends IReference { 8 | public StructReference(@NotNull IdentifierPSINode element) { 9 | super(element); 10 | } 11 | 12 | @Override 13 | public boolean isFieldTypeSubTree(PsiElement element) { 14 | return element instanceof ApiRootNode; 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /goland/src/main/java/cn/xiaoheiban/psi/nodes/NormalField.java: -------------------------------------------------------------------------------- 1 | package cn.xiaoheiban.psi.nodes; 2 | 3 | import com.intellij.lang.ASTNode; 4 | import com.intellij.psi.PsiElement; 5 | import org.jetbrains.annotations.NotNull; 6 | 7 | public class NormalField extends IPsiNode { 8 | public NormalField(@NotNull ASTNode node) { 9 | super(node); 10 | } 11 | 12 | public String getFieldName() { 13 | return getFieldNode().getText(); 14 | } 15 | 16 | public PsiElement getFieldNode() { 17 | return this.getFirstChild(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /vscode/README.md: -------------------------------------------------------------------------------- 1 | # Goctl for Visual Studio Code 2 | 3 | ## 功能列表 4 | 5 | 已实现功能 6 | 7 | * 语法高亮 8 | * 跳转到定义/引用 9 | * 代码格式化 10 | * 代码块提示 11 | 12 | 未实现功能: 13 | 14 | * 语法错误检查 15 | * 跨文件代码跳转 16 | * goctl 命令行调用 17 | 18 | ### 语法高亮 19 | 20 | ### 代码跳转 21 | 22 | ![jump](docs/images/jump.gif) 23 | 24 | ### 代码格式化 25 | 26 | 调用 goctl 命令行格式化工具,使用前请确认 goctl 已加入 `$PATH` 且有可执行权限 27 | 28 | ### 代码块提示 29 | 30 | #### info 代码块 31 | 32 | ![info](docs/images/info.gif) 33 | 34 | #### type 代码块 35 | 36 | ![type](docs/images/type.gif) 37 | 38 | #### service 代码块 39 | 40 | ![type](docs/images/service.gif) 41 | 42 | #### handler 代码块 43 | 44 | ![type](docs/images/handler.gif) 45 | -------------------------------------------------------------------------------- /goland/src/main/java/cn/xiaoheiban/highlighting/ApiSyntaxHighlighterFactory.java: -------------------------------------------------------------------------------- 1 | package cn.xiaoheiban.highlighting; 2 | 3 | import com.intellij.openapi.fileTypes.SyntaxHighlighter; 4 | import com.intellij.openapi.fileTypes.SyntaxHighlighterFactory; 5 | import com.intellij.openapi.project.Project; 6 | import com.intellij.openapi.vfs.VirtualFile; 7 | import org.jetbrains.annotations.NotNull; 8 | 9 | public class ApiSyntaxHighlighterFactory extends SyntaxHighlighterFactory { 10 | @NotNull 11 | @Override 12 | public SyntaxHighlighter getSyntaxHighlighter(Project project, VirtualFile virtualFile) { 13 | return new ApiSyntaxHighlighter(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /goland/src/main/java/cn/xiaoheiban/template/ApiLiveTemplatesProvider.java: -------------------------------------------------------------------------------- 1 | package cn.xiaoheiban.template; 2 | 3 | 4 | import com.intellij.codeInsight.template.impl.DefaultLiveTemplatesProvider; 5 | import org.jetbrains.annotations.Nullable; 6 | 7 | public class ApiLiveTemplatesProvider implements DefaultLiveTemplatesProvider { 8 | 9 | @Override 10 | public String[] getDefaultLiveTemplateFiles() { 11 | return new String[]{"/liveTemplates/api", "/liveTemplates/apiTags"}; 12 | } 13 | 14 | @Nullable 15 | @Override 16 | public String[] getHiddenLiveTemplateFiles() { 17 | return new String[]{"/liveTemplates/apiHidden"}; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /goland/src/main/resources/fileTemplates/internal/Api Template.api.ft: -------------------------------------------------------------------------------- 1 | info( 2 | title: "type title here" 3 | desc: "type desc here" 4 | author: "type author here" 5 | email: "type email here" 6 | version: "type version here" 7 | ) 8 | 9 | 10 | type request { 11 | // TODO: add members here and delete this comment 12 | } 13 | 14 | type response { 15 | // TODO: add members here and delete this comment 16 | } 17 | 18 | @server( 19 | jwt: Auth 20 | group: template 21 | ) 22 | service template { 23 | @handler handlerName // TODO: replace handler name and delete this comment 24 | get /users/id/:userId(request) returns(response) 25 | } 26 | -------------------------------------------------------------------------------- /vscode/src/util.ts: -------------------------------------------------------------------------------- 1 | 2 | import vscode = require('vscode'); 3 | import kill = require('tree-kill'); 4 | 5 | export function getGoConfig(uri?: vscode.Uri): vscode.WorkspaceConfiguration { 6 | if (!uri) { 7 | if (vscode.window.activeTextEditor) { 8 | return vscode.workspace.getConfiguration('goctl', uri); 9 | } else { 10 | return vscode.workspace.getConfiguration('goctl', null); 11 | } 12 | } 13 | return vscode.workspace.getConfiguration('goctl', null); 14 | } 15 | 16 | 17 | export const killTree = (processId: number): void => { 18 | kill(processId, (err) => { 19 | if (err) { 20 | console.log('Error killing process tree: ' + err); 21 | } 22 | }); 23 | }; -------------------------------------------------------------------------------- /goland/CHANGE_LOG.md: -------------------------------------------------------------------------------- 1 | # 2020-10-18(v0.6.8) 2 | * 新增支持import语法 3 | * 新增支持@handler语法 4 | 5 | # 2020-10-13(v0.6.7) 6 | * 性能大幅度优化提升 7 | * 优化grammar语法 8 | 9 | # 2020-10-09(v0.6.4) 10 | * 新增支持inline type 11 | * 优化grammar语法 12 | 13 | # 2020-09-25(v0.6.2) 14 | * 添加关键字提示 15 | * 添加上下文内容提示 16 | * 新增模板功能 17 | * 语法优化 18 | 19 | # 2020-09-22(v0.6) 20 | * 代码格式化 21 | * 代码提示 22 | 23 | # 2020-09-20(v0.5) 24 | * 类型跳转 25 | * type内字段类型及路由参数跳转到declare处 26 | * 替换bnf语法为altlr4语法解析 27 | * 检测声明类型是否定义 28 | * 上下文菜单整理 29 | 30 | # 2020-09-13(v0.3) 31 | * 增加api、rpc、model生成上下文菜单 32 | * 优化语法高亮显示 33 | 34 | # 2020-09-04(v0.2-beta) 35 | * 增加api文件语法高亮 36 | * 增加struct声明类型、路由、handler名称重复报错提示 37 | * 增加field、路由request、response声明类型存在不存在报错提示 38 | 39 | -------------------------------------------------------------------------------- /vscode/src/extension.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | import { GOCTL } from './goctlMode'; 3 | import { GoctlDocumentFormattingEditProvider } from './goctlFormat'; 4 | import { GoctlDefinitionProvider } from './goctlDeclaration'; 5 | 6 | enum DocSegmentType { 7 | Type, 8 | Service, 9 | None 10 | } 11 | 12 | export function activate(context: vscode.ExtensionContext) { 13 | registerUsualProviders(context); 14 | } 15 | 16 | function registerUsualProviders(context: vscode.ExtensionContext) { 17 | context.subscriptions.push(vscode.languages.registerDefinitionProvider(GOCTL, new GoctlDefinitionProvider())); 18 | context.subscriptions.push(vscode.languages.registerDocumentFormattingEditProvider(GOCTL, new GoctlDocumentFormattingEditProvider())); 19 | } -------------------------------------------------------------------------------- /vscode/src/test/runTest.ts: -------------------------------------------------------------------------------- 1 | import * as path from "path"; 2 | 3 | import { runTests } from "vscode-test"; 4 | 5 | async function main() { 6 | try { 7 | // The folder containing the Extension Manifest package.json 8 | // Passed to `--extensionDevelopmentPath` 9 | const extensionDevelopmentPath = path.resolve(__dirname, "../../"); 10 | 11 | // The path to the extension test runner script 12 | // Passed to --extensionTestsPath 13 | const extensionTestsPath = path.resolve(__dirname, "./suite/index"); 14 | 15 | // Download VS Code, unzip it and run the integration test 16 | await runTests({ extensionDevelopmentPath, extensionTestsPath }); 17 | } catch (err) { 18 | console.error("Failed to run tests"); 19 | process.exit(1); 20 | } 21 | } 22 | 23 | main(); -------------------------------------------------------------------------------- /goland/src/main/java/cn/xiaoheiban/psi/nodes/AnonymousField.java: -------------------------------------------------------------------------------- 1 | package cn.xiaoheiban.psi.nodes; 2 | 3 | import cn.xiaoheiban.antlr4.ApiParser; 4 | import cn.xiaoheiban.parser.ApiParserDefinition; 5 | import com.intellij.lang.ASTNode; 6 | import com.intellij.psi.PsiElement; 7 | import org.jetbrains.annotations.NotNull; 8 | 9 | public class AnonymousField extends IPsiNode { 10 | public AnonymousField(@NotNull ASTNode node) { 11 | super(node); 12 | } 13 | 14 | @Override 15 | public String getName() { 16 | PsiElement child = this.findChildByType(ApiParserDefinition.rule(ApiParser.RULE_referenceId)); 17 | return child == null ? "" : child.getText(); 18 | } 19 | 20 | public PsiElement getNameNode() { 21 | return findChildByClass(ReferenceIdNode.class); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /goland/src/main/java/cn/xiaoheiban/util/FileReload.java: -------------------------------------------------------------------------------- 1 | package cn.xiaoheiban.util; 2 | 3 | import com.intellij.openapi.actionSystem.AnActionEvent; 4 | import com.intellij.openapi.actionSystem.PlatformDataKeys; 5 | import com.intellij.openapi.project.Project; 6 | import com.intellij.openapi.vfs.VirtualFile; 7 | 8 | public class FileReload { 9 | public static void reloadFromDisk(AnActionEvent e) { 10 | if (e == null) { 11 | return; 12 | } 13 | Project project = e.getProject(); 14 | if (project == null) { 15 | return; 16 | } 17 | project.save(); 18 | VirtualFile file = e.getData(PlatformDataKeys.VIRTUAL_FILE); 19 | if (file == null) { 20 | return; 21 | } 22 | file.getFileSystem().refresh(true); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /goland/src/main/java/cn/xiaoheiban/language/ApiCommenter.java: -------------------------------------------------------------------------------- 1 | package cn.xiaoheiban.language; 2 | 3 | import com.intellij.lang.Commenter; 4 | import org.jetbrains.annotations.Nullable; 5 | 6 | public class ApiCommenter implements Commenter { 7 | @Override 8 | public @Nullable String getLineCommentPrefix() { 9 | return "//"; 10 | } 11 | 12 | @Override 13 | public @Nullable String getBlockCommentPrefix() { 14 | return ""; 15 | } 16 | 17 | @Override 18 | public @Nullable String getBlockCommentSuffix() { 19 | return ""; 20 | } 21 | 22 | @Override 23 | public @Nullable String getCommentedBlockCommentPrefix() { 24 | return null; 25 | } 26 | 27 | @Override 28 | public @Nullable String getCommentedBlockCommentSuffix() { 29 | return null; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /goland/src/main/java/cn/xiaoheiban/io/IO.java: -------------------------------------------------------------------------------- 1 | package cn.xiaoheiban.io; 2 | 3 | import java.io.InputStream; 4 | import java.io.InputStreamReader; 5 | import java.io.LineNumberReader; 6 | import java.nio.charset.StandardCharsets; 7 | 8 | public class IO { 9 | public static String read(InputStream in) { 10 | try { 11 | InputStreamReader reader = new InputStreamReader(in, StandardCharsets.UTF_8); 12 | LineNumberReader line = new LineNumberReader(reader); 13 | StringBuilder buffer = new StringBuilder(); 14 | String str; 15 | while ((str = line.readLine()) != null) { 16 | buffer.append(str).append("\r\n"); 17 | } 18 | return buffer.toString(); 19 | } catch (Exception e) { 20 | e.printStackTrace(); 21 | return e.getMessage(); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /goland/src/main/java/cn/xiaoheiban/completion/ApiCompletionContributor.java: -------------------------------------------------------------------------------- 1 | package cn.xiaoheiban.completion; 2 | 3 | import cn.xiaoheiban.editor.AutoInsertHandler; 4 | import cn.xiaoheiban.parser.ApiParserDefinition; 5 | import com.intellij.codeInsight.completion.CompletionContributor; 6 | import com.intellij.codeInsight.completion.CompletionType; 7 | import com.intellij.patterns.ElementPattern; 8 | import com.intellij.psi.PsiElement; 9 | 10 | import static com.intellij.patterns.PlatformPatterns.psiElement; 11 | 12 | public class ApiCompletionContributor extends CompletionContributor { 13 | 14 | public ApiCompletionContributor() { 15 | extend(CompletionType.BASIC, typeDeclaration(), new ApiCompletionProvider(Priority.KEYWORD_PRIORITY, new AutoInsertHandler(""))); 16 | } 17 | 18 | private static ElementPattern typeDeclaration() { 19 | return psiElement(ApiParserDefinition.IDENTIFIER); 20 | } 21 | } 22 | 23 | -------------------------------------------------------------------------------- /goland/src/main/java/cn/xiaoheiban/template/ApiEverywhereContextType.java: -------------------------------------------------------------------------------- 1 | package cn.xiaoheiban.template; 2 | 3 | import cn.xiaoheiban.antlr4.ApiLexer; 4 | import cn.xiaoheiban.parser.ApiParserDefinition; 5 | import com.intellij.codeInsight.template.EverywhereContextType; 6 | import com.intellij.psi.PsiComment; 7 | import com.intellij.psi.PsiElement; 8 | import com.intellij.psi.impl.source.tree.LeafPsiElement; 9 | import org.jetbrains.annotations.NotNull; 10 | 11 | public class ApiEverywhereContextType extends ApiLiveTemplateContextType { 12 | protected ApiEverywhereContextType() { 13 | super("API", "Api", EverywhereContextType.class); 14 | } 15 | 16 | @Override 17 | protected boolean isInContext(@NotNull PsiElement element) { 18 | return !(element instanceof PsiComment || 19 | element instanceof LeafPsiElement && ((LeafPsiElement) element).getElementType() == ApiParserDefinition.token(ApiLexer.VALUE)); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /vscode/language-configuration.json: -------------------------------------------------------------------------------- 1 | { 2 | "comments": { 3 | // symbol used for single line comment. Remove this entry if your language does not support line comments 4 | "lineComment": "//", 5 | // symbols used for start and end a block comment. Remove this entry if your language does not support block comments 6 | "blockComment": [ "/*", "*/" ] 7 | }, 8 | // symbols used as brackets 9 | "brackets": [ 10 | ["{", "}"], 11 | ["[", "]"], 12 | ["(", ")"] 13 | ], 14 | // symbols that are auto closed when typing 15 | "autoClosingPairs": [ 16 | ["{", "}"], 17 | ["[", "]"], 18 | ["(", ")"], 19 | ["\"", "\""], 20 | ["'", "'"] 21 | ], 22 | // symbols that that can be used to surround a selection 23 | "surroundingPairs": [ 24 | ["{", "}"], 25 | ["[", "]"], 26 | ["(", ")"], 27 | ["\"", "\""], 28 | ["'", "'"] 29 | ] 30 | } -------------------------------------------------------------------------------- /vscode/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## v0.1.6 2 | 3 | ### Support 4 | 5 | * `@handler` annotation syntax highlighting. 6 | * support for route jump without returns to definition. 7 | 8 | ### Fixed 9 | 10 | * fix some syntax highlighting errors, example `get /greet/to/:name`. 11 | 12 | ## v0.1.5 13 | 14 | ### Optimize 15 | 16 | * Add go-zero logo. 17 | * Optimize the formatting logic, when formatting the file, there is no need to save the file to disk. 18 | 19 | ### Fixed 20 | 21 | * Fix the bug that the file cannot be saved when the user sets `editor.formatOnSave` is `true`. 22 | 23 | **Note:** To upgrade this version, please make sure that the goctl command line tool is also upgraded to the latest version. To upgrade the goctl command line tool, you can rerun the [goctl command line tool installation](https://github.com/tal-tech/go-zero#5-quick-start) command. 24 | 25 | ## v0.1.4 26 | 27 | ### Snippet improvements 28 | 29 | * Add `@handler` snippet. 30 | * Add go style snippet (`tys` and `im`). 31 | 32 | ## v0.1.3 33 | 34 | ### Fixed 35 | 36 | * Fixed cannot be jump to code definition in some cases. -------------------------------------------------------------------------------- /goland/src/main/java/cn/xiaoheiban/language/ApiFileType.java: -------------------------------------------------------------------------------- 1 | package cn.xiaoheiban.language; 2 | 3 | import cn.xiaoheiban.icon.ApiIcon; 4 | import com.intellij.openapi.fileTypes.LanguageFileType; 5 | import org.jetbrains.annotations.NotNull; 6 | import org.jetbrains.annotations.Nullable; 7 | 8 | import javax.swing.*; 9 | 10 | public class ApiFileType extends LanguageFileType { 11 | 12 | public static final String FILE_EXTENSION = "api"; 13 | 14 | public static final ApiFileType INSTANCE = new ApiFileType(); 15 | 16 | private ApiFileType() { 17 | super(ApiLanguage.INSTANCE); 18 | } 19 | 20 | @NotNull 21 | @Override 22 | public String getName() { 23 | return "Api File"; 24 | } 25 | 26 | @NotNull 27 | @Override 28 | public String getDescription() { 29 | return "Api language file"; 30 | } 31 | 32 | @NotNull 33 | @Override 34 | public String getDefaultExtension() { 35 | return FILE_EXTENSION; 36 | } 37 | 38 | @Nullable 39 | @Override 40 | public Icon getIcon() { 41 | return ApiIcon.FILE; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 好未来技术 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /goland/src/main/java/cn/xiaoheiban/psi/ApiASTFactory.java: -------------------------------------------------------------------------------- 1 | package cn.xiaoheiban.psi; 2 | 3 | import cn.xiaoheiban.antlr4.ApiLexer; 4 | import com.intellij.lang.DefaultASTFactoryImpl; 5 | import com.intellij.psi.impl.source.tree.CompositeElement; 6 | import com.intellij.psi.impl.source.tree.LeafElement; 7 | import com.intellij.psi.tree.IElementType; 8 | import org.antlr.jetbrains.adapter.lexer.TokenIElementType; 9 | import org.jetbrains.annotations.NotNull; 10 | 11 | public class ApiASTFactory extends DefaultASTFactoryImpl { 12 | @NotNull 13 | @Override 14 | public CompositeElement createComposite(@NotNull IElementType type) { 15 | return super.createComposite(type); 16 | } 17 | 18 | @NotNull 19 | @Override 20 | public LeafElement createLeaf(@NotNull IElementType type, @NotNull CharSequence text) { 21 | // 控制跳转的字段类型 22 | if (type instanceof TokenIElementType) { 23 | TokenIElementType elementType = (TokenIElementType) type; 24 | int antlrTokenType = elementType.getAntlrTokenType(); 25 | if (antlrTokenType == ApiLexer.IDENT) { 26 | return new IdentifierPSINode(type, text); 27 | } 28 | } 29 | return super.createLeaf(type, text); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /goland/src/main/java/cn/xiaoheiban/editor/ApiPairedBraceMatcher.java: -------------------------------------------------------------------------------- 1 | package cn.xiaoheiban.editor; 2 | 3 | import cn.xiaoheiban.parser.ApiParserDefinition; 4 | import com.intellij.lang.BracePair; 5 | import com.intellij.lang.PairedBraceMatcher; 6 | import com.intellij.psi.PsiFile; 7 | import com.intellij.psi.tree.IElementType; 8 | import org.jetbrains.annotations.NotNull; 9 | import org.jetbrains.annotations.Nullable; 10 | 11 | public class ApiPairedBraceMatcher implements PairedBraceMatcher { 12 | private static final BracePair[] PAIRS = { 13 | new BracePair(ApiParserDefinition.LPAREN, ApiParserDefinition.RPAREN, false), 14 | new BracePair(ApiParserDefinition.LBRACE, ApiParserDefinition.RBRACE, true), 15 | new BracePair(ApiParserDefinition.LBRACK, ApiParserDefinition.RBRACK, false), 16 | }; 17 | 18 | @Override 19 | public @NotNull BracePair[] getPairs() { 20 | return PAIRS; 21 | } 22 | 23 | @Override 24 | public boolean isPairedBracesAllowedBeforeType(@NotNull IElementType lbraceType, @Nullable IElementType contextType) { 25 | return true; 26 | } 27 | 28 | @Override 29 | public int getCodeConstructStart(PsiFile file, int openingBraceOffset) { 30 | return openingBraceOffset; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /vscode/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | // A launch configuration that launches the extension inside a new window 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | { 6 | "version": "0.2.0", 7 | "configurations": [ 8 | { 9 | "name": "Run Extension", 10 | "type": "extensionHost", 11 | "request": "launch", 12 | "runtimeExecutable": "${execPath}", 13 | "args": [ 14 | "--disable-extensions", 15 | "--extensionDevelopmentPath=${workspaceFolder}" 16 | ], 17 | "outFiles": [ 18 | "${workspaceFolder}/out/**/*.js" 19 | ], 20 | "preLaunchTask": "npm: watch" 21 | }, 22 | { 23 | "name": "Run Extension Tests", 24 | "type": "extensionHost", 25 | "request": "launch", 26 | "runtimeExecutable": "${execPath}", 27 | "args": [ 28 | "--extensionDevelopmentPath=${workspaceFolder}", 29 | "--extensionTestsPath=${workspaceFolder}/out/test/suite" 30 | ], 31 | "outFiles": [ 32 | "${workspaceFolder}/out/test/**/*.js" 33 | ], 34 | "preLaunchTask": "npm: watch" 35 | } 36 | ] 37 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # goctl-plugins 2 | 3 | [English](README-en.md) | 简体中文 4 | 5 | ## **已废弃, 请使用** 6 | 7 | [https://github.com/zeromicro/goctl-vscode](https://github.com/zeromicro/goctl-vscode) 8 | 9 | [https://github.com/zeromicro/goctl-intellij](https://github.com/zeromicro/goctl-intellij) 10 | 11 | 12 | goctl 插件/扩展集,例如 vscode、goland 等。 13 | 14 | **注意:** goctl goland 插件,goctl vscode 扩展均依赖 goctl 命令行工具,使用前请先[安装 goctl 命令行工具](https://github.com/tal-tech/go-zero#5-quick-start)。 15 | 16 | ## 适用于 IntelliJ 平台的 goctl 插件 17 | 18 | ### 安装此插件 19 | 20 | 该插件可以安装在以下基于IntelliJ的版本上: 21 | 22 | * IntelliJ 2019.3+ (Ultimate or Community) 23 | * Goland 2019.3+ 24 | * WebStorm 2019.3+ 25 | * PhpStorm 2019.3+ 26 | * PyCharm 2019.3+ 27 | * RubyMine 2019.3+ 28 | * CLion 2019.3+ 29 | 30 | 确保你的 IDE 符合上述要求,并已安装 goctl 命令行工具,在 IntelliJ Plugin 商店中,搜索 `Goctl` 安装即可(发布者为 “xiaoheiban” )。 31 | 32 | > IntelliJ 插件使用请参考[这里](https://www.jetbrains.com/idea/help/managing-enterprise-plugin-repositories.html)。 33 | 34 | 更多信息请点击[这里](goland/README.md)。 35 | 36 | ## 适用于 Visual Studio Code 的 goctl 插件 37 | 38 | ### 安装此扩展 39 | 40 | 该插件可以安装在 1.46.0+ 版本的 Visual Studio Code 上,首先请确保你的 Visual Studio Code 版本符合要求,并已安装 goctl 命令行工具。如果尚未安装 Visual Studio Code,请安装并打开 Visual Studio Code。 导航到“扩展”窗格,搜索 `goctl` 并安装此扩展(发布者ID为 “xiaoxin-technology.goctl”)。 41 | 42 | > Visual Studio Code 扩展使用请参考[这里](https://code.visualstudio.com/docs/editor/extension-gallery)。 43 | 44 | 更多信息请点击[这里](vscode/README.md)。 45 | -------------------------------------------------------------------------------- /goland/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'antlr' 3 | id 'java' 4 | id 'org.jetbrains.intellij' version '0.4.21' 5 | } 6 | 7 | group 'cn.xiaoheiban' 8 | version '0.7.11' 9 | 10 | repositories { 11 | mavenCentral() 12 | maven { 13 | url "https://dl.bintray.com/antlr/maven/" 14 | } 15 | } 16 | 17 | compileJava { 18 | sourceCompatibility = '1.8' 19 | targetCompatibility = '1.8' 20 | } 21 | 22 | dependencies { 23 | antlr("org.antlr:antlr4:$antlr4Version") { // use ANTLR version 4 24 | exclude group: 'com.ibm.icu', module: 'icu4j' 25 | } 26 | compile('org.antlr:antlr4-jetbrains-adapter:3.0.snapshot.efd2349681fbe1719e7115f0a4e8b82ecafe21b9') { 27 | exclude group: 'com.jetbrains' 28 | exclude group: 'org.slf4j' 29 | } 30 | testCompile group: 'junit', name: 'junit', version: '4.12' 31 | } 32 | 33 | // See https://github.com/JetBrains/gradle-intellij-plugin/ 34 | intellij { 35 | version = '2020.2.1' 36 | pluginName = 'Goctl' 37 | downloadSources true 38 | updateSinceUntilBuild false 39 | } 40 | 41 | patchPluginXml { 42 | sinceBuild '193.0' 43 | changeNotes """ 44 |

# 2020-11-24(v0.7.11-latest)

45 | 49 | """ 50 | } 51 | 52 | publishPlugin { 53 | token = System.getenv("ORG_GRADLE_PROJECT_intellijPublishToken") 54 | channels 'beta' 55 | } -------------------------------------------------------------------------------- /goland/src/main/java/cn/xiaoheiban/psi/nodes/IPsiNode.java: -------------------------------------------------------------------------------- 1 | package cn.xiaoheiban.psi.nodes; 2 | 3 | import com.intellij.lang.ASTNode; 4 | import org.antlr.jetbrains.adapter.psi.AntlrPsiNode; 5 | import org.jetbrains.annotations.NotNull; 6 | 7 | public class IPsiNode extends AntlrPsiNode { 8 | public IPsiNode(@NotNull ASTNode node) { 9 | super(node); 10 | } 11 | 12 | @NotNull 13 | public String getPath() { 14 | String path = null; 15 | if (this.getContainingFile() != null && this.getContainingFile().getVirtualFile() != null) { 16 | path = this.getContainingFile().getVirtualFile().getParent().getPath(); 17 | } 18 | if (path == null) { 19 | path = ""; 20 | } 21 | return path; 22 | } 23 | 24 | @NotNull 25 | public String getFileName() { 26 | String path = null; 27 | if (this.getContainingFile() != null && this.getContainingFile().getVirtualFile() != null) { 28 | path = this.getContainingFile().getVirtualFile().getPath(); 29 | } 30 | if (path == null) { 31 | path = ""; 32 | } 33 | return path; 34 | } 35 | 36 | @Override 37 | public String getName() { 38 | return this.getText(); 39 | } 40 | 41 | @NotNull 42 | public String getKey() { 43 | if (this.getName() == null) { 44 | return getFileName(); 45 | } 46 | return getFileName() + ":" + this.getName(); 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /goland/src/main/java/cn/xiaoheiban/psi/nodes/StructNode.java: -------------------------------------------------------------------------------- 1 | package cn.xiaoheiban.psi.nodes; 2 | 3 | import com.intellij.lang.ASTNode; 4 | import com.intellij.psi.PsiElement; 5 | import org.jetbrains.annotations.NotNull; 6 | 7 | import java.util.HashMap; 8 | import java.util.HashSet; 9 | import java.util.Map; 10 | import java.util.Set; 11 | 12 | public class StructNode extends IPsiNode { 13 | public StructNode(@NotNull ASTNode node) { 14 | super(node); 15 | } 16 | 17 | public String getStructName() { 18 | return getStructNameNode().getText(); 19 | } 20 | 21 | public PsiElement getStructNameNode() { 22 | return this.getFirstChild(); 23 | } 24 | 25 | public Map> getDuplicateField() { 26 | Map> tmp = new HashMap<>(); 27 | Map> ret = new HashMap<>(); 28 | @NotNull FieldNode[] children = this.findChildrenByClass(FieldNode.class); 29 | for (FieldNode node : children) { 30 | String key = node.getFiledName(); 31 | Set set = tmp.get(key); 32 | if (set == null) { 33 | set = new HashSet<>(); 34 | } 35 | if (node.getFiledNameNode() != null) { 36 | set.add(node.getFiledNameNode()); 37 | } 38 | tmp.put(key, set); 39 | } 40 | tmp.forEach((s, psiElements) -> { 41 | if (psiElements.size() > 1) { 42 | ret.put(s, psiElements); 43 | } 44 | }); 45 | return ret; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /goland/src/main/resources/liveTemplates/apiTags.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 18 | 25 | 32 | -------------------------------------------------------------------------------- /goland/src/main/java/cn/xiaoheiban/completion/KeywordCompletionContributor.java: -------------------------------------------------------------------------------- 1 | package cn.xiaoheiban.completion; 2 | 3 | import cn.xiaoheiban.parser.ApiParserDefinition; 4 | import com.intellij.codeInsight.completion.CompletionContributor; 5 | import com.intellij.codeInsight.completion.CompletionType; 6 | import com.intellij.codeInsight.lookup.AutoCompletionPolicy; 7 | import com.intellij.openapi.project.DumbAware; 8 | import com.intellij.patterns.ElementPattern; 9 | import com.intellij.psi.PsiElement; 10 | 11 | import static com.intellij.patterns.PlatformPatterns.psiElement; 12 | 13 | 14 | public class KeywordCompletionContributor extends CompletionContributor implements DumbAware { 15 | private static final String[] keywords = new String[]{"import", "map", "type", "bool", "uint8", "uint16", "uint32", "uint64", "int8", "int16", "int32", "int64", "float32", "float64", "complex64", "complex128", "string", "int", "uint", "uintptr", "byte", "rune", 16 | "get", "head", "post", "put", "patch", "delete", "connect", "options", "trace", "title", "desc", "author", "email", "version", "summary", "title", "desc", "author", "email", "version", "summary", 17 | "group", "jwt", "doc", "server", "service", "info", "handler", "middleware"}; 18 | 19 | public KeywordCompletionContributor() { 20 | for (String keyword : keywords) { 21 | extend(CompletionType.BASIC, typeDeclaration(), new ApiKeywordCompletionProvider(Priority.KEYWORD_PRIORITY, AutoCompletionPolicy.ALWAYS_AUTOCOMPLETE, keyword)); 22 | } 23 | } 24 | 25 | private static ElementPattern typeDeclaration() { 26 | return psiElement(ApiParserDefinition.IDENTIFIER); 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /README-en.md: -------------------------------------------------------------------------------- 1 | # goctl-plugins 2 | 3 | English | [简体中文](README.md) 4 | 5 | The plugins/extensions for goctl, like vscode or goland etc. 6 | 7 | **Note:** goctl goland plugin and goctl vscode extensions depend on goctl command line tool, please [install goctl command line tool](https://github.com/tal-tech/go-zero#5-quick-start) first. 8 | 9 | ## goctl plugin for IntelliJ Platform 10 | 11 | ### Install this plugin 12 | 13 | The plugin can be installed on following IntelliJ-based: 14 | 15 | * IntelliJ 2019.3+ (Ultimate or Community) 16 | * Goland 2019.3+ 17 | * WebStorm 2019.3+ 18 | * PhpStorm 2019.3+ 19 | * PyCharm 2019.3+ 20 | * RubyMine 2019.3+ 21 | * CLion 2019.3+ 22 | 23 | Make sure your IDE meets the above requirements and have installed the goctl command line tool. In the IntelliJ Plugin store, search for `Goctl` to install (publisher is "xiaoheiban"). 24 | 25 | > For IntelliJ plug-in use, please refer to [here](https://www.jetbrains.com/idea/help/managing-enterprise-plugin-repositories.html). 26 | 27 | For more information please check [here](goland/README.md). 28 | 29 | ## goctl plugin for Visual Studio Code 30 | 31 | ### Install this extension 32 | 33 | The plug-in can be installed on the 1.46.0+ version of Visual Studio Code. First of all, please make sure that your Visual Studio Code version meets the requirements and have installed the goctl command line tool. If Visual Studio Code is not already installed, please install and open Visual Studio Code. Navigate to the "Extensions" pane, Search for `goctl` and install this extension (publisher ID is "xiaoxin-technology.goctl"). 34 | 35 | > For the extension of Visual Studio Code, please refer to [here](https://code.visualstudio.com/docs/editor/extension-gallery). 36 | 37 | For more information please check [here](vscode/README.md). -------------------------------------------------------------------------------- /goland/src/main/java/cn/xiaoheiban/action/ApiCreateFileAction.java: -------------------------------------------------------------------------------- 1 | package cn.xiaoheiban.action; 2 | 3 | import cn.xiaoheiban.icon.ApiIcon; 4 | import com.intellij.ide.actions.CreateFileFromTemplateAction; 5 | import com.intellij.ide.actions.CreateFileFromTemplateDialog; 6 | import com.intellij.openapi.project.DumbAware; 7 | import com.intellij.openapi.project.Project; 8 | import com.intellij.openapi.util.NlsContexts; 9 | import com.intellij.psi.PsiDirectory; 10 | import org.jetbrains.annotations.NotNull; 11 | import org.jetbrains.annotations.Nullable; 12 | 13 | public class ApiCreateFileAction extends CreateFileFromTemplateAction implements DumbAware { 14 | public static final String FILE_TEMPLATE = "Api File"; 15 | public static final String APPLICATION_TEMPLATE = "Api Template"; 16 | private static final String NEW_API_FILE = "New Api File"; 17 | 18 | private static final String DEFAULT_API_TEMPLATE_PROPERTY = "DefaultApiTemplateProperty"; 19 | 20 | 21 | public ApiCreateFileAction() { 22 | super(NEW_API_FILE, "", ApiIcon.FILE); 23 | } 24 | 25 | @Override 26 | protected void buildDialog(@NotNull Project project, @NotNull PsiDirectory directory, CreateFileFromTemplateDialog.@NotNull Builder builder) { 27 | builder.setTitle(FILE_TEMPLATE) 28 | .addKind("Empty file", ApiIcon.FILE, FILE_TEMPLATE) 29 | .addKind("Api Template", ApiIcon.FILE, APPLICATION_TEMPLATE); 30 | } 31 | 32 | @Override 33 | protected @NlsContexts.Command String getActionName(PsiDirectory directory, @NotNull String newName, String templateName) { 34 | return NEW_API_FILE; 35 | } 36 | 37 | @Override 38 | protected @Nullable String getDefaultTemplateProperty() { 39 | return DEFAULT_API_TEMPLATE_PROPERTY; 40 | } 41 | 42 | @Override 43 | public boolean equals(Object obj) { 44 | return obj instanceof ApiCreateFileAction; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /goland/src/main/java/cn/xiaoheiban/psi/IReference.java: -------------------------------------------------------------------------------- 1 | package cn.xiaoheiban.psi; 2 | 3 | import com.intellij.openapi.util.TextRange; 4 | import com.intellij.psi.PsiElement; 5 | import com.intellij.psi.PsiNameIdentifierOwner; 6 | import com.intellij.psi.PsiReferenceBase; 7 | import com.intellij.util.IncorrectOperationException; 8 | import org.antlr.jetbrains.adapter.psi.ScopeNode; 9 | import org.jetbrains.annotations.NotNull; 10 | import org.jetbrains.annotations.Nullable; 11 | 12 | public abstract class IReference extends PsiReferenceBase { 13 | public IReference(@NotNull IdentifierPSINode element) { 14 | super(element, new TextRange(0, element.getText().length())); 15 | } 16 | 17 | @NotNull 18 | @Override 19 | public Object[] getVariants() { 20 | return new Object[0]; 21 | } 22 | 23 | @Override 24 | public PsiElement handleElementRename(String newElementName) throws IncorrectOperationException { 25 | return myElement.setName(newElementName); 26 | } 27 | 28 | @Nullable 29 | @Override 30 | public PsiElement resolve() { 31 | ScopeNode scope = (ScopeNode) myElement.getContext(); 32 | if (scope == null) return null; 33 | 34 | return scope.resolve(myElement); 35 | } 36 | 37 | @Override 38 | public boolean isReferenceTo(PsiElement def) { 39 | String refName = myElement.getName(); 40 | if (def instanceof IdentifierPSINode && isFieldTypeSubTree(def.getParent())) { 41 | def = def.getParent(); 42 | } 43 | if (isFieldTypeSubTree(def)) { 44 | PsiElement id = ((PsiNameIdentifierOwner) def).getNameIdentifier(); 45 | String defName = id != null ? id.getText() : null; 46 | return refName != null && defName != null && refName.equals(defName); 47 | } 48 | return false; 49 | } 50 | 51 | public abstract boolean isFieldTypeSubTree(PsiElement def); 52 | } 53 | -------------------------------------------------------------------------------- /goland/src/main/java/cn/xiaoheiban/action/ProtoCreateFileAction.java: -------------------------------------------------------------------------------- 1 | package cn.xiaoheiban.action; 2 | 3 | import com.intellij.icons.AllIcons; 4 | import com.intellij.ide.actions.CreateFileFromTemplateAction; 5 | import com.intellij.ide.actions.CreateFileFromTemplateDialog; 6 | import com.intellij.openapi.project.DumbAware; 7 | import com.intellij.openapi.project.Project; 8 | import com.intellij.openapi.util.NlsContexts; 9 | import com.intellij.psi.PsiDirectory; 10 | import com.intellij.util.Icons; 11 | import org.jetbrains.annotations.NotNull; 12 | import org.jetbrains.annotations.Nullable; 13 | 14 | public class ProtoCreateFileAction extends CreateFileFromTemplateAction implements DumbAware { 15 | public static final String FILE_TEMPLATE = "Proto File"; 16 | public static final String APPLICATION_TEMPLATE = "Proto Template"; 17 | private static final String NEW_PROTO_FILE = "New Proto File"; 18 | 19 | private static final String DEFAULT_API_TEMPLATE_PROPERTY = "DefaultProtoTemplateProperty"; 20 | 21 | 22 | public ProtoCreateFileAction() { 23 | super(NEW_PROTO_FILE, "", AllIcons.FileTypes.Text); 24 | } 25 | 26 | @Override 27 | protected void buildDialog(@NotNull Project project, @NotNull PsiDirectory directory, CreateFileFromTemplateDialog.@NotNull Builder builder) { 28 | builder.setTitle(FILE_TEMPLATE) 29 | .addKind("Empty file", AllIcons.FileTypes.Text, FILE_TEMPLATE) 30 | .addKind("Proto Template", AllIcons.FileTypes.Text, APPLICATION_TEMPLATE); 31 | } 32 | 33 | @Override 34 | protected @NlsContexts.Command String getActionName(PsiDirectory directory, @NotNull String newName, String templateName) { 35 | return NEW_PROTO_FILE; 36 | } 37 | 38 | @Override 39 | protected @Nullable String getDefaultTemplateProperty() { 40 | return DEFAULT_API_TEMPLATE_PROPERTY; 41 | } 42 | 43 | @Override 44 | public boolean equals(Object obj) { 45 | return obj instanceof ProtoCreateFileAction; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /vscode/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "goctl", 3 | "displayName": "goctl", 4 | "description": "goctl visual studio code extension", 5 | "version": "0.1.6", 6 | "publisher": "xiaoxin-technology", 7 | "icon": "images/go-zero-logo.png", 8 | "engines": { 9 | "vscode": "^1.46.0" 10 | }, 11 | "categories": [ 12 | "Programming Languages" 13 | ], 14 | "activationEvents": [ 15 | "onLanguage:goctl" 16 | ], 17 | "repository": { 18 | "type": "git", 19 | "url": "https://github.com/tal-tech/goctl-plugins.git" 20 | }, 21 | "main": "./out/extension.js", 22 | "contributes": { 23 | "languages": [ 24 | { 25 | "id": "goctl", 26 | "aliases": [ 27 | "goctl", 28 | "goctl" 29 | ], 30 | "extensions": [ 31 | ".api" 32 | ], 33 | "configuration": "./language-configuration.json" 34 | } 35 | ], 36 | "grammars": [ 37 | { 38 | "language": "goctl", 39 | "scopeName": "source.goctl", 40 | "path": "./syntaxes/goctl.tmLanguage.json" 41 | } 42 | ], 43 | "snippets": [ 44 | { 45 | "language": "goctl", 46 | "path": "./snippets/goctl.json" 47 | } 48 | ] 49 | }, 50 | "scripts": { 51 | "vscode:prepublish": "npm run compile", 52 | "compile": "tsc -p ./", 53 | "lint": "tslint -p ./", 54 | "watch": "tsc -watch -p ./", 55 | "package": "vsce package", 56 | "test": "npm run compile && npm run lint" 57 | }, 58 | "devDependencies": { 59 | "@types/node": "^12.12.47", 60 | "@types/vscode": "^1.46.0", 61 | "tslint": "^5.20.1", 62 | "typescript": "^3.9.5", 63 | "vscode-test": "^1.4.0" 64 | }, 65 | "dependencies": { 66 | "tree-kill": "^1.2.2" 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /goland/src/main/java/cn/xiaoheiban/psi/nodes/FieldNode.java: -------------------------------------------------------------------------------- 1 | package cn.xiaoheiban.psi.nodes; 2 | 3 | import cn.xiaoheiban.antlr4.ApiParser; 4 | import cn.xiaoheiban.parser.ApiParserDefinition; 5 | import cn.xiaoheiban.psi.ApiFile; 6 | import com.intellij.lang.ASTNode; 7 | import com.intellij.psi.PsiElement; 8 | import org.jetbrains.annotations.NotNull; 9 | 10 | public class FieldNode extends IPsiNode { 11 | 12 | public FieldNode(@NotNull ASTNode node) { 13 | super(node); 14 | } 15 | 16 | public String getFiledName() { 17 | PsiElement firstChild = this.getFirstChild(); 18 | if (firstChild instanceof AnonymousField) { 19 | AnonymousField child = (AnonymousField) firstChild; 20 | return child.getName(); 21 | } else if (firstChild instanceof NormalField) { 22 | NormalField child = (NormalField) firstChild; 23 | return child.getFieldName(); 24 | } else if (firstChild instanceof StructNode) { 25 | StructNode child = (StructNode) firstChild; 26 | return child.getStructName(); 27 | } else { 28 | return ""; 29 | } 30 | } 31 | 32 | public PsiElement getFiledNameNode() { 33 | PsiElement firstChild = this.getFirstChild(); 34 | if (firstChild instanceof AnonymousField) { 35 | AnonymousField child = (AnonymousField) firstChild; 36 | return child.getNameNode(); 37 | } else if (firstChild instanceof NormalField) { 38 | NormalField child = (NormalField) firstChild; 39 | return child.getFieldNode(); 40 | } else if (firstChild instanceof StructNode) { 41 | StructNode child = (StructNode) firstChild; 42 | return child.getStructNameNode(); 43 | } else { 44 | return null; 45 | } 46 | } 47 | 48 | 49 | public TagNode getFieldTag() { 50 | ASTNode node = ApiFile.findChild(this, ApiParserDefinition.rule(ApiParser.RULE_tag)); 51 | if (node == null) { 52 | return null; 53 | } 54 | return new TagNode(node); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /goland/src/main/java/cn/xiaoheiban/psi/IdentifierPSINode.java: -------------------------------------------------------------------------------- 1 | package cn.xiaoheiban.psi; 2 | 3 | import cn.xiaoheiban.antlr4.ApiParser; 4 | import cn.xiaoheiban.language.ApiLanguage; 5 | import cn.xiaoheiban.parser.ApiParserDefinition; 6 | import com.intellij.psi.PsiElement; 7 | import com.intellij.psi.PsiNamedElement; 8 | import com.intellij.psi.PsiReference; 9 | import com.intellij.psi.tree.IElementType; 10 | import com.intellij.util.IncorrectOperationException; 11 | import org.antlr.jetbrains.adapter.lexer.RuleIElementType; 12 | import org.antlr.jetbrains.adapter.psi.AntlrPsiLeafNode; 13 | import org.antlr.jetbrains.adapter.psi.Trees; 14 | import org.jetbrains.annotations.NonNls; 15 | import org.jetbrains.annotations.NotNull; 16 | 17 | 18 | public class IdentifierPSINode extends AntlrPsiLeafNode implements PsiNamedElement { 19 | 20 | public IdentifierPSINode(IElementType type, CharSequence text) { 21 | super(type, text); 22 | } 23 | 24 | @Override 25 | public String getName() { 26 | return getText(); 27 | } 28 | 29 | 30 | @Override 31 | public PsiElement setName(@NonNls @NotNull String name) throws IncorrectOperationException { 32 | if (getParent() == null) return this; // weird but it happened once 33 | PsiElement newID = Trees.createLeafFromText(getProject(), 34 | ApiLanguage.INSTANCE, 35 | getContext(), 36 | name, 37 | ApiParserDefinition.IDENTIFIER); 38 | if (newID != null) { 39 | return this.replace(newID); 40 | } 41 | return this; 42 | } 43 | 44 | @Override 45 | public PsiReference getReference() { 46 | PsiElement parent = getParent(); 47 | IElementType elType = parent.getNode().getElementType(); 48 | if (elType instanceof RuleIElementType) { 49 | switch (((RuleIElementType) elType).getRuleIndex()) { 50 | // 允许跳转到struct reference的规则 51 | case ApiParser.RULE_referenceId: 52 | return new StructReference(this); 53 | 54 | } 55 | } 56 | return null; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /goland/src/main/java/cn/xiaoheiban/action/RpcNewAction.java: -------------------------------------------------------------------------------- 1 | package cn.xiaoheiban.action; 2 | 3 | import cn.xiaoheiban.notification.Notification; 4 | import cn.xiaoheiban.util.Exec; 5 | import cn.xiaoheiban.util.FileReload; 6 | import com.intellij.openapi.actionSystem.AnAction; 7 | import com.intellij.openapi.actionSystem.AnActionEvent; 8 | import com.intellij.openapi.actionSystem.PlatformDataKeys; 9 | import com.intellij.openapi.application.ModalityState; 10 | import com.intellij.openapi.progress.*; 11 | import com.intellij.openapi.project.Project; 12 | import com.intellij.openapi.util.NlsContexts; 13 | import com.intellij.openapi.vfs.VirtualFile; 14 | import org.jetbrains.annotations.NotNull; 15 | import org.jetbrains.annotations.Nullable; 16 | 17 | public class RpcNewAction extends AnAction { 18 | @Override 19 | public void actionPerformed(@NotNull AnActionEvent e) { 20 | Project project = e.getProject(); 21 | if (project == null) { 22 | return; 23 | } 24 | VirtualFile file = e.getData(PlatformDataKeys.VIRTUAL_FILE); 25 | if (file == null) { 26 | return; 27 | } 28 | ProgressManager.getInstance().run(new Task.Backgroundable(project, "generating rpc greet service ...") { 29 | @Override 30 | public void run(@NotNull ProgressIndicator indicator) { 31 | String path = file.getPath(); 32 | boolean done = Exec.runGoctl(project, "rpc new " + path+" -idea"); 33 | if (done) { 34 | FileReload.reloadFromDisk(e); 35 | Notification.getInstance().notify(project, "generate rpc greet service successful"); 36 | } 37 | } 38 | }); 39 | } 40 | 41 | @Override 42 | public void update(@NotNull AnActionEvent e) { 43 | VirtualFile file = e.getData(PlatformDataKeys.VIRTUAL_FILE); 44 | if (file == null) { 45 | e.getPresentation().setEnabledAndVisible(false); 46 | return; 47 | } 48 | if (!file.isDirectory()) { 49 | e.getPresentation().setEnabledAndVisible(false); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /goland/src/main/java/cn/xiaoheiban/completion/ApiKeywordCompletionProvider.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013-2016 Sergey Ignatov, Alexander Zolotov, Florin Patan 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package cn.xiaoheiban.completion; 18 | 19 | import com.intellij.codeInsight.completion.CompletionParameters; 20 | import com.intellij.codeInsight.completion.CompletionResultSet; 21 | import com.intellij.codeInsight.completion.InsertHandler; 22 | import com.intellij.codeInsight.lookup.AutoCompletionPolicy; 23 | import com.intellij.codeInsight.lookup.LookupElement; 24 | import com.intellij.util.ProcessingContext; 25 | import org.jetbrains.annotations.NotNull; 26 | import org.jetbrains.annotations.Nullable; 27 | 28 | public class ApiKeywordCompletionProvider extends ApiProvider { 29 | 30 | @NotNull 31 | private final String[] myKeywords; 32 | 33 | public ApiKeywordCompletionProvider(int priority, @Nullable InsertHandler insertHandler, @NotNull String... keywords) { 34 | super(priority, insertHandler); 35 | myKeywords = keywords; 36 | } 37 | 38 | public ApiKeywordCompletionProvider(int priority, @Nullable AutoCompletionPolicy completionPolicy, @NotNull String... keywords) { 39 | super(priority, completionPolicy); 40 | myKeywords = keywords; 41 | } 42 | 43 | 44 | @Override 45 | protected void addCompletions(@NotNull CompletionParameters parameters, ProcessingContext context, @NotNull CompletionResultSet result) { 46 | for (String keyword : myKeywords) { 47 | result.addElement(createKeywordLookupElement(keyword)); 48 | } 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /goland/src/main/java/cn/xiaoheiban/action/ApiNewAction.java: -------------------------------------------------------------------------------- 1 | package cn.xiaoheiban.action; 2 | 3 | import cn.xiaoheiban.notification.Notification; 4 | import cn.xiaoheiban.util.Exec; 5 | import cn.xiaoheiban.util.FileReload; 6 | import com.intellij.openapi.actionSystem.AnAction; 7 | import com.intellij.openapi.actionSystem.AnActionEvent; 8 | import com.intellij.openapi.actionSystem.PlatformDataKeys; 9 | import com.intellij.openapi.progress.ProgressIndicator; 10 | import com.intellij.openapi.progress.ProgressManager; 11 | import com.intellij.openapi.progress.Task; 12 | import com.intellij.openapi.project.Project; 13 | import com.intellij.openapi.vfs.VirtualFile; 14 | import com.intellij.psi.PsiManager; 15 | import com.intellij.testFramework.propertyBased.PsiIndexConsistencyTester; 16 | import org.jetbrains.annotations.NotNull; 17 | 18 | import java.io.File; 19 | 20 | public class ApiNewAction extends AnAction { 21 | @Override 22 | public void actionPerformed(@NotNull AnActionEvent e) { 23 | Project project = e.getProject(); 24 | if (project == null) { 25 | return; 26 | } 27 | VirtualFile file = e.getData(PlatformDataKeys.VIRTUAL_FILE); 28 | if (file == null) { 29 | return; 30 | } 31 | ProgressManager.getInstance().run(new Task.Backgroundable(project, "generating api greet service ...") { 32 | @Override 33 | public void run(@NotNull ProgressIndicator indicator) { 34 | String path = file.getPath(); 35 | boolean done = Exec.runGoctl(project, "api new " + path); 36 | if (done) { 37 | FileReload.reloadFromDisk(e); 38 | Notification.getInstance().notify(project, "generate api greet service successful"); 39 | } 40 | } 41 | }); 42 | } 43 | 44 | @Override 45 | public void update(@NotNull AnActionEvent e) { 46 | VirtualFile file = e.getData(PlatformDataKeys.VIRTUAL_FILE); 47 | if (file == null) { 48 | e.getPresentation().setEnabledAndVisible(false); 49 | return; 50 | } 51 | if (!file.isDirectory()) { 52 | e.getPresentation().setEnabledAndVisible(false); 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /goland/src/main/java/cn/xiaoheiban/antlr4/ApiParser.g4: -------------------------------------------------------------------------------- 1 | parser grammar ApiParser; 2 | 3 | options { 4 | tokenVocab = ApiLexer; 5 | } 6 | 7 | api: 8 | ( 9 | importStatement 10 | |infoStatement 11 | |apiBody 12 | )* 13 | EOF; 14 | 15 | apiBody: 16 | typeStatement 17 | |serviceStatement; 18 | 19 | importStatement:importSpec+; 20 | 21 | importSpec: IMPORT importValue; 22 | // info 23 | infoStatement: INFO LPAREN pair RPAREN; 24 | 25 | // types 26 | typeStatement: (typeSingleSpec|typeGroupSpec); 27 | 28 | // eg: type (...) 29 | typeGroupSpec:TYPE LPAREN typeGroupBody RPAREN; 30 | typeGroupBody:(typeGroupAlias|structType)*; 31 | typeGroupAlias:structNameId normalFieldType; 32 | 33 | // eg: type xx struct {...} 34 | typeSingleSpec: typeAlias|typeStruct; 35 | typeStruct:TYPE structType; 36 | // eg: type Integer int 37 | typeAlias:TYPE structNameId '='? normalFieldType; 38 | typeFiled:anonymousField|normalField |structType; 39 | normalField:fieldName fieldType tag?; 40 | fieldType:normalFieldType|starFieldType|mapFieldType|arrayOrSliceType; 41 | anonymousField: STAR? referenceId; 42 | normalFieldType: GOTYPE|referenceId|(INTERFACE LBRACE RBRACE); 43 | starFieldType: STAR normalFieldType; 44 | mapFieldType: MAP LBRACK GOTYPE RBRACK objType; 45 | arrayOrSliceType: (LBRACK RBRACK)+ objType; 46 | structType: structNameId STRUCT? LBRACE (typeFiled)* RBRACE; 47 | objType: normalFieldType|starFieldType; 48 | structNameId:IDENT; 49 | fieldName:IDENT; 50 | referenceId:IDENT; 51 | tag: RAW_STRING; 52 | 53 | // service 54 | serviceStatement: (serviceServerSpec? serviceSpec); 55 | serviceServerSpec: ATSERVER LPAREN identPair RPAREN; 56 | 57 | 58 | serviceSpec: SERVICE serviceName LBRACE serviceBody+ RBRACE; 59 | serviceName:IDENT; 60 | serviceBody:serviceDoc? (serviceHandler|serviceHandlerNew) serviceRoute; 61 | serviceDoc: ATDOC LPAREN pair RPAREN; 62 | serviceHandler: ATSERVER LPAREN handlerPair RPAREN; 63 | serviceHandlerNew: ATHANDLER handlerValue; 64 | serviceRoute:httpRoute (LPAREN referenceId? RPAREN)? (RETURNS LPAREN referenceId? RPAREN)? SMICOLON?; 65 | httpRoute:HTTPMETHOD PATH; 66 | identPair:(key COLON identValue)*; 67 | handlerPair:(key COLON handlerValue)+; 68 | identValue:(IDENT ','?)+; 69 | handlerValue:IDENT; 70 | importValue:VALUE; 71 | pair:(key COLON VALUE?)*; 72 | key:IDENT; -------------------------------------------------------------------------------- /goland/src/main/java/cn/xiaoheiban/action/ModelAction.java: -------------------------------------------------------------------------------- 1 | package cn.xiaoheiban.action; 2 | 3 | import cn.xiaoheiban.contsant.Constant; 4 | import cn.xiaoheiban.notification.Notification; 5 | import cn.xiaoheiban.util.Exec; 6 | import cn.xiaoheiban.util.FileReload; 7 | import com.intellij.openapi.actionSystem.AnAction; 8 | import com.intellij.openapi.actionSystem.AnActionEvent; 9 | import com.intellij.openapi.actionSystem.PlatformDataKeys; 10 | import com.intellij.openapi.progress.ProgressIndicator; 11 | import com.intellij.openapi.progress.ProgressManager; 12 | import com.intellij.openapi.progress.Task; 13 | import com.intellij.openapi.project.Project; 14 | import com.intellij.openapi.util.text.StringUtil; 15 | import com.intellij.openapi.vfs.VirtualFile; 16 | import org.jetbrains.annotations.NotNull; 17 | 18 | import java.io.File; 19 | 20 | public class ModelAction extends AnAction { 21 | @Override 22 | public void update(@NotNull AnActionEvent e) { 23 | VirtualFile file = e.getData(PlatformDataKeys.VIRTUAL_FILE); 24 | if (file == null) { 25 | e.getPresentation().setEnabledAndVisible(false); 26 | return; 27 | } 28 | String extension = file.getExtension(); 29 | if (StringUtil.isEmpty(extension)) { 30 | e.getPresentation().setEnabledAndVisible(false); 31 | return; 32 | } 33 | if (!extension.equals(Constant.MODEL_EXTENSION)) { 34 | e.getPresentation().setEnabledAndVisible(false); 35 | } 36 | } 37 | 38 | @Override 39 | public void actionPerformed(@NotNull AnActionEvent e) { 40 | Project project = e.getProject(); 41 | if (project == null) { 42 | return; 43 | } 44 | VirtualFile file = e.getData(PlatformDataKeys.VIRTUAL_FILE); 45 | if (file == null) { 46 | return; 47 | } 48 | ProgressManager.getInstance().run(new Task.Backgroundable(project, "generating model ...") { 49 | @Override 50 | public void run(@NotNull ProgressIndicator indicator) { 51 | String path = file.getPath(); 52 | boolean done = Exec.runGoctl(project, "model mysql ddl -c -src " + path + " -dir " + file.getParent().getPath()+ " -idea"); 53 | if (done) { 54 | FileReload.reloadFromDisk(e); 55 | Notification.getInstance().notify(project, "generate model done"); 56 | } 57 | } 58 | }); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /goland/src/main/java/cn/xiaoheiban/notification/Notification.java: -------------------------------------------------------------------------------- 1 | package cn.xiaoheiban.notification; 2 | 3 | import com.intellij.notification.NotificationDisplayType; 4 | import com.intellij.notification.NotificationGroup; 5 | import com.intellij.notification.NotificationType; 6 | import com.intellij.openapi.project.Project; 7 | 8 | public class Notification { 9 | private static Notification notification; 10 | private final NotificationGroup POP = new NotificationGroup("goctl-pop", NotificationDisplayType.BALLOON, true); 11 | private final NotificationGroup LOG = new NotificationGroup("goctl-log", NotificationDisplayType.NONE, true); 12 | 13 | private Notification() { 14 | } 15 | 16 | public static Notification getInstance() { 17 | if (notification == null) { 18 | notification = new Notification(); 19 | } 20 | return notification; 21 | } 22 | 23 | public void notify(Project project, String content) { 24 | if (project == null) { 25 | return; 26 | } 27 | final com.intellij.notification.Notification notification = POP.createNotification(unWrapMsg(content), NotificationType.INFORMATION); 28 | notification.notify(project); 29 | } 30 | 31 | public void log(Project project, String content) { 32 | if (project == null) { 33 | return; 34 | } 35 | final com.intellij.notification.Notification notification = LOG.createNotification(unWrapMsg(content), NotificationType.INFORMATION); 36 | notification.notify(project); 37 | } 38 | 39 | public void error(Project project, String content) { 40 | if (project == null) { 41 | return; 42 | } 43 | final com.intellij.notification.Notification notification = POP.createNotification(unWrapMsg(content), NotificationType.ERROR); 44 | notification.notify(project); 45 | } 46 | 47 | public void warning(Project project, String content) { 48 | if (project == null) { 49 | return; 50 | } 51 | final com.intellij.notification.Notification notification = LOG.createNotification(unWrapMsg(content), NotificationType.WARNING); 52 | notification.notify(project); 53 | } 54 | 55 | private String unWrapMsg(String msg) { 56 | if (msg.startsWith("info-")) { 57 | return msg.replaceFirst("info-", ""); 58 | } 59 | if (msg.startsWith("error-")) { 60 | return msg.replaceFirst("error-", ""); 61 | } 62 | if (msg.startsWith("warning-")) { 63 | return msg.replaceFirst("warning-", ""); 64 | } 65 | return msg; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /vscode/src/goctlFormat.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | import cp = require('child_process'); 3 | import * as util from './util'; 4 | 5 | export class GoctlDocumentFormattingEditProvider implements vscode.DocumentFormattingEditProvider { 6 | provideDocumentFormattingEdits( 7 | document: vscode.TextDocument, 8 | options: vscode.FormattingOptions, 9 | token: vscode.CancellationToken): vscode.ProviderResult { 10 | 11 | return this.runFormatter(document, token).then( 12 | (edits) => edits, 13 | (err) => { 14 | if (err) { 15 | let errs = err.split('\n'); 16 | errs.forEach((element: string) => { 17 | if (element.trim().length === 0) { 18 | return; 19 | } 20 | vscode.window.showErrorMessage(element); 21 | }); 22 | return Promise.reject(); 23 | } 24 | } 25 | ); 26 | } 27 | 28 | private runFormatter( 29 | // formatTool: string, 30 | // formatFlags: string[], 31 | document: vscode.TextDocument, 32 | token: vscode.CancellationToken 33 | ): Thenable { 34 | 35 | const formatFlags = ['api', 'format', '-iu', '--stdin']; 36 | return new Promise((resolve, reject) => { 37 | let stdout = ''; 38 | let stderr = ''; 39 | 40 | const p = cp.spawn("goctl", formatFlags); 41 | 42 | token.onCancellationRequested(() => !p.killed && util.killTree(p.pid)); 43 | p.stdout.setEncoding('utf8'); 44 | p.stdout.on('data', (data) => (stdout += data)); 45 | p.stderr.on('data', (data) => (stderr += data)); 46 | p.on('error', (err) => { 47 | if (err && (err).code === 'ENOENT') { 48 | // promptForMissingTool(formatTool); 49 | vscode.window.showWarningMessage('Check the console in goctl when formatting. goctl seem not in your $PATH , please try in terminal.'); 50 | return reject(); 51 | } 52 | }); 53 | p.on('close', (code) => { 54 | if (code !== 0) { 55 | return reject(stderr); 56 | } 57 | 58 | // Return the complete file content in the edit. 59 | // VS Code will calculate minimal edits to be applied 60 | const fileStart = new vscode.Position(0, 0); 61 | const fileEnd = document.lineAt(document.lineCount - 1).range.end; 62 | const textEdits: vscode.TextEdit[] = [ 63 | new vscode.TextEdit(new vscode.Range(fileStart, fileEnd), stdout) 64 | ]; 65 | 66 | // const timeTaken = Date.now() - t0; 67 | // sendTelemetryEventForFormatting(formatTool, timeTaken); 68 | // if (timeTaken > 750) { 69 | // console.log(`Formatting took too long(${timeTaken}ms). Format On Save feature could be aborted.`); 70 | // } 71 | return resolve(textEdits); 72 | }); 73 | if (p.pid) { 74 | p.stdin.end(document.getText()); 75 | } 76 | }); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /goland/src/main/java/cn/xiaoheiban/action/ApiAction.java: -------------------------------------------------------------------------------- 1 | package cn.xiaoheiban.action; 2 | 3 | import cn.xiaoheiban.contsant.Constant; 4 | import cn.xiaoheiban.notification.Notification; 5 | import cn.xiaoheiban.ui.FileChooseDialog; 6 | import cn.xiaoheiban.util.Exec; 7 | import cn.xiaoheiban.util.FileReload; 8 | import com.intellij.openapi.actionSystem.AnAction; 9 | import com.intellij.openapi.actionSystem.AnActionEvent; 10 | import com.intellij.openapi.actionSystem.PlatformDataKeys; 11 | import com.intellij.openapi.progress.ProgressIndicator; 12 | import com.intellij.openapi.progress.ProgressManager; 13 | import com.intellij.openapi.progress.Task; 14 | import com.intellij.openapi.project.Project; 15 | import com.intellij.openapi.util.text.StringUtil; 16 | import com.intellij.openapi.vfs.VirtualFile; 17 | import org.jetbrains.annotations.NotNull; 18 | 19 | public class ApiAction extends AnAction { 20 | @Override 21 | public void update(@NotNull AnActionEvent e) { 22 | VirtualFile file = e.getData(PlatformDataKeys.VIRTUAL_FILE); 23 | if (file == null) { 24 | return; 25 | } 26 | String extension = file.getExtension(); 27 | if (StringUtil.isEmpty(extension)) { 28 | e.getPresentation().setEnabledAndVisible(false); 29 | return; 30 | } 31 | if (!extension.equals(Constant.API_EXTENSION)) { 32 | e.getPresentation().setEnabledAndVisible(false); 33 | } 34 | } 35 | 36 | @Override 37 | public void actionPerformed(@NotNull AnActionEvent e) { 38 | VirtualFile file = e.getData(PlatformDataKeys.VIRTUAL_FILE); 39 | if (file == null) { 40 | return; 41 | } 42 | Project project = e.getProject(); 43 | if (project == null) { 44 | return; 45 | } 46 | String parent = file.getParent().getPath(); 47 | FileChooseDialog dialog = new FileChooseDialog("请选择生成目录", "取消"); 48 | dialog.setDefaultPath(parent); 49 | dialog.setOnClickListener(new FileChooseDialog.OnClickListener() { 50 | @Override 51 | public void onOk(String p) { 52 | ProgressManager.getInstance().run(new Task.Backgroundable(project, "generating api ...") { 53 | @Override 54 | public void run(@NotNull ProgressIndicator indicator) { 55 | boolean done = Exec.runGoctl(project, "api go -api " + file.getPath() + " -dir " + p); 56 | if (done) { 57 | FileReload.reloadFromDisk(e); 58 | Notification.getInstance().notify(project, "generate api done"); 59 | } 60 | } 61 | }); 62 | } 63 | 64 | @Override 65 | public void onJump() { 66 | 67 | } 68 | }); 69 | dialog.showAndGet(); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /goland/src/main/java/cn/xiaoheiban/completion/ApiCompletionProvider.java: -------------------------------------------------------------------------------- 1 | package cn.xiaoheiban.completion; 2 | 3 | import cn.xiaoheiban.antlr4.ApiParser; 4 | import cn.xiaoheiban.parser.ApiParserDefinition; 5 | import cn.xiaoheiban.psi.ApiFile; 6 | import cn.xiaoheiban.psi.nodes.ApiRootNode; 7 | import cn.xiaoheiban.psi.nodes.StructNameNode; 8 | import com.intellij.codeInsight.completion.CompletionParameters; 9 | import com.intellij.codeInsight.completion.CompletionResultSet; 10 | import com.intellij.codeInsight.completion.InsertHandler; 11 | import com.intellij.codeInsight.lookup.AutoCompletionPolicy; 12 | import com.intellij.codeInsight.lookup.LookupElement; 13 | import com.intellij.lang.ASTNode; 14 | import com.intellij.psi.PsiElement; 15 | import com.intellij.psi.tree.IElementType; 16 | import com.intellij.util.ProcessingContext; 17 | import org.jetbrains.annotations.NotNull; 18 | import org.jetbrains.annotations.Nullable; 19 | 20 | import java.util.Map; 21 | import java.util.Set; 22 | 23 | public class ApiCompletionProvider extends ApiProvider { 24 | 25 | public ApiCompletionProvider(int priority, @Nullable AutoCompletionPolicy completionPolicy) { 26 | super(priority, completionPolicy); 27 | } 28 | 29 | public ApiCompletionProvider(int priority, @Nullable InsertHandler insertHandler) { 30 | super(priority, insertHandler); 31 | } 32 | 33 | 34 | @Override 35 | protected void addCompletions(@NotNull CompletionParameters parameters, @NotNull ProcessingContext context, @NotNull CompletionResultSet result) { 36 | PsiElement position = parameters.getPosition(); 37 | ASTNode node = position.getNode(); 38 | if (node == null) { 39 | return; 40 | } 41 | IElementType elementType = node.getElementType(); 42 | boolean canAdd = false; 43 | if (elementType.equals(ApiParserDefinition.rule(ApiParser.RULE_referenceId))) { 44 | canAdd = true; 45 | } else { 46 | ASTNode treeParent = node.getTreeParent(); 47 | if (treeParent != null) { 48 | IElementType elementType1 = treeParent.getElementType(); 49 | if (elementType1.equals(ApiParserDefinition.rule(ApiParser.RULE_normalFieldType)) 50 | || elementType1.equals(ApiParserDefinition.rule(ApiParser.RULE_serviceRoute)) 51 | || elementType1.equals(ApiParserDefinition.rule(ApiParser.RULE_referenceId))) { 52 | canAdd = true; 53 | } 54 | } 55 | } 56 | if (canAdd) { 57 | ApiRootNode root = ApiFile.getRoot(parameters.getOriginalFile()); 58 | if (root == null) { 59 | return; 60 | } 61 | Map> allStructMap = root.getAllStructMap(); 62 | allStructMap.forEach((s, typeNodes) -> { 63 | result.addElement(createKeywordLookupElement(s)); 64 | }); 65 | } 66 | 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /vscode/snippets/goctl.json: -------------------------------------------------------------------------------- 1 | { 2 | ".source.goctl": { 3 | "single import": { 4 | "prefix": "im", 5 | "body": "import \"${1:package}\"", 6 | "description": "Snippet for import statement" 7 | }, 8 | "type struct declaration": { 9 | "prefix": "tys", 10 | "body": "type ${1:name} struct {\n\t$0\n}", 11 | "description": "Snippet for a struct declaration" 12 | }, 13 | "info": { 14 | "prefix": "info", 15 | "body": [ 16 | "info(", 17 | "\ttitle: $1", 18 | "\tdesc: $2", 19 | "\tauthor: $3", 20 | "\temail: $4@xiaoheiban.cn", 21 | "\tversion: ${5:1.0}", 22 | ")", 23 | "", 24 | "$0" 25 | ], 26 | "description": "goctl api doc info" 27 | }, 28 | "service": { 29 | "prefix": "service", 30 | "body": [ 31 | "service ${1:xxx-api} {", 32 | "\t$0", 33 | "}" 34 | ], 35 | "description": "route group" 36 | }, 37 | "type": { 38 | "prefix": "type", 39 | "body": [ 40 | "type $1 struct {", 41 | "\t$0", 42 | "}" 43 | ], 44 | "description": "struct declaration" 45 | }, 46 | "handler": { 47 | "prefix": "handler", 48 | "body": [ 49 | "@doc(", 50 | "\tsummary: $1", 51 | ")", 52 | "@handler ${2:handlerName}", 53 | "${3:http_method} /${4:your/url/path}(${5:RequestBoday}) returns(${6:ResponseBoday})", 54 | "", 55 | "$0" 56 | ], 57 | "description": "route rule" 58 | }, 59 | "json": { 60 | "prefix": "json", 61 | "body": [ 62 | "`json:\"$0\"`" 63 | ], 64 | "description": "json tag" 65 | }, 66 | "path": { 67 | "prefix": "path", 68 | "body": [ 69 | "`path:\"$0\"`" 70 | ], 71 | "description": "path tag" 72 | }, 73 | "form": { 74 | "prefix": "form", 75 | "body": [ 76 | "`form:\"$0\"`" 77 | ], 78 | "description": "form tag" 79 | }, 80 | "doc": { 81 | "prefix": "@doc", 82 | "body": [ 83 | "@doc(", 84 | " summary: $1", 85 | ")", 86 | "$0" 87 | ], 88 | "description": "doc" 89 | }, 90 | "server": { 91 | "prefix": "@server", 92 | "body": [ 93 | "@server(", 94 | " handler: $1", 95 | ")", 96 | "$0" 97 | ] 98 | }, 99 | "@handler": { 100 | "prefix": "@handler", 101 | "body": [ 102 | "@handler $1", 103 | "$0" 104 | ] 105 | }, 106 | "post": { 107 | "prefix": "post", 108 | "body": [ 109 | "post /${1:your/url/path}($2) returns($3)", 110 | "", 111 | "$0" 112 | ] 113 | }, 114 | "get": { 115 | "prefix": "get", 116 | "body": [ 117 | "get /${1:your/url/path}($2) returns($3)", 118 | "", 119 | "$0" 120 | ] 121 | }, 122 | "delete": { 123 | "prefix": "delete", 124 | "body": [ 125 | "delete /${1:your/url/path}($2) returns($3)", 126 | "", 127 | "$0" 128 | ] 129 | }, 130 | "put": { 131 | "prefix": "put", 132 | "body": [ 133 | "put /${1:your/url/path}($2) returns($3)", 134 | "", 135 | "$0" 136 | ] 137 | } 138 | } 139 | } -------------------------------------------------------------------------------- /goland/src/main/java/cn/xiaoheiban/ui/FileChooseDialog.java: -------------------------------------------------------------------------------- 1 | package cn.xiaoheiban.ui; 2 | 3 | import com.intellij.openapi.fileChooser.FileChooserDescriptor; 4 | import com.intellij.openapi.ui.DialogWrapper; 5 | import com.intellij.openapi.ui.TextBrowseFolderListener; 6 | import com.intellij.openapi.ui.TextFieldWithBrowseButton; 7 | import com.intellij.openapi.ui.ValidationInfo; 8 | import com.intellij.openapi.vfs.LocalFileSystem; 9 | import com.intellij.openapi.vfs.VirtualFile; 10 | import org.jetbrains.annotations.Nullable; 11 | 12 | import javax.swing.*; 13 | import java.awt.*; 14 | import java.awt.event.ActionEvent; 15 | import java.awt.event.ActionListener; 16 | import java.awt.event.KeyAdapter; 17 | import java.awt.event.KeyEvent; 18 | import java.beans.PropertyChangeEvent; 19 | import java.beans.PropertyChangeListener; 20 | 21 | public class FileChooseDialog extends DialogWrapper { 22 | private OnClickListener onOkClickListener; 23 | private String title; 24 | 25 | public interface OnClickListener { 26 | void onOk(String path); 27 | 28 | void onJump(); 29 | } 30 | 31 | private TextFieldWithBrowseButton textFieldWithBrowseButton; 32 | 33 | 34 | public FileChooseDialog(String title,String cancelText) { 35 | super(true); 36 | this.title = title; 37 | init(); 38 | setTitle(title); 39 | setOKButtonText("确认"); 40 | setCancelButtonText(cancelText); 41 | this.getButton(myCancelAction).addActionListener(e -> { 42 | if (onOkClickListener!=null){ 43 | onOkClickListener.onJump(); 44 | } 45 | }); 46 | } 47 | 48 | 49 | public void setDefaultPath(String path) { 50 | textFieldWithBrowseButton.setText(path); 51 | } 52 | 53 | @Nullable 54 | @Override 55 | protected JComponent createCenterPanel() { 56 | JPanel panel = new JPanel(); 57 | textFieldWithBrowseButton = new TextFieldWithBrowseButton(); 58 | FileChooserDescriptor chooserDescriptor = new FileChooserDescriptor(false, true, false, false, false, false); 59 | chooserDescriptor.setForcedToUseIdeaFileChooser(true); 60 | chooserDescriptor.setTitle(title); 61 | TextBrowseFolderListener listener = new TextBrowseFolderListener(chooserDescriptor); 62 | textFieldWithBrowseButton.addBrowseFolderListener(listener); 63 | panel.setLayout(new BorderLayout()); 64 | panel.setPreferredSize(new Dimension(400, 40)); 65 | panel.add(textFieldWithBrowseButton, BorderLayout.CENTER); 66 | return panel; 67 | } 68 | 69 | @Nullable 70 | @Override 71 | protected ValidationInfo doValidate() { 72 | String filePath = textFieldWithBrowseButton.getText(); 73 | VirtualFile virtualFile = LocalFileSystem.getInstance().findFileByPath(filePath); 74 | if (virtualFile != null) { 75 | if (this.onOkClickListener != null) { 76 | this.onOkClickListener.onOk(virtualFile.getPath()); 77 | } 78 | } 79 | return null; 80 | } 81 | 82 | 83 | public void setOnClickListener(OnClickListener listener) { 84 | this.onOkClickListener = listener; 85 | } 86 | 87 | } 88 | -------------------------------------------------------------------------------- /goland/src/main/java/cn/xiaoheiban/antlr4/ApiLexer.g4: -------------------------------------------------------------------------------- 1 | lexer grammar ApiLexer; 2 | 3 | // TOKEN 4 | INFO: 'info'; 5 | MAP: 'map'; 6 | STRUCT: 'struct'; 7 | INTERFACE: 'interface'; 8 | TYPE: 'type'; 9 | ATSERVER: '@server'; 10 | ATDOC: '@doc'; 11 | ATHANDLER: '@handler'; 12 | SERVICE: 'service'; 13 | RETURNS: 'returns'; 14 | IMPORT: 'import'; 15 | 16 | 17 | // HTTP METHOD 18 | HTTPMETHOD:GET|HEAD|POST|PUT|PATCH|DELETE|CONNECT|OPTIONS|TRACE; 19 | GET : 'get'; 20 | HEAD : 'head'; 21 | POST: 'post'; 22 | PUT: 'put'; 23 | PATCH : 'patch'; 24 | DELETE : 'delete'; 25 | CONNECT: 'connect'; 26 | OPTIONS: 'options'; 27 | TRACE : 'trace'; 28 | 29 | // DATA TYPE 30 | GOTYPE: BOOL|UINT8|UINT16|UINT32|UINT64|INT8|INT16|INT32|INT64|FLOAT32|FLOAT32|FLOAT64|COMPLEX64|COMPLEX128|STRING|INT|UINT|UINTPTR|BYTE|RUNE|TIME; 31 | BOOL: 'bool'; 32 | UINT8: 'uint8'; 33 | UINT16: 'uint16'; 34 | UINT32: 'uint32'; 35 | UINT64: 'uint64'; 36 | INT8: 'int8'; 37 | INT16: 'int16'; 38 | INT32: 'int32'; 39 | INT64: 'int64'; 40 | FLOAT32: 'float32'; 41 | FLOAT64: 'float64'; 42 | COMPLEX64: 'complex64'; 43 | COMPLEX128: 'complex128'; 44 | STRING: 'string'; 45 | INT: 'int'; 46 | UINT: 'uint'; 47 | UINTPTR: 'uintptr'; 48 | BYTE: 'byte'; 49 | RUNE: 'rune'; 50 | TIME: 'time.Time'; 51 | 52 | // HTTP PATH 53 | PATH: (('/'|'/:'|'-') IDENT)+; 54 | 55 | // MAKER 56 | LPAREN: '('; 57 | RPAREN: ')'; 58 | LBRACE: '{'; 59 | RBRACE: '}'; 60 | LBRACK: '['; 61 | RBRACK: ']'; 62 | DOT: '.'; 63 | SMICOLON: ';'; 64 | COMMA: ','; 65 | STAR: '*'; 66 | BAR: '-'; 67 | ASSIGN: '='; 68 | COLON: ':'; 69 | NUMBER: DIGIT+; 70 | HOSTVALUE: DIGIT+ '.' DIGIT+ '.' DIGIT+ '.' DIGIT+; 71 | 72 | // IDTIFIER 73 | IDENT: (ALPHA|UNDERSCORE)(ALPHA|DIGIT|UNDERSCORE|'-')*; 74 | 75 | 76 | 77 | // WHITE SPACE 78 | WS: [ \t\r\n]+ -> channel(HIDDEN) ; 79 | 80 | 81 | 82 | VALUE: STRING_F ~('\r'|'\n'|':')* STRING_F; 83 | 84 | RAW_STRING: RAW_STRING_F ~('`'|'\r'|'\n')* RAW_STRING_F; 85 | 86 | // COMMENT 87 | COMMENT: COMMENT_FLAG ~('\n')* -> channel(HIDDEN); 88 | 89 | fragment COMMENT_FLAG: '//'; 90 | 91 | fragment RAW_STRING_F: '`'; 92 | 93 | fragment STRING_F: '"'; 94 | 95 | fragment DIGIT: [0-9]; 96 | 97 | fragment UNDERSCORE: '_'; 98 | 99 | fragment ALPHA: [a-zA-Z]; 100 | 101 | //ERRCHAR: . -> channel(HIDDEN); 102 | 103 | fragment HEX_DIGIT 104 | : [0-9a-fA-F] 105 | ; 106 | fragment OCT_DIGIT 107 | : [0-7] 108 | ; 109 | fragment ESC_SEQ 110 | : '\\' ('a'|'v'|'b'|'t'|'n'|'f'|'r'|'?'|'"'|'\''|'\\') 111 | | '\\' ('x'|'X') HEX_DIGIT HEX_DIGIT 112 | | UNICODE_ESC 113 | | OCTAL_ESC 114 | ; 115 | fragment OCTAL_ESC 116 | : '\\' [0-3] OCT_DIGIT OCT_DIGIT 117 | | '\\' OCT_DIGIT OCT_DIGIT 118 | | '\\' OCT_DIGIT 119 | ; 120 | fragment UNICODE_ESC 121 | : '\\' 'u' HEX_DIGIT HEX_DIGIT HEX_DIGIT HEX_DIGIT 122 | ; 123 | 124 | ERRCHAR 125 | : . -> channel(HIDDEN) 126 | ; 127 | -------------------------------------------------------------------------------- /goland/src/main/java/cn/xiaoheiban/template/ApiFieldNameMacro.java: -------------------------------------------------------------------------------- 1 | 2 | 3 | package cn.xiaoheiban.template; 4 | 5 | import cn.xiaoheiban.antlr4.ApiParser; 6 | import cn.xiaoheiban.parser.ApiParserDefinition; 7 | import com.intellij.codeInsight.lookup.LookupElement; 8 | import com.intellij.codeInsight.lookup.LookupElementBuilder; 9 | import com.intellij.codeInsight.template.*; 10 | import com.intellij.lang.ASTNode; 11 | import com.intellij.openapi.util.text.StringUtil; 12 | import com.intellij.psi.PsiElement; 13 | import com.intellij.psi.tree.IElementType; 14 | import com.intellij.util.containers.ContainerUtil; 15 | import org.jetbrains.annotations.NotNull; 16 | import org.jetbrains.annotations.Nullable; 17 | 18 | import java.util.Collections; 19 | import java.util.HashSet; 20 | import java.util.Set; 21 | 22 | public class ApiFieldNameMacro extends Macro { 23 | @Override 24 | public String getName() { 25 | return "apiFieldName"; 26 | } 27 | 28 | @Override 29 | public String getPresentableName() { 30 | return "apiFieldName()"; 31 | } 32 | 33 | @Nullable 34 | @Override 35 | public Result calculateResult(@NotNull Expression[] params, ExpressionContext context) { 36 | String name = ContainerUtil.getFirstItem(fieldNames(context)); 37 | return StringUtil.isNotEmpty(name) ? new TextResult(name) : null; 38 | } 39 | 40 | @Nullable 41 | @Override 42 | public LookupElement[] calculateLookupItems(@NotNull Expression[] params, ExpressionContext context) { 43 | return ContainerUtil.map2Array(fieldNames(context), LookupElement.class, LookupElementBuilder::create); 44 | } 45 | 46 | @Override 47 | public boolean isAcceptableInContext(TemplateContextType context) { 48 | return context instanceof ApiLiveTemplateContextType.Tag || context instanceof ApiLiveTemplateContextType.TagLiteral; 49 | } 50 | 51 | private static Set fieldNames(ExpressionContext context) { 52 | PsiElement psiElement = context != null ? context.getPsiElementAtStartOffset() : null; 53 | if (psiElement == null) { 54 | return Collections.emptySet(); 55 | } 56 | ASTNode node = psiElement.getNode(); 57 | while (true) { 58 | if (node == null) { 59 | return Collections.emptySet(); 60 | } 61 | if (node.getElementType().equals(ApiParserDefinition.rule(ApiParser.RULE_normalField))) { 62 | ASTNode fieldNameNode = node.getFirstChildNode(); 63 | if (fieldNameNode == null) { 64 | return Collections.emptySet(); 65 | } 66 | IElementType elementType = fieldNameNode.getElementType(); 67 | if (elementType.equals(ApiParserDefinition.rule(ApiParser.RULE_fieldName))) { 68 | Set set = new HashSet(); 69 | set.add(fieldNameNode.getText()); 70 | return set; 71 | } 72 | } else if (node.getElementType().equals(ApiParserDefinition.rule(ApiParser.RULE_fieldName))) { 73 | Set set = new HashSet(); 74 | set.add(node.getText()); 75 | return set; 76 | } 77 | node = node.getTreeParent(); 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /goland/src/main/java/cn/xiaoheiban/editor/QuoteHandler.java: -------------------------------------------------------------------------------- 1 | package cn.xiaoheiban.editor; 2 | 3 | import com.intellij.codeInsight.editorActions.MultiCharQuoteHandler; 4 | import com.intellij.openapi.editor.Document; 5 | import com.intellij.openapi.editor.Editor; 6 | import com.intellij.openapi.editor.highlighter.HighlighterIterator; 7 | import com.intellij.openapi.util.TextRange; 8 | import org.jetbrains.annotations.NotNull; 9 | import org.jetbrains.annotations.Nullable; 10 | 11 | import java.util.HashMap; 12 | import java.util.Map; 13 | 14 | public class QuoteHandler implements MultiCharQuoteHandler { 15 | private static Map quete = new HashMap(); 16 | 17 | static { 18 | quete.put("\"", "\""); 19 | quete.put("`", "`"); 20 | quete.put("'", "'"); 21 | } 22 | 23 | @Override 24 | public @Nullable CharSequence getClosingQuote(@NotNull HighlighterIterator highlighterIterator, int offset) { 25 | Document document = highlighterIterator.getDocument(); 26 | CharSequence closingQuote = null; 27 | 28 | if (document != null) { 29 | String openingQuote; 30 | if (offset >= 3) { 31 | openingQuote = document.getText(new TextRange(offset - 3, offset)); 32 | closingQuote = quete.get(openingQuote); 33 | } 34 | if (closingQuote == null && offset >= 1) { 35 | openingQuote = document.getText(new TextRange(offset - 1, offset)); 36 | closingQuote = quete.get(openingQuote); 37 | } 38 | } 39 | return closingQuote; 40 | } 41 | 42 | @Override 43 | public boolean isClosingQuote(HighlighterIterator highlighterIterator, int offset) { 44 | Document document = highlighterIterator.getDocument(); 45 | if (document != null) { 46 | if (offset > 3) { 47 | String text = document.getText(new TextRange(offset - 3, offset)); 48 | if (quete.containsValue(text)) { 49 | return true; 50 | } 51 | } 52 | if (offset > 1) { 53 | String text = document.getText(new TextRange(offset - 1, offset)); 54 | return quete.containsValue(text); 55 | } 56 | 57 | } 58 | return false; 59 | } 60 | 61 | @Override 62 | public boolean isOpeningQuote(HighlighterIterator highlighterIterator, int offset) { 63 | Document document = highlighterIterator.getDocument(); 64 | if (document != null) { 65 | if (offset > 3) { 66 | String text = document.getText(new TextRange(offset - 3, offset)); 67 | if (quete.containsValue(text)) { 68 | return true; 69 | } 70 | } 71 | if (offset > 1) { 72 | String text = document.getText(new TextRange(offset - 1, offset)); 73 | return quete.containsValue(text); 74 | } 75 | 76 | } 77 | return false; 78 | } 79 | 80 | @Override 81 | public boolean hasNonClosedLiteral(Editor editor, HighlighterIterator iterator, int offset) { 82 | return true; 83 | } 84 | 85 | @Override 86 | public boolean isInsideLiteral(HighlighterIterator iterator) { 87 | return false; 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /goland/gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 33 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 34 | 35 | @rem Find java.exe 36 | if defined JAVA_HOME goto findJavaFromJavaHome 37 | 38 | set JAVA_EXE=java.exe 39 | %JAVA_EXE% -version >NUL 2>&1 40 | if "%ERRORLEVEL%" == "0" goto init 41 | 42 | echo. 43 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 44 | echo. 45 | echo Please set the JAVA_HOME variable in your environment to match the 46 | echo location of your Java installation. 47 | 48 | goto fail 49 | 50 | :findJavaFromJavaHome 51 | set JAVA_HOME=%JAVA_HOME:"=% 52 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 53 | 54 | if exist "%JAVA_EXE%" goto init 55 | 56 | echo. 57 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 58 | echo. 59 | echo Please set the JAVA_HOME variable in your environment to match the 60 | echo location of your Java installation. 61 | 62 | goto fail 63 | 64 | :init 65 | @rem Get command-line arguments, handling Windows variants 66 | 67 | if not "%OS%" == "Windows_NT" goto win9xME_args 68 | 69 | :win9xME_args 70 | @rem Slurp the command line arguments. 71 | set CMD_LINE_ARGS= 72 | set _SKIP=2 73 | 74 | :win9xME_args_slurp 75 | if "x%~1" == "x" goto execute 76 | 77 | set CMD_LINE_ARGS=%* 78 | 79 | :execute 80 | @rem Setup the command line 81 | 82 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 83 | 84 | @rem Execute Gradle 85 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 86 | 87 | :end 88 | @rem End local scope for the variables with windows NT shell 89 | if "%ERRORLEVEL%"=="0" goto mainEnd 90 | 91 | :fail 92 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 93 | rem the _cmd.exe /c_ return code! 94 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 95 | exit /b 1 96 | 97 | :mainEnd 98 | if "%OS%"=="Windows_NT" endlocal 99 | 100 | :omega 101 | -------------------------------------------------------------------------------- /goland/src/main/java/cn/xiaoheiban/editor/AutoInsertHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013-2016 Sergey Ignatov, Alexander Zolotov, Florin Patan 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package cn.xiaoheiban.editor; 18 | 19 | import com.intellij.codeInsight.completion.InsertHandler; 20 | import com.intellij.codeInsight.completion.InsertionContext; 21 | import com.intellij.codeInsight.lookup.LookupElement; 22 | import com.intellij.codeInsight.template.Template; 23 | import com.intellij.codeInsight.template.TemplateManager; 24 | import com.intellij.openapi.actionSystem.IdeActions; 25 | import com.intellij.openapi.application.ApplicationManager; 26 | import com.intellij.openapi.editor.Editor; 27 | import com.intellij.openapi.editor.actionSystem.EditorActionHandler; 28 | import com.intellij.openapi.editor.actionSystem.EditorActionManager; 29 | import com.intellij.openapi.editor.ex.EditorEx; 30 | import com.intellij.openapi.project.Project; 31 | import com.intellij.openapi.util.text.StringUtil; 32 | import com.intellij.psi.tree.IElementType; 33 | import org.jetbrains.annotations.NotNull; 34 | 35 | public class AutoInsertHandler implements InsertHandler { 36 | 37 | private final String template; 38 | 39 | public AutoInsertHandler(String template) { 40 | this.template = template; 41 | } 42 | 43 | @Override 44 | public void handleInsert(@NotNull InsertionContext context, LookupElement item) { 45 | Editor editor = context.getEditor(); 46 | CharSequence documentText = context.getDocument().getImmutableCharSequence(); 47 | int offset = skipWhiteSpaces(editor.getCaretModel().getOffset(), documentText); 48 | if (documentText.charAt(offset) != '{') { 49 | Project project = context.getProject(); 50 | Template template = TemplateManager.getInstance(project).createTemplate("braces", "api", this.template); 51 | template.setToReformat(true); 52 | TemplateManager.getInstance(project).startTemplate(editor, template); 53 | } else { 54 | editor.getCaretModel().moveToOffset(offset); 55 | ApplicationManager.getApplication().runWriteAction(() -> { 56 | EditorActionHandler enterAction = EditorActionManager.getInstance().getActionHandler(IdeActions.ACTION_EDITOR_START_NEW_LINE); 57 | enterAction.execute(editor, editor.getCaretModel().getCurrentCaret(), ((EditorEx) editor).getDataContext()); 58 | }); 59 | } 60 | } 61 | 62 | private static int skipWhiteSpaces(int offset, @NotNull CharSequence documentText) { 63 | while (offset < documentText.length() && StringUtil.isWhiteSpace(documentText.charAt(offset))) { 64 | offset += 1; 65 | } 66 | return Math.min(documentText.length() - 1, offset); 67 | } 68 | } -------------------------------------------------------------------------------- /goland/src/main/java/cn/xiaoheiban/psi/nodes/StructNameNode.java: -------------------------------------------------------------------------------- 1 | package cn.xiaoheiban.psi.nodes; 2 | 3 | import cn.xiaoheiban.antlr4.ApiParser; 4 | import cn.xiaoheiban.parser.ApiParserDefinition; 5 | import cn.xiaoheiban.psi.ApiFile; 6 | import com.intellij.lang.ASTNode; 7 | import com.intellij.psi.PsiElement; 8 | import com.intellij.psi.tree.IElementType; 9 | import org.antlr.jetbrains.adapter.lexer.RuleIElementType; 10 | import org.apache.commons.collections.map.HashedMap; 11 | import org.jetbrains.annotations.NotNull; 12 | 13 | import java.util.HashSet; 14 | import java.util.List; 15 | import java.util.Map; 16 | import java.util.Set; 17 | 18 | // case 1: type User int 19 | // case 2: type User struct { 20 | // ... 21 | // } 22 | public class StructNameNode extends IPsiNode { 23 | 24 | private boolean isTypeLit; 25 | 26 | public StructNameNode(@NotNull ASTNode node) { 27 | super(node); 28 | if (node.getElementType().equals(ApiParserDefinition.rule(ApiParser.RULE_typeAlias)) || node.getElementType().equals(ApiParserDefinition.rule(ApiParser.RULE_typeGroupAlias))) { 29 | this.setIsTypeList(true); 30 | } 31 | } 32 | 33 | 34 | @NotNull 35 | public Map> getFields() { 36 | Map> filedSet = new HashedMap(); 37 | List fieldChildren = ApiFile.findChildren(this, ApiParserDefinition.rule(ApiParser.RULE_typeFiled)); 38 | for (ASTNode node : fieldChildren) { 39 | ASTNode childByType = node.findChildByType(ApiParserDefinition.rule(ApiParser.RULE_fieldName)); 40 | if (childByType == null) { 41 | continue; 42 | } 43 | ASTNode parentStruct = getParentStruct(childByType.getPsi()); 44 | // 防止将child struct中的field错误归类 45 | if (parentStruct != null && !parentStruct.equals(this.getNode())) { 46 | continue; 47 | } 48 | String text = childByType.getText(); 49 | Set nodeSet = filedSet.get(text); 50 | if (nodeSet == null) { 51 | nodeSet = new HashSet<>(); 52 | } 53 | FieldNode fieldNode = new FieldNode(node); 54 | nodeSet.add(fieldNode); 55 | filedSet.put(text, nodeSet); 56 | } 57 | return filedSet; 58 | } 59 | 60 | private ASTNode getParentStruct(@NotNull PsiElement element) { 61 | while (true) { 62 | PsiElement parent = element.getParent(); 63 | if (parent == null) { 64 | return null; 65 | } 66 | ASTNode node = parent.getNode(); 67 | if (node == null) { 68 | return null; 69 | } 70 | IElementType elementType = node.getElementType(); 71 | if (elementType instanceof RuleIElementType) { 72 | RuleIElementType type = (RuleIElementType) elementType; 73 | if (type.getRuleIndex() == ApiParser.RULE_structType) { 74 | return parent.getNode(); 75 | } 76 | } 77 | element = parent; 78 | } 79 | } 80 | 81 | public boolean isTypeLit() { 82 | return isTypeLit; 83 | } 84 | 85 | public void setIsTypeList(boolean typeLit) { 86 | this.isTypeLit = typeLit; 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /goland/src/main/java/cn/xiaoheiban/language/ApiAnnotator.java: -------------------------------------------------------------------------------- 1 | package cn.xiaoheiban.language; 2 | 3 | import cn.xiaoheiban.antlr4.ApiParser; 4 | import cn.xiaoheiban.highlighting.ApiSyntaxHighlighter; 5 | import cn.xiaoheiban.parser.ApiParserDefinition; 6 | import cn.xiaoheiban.psi.ApiFile; 7 | import cn.xiaoheiban.psi.nodes.*; 8 | import com.intellij.lang.ASTNode; 9 | import com.intellij.lang.annotation.AnnotationHolder; 10 | import com.intellij.lang.annotation.Annotator; 11 | import com.intellij.psi.PsiElement; 12 | import com.intellij.psi.tree.IElementType; 13 | import org.jetbrains.annotations.NotNull; 14 | 15 | import java.util.List; 16 | import java.util.Map; 17 | import java.util.Set; 18 | 19 | public class ApiAnnotator implements Annotator { 20 | 21 | private AnnotationHolder mHolder; 22 | private Map> allNode; 23 | 24 | @Override 25 | public void annotate(@NotNull PsiElement element, @NotNull AnnotationHolder holder) { 26 | if (!(element instanceof IPsiNode)) { 27 | return; 28 | } 29 | mHolder = holder; 30 | if (element instanceof ApiRootNode) { 31 | ApiRootNode root = (ApiRootNode) element; 32 | allNode = root.getAllNode(); 33 | Map> duplicateNode = ApiRootNode.getAllDuplicateNode(allNode); 34 | duplicateNode.forEach((et, nodes) -> { 35 | if (nodes.size() > 1) { 36 | if (et.equals(ApiParserDefinition.rule(ApiParser.RULE_structNameId))) { 37 | for (ASTNode node : nodes) { 38 | mHolder.createErrorAnnotation(node, "duplicate struct " + node.getText()); 39 | } 40 | } else if (et.equals(ApiParserDefinition.rule(ApiParser.RULE_handlerValue))) { 41 | for (ASTNode node : nodes) { 42 | mHolder.createErrorAnnotation(node, "duplicate handler " + node.getText()); 43 | } 44 | } else {// route 45 | for (ASTNode node : nodes) { 46 | mHolder.createErrorAnnotation(node, "duplicate route " + node.getText()); 47 | } 48 | } 49 | } 50 | }); 51 | 52 | } else if (element instanceof StructNode) { 53 | StructNode node = (StructNode) element; 54 | Map> duplicateField = node.getDuplicateField(); 55 | duplicateField.forEach((s, psiElements) -> { 56 | if (psiElements == null || s == null) return; 57 | psiElements.forEach(el -> { 58 | if (el == null) return; 59 | mHolder.createErrorAnnotation(el, "filed [" + s + "] redeclare in this struct"); 60 | }); 61 | }); 62 | } else if (element instanceof ServiceNameNode) { 63 | mHolder.createInfoAnnotation(element, element.getText()).setTextAttributes(ApiSyntaxHighlighter.IDENTIFIER); 64 | } else if (element instanceof ReferenceIdNode) {//RULE_referenceId 65 | if (allNode == null) { 66 | ApiRootNode root = ApiFile.getRoot(element); 67 | if (root == null) { 68 | return; 69 | } 70 | allNode = root.getAllNode(); 71 | } 72 | String name = ((ReferenceIdNode) element).getName(); 73 | if (ApiRootNode.resolve(allNode, ApiParserDefinition.rule(ApiParser.RULE_structNameId), name)) { 74 | holder.createInfoAnnotation(element, element.getText()).setTextAttributes(ApiSyntaxHighlighter.IDENTIFIER); 75 | return; 76 | } 77 | holder.createErrorAnnotation(element, "can not resolve " + name); 78 | } 79 | } 80 | 81 | } 82 | -------------------------------------------------------------------------------- /goland/src/main/java/cn/xiaoheiban/completion/ApiProvider.java: -------------------------------------------------------------------------------- 1 | package cn.xiaoheiban.completion; 2 | 3 | import com.intellij.codeInsight.completion.CompletionParameters; 4 | import com.intellij.codeInsight.completion.CompletionProvider; 5 | import com.intellij.codeInsight.completion.InsertHandler; 6 | import com.intellij.codeInsight.completion.PrioritizedLookupElement; 7 | import com.intellij.codeInsight.lookup.AutoCompletionPolicy; 8 | import com.intellij.codeInsight.lookup.LookupElement; 9 | import com.intellij.codeInsight.lookup.LookupElementBuilder; 10 | import com.intellij.codeInsight.template.Template; 11 | import com.intellij.codeInsight.template.TemplateManager; 12 | import com.intellij.codeInsight.template.impl.TemplateSettings; 13 | import com.intellij.openapi.editor.Editor; 14 | import com.intellij.openapi.editor.EditorModificationUtil; 15 | import com.intellij.util.ObjectUtils; 16 | import org.jetbrains.annotations.NotNull; 17 | import org.jetbrains.annotations.Nullable; 18 | 19 | public abstract class ApiProvider extends CompletionProvider { 20 | private final int myPriority; 21 | @Nullable 22 | private final InsertHandler myInsertHandler; 23 | @Nullable 24 | private final AutoCompletionPolicy myCompletionPolicy; 25 | 26 | public ApiProvider(int priority, @Nullable AutoCompletionPolicy completionPolicy) { 27 | this(priority, null, completionPolicy); 28 | } 29 | 30 | public ApiProvider(int priority, @Nullable InsertHandler insertHandler) { 31 | this(priority, insertHandler, null); 32 | } 33 | 34 | private ApiProvider(int priority, 35 | @Nullable InsertHandler insertHandler, 36 | @Nullable AutoCompletionPolicy completionPolicy) { 37 | myPriority = priority; 38 | myInsertHandler = insertHandler; 39 | myCompletionPolicy = completionPolicy; 40 | } 41 | 42 | @NotNull 43 | public LookupElement createKeywordLookupElement(@NotNull String keyword) { 44 | InsertHandler insertHandler = ObjectUtils.chooseNotNull(myInsertHandler, 45 | createTemplateBasedInsertHandler("api_" + keyword)); 46 | LookupElement result = createKeywordLookupElement(keyword, myPriority, insertHandler); 47 | return myCompletionPolicy != null ? myCompletionPolicy.applyPolicy(result) : result; 48 | } 49 | 50 | public static LookupElement createKeywordLookupElement(@NotNull String keyword, 51 | int priority, 52 | @Nullable InsertHandler insertHandler) { 53 | LookupElementBuilder builder = LookupElementBuilder.create(keyword).withBoldness(true).withInsertHandler(insertHandler); 54 | return PrioritizedLookupElement.withPriority(builder, priority); 55 | } 56 | 57 | @Nullable 58 | public static InsertHandler createTemplateBasedInsertHandler(@NotNull String templateId) { 59 | return (context, item) -> { 60 | Template template = TemplateSettings.getInstance().getTemplateById(templateId); 61 | Editor editor = context.getEditor(); 62 | if (template != null) { 63 | editor.getDocument().deleteString(context.getStartOffset(), context.getTailOffset()); 64 | TemplateManager.getInstance(context.getProject()).startTemplate(editor, template); 65 | } else { 66 | int currentOffset = editor.getCaretModel().getOffset(); 67 | CharSequence documentText = editor.getDocument().getImmutableCharSequence(); 68 | if (documentText.length() <= currentOffset || documentText.charAt(currentOffset) != ' ') { 69 | EditorModificationUtil.insertStringAtCaret(editor, " "); 70 | } else { 71 | EditorModificationUtil.moveCaretRelatively(editor, 1); 72 | } 73 | } 74 | }; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /goland/src/main/java/cn/xiaoheiban/action/RpcAction.java: -------------------------------------------------------------------------------- 1 | package cn.xiaoheiban.action; 2 | 3 | import cn.xiaoheiban.contsant.Constant; 4 | import cn.xiaoheiban.io.IO; 5 | import cn.xiaoheiban.notification.Notification; 6 | import cn.xiaoheiban.ui.FileChooseDialog; 7 | import cn.xiaoheiban.util.Exec; 8 | import cn.xiaoheiban.util.FileReload; 9 | import com.intellij.openapi.actionSystem.AnAction; 10 | import com.intellij.openapi.actionSystem.AnActionEvent; 11 | import com.intellij.openapi.actionSystem.PlatformDataKeys; 12 | import com.intellij.openapi.fileChooser.FileChooserDialog; 13 | import com.intellij.openapi.progress.ProgressIndicator; 14 | import com.intellij.openapi.progress.ProgressManager; 15 | import com.intellij.openapi.progress.Task; 16 | import com.intellij.openapi.project.Project; 17 | import com.intellij.openapi.util.text.StringUtil; 18 | import com.intellij.openapi.vfs.VirtualFile; 19 | import org.jetbrains.annotations.NotNull; 20 | 21 | import java.io.IOException; 22 | 23 | public class RpcAction extends AnAction { 24 | 25 | @Override 26 | public void update(@NotNull AnActionEvent e) { 27 | VirtualFile file = e.getData(PlatformDataKeys.VIRTUAL_FILE); 28 | if (file == null) { 29 | e.getPresentation().setEnabledAndVisible(false); 30 | return; 31 | } 32 | String extension = file.getExtension(); 33 | if (StringUtil.isEmpty(extension)) { 34 | e.getPresentation().setEnabledAndVisible(false); 35 | return; 36 | } 37 | if (!extension.equals(Constant.RPC_EXTENSION)) { 38 | e.getPresentation().setEnabledAndVisible(false); 39 | } 40 | } 41 | 42 | @Override 43 | public void actionPerformed(@NotNull AnActionEvent e) { 44 | VirtualFile file = e.getData(PlatformDataKeys.VIRTUAL_FILE); 45 | if (file == null) { 46 | return; 47 | } 48 | String path = file.getPath(); 49 | Project project = e.getProject(); 50 | if (project == null) { 51 | return; 52 | } 53 | try { 54 | String parent = file.getParent().getPath(); 55 | String content = IO.read(file.getInputStream()); 56 | if (content.contains("import")) { 57 | FileChooseDialog dialog = new FileChooseDialog("请选择proto_path","跳过"); 58 | dialog.setDefaultPath(parent); 59 | dialog.setOnClickListener(new FileChooseDialog.OnClickListener() { 60 | @Override 61 | public void onOk(String p) { 62 | generateRpc(project, p, path, parent, e); 63 | } 64 | 65 | @Override 66 | public void onJump() { 67 | generateRpc(project, "", path, parent, e); 68 | } 69 | }); 70 | dialog.showAndGet(); 71 | return; 72 | } 73 | generateRpc(project, "", path, parent, e); 74 | } catch (IOException ioException) { 75 | ioException.printStackTrace(); 76 | } 77 | } 78 | 79 | private void generateRpc(Project project, String protoPath, String src, String target, AnActionEvent e) { 80 | ProgressManager.getInstance().run(new Task.Backgroundable(project, "generating rpc ...") { 81 | @Override 82 | public void run(@NotNull ProgressIndicator indicator) { 83 | String command = "rpc proto -src " + src + " -dir " + target + " -idea"; 84 | if (!StringUtil.isEmptyOrSpaces(protoPath)) { 85 | command = "rpc proto -src " + src + " -I=" + protoPath + " -dir " + target + " -idea"; 86 | } 87 | boolean done = Exec.runGoctl(project, command); 88 | if (done) { 89 | FileReload.reloadFromDisk(e); 90 | Notification.getInstance().notify(project, "generate rpc done"); 91 | } 92 | } 93 | }); 94 | } 95 | 96 | 97 | } 98 | -------------------------------------------------------------------------------- /goland/src/main/java/cn/xiaoheiban/highlighting/ApiColorSettingsPage.java: -------------------------------------------------------------------------------- 1 | package cn.xiaoheiban.highlighting; 2 | 3 | import cn.xiaoheiban.icon.ApiIcon; 4 | import com.intellij.openapi.editor.colors.TextAttributesKey; 5 | import com.intellij.openapi.fileTypes.SyntaxHighlighter; 6 | import com.intellij.openapi.options.colors.AttributesDescriptor; 7 | import com.intellij.openapi.options.colors.ColorDescriptor; 8 | import com.intellij.openapi.options.colors.ColorSettingsPage; 9 | import com.intellij.openapi.util.NlsContexts; 10 | import org.jetbrains.annotations.NotNull; 11 | import org.jetbrains.annotations.Nullable; 12 | 13 | import javax.swing.*; 14 | import java.util.Map; 15 | 16 | public class ApiColorSettingsPage implements ColorSettingsPage { 17 | private static final AttributesDescriptor[] DESCRIPTORS = new AttributesDescriptor[]{ 18 | new AttributesDescriptor("Separator", ApiSyntaxHighlighter.SEPARATOR), 19 | new AttributesDescriptor("Keyword", ApiSyntaxHighlighter.KEYWORD), 20 | new AttributesDescriptor("Identifier", ApiSyntaxHighlighter.IDENTIFIER), 21 | new AttributesDescriptor("String", ApiSyntaxHighlighter.STRING), 22 | new AttributesDescriptor("Comment", ApiSyntaxHighlighter.COMMENT) 23 | }; 24 | private static final String template = "info (\n" + 25 | " title:\"api highlighter\"\n" + 26 | " desc:\"api highlighter description\"\n" + 27 | " author:\"xiaoheiban\"\n" + 28 | " email:\"\"\n" + 29 | " version:\"1.0\"\n" + 30 | ")\n" + 31 | "\n" + 32 | "type Gender int\n" + 33 | "\n" + 34 | "type Anonymous struct{}\n" + 35 | "\n" + 36 | "type Pointer struct{}\n" + 37 | "\n" + 38 | "type Struct struct {\n" + 39 | " // 字符串\n" + 40 | " String string `json:\"string\"`\n" + 41 | " // 性别\n" + 42 | " Gender Gender `json:\"gender\"`\n" + 43 | " // 匿名类型\n" + 44 | " Anonymous\n" + 45 | " // 指针类型\n" + 46 | " Pointer *Pointer\n" + 47 | " // map类型:key必须为go系统类型中的比较类型(number、string、bool)\n" + 48 | " M1 map[string]int\n" + 49 | " // 数组或切片类型\n" + 50 | " Array []string\n" + 51 | " // inline\n" + 52 | " Inline struct {\n" + 53 | " Name string\n" + 54 | " }\n" + 55 | "}\n" + 56 | "\n" + 57 | "type (\n" + 58 | " LoginRequest struct{\n" + 59 | " // todo\n" + 60 | " }\n" + 61 | "\n" + 62 | " LoginResponse struct{\n" + 63 | " // todo\n" + 64 | " }\n" + 65 | "\n" + 66 | " UserInfoRequest struct{\n" + 67 | " Id string `path:\"id\"`\n" + 68 | " }\n" + 69 | "\n" + 70 | " UserInfoResponse struct {\n" + 71 | " // todo\n" + 72 | " }\n" + 73 | ")\n" + 74 | "\n" + 75 | "@server (\n" + 76 | " jwt: Auth\n" + 77 | " folder: user\n" + 78 | ")\n" + 79 | "service user {\n" + 80 | " @doc(\n" + 81 | " summary:\"login\"\n" + 82 | " )\n" + 83 | " @server(\n" + 84 | " handler: login\n" + 85 | " )\n" + 86 | " post /user/login (LoginRequest)returns(LoginResponse);\n" + 87 | " \n" + 88 | " @server(\n" + 89 | " handler: userInfo\n" + 90 | " )\n" + 91 | " get /user/info/:id () returns (UserInfoResponse);\n" + 92 | "}\n" + 93 | "\n"; 94 | 95 | @Override 96 | public @Nullable Icon getIcon() { 97 | return ApiIcon.FILE; 98 | } 99 | 100 | @Override 101 | public @NotNull SyntaxHighlighter getHighlighter() { 102 | return new ApiSyntaxHighlighter(); 103 | } 104 | 105 | @Override 106 | public @NotNull String getDemoText() { 107 | return template; 108 | } 109 | 110 | @Override 111 | public @Nullable Map getAdditionalHighlightingTagToDescriptorMap() { 112 | return null; 113 | } 114 | 115 | @Override 116 | public @NotNull AttributesDescriptor[] getAttributeDescriptors() { 117 | return DESCRIPTORS; 118 | } 119 | 120 | @Override 121 | public @NotNull ColorDescriptor[] getColorDescriptors() { 122 | return ColorDescriptor.EMPTY_ARRAY; 123 | } 124 | 125 | @Override 126 | public @NotNull @NlsContexts.ConfigurableName String getDisplayName() { 127 | return "Api"; 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /goland/src/main/java/cn/xiaoheiban/highlighting/ApiSyntaxHighlighter.java: -------------------------------------------------------------------------------- 1 | package cn.xiaoheiban.highlighting; 2 | 3 | import cn.xiaoheiban.antlr4.ApiLexer; 4 | import cn.xiaoheiban.language.ApiLanguage; 5 | import cn.xiaoheiban.parser.ApiParserDefinition; 6 | import com.intellij.lexer.Lexer; 7 | import com.intellij.openapi.editor.DefaultLanguageHighlighterColors; 8 | import com.intellij.openapi.editor.HighlighterColors; 9 | import com.intellij.openapi.editor.colors.TextAttributesKey; 10 | import com.intellij.openapi.fileTypes.SyntaxHighlighterBase; 11 | import com.intellij.psi.TokenType; 12 | import com.intellij.psi.tree.IElementType; 13 | import org.antlr.jetbrains.adapter.lexer.AntlrLexerAdapter; 14 | import org.antlr.jetbrains.adapter.lexer.TokenIElementType; 15 | import org.jetbrains.annotations.NotNull; 16 | 17 | import static com.intellij.openapi.editor.colors.TextAttributesKey.createTextAttributesKey; 18 | 19 | public class ApiSyntaxHighlighter extends SyntaxHighlighterBase { 20 | 21 | public static final TextAttributesKey SEPARATOR = 22 | createTextAttributesKey("API_SEPARATOR", DefaultLanguageHighlighterColors.OPERATION_SIGN); 23 | public static final TextAttributesKey KEYWORD = 24 | createTextAttributesKey("API_KEY", DefaultLanguageHighlighterColors.KEYWORD); 25 | public static final TextAttributesKey IDENTIFIER = 26 | createTextAttributesKey("API_IDENTIFIER", DefaultLanguageHighlighterColors.CLASS_NAME); 27 | public static final TextAttributesKey STRING = 28 | createTextAttributesKey("API_STRING", DefaultLanguageHighlighterColors.STRING); 29 | public static final TextAttributesKey COMMENT = 30 | createTextAttributesKey("API_COMMENT", DefaultLanguageHighlighterColors.LINE_COMMENT); 31 | public static final TextAttributesKey BAD_CHARACTER = 32 | createTextAttributesKey("API_BAD_CHARACTER", HighlighterColors.BAD_CHARACTER); 33 | 34 | private static final TextAttributesKey[] EMPTY_KEYS = new TextAttributesKey[0]; 35 | private static final TextAttributesKey[] ATTR_SEPARATOR = new TextAttributesKey[]{SEPARATOR}; 36 | private static final TextAttributesKey[] ATTR_KEYWORD = new TextAttributesKey[]{KEYWORD}; 37 | private static final TextAttributesKey[] ATTR_IDENTIFIER = new TextAttributesKey[]{IDENTIFIER}; 38 | private static final TextAttributesKey[] ATTR_STRING = new TextAttributesKey[]{STRING}; 39 | private static final TextAttributesKey[] ATTR_COMMENT = new TextAttributesKey[]{COMMENT}; 40 | private static final TextAttributesKey[] ATTR_BAD_CHARACTER = new TextAttributesKey[]{BAD_CHARACTER}; 41 | 42 | 43 | @Override 44 | public @NotNull Lexer getHighlightingLexer() { 45 | ApiLexer lexer = new ApiLexer(null); 46 | return new AntlrLexerAdapter(ApiLanguage.INSTANCE, lexer, ApiParserDefinition.ELEMENT_FACTORY); 47 | } 48 | 49 | 50 | @NotNull 51 | @Override 52 | public TextAttributesKey[] getTokenHighlights(IElementType tokenType) { 53 | TextAttributesKey[] attrs; 54 | if (tokenType instanceof TokenIElementType) { 55 | TokenIElementType myType = (TokenIElementType) tokenType; 56 | int ttype = myType.getAntlrTokenType(); 57 | switch (ttype) { 58 | case ApiLexer.INFO: 59 | case ApiLexer.MAP: 60 | case ApiLexer.STRUCT: 61 | case ApiLexer.TYPE: 62 | case ApiLexer.ATSERVER: 63 | case ApiLexer.ATHANDLER: 64 | case ApiLexer.ATDOC: 65 | case ApiLexer.SERVICE: 66 | case ApiLexer.RETURNS: 67 | case ApiLexer.HTTPMETHOD: 68 | case ApiLexer.INTERFACE: 69 | case ApiLexer.IMPORT: 70 | attrs = ATTR_KEYWORD; 71 | break; 72 | case ApiLexer.GOTYPE: 73 | attrs = ATTR_IDENTIFIER; 74 | break; 75 | case ApiLexer.RAW_STRING: 76 | case ApiLexer.VALUE: 77 | attrs = ATTR_STRING; 78 | break; 79 | case ApiLexer.COMMENT: 80 | attrs = ATTR_COMMENT; 81 | break; 82 | case ApiLexer.COLON: 83 | attrs = ATTR_SEPARATOR; 84 | break; 85 | case ApiLexer.ERRCHAR: 86 | attrs = ATTR_BAD_CHARACTER; 87 | break; 88 | default: 89 | attrs = EMPTY_KEYS; 90 | } 91 | } else { 92 | if (tokenType.equals(TokenType.BAD_CHARACTER) || tokenType.equals(TokenType.ERROR_ELEMENT)) { 93 | attrs = ATTR_BAD_CHARACTER; 94 | } else { 95 | attrs = ATTR_BAD_CHARACTER; 96 | } 97 | } 98 | 99 | return attrs; 100 | } 101 | 102 | } 103 | -------------------------------------------------------------------------------- /goland/src/main/java/cn/xiaoheiban/psi/ApiFile.java: -------------------------------------------------------------------------------- 1 | package cn.xiaoheiban.psi; 2 | 3 | import cn.xiaoheiban.antlr4.ApiParser; 4 | import cn.xiaoheiban.language.ApiFileType; 5 | import cn.xiaoheiban.language.ApiLanguage; 6 | import cn.xiaoheiban.psi.nodes.ApiRootNode; 7 | import com.intellij.extapi.psi.PsiFileBase; 8 | import com.intellij.lang.ASTNode; 9 | import com.intellij.openapi.fileTypes.FileType; 10 | import com.intellij.psi.FileViewProvider; 11 | import com.intellij.psi.PsiElement; 12 | import com.intellij.psi.tree.IElementType; 13 | import org.antlr.jetbrains.adapter.lexer.RuleIElementType; 14 | import org.antlr.jetbrains.adapter.psi.ScopeNode; 15 | import org.apache.commons.collections.map.HashedMap; 16 | import org.jetbrains.annotations.NotNull; 17 | 18 | import java.util.ArrayList; 19 | import java.util.List; 20 | import java.util.Map; 21 | import java.util.Set; 22 | 23 | public class ApiFile extends PsiFileBase { 24 | 25 | public ApiFile(@NotNull FileViewProvider viewProvider) { 26 | super(viewProvider, ApiLanguage.INSTANCE); 27 | } 28 | 29 | @Override 30 | public ScopeNode getContext() { 31 | return null; 32 | } 33 | 34 | @Override 35 | public String toString() { 36 | return "Api File"; 37 | } 38 | 39 | public static Map> findChildren(PsiElement element, Set elementType) { 40 | if (element == null) { 41 | return null; 42 | } 43 | PsiElement[] children = element.getChildren(); 44 | if (children == null) { 45 | return null; 46 | } 47 | Map> ret = new HashedMap(); 48 | for (PsiElement el : children) { 49 | ASTNode node = el.getNode(); 50 | if (node == null) { 51 | continue; 52 | } 53 | IElementType et = node.getElementType(); 54 | List list = ret.get(et); 55 | if (list == null) { 56 | list = new ArrayList<>(); 57 | } 58 | if (elementType.contains(et)) { 59 | list.add(node); 60 | ret.put(et, list); 61 | } 62 | 63 | Map> astNodes = findChildren(el, elementType); 64 | astNodes.forEach((elementType1, astNodes1) -> { 65 | List list1 = ret.get(elementType1); 66 | if (list1 == null) { 67 | list1 = new ArrayList<>(); 68 | } 69 | list1.addAll(astNodes1); 70 | ret.put(elementType1, list1); 71 | }); 72 | } 73 | return ret; 74 | } 75 | 76 | public static List findChildren(PsiElement element, IElementType elementType) { 77 | if (element == null) { 78 | return null; 79 | } 80 | PsiElement[] children = element.getChildren(); 81 | if (children == null) { 82 | return null; 83 | } 84 | List list = new ArrayList<>(); 85 | for (PsiElement el : children) { 86 | ASTNode node = el.getNode(); 87 | if (node == null) { 88 | continue; 89 | } 90 | IElementType et = node.getElementType(); 91 | if (et.equals(elementType)) { 92 | list.add(node); 93 | } 94 | List astNodes = findChildren(el, elementType); 95 | list.addAll(astNodes); 96 | } 97 | return list; 98 | } 99 | 100 | public static ASTNode findChild(PsiElement element, IElementType elementType) { 101 | if (element == null) { 102 | return null; 103 | } 104 | PsiElement[] children = element.getChildren(); 105 | if (children == null) { 106 | return null; 107 | } 108 | List list = new ArrayList<>(); 109 | for (PsiElement el : children) { 110 | ASTNode node = el.getNode(); 111 | if (node == null) { 112 | continue; 113 | } 114 | IElementType et = node.getElementType(); 115 | if (et.equals(elementType)) { 116 | list.add(node); 117 | } 118 | List astNodes = findChildren(el, elementType); 119 | list.addAll(astNodes); 120 | } 121 | if (list.size() > 0) { 122 | return list.get(0); 123 | } 124 | return null; 125 | } 126 | 127 | public static ApiRootNode getRoot(PsiElement element) { 128 | while (true) { 129 | if (element == null) { 130 | return null; 131 | } 132 | if (element instanceof ApiFile) { 133 | return new ApiRootNode(element.getFirstChild().getNode()); 134 | } 135 | ASTNode node = element.getNode(); 136 | if (node == null) { 137 | return null; 138 | } 139 | IElementType elementType = node.getElementType(); 140 | if (elementType instanceof RuleIElementType) { 141 | RuleIElementType ruleElType = (RuleIElementType) elementType; 142 | int ruleIndex = ruleElType.getRuleIndex(); 143 | if (ruleIndex == ApiParser.RULE_api) { 144 | return new ApiRootNode(node); 145 | } 146 | } 147 | element = element.getParent(); 148 | } 149 | } 150 | 151 | @Override 152 | public @NotNull FileType getFileType() { 153 | return ApiFileType.INSTANCE; 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /goland/README.md: -------------------------------------------------------------------------------- 1 | # Go-Zero Plugin 2 | 3 | 一款支持go-zero api语言结构语法高亮、检测以及api、rpc、model快捷生成的插件工具。 4 | 5 | 6 | # idea版本要求 7 | 8 | 不低于193.0(2019.3) 9 | 10 | # 版本特性 11 | 12 | * api语法高亮 13 | * api语法、语义检测 14 | * struct、route、handler重复定义检测 15 | * type跳转到类型声明位置 16 | * 上下文菜单中支持api、rpc、mode相关menu选项 17 | * 代码格式化(option+command+L) 18 | * 代码提示 19 | 20 | # 安装方式 21 | 22 | ## 方式一 23 | 在github的release中找到最新的zip包,下载本地安装即可。(无需解压) 24 | 25 | ## 方式二 26 | 在plugin商店中,搜索`Goctl`安装即可(目前商店中已通过的版本比较低。) 27 | 28 | 29 | # 预览 30 | ![preview](./src/main/resources/static/api_colorful.png) 31 | 32 | # 新建 Api file 33 | 34 | 在工程区域目标文件夹`右键->New-> New Api File ->Empty File/Api Template`,如图: 35 | 36 | ![preview](./src/main/resources/static/new.png) 37 | 38 | # 新建 Proto File 39 | 在工程区目标文件夹`右键->New-> New Proto File -> Empty File/Proto Template`,如图: 40 | 41 | ![preview](./src/main/resources/static/proto.png) 42 | 43 | # 快速生成api/rpc服务 44 | 在目标文件夹`右键->New->Go Zero -> Api Greet Service/Rpc Greet Service` 45 | 46 | ![preview](./src/main/resources/static/service.png) 47 | 48 | # Api/Rpc/Model Code生成 49 | 50 | ## 方法一(工程区域) 51 | 52 | 对应文件(api、proto、sql)`右键->New->Go Zero-> Api/Rpc/Model Code`,如图: 53 | 54 | ![preview](./src/main/resources/static/project_generate_code.png) 55 | 56 | ## 方法二(编辑区域) 57 | 对应文件(api、proto、sql)`右键-> Generate-> Api/Rpc/Model Code` 58 | 59 | 60 | # 错误提示 61 | ![context menu](./src/main/resources/static/alert.png) 62 | 63 | # api语法说明 64 | 65 | ## 关键字 66 | info、map、struct、interface、type、@server、@doc、service、returns、title、desc、author、email、version、group、jwt、summary、handler、get、head、post、put、patch、delete、connect、options、trace、bool、uint8、uint16、uint32、uint64、int8、int16、int32、int64、float32、float64、complex64、complex128、string、int、uint、uintptr、byte、rune。 67 | 68 | ## api文件 69 | 70 | ### 语法顺序 71 | ``` 72 | {import statement}? 73 | 74 | {info block}? 75 | 76 | {type block}? 77 | 78 | {service block}? 79 | ``` 80 | ### import statement 81 | 82 | ##### 是否必须:`NO` 83 | 84 | ##### 语法示例 85 | 86 | ```golang 87 | import "user.api" 88 | import "class.api" 89 | ``` 90 | 91 | #### info block 92 | 93 | ##### 是否必须:`NO` 94 | 95 | ##### 语法示例 96 | ``` 97 | info ( 98 | // ${key}: ${value} 99 | title: "api grammar" 100 | desc: "how to code api" 101 | author: "anqiansong" 102 | email: "anqiansong@xiaoheiban.cn" 103 | ) 104 | ``` 105 | ##### 语法说明: 106 | key:`title`|`desc`|`author`|`email`|`version` 107 | 108 | value:由`""`包围的非换页换行字符 109 | 110 | 111 | ### type block 112 | 113 | ##### 是否必须: `YES` 114 | 115 | ##### 语法示例: 116 | ```golang 117 | type Alias = int 118 | type Alias2 int 119 | type User struct { 120 | Name string 121 | } 122 | type ( 123 | Person struct{} 124 | Student struct { 125 | Gender int `json:"gender"` 126 | Description interface{} `json:"description"` 127 | } 128 | Request struct{} 129 | Response struct{} 130 | ) 131 | ``` 132 | ##### 语法说明 133 | 134 | 和go语法一致,但map的key不支持非基本类型(string、boolean、number)及基本类型的别名 135 | 136 | ### service block 137 | 138 | ##### 是否必须: `YES` 139 | 140 | ##### 语法示例: 141 | 142 | ```golang 143 | @server( 144 | jwt: "Auth" // ${jwtValue}jwt鉴权声明 145 | group: "User" // user服务生成代码后handler、logic按照此值归类 146 | ) 147 | service ${serviceName} { 148 | @doc( 149 | summary: "路由说明" // ${summaryValue} 路由说明 150 | ) 151 | // @server( 152 | // @handler: login 153 | // ) 154 | @handler login 155 | // 路由 156 | post /user/login (Request) returns (Response); 157 | // 可定义多个路由 158 | .... 159 | } 160 | ``` 161 | ##### 语法说明 162 | * ${serviceName}: 服务名称,由英文字母开头,并由字母、数字、下划线(_)、横杠(-)组成 163 | * jwt: jwt鉴权声明,代码该服务需要jwt鉴权 164 | * ${jwtValue}: jwt鉴权参数值 165 | * @doc(...): 路由说明 166 | * summary: 路由说明key 167 | * ${summaryValue}: 路由说明内容,由`""`包裹的字符串 168 | * @server: 内由kv结构组成,支持的key有 `jwt`(service外)、`folder`(service外)、`handler`(service内) 169 | * ${handlerValue}: handler中go文件名称。必须为字母开头,并由字母、数字、下划线(_)组成,不能以`"`包裹 170 | * 路由: 由`小写的请求方法名+路由path (请求体) returns (响应体);`格式组成 171 | * 请求体、响应体:均为可选参必须为已定义的struct,仅支持普通struct,不支持指针,数组,map等其他类型,如需要以上结构请用struct包裹 172 | 173 | # Live Template 174 | Live Template可以加快我们对api文件的编写,比如我们在go文件中输入`main`关键字根据tip回车后会插入一段模板代码 175 | ```golang 176 | func main(){ 177 | 178 | } 179 | ``` 180 | 或者说看到下图你会更加熟悉,曾几何时你还在这里定义过template 181 | ![context menu](./src/main/resources/static/go_live_template.png) 182 | 183 | 下面就进入今天api语法中的模板使用说明吧,我们先来看看service模板的效果 184 | ![context menu](./src/main/resources/static/live_template.gif) 185 | 186 | 首先上一张图了解一下api文件中几个模板生效区域(psiTree元素区域) 187 | ![context menu](./src/main/resources/static/psiTree.png) 188 | 189 | #### 预设模板及生效区域 190 | | 模板关键字 | psiTree生效区域 |描述 191 | | ---- | ---- | ---- | 192 | | @doc | ApiService |doc注释模板| 193 | | doc | ApiService |doc注释模板| 194 | | struct | Struct |struct声明模板| 195 | | info | ApiFile |info block模板| 196 | | type | ApiFile |type group模板| 197 | | handler | ApiService |handler文件名模板| 198 | | get | ApiService |get方法路由模板| 199 | | head | ApiService |head方法路由模板| 200 | | post | ApiService |post方法路由模板| 201 | | put | ApiService |put方法路由模板| 202 | | delete | ApiService |delete方法路由模板| 203 | | connect | ApiService |connect方法路由模板| 204 | | options | ApiService |options方法路由模板| 205 | | trace | ApiService |trace方法路由模板| 206 | | service | ApiFile |service服务block模板| 207 | | json | Tag|Tag literal |tag模板| 208 | | xml | Tag|Tag literal |tag模板| 209 | | path | Tag|Tag literal |tag模板| 210 | | form | Tag|Tag literal |tag模板| 211 | 212 | 关于每个模板对应内容可在`Goland(mac Os)->Preference->Editor->Live Templates-> Api|Api Tags`中查看详细模板内容,如json tag模板内容为 213 | ```golang 214 | json:"$FIELD_NAME$" 215 | ``` 216 | ![context menu](./src/main/resources/static/json_tag.png) 217 | 218 | > 注意: 关键字区分大小写 219 | 220 | 221 | 222 | # 注意事项 223 | 目前api文件中,仅type block支持单行注释,但目前idea并没有对其他block单行注释进行报错,这个是需要后期完善的功能。后续陆续将api文件中都支持单行注释。 224 | 225 | # 待完善功能 226 | * 语法、语义检测优化 227 | * 完善高亮优化 228 | * 完善智能提示 229 | * Find Usages 230 | -------------------------------------------------------------------------------- /goland/src/main/java/cn/xiaoheiban/psi/nodes/ApiRootNode.java: -------------------------------------------------------------------------------- 1 | package cn.xiaoheiban.psi.nodes; 2 | 3 | import cn.xiaoheiban.antlr4.ApiParser; 4 | import cn.xiaoheiban.parser.ApiParserDefinition; 5 | import cn.xiaoheiban.psi.ApiFile; 6 | import com.intellij.lang.ASTNode; 7 | import com.intellij.psi.PsiElement; 8 | import com.intellij.psi.PsiNamedElement; 9 | import com.intellij.psi.tree.IElementType; 10 | import org.antlr.jetbrains.adapter.SymtabUtils; 11 | import org.antlr.jetbrains.adapter.psi.ScopeNode; 12 | import org.apache.commons.collections.map.HashedMap; 13 | import org.jetbrains.annotations.NotNull; 14 | import org.jetbrains.annotations.Nullable; 15 | 16 | import java.util.*; 17 | 18 | public class ApiRootNode extends IPsiNode implements ScopeNode { 19 | 20 | public ApiRootNode(@NotNull ASTNode node) { 21 | super(node); 22 | } 23 | 24 | public Map> getAllNode() { 25 | Set elementTypeSet = new HashSet<>(); 26 | elementTypeSet.add(ApiParserDefinition.rule(ApiParser.RULE_handlerValue)); 27 | elementTypeSet.add(ApiParserDefinition.rule(ApiParser.RULE_structNameId)); 28 | elementTypeSet.add(ApiParserDefinition.rule(ApiParser.RULE_httpRoute)); 29 | return ApiFile.findChildren(this, elementTypeSet); 30 | } 31 | 32 | public static boolean resolve(Map> children, IElementType elementType, String name) { 33 | if (children == null || elementType == null || name == null) { 34 | return true; 35 | } 36 | Set nameSet = new HashSet<>(); 37 | children.forEach((et, astNodes) -> { 38 | if (!et.equals(elementType)) { 39 | return; 40 | } 41 | for (ASTNode node : astNodes) { 42 | nameSet.add(node.getText()); 43 | } 44 | }); 45 | return nameSet.contains(name); 46 | } 47 | 48 | public static Map> getAllDuplicateNode(Map> children) { 49 | Map> tmp = new HashedMap(); 50 | children.forEach((elementType, astNodes) -> { 51 | for (ASTNode node : astNodes) { 52 | IPsiNode psiNode = new IPsiNode(node); 53 | String key = elementType.hashCode() + psiNode.getKey(); 54 | Set set = tmp.get(key); 55 | if (set == null) { 56 | set = new HashSet<>(); 57 | } 58 | if (set.contains(node)) { 59 | continue; 60 | } 61 | set.add(node); 62 | tmp.put(key, set); 63 | } 64 | }); 65 | Map> ret = new HashedMap(); 66 | tmp.forEach((key, set) -> { 67 | if (set.size() > 1) { 68 | Object[] objects = set.toArray(); 69 | Object obj = objects[0]; 70 | ASTNode node = (ASTNode) obj; 71 | ret.put(node.getElementType(), set); 72 | } 73 | }); 74 | return ret; 75 | } 76 | 77 | public Map> getAllStructMap() { 78 | Map> ret = new HashedMap(); 79 | Set nodeSet = new HashSet<>(); 80 | List structs = ApiFile.findChildren(this, ApiParserDefinition.rule(ApiParser.RULE_structType)); 81 | List alias = ApiFile.findChildren(this, ApiParserDefinition.rule(ApiParser.RULE_typeAlias)); 82 | List groupAlias = ApiFile.findChildren(this, ApiParserDefinition.rule(ApiParser.RULE_typeGroupAlias)); 83 | Set children = new HashSet<>(); 84 | children.addAll(structs); 85 | children.addAll(alias); 86 | children.addAll(groupAlias); 87 | for (ASTNode node : children) { 88 | ASTNode childByType = node.findChildByType(ApiParserDefinition.rule(ApiParser.RULE_structNameId)); 89 | if (childByType == null) { 90 | continue; 91 | } 92 | if (nodeSet.contains(childByType)) { 93 | continue; 94 | } 95 | nodeSet.add(childByType); 96 | StructNameNode nameNode = new StructNameNode(childByType); 97 | String text = nameNode.getText(); 98 | Set duplicateNodeSet = ret.get(text); 99 | if (duplicateNodeSet == null) { 100 | duplicateNodeSet = new HashSet<>(); 101 | } 102 | duplicateNodeSet.add(nameNode); 103 | ret.put(text, duplicateNodeSet); 104 | } 105 | return ret; 106 | } 107 | 108 | 109 | @Override 110 | public @Nullable PsiElement resolve(PsiNamedElement element) { 111 | PsiElement psiElement = SymtabUtils.resolve(this, ApiParserDefinition.ELEMENT_FACTORY, element, "/api/apiBody/typeStatement/typeSingleSpec/typeAlias/structNameId/IDENT"); 112 | if (psiElement != null) { 113 | return psiElement; 114 | } 115 | psiElement = SymtabUtils.resolve(this, ApiParserDefinition.ELEMENT_FACTORY, element, "/api/apiBody/typeStatement/typeSingleSpec/typeStruct/structType/structNameId/IDENT"); 116 | if (psiElement != null) { 117 | return psiElement; 118 | } 119 | psiElement = SymtabUtils.resolve(this, ApiParserDefinition.ELEMENT_FACTORY, element, "/api/apiBody/typeStatement/typeGroupSpec/typeGroupBody/typeGroupAlias/structNameId/IDENT"); 120 | if (psiElement != null) { 121 | return psiElement; 122 | } 123 | 124 | psiElement = SymtabUtils.resolve(this, ApiParserDefinition.ELEMENT_FACTORY, element, "/api/apiBody/typeStatement/typeGroupSpec/typeGroupBody/structType/structNameId/IDENT"); 125 | return psiElement; 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /goland/src/main/resources/liveTemplates/api.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 17 | 18 | 24 | 25 | 31 | 32 | 38 | 44 | 50 | 56 | 62 | 68 | 74 | 80 | 81 | 87 | 88 | 94 | 95 | 101 | 102 | 108 | 114 | 115 | 121 | 122 | 128 | 129 | -------------------------------------------------------------------------------- /goland/src/main/resources/META-INF/plugin.xml: -------------------------------------------------------------------------------- 1 | 2 | cn.xiaoheiban.go-zero 3 | Goctl 4 | TAL 5 | TAL support for JetBrains products. 7 |
8 |
9 | Support for Api programming language of tal. 10 |
11 |
12 | go-zero is a web and rpc framework for golang that with 13 | lots of engineering practices builtin. It’s born to ensure the stability of the busy services with resilience design, 14 | and has been serving sites with tens of millions users for years 15 |
16 |
17 |

Features

18 |
    19 |
  • Api File Import
  • 20 |
  • Find reference
  • 21 |
  • Add context menu
  • 22 |
  • Highlighting of api
  • 23 |
  • Grammar and lexer check
  • 24 |
  • Duplicate define check
  • 25 |
  • Keyword completion contributor
  • 26 |
  • Support Code format
  • 27 |
28 | ]]>
29 | 30 | 32 | com.intellij.modules.lang 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 68 | 69 | 70 | 71 | 73 | 74 | 75 | 76 | 77 | 78 | 80 | 82 | 84 | 85 | 86 | 87 | 88 | 90 | 92 | 94 | 96 | 98 | 99 | 100 |
-------------------------------------------------------------------------------- /goland/src/main/java/cn/xiaoheiban/util/Exec.java: -------------------------------------------------------------------------------- 1 | package cn.xiaoheiban.util; 2 | 3 | import cn.xiaoheiban.contsant.Constant; 4 | import cn.xiaoheiban.io.IO; 5 | import cn.xiaoheiban.notification.Notification; 6 | import com.intellij.execution.configurations.GeneralCommandLine; 7 | import com.intellij.openapi.project.Project; 8 | import com.intellij.openapi.util.text.StringUtil; 9 | 10 | import java.io.File; 11 | import java.util.Arrays; 12 | import java.util.List; 13 | 14 | public class Exec { 15 | 16 | public static String lookPath(Project project, String s) { 17 | 18 | String os = System.getProperty("os.name"); 19 | os = os.toLowerCase(); 20 | String pathSeparator = File.pathSeparator; 21 | String pathCmd = ""; 22 | if (os.startsWith("mac") || os.startsWith("linux")) { 23 | pathCmd = "echo $PATH"; 24 | } else if (os.startsWith("windows")) { 25 | pathCmd = "echo %Path%"; 26 | if (!s.endsWith(".exe")){ 27 | s=s.concat(".exe"); 28 | } 29 | pathSeparator = ";"; 30 | } else { 31 | Notification.getInstance().error(project, "unsupport os: " + os); 32 | return null; 33 | } 34 | 35 | if (s.contains(File.separator)) { 36 | if (findExecutable(s)) { 37 | return s; 38 | } 39 | } 40 | ExecResult result = run(project, pathCmd); 41 | if (result == null) { 42 | return ""; 43 | } 44 | if (!StringUtil.isEmptyOrSpaces(result.getStderr())) { 45 | Notification.getInstance().error(project, result.getStderr()); 46 | return ""; 47 | } 48 | String path = result.getStdout(); 49 | if (StringUtil.isEmptyOrSpaces(path)) { 50 | return ""; 51 | } 52 | 53 | String[] splits = path.split(pathSeparator); 54 | for (String dir : splits) { 55 | // Unix shell semantics: path element "" means "." 56 | if (StringUtil.isEmptyOrSpaces(dir)) { 57 | dir = "."; 58 | } 59 | dir = Stringx.trimSpace(dir); 60 | path = dir + File.separator + s; 61 | boolean find = findExecutable(path); 62 | if (!find) { 63 | continue; 64 | } 65 | return path; 66 | } 67 | return ""; 68 | } 69 | 70 | private static boolean findExecutable(String path) { 71 | File file = new File(path); 72 | return file.canExecute(); 73 | } 74 | 75 | private static List warpCmd(Project project, String arg) { 76 | String os = System.getProperty("os.name"); 77 | os = os.toLowerCase(); 78 | if (os.startsWith("mac") || os.startsWith("linux")) { 79 | String shell = System.getenv("SHELL"); 80 | if (StringUtil.isEmptyOrSpaces(shell)) { 81 | shell = Constant.SH; 82 | } 83 | return Arrays.asList(shell, "-c", arg); 84 | } else if (os.startsWith("windows")) { 85 | return Arrays.asList(Constant.CMD, "/c", arg); 86 | } else { 87 | Notification.getInstance().error(project, "unsupport os: " + os); 88 | return null; 89 | } 90 | } 91 | 92 | public static class ExecResult { 93 | private String stdout; 94 | private String stderr; 95 | 96 | public String getStdout() { 97 | return stdout; 98 | } 99 | 100 | public void setStdout(String stdout) { 101 | this.stdout = stdout; 102 | } 103 | 104 | public String getStderr() { 105 | return stderr; 106 | } 107 | 108 | public void setStderr(String stderr) { 109 | this.stderr = stderr; 110 | } 111 | } 112 | 113 | public static ExecResult run(Project project, String arg) { 114 | ExecResult result = new ExecResult(); 115 | try { 116 | List cmd = warpCmd(project, arg); 117 | if (cmd == null) { 118 | return null; 119 | } 120 | GeneralCommandLine commandLine = new GeneralCommandLine(cmd); 121 | Process process = commandLine.createProcess(); 122 | result.setStdout(IO.read(process.getInputStream())); 123 | result.setStderr(IO.read(process.getErrorStream())); 124 | return result; 125 | } catch (Exception e) { 126 | e.printStackTrace(); 127 | result.setStderr(e.toString()); 128 | } 129 | return result; 130 | } 131 | 132 | public static boolean runGoctl(Project project, String arg) { 133 | String goctl = lookPath(project, "goctl"); 134 | Notification.getInstance().log(project, "goctl:" + goctl); 135 | if (StringUtil.isEmptyOrSpaces(goctl)) { 136 | Notification.getInstance().error(project, "goctl not found"); 137 | return false; 138 | } 139 | String cmd = goctl + " " + arg; 140 | ExecResult result = run(project, cmd); 141 | if (result == null) { 142 | return false; 143 | } 144 | Notification.getInstance().log(project, "$ goctl " + arg); 145 | String stdout = result.getStdout(); 146 | String stderr = result.getStderr(); 147 | if (!StringUtil.isEmptyOrSpaces(stdout)) { 148 | if (stdout.startsWith(Constant.PREFIX_SUCCESS)) { 149 | Notification.getInstance().log(project, result.getStdout()); 150 | } else if (stdout.startsWith(Constant.PREFIX_WARNING)) { 151 | Notification.getInstance().warning(project, result.getStdout()); 152 | } else if (stdout.startsWith(Constant.PREFIX_ERROR) || stdout.contains(Constant.PREFIX_ERROR2)) { 153 | Notification.getInstance().error(project, result.getStdout()); 154 | return false; 155 | } else { 156 | Notification.getInstance().log(project, result.getStdout()); 157 | } 158 | return true; 159 | } 160 | if (!StringUtil.isEmptyOrSpaces(stderr)) { 161 | Notification.getInstance().error(project, stderr); 162 | } 163 | return false; 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /goland/gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | # Determine the Java command to use to start the JVM. 86 | if [ -n "$JAVA_HOME" ] ; then 87 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 88 | # IBM's JDK on AIX uses strange locations for the executables 89 | JAVACMD="$JAVA_HOME/jre/sh/java" 90 | else 91 | JAVACMD="$JAVA_HOME/bin/java" 92 | fi 93 | if [ ! -x "$JAVACMD" ] ; then 94 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 95 | 96 | Please set the JAVA_HOME variable in your environment to match the 97 | location of your Java installation." 98 | fi 99 | else 100 | JAVACMD="java" 101 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 102 | 103 | Please set the JAVA_HOME variable in your environment to match the 104 | location of your Java installation." 105 | fi 106 | 107 | # Increase the maximum file descriptors if we can. 108 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 109 | MAX_FD_LIMIT=`ulimit -H -n` 110 | if [ $? -eq 0 ] ; then 111 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 112 | MAX_FD="$MAX_FD_LIMIT" 113 | fi 114 | ulimit -n $MAX_FD 115 | if [ $? -ne 0 ] ; then 116 | warn "Could not set maximum file descriptor limit: $MAX_FD" 117 | fi 118 | else 119 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 120 | fi 121 | fi 122 | 123 | # For Darwin, add options to specify how the application appears in the dock 124 | if $darwin; then 125 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 126 | fi 127 | 128 | # For Cygwin or MSYS, switch paths to Windows format before running java 129 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 130 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 131 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 132 | JAVACMD=`cygpath --unix "$JAVACMD"` 133 | 134 | # We build the pattern for arguments to be converted via cygpath 135 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 136 | SEP="" 137 | for dir in $ROOTDIRSRAW ; do 138 | ROOTDIRS="$ROOTDIRS$SEP$dir" 139 | SEP="|" 140 | done 141 | OURCYGPATTERN="(^($ROOTDIRS))" 142 | # Add a user-defined pattern to the cygpath arguments 143 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 144 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 145 | fi 146 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 147 | i=0 148 | for arg in "$@" ; do 149 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 150 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 151 | 152 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 153 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 154 | else 155 | eval `echo args$i`="\"$arg\"" 156 | fi 157 | i=`expr $i + 1` 158 | done 159 | case $i in 160 | 0) set -- ;; 161 | 1) set -- "$args0" ;; 162 | 2) set -- "$args0" "$args1" ;; 163 | 3) set -- "$args0" "$args1" "$args2" ;; 164 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 165 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 166 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 167 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 168 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 169 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 170 | esac 171 | fi 172 | 173 | # Escape application args 174 | save () { 175 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 176 | echo " " 177 | } 178 | APP_ARGS=`save "$@"` 179 | 180 | # Collect all arguments for the java command, following the shell quoting and substitution rules 181 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 182 | 183 | exec "$JAVACMD" "$@" 184 | -------------------------------------------------------------------------------- /vscode/src/goctlDeclaration.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | 3 | enum CodeLineType { 4 | Type, 5 | InType, 6 | Url, 7 | None 8 | } 9 | 10 | 11 | function REX_TYPE(target: string): string { 12 | return `(|type)\\s*\\b${target}\\b\\s*(|struct)\\s*\\{`; 13 | } 14 | 15 | function REX_IN_TYPE(target: string): string { 16 | return `(|\\*|\\[\\]|\\[\\]\\*)\\b${target}\\b\\s*\`(json|form|path)`; 17 | } 18 | 19 | function REX_URL_RETURN(target: string): string { 20 | return `returns\\s*\\(\\s*\\b${target}\\b\\s*\\)`; 21 | } 22 | 23 | function REX_URL_METHOD(target: string): string { 24 | return `\\(\\s*\\b${target}\\b\\s*\\)`; 25 | } 26 | 27 | export class GoctlDefinitionProvider implements vscode.DefinitionProvider { 28 | provideDefinition(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken): vscode.ProviderResult { 29 | 30 | const word = document.getText(document.getWordRangeAtPosition(position)); 31 | 32 | const apiDoc = document.getText(); 33 | 34 | const line = document.lineAt(position.line).text; 35 | const lineType = matchLineTypeDefinition(line, word); 36 | 37 | switch (lineType) { 38 | case CodeLineType.Type: 39 | return this.findInTypeDefinitionAndUrl(document, word, position.line); 40 | case CodeLineType.InType: 41 | return this.findTypeAndUrlDefinition(document, word, position.line); 42 | case CodeLineType.Url: 43 | return this.findTypeDefinition(document, word, position.line); 44 | case CodeLineType.None: 45 | return null; 46 | default: 47 | console.log("code line type don't matched"); 48 | return null; 49 | } 50 | } 51 | 52 | private findTypeDefinition(document: vscode.TextDocument, target: string, targetLineNum: number): vscode.ProviderResult { 53 | 54 | let locations: vscode.Location[] = []; 55 | 56 | const docData = document.getText(); 57 | const lines = docData.split('\n'); 58 | for (let lineIndex = 0; lineIndex < lines.length; lineIndex++) { 59 | if (lineIndex === targetLineNum) { 60 | continue; 61 | } 62 | const line = lines[lineIndex]; 63 | // type 64 | const messageDefinitionRegexMatch = new RegExp(REX_TYPE(target)).exec(line); 65 | if (messageDefinitionRegexMatch && messageDefinitionRegexMatch.length) { 66 | const range = this.getTargetLocationInline(lineIndex, line, target, messageDefinitionRegexMatch); 67 | locations.push(new vscode.Location(document.uri, range)); 68 | } 69 | } 70 | return locations; 71 | } 72 | 73 | 74 | private findInTypeDefinitionAndUrl(document: vscode.TextDocument, target: string, targetLineNum: number): vscode.ProviderResult { 75 | let locations: vscode.Location[] = []; 76 | 77 | const docData = document.getText(); 78 | const lines = docData.split('\n'); 79 | for (let lineIndex = 0; lineIndex < lines.length; lineIndex++) { 80 | if (lineIndex === targetLineNum) { 81 | continue; 82 | } 83 | const line = lines[lineIndex]; 84 | // intype 85 | const messageDefinitionRegexMatch = new RegExp(REX_IN_TYPE(target)).exec(line); 86 | if (messageDefinitionRegexMatch && messageDefinitionRegexMatch.length) { 87 | const range = this.getTargetLocationInline(lineIndex, line, target, messageDefinitionRegexMatch); 88 | locations.push(new vscode.Location(document.uri, range)); 89 | continue; 90 | } 91 | // service request 92 | const requestMessageDefinitionRegexMatch = new RegExp(REX_URL_METHOD(target)).exec(line); 93 | if (requestMessageDefinitionRegexMatch && requestMessageDefinitionRegexMatch.length) { 94 | const range = this.getTargetLocationInline(lineIndex, line, target, requestMessageDefinitionRegexMatch); 95 | locations.push(new vscode.Location(document.uri, range)); 96 | continue; 97 | } 98 | // service request 99 | const responseMessageDefinitionRegexMatch = new RegExp(REX_URL_RETURN(target)).exec(line); 100 | if (responseMessageDefinitionRegexMatch && responseMessageDefinitionRegexMatch.length) { 101 | const range = this.getTargetLocationInline(lineIndex, line, target, responseMessageDefinitionRegexMatch); 102 | locations.push(new vscode.Location(document.uri, range)); 103 | continue; 104 | } 105 | } 106 | return locations; 107 | } 108 | private findTypeAndUrlDefinition(document: vscode.TextDocument, target: string, targetLineNum: number): vscode.ProviderResult { 109 | let locations: vscode.Location[] = []; 110 | 111 | const docData = document.getText(); 112 | const lines = docData.split('\n'); 113 | for (let lineIndex = 0; lineIndex < lines.length; lineIndex++) { 114 | if (lineIndex === targetLineNum) { 115 | continue; 116 | } 117 | const line = lines[lineIndex]; 118 | // type 119 | const messageDefinitionRegexMatch = new RegExp(REX_TYPE(target)).exec(line); 120 | if (messageDefinitionRegexMatch && messageDefinitionRegexMatch.length) { 121 | const range = this.getTargetLocationInline(lineIndex, line, target, messageDefinitionRegexMatch); 122 | locations.push(new vscode.Location(document.uri, range)); 123 | } 124 | // service request 125 | const requestMessageDefinitionRegexMatch = new RegExp(REX_URL_METHOD(target)).exec(line); 126 | if (requestMessageDefinitionRegexMatch && requestMessageDefinitionRegexMatch.length) { 127 | const range = this.getTargetLocationInline(lineIndex, line, target, requestMessageDefinitionRegexMatch); 128 | locations.push(new vscode.Location(document.uri, range)); 129 | } 130 | // service request 131 | const responseMessageDefinitionRegexMatch = new RegExp(REX_URL_RETURN(target)).exec(line); 132 | if (responseMessageDefinitionRegexMatch && responseMessageDefinitionRegexMatch.length) { 133 | const range = this.getTargetLocationInline(lineIndex, line, target, responseMessageDefinitionRegexMatch); 134 | locations.push(new vscode.Location(document.uri, range)); 135 | } 136 | } 137 | return locations; 138 | } 139 | 140 | private getTargetLocationInline(lineIndex: number, line: string, target: string, definitionRegexMatch: RegExpExecArray): vscode.Range { 141 | 142 | const matchedStr = definitionRegexMatch[0]; 143 | const index = line.indexOf(matchedStr) + matchedStr.indexOf(target); 144 | 145 | const definitionStartPosition = new vscode.Position(lineIndex, index); 146 | const definitionEndPosition = new vscode.Position(lineIndex, index + target.length); 147 | return new vscode.Range(definitionStartPosition, definitionEndPosition); 148 | } 149 | } 150 | 151 | 152 | function matchLineTypeDefinition(line: string, target: string): CodeLineType { 153 | 154 | if (line.match(REX_TYPE(target))) { 155 | return CodeLineType.Type; 156 | } else if ((line.match(REX_IN_TYPE(target)))) { 157 | return CodeLineType.InType; 158 | } else if (line.match(REX_URL_RETURN(target))) { 159 | return CodeLineType.Url; 160 | } else if (line.match(REX_URL_METHOD(target))) { 161 | return CodeLineType.Url; 162 | } 163 | return CodeLineType.None; 164 | } -------------------------------------------------------------------------------- /goland/src/main/java/cn/xiaoheiban/template/ApiLiveTemplateContextType.java: -------------------------------------------------------------------------------- 1 | 2 | package cn.xiaoheiban.template; 3 | 4 | import cn.xiaoheiban.antlr4.ApiLexer; 5 | import cn.xiaoheiban.antlr4.ApiParser; 6 | import cn.xiaoheiban.highlighting.ApiSyntaxHighlighter; 7 | import cn.xiaoheiban.language.ApiLanguage; 8 | import cn.xiaoheiban.parser.ApiParserDefinition; 9 | import cn.xiaoheiban.psi.ApiFile; 10 | import com.intellij.codeInsight.template.TemplateContextType; 11 | import com.intellij.lang.ASTNode; 12 | import com.intellij.openapi.fileTypes.SyntaxHighlighter; 13 | import com.intellij.psi.*; 14 | import com.intellij.psi.impl.source.tree.LeafPsiElement; 15 | import com.intellij.psi.tree.IElementType; 16 | import com.intellij.psi.util.PsiTreeUtil; 17 | import com.intellij.psi.util.PsiUtilCore; 18 | import com.intellij.util.ObjectUtils; 19 | import org.jetbrains.annotations.NonNls; 20 | import org.jetbrains.annotations.NotNull; 21 | import org.jetbrains.annotations.Nullable; 22 | 23 | abstract public class ApiLiveTemplateContextType extends TemplateContextType { 24 | protected ApiLiveTemplateContextType(@NotNull @NonNls String id, 25 | @NotNull String presentableName, 26 | @Nullable Class baseContextType) { 27 | super(id, presentableName, baseContextType); 28 | } 29 | 30 | @Override 31 | public boolean isInContext(@NotNull PsiFile file, int offset) { 32 | if (PsiUtilCore.getLanguageAtOffset(file, offset).isKindOf(ApiLanguage.INSTANCE)) { 33 | PsiElement psiElement = ObjectUtils.notNull(file.findElementAt(offset), file); 34 | if (!acceptLeaf()) { 35 | psiElement = getFirstCompositeElement(psiElement); 36 | } 37 | return psiElement != null && isInContext(psiElement); 38 | } 39 | 40 | return false; 41 | } 42 | 43 | protected boolean acceptLeaf() { 44 | return false; 45 | } 46 | 47 | @Nullable 48 | public static PsiElement prevVisibleLeafOrNewLine(PsiElement element) { 49 | PsiElement prevLeaf = element; 50 | while ((prevLeaf = PsiTreeUtil.prevLeaf(prevLeaf)) != null) { 51 | if (prevLeaf instanceof PsiComment || prevLeaf instanceof PsiErrorElement) { 52 | continue; 53 | } 54 | if (prevLeaf instanceof PsiWhiteSpace) { 55 | if (prevLeaf.textContains('\n')) { 56 | return prevLeaf; 57 | } 58 | continue; 59 | } 60 | break; 61 | } 62 | return prevLeaf; 63 | } 64 | 65 | @Nullable 66 | private static PsiElement getFirstCompositeElement(@Nullable PsiElement at) { 67 | if (at instanceof PsiComment || at instanceof LeafPsiElement && ((LeafPsiElement) at).getElementType() == ApiParserDefinition.token(ApiLexer.VALUE)) 68 | return at; 69 | PsiElement result = at; 70 | while (result != null && (result instanceof PsiWhiteSpace || result.getNode().getChildren(null).length == 0)) { 71 | result = result.getParent(); 72 | } 73 | return result; 74 | } 75 | 76 | protected abstract boolean isInContext(@NotNull PsiElement element); 77 | 78 | @Override 79 | public SyntaxHighlighter createHighlighter() { 80 | return new ApiSyntaxHighlighter(); 81 | } 82 | 83 | 84 | // struct域 85 | public static class Struct extends ApiLiveTemplateContextType { 86 | protected Struct() { 87 | super("API_STRUCT", "Struct", ApiEverywhereContextType.class); 88 | } 89 | 90 | @Override 91 | protected boolean isInContext(@NotNull PsiElement element) { 92 | ASTNode node = element.getNode(); 93 | if (node == null) { 94 | return false; 95 | } 96 | ASTNode treeParent = node.getTreeParent(); 97 | if (treeParent == null) { 98 | return false; 99 | } 100 | 101 | return treeParent.getElementType().equals(ApiParserDefinition.rule(ApiParser.RULE_normalFieldType)); 102 | } 103 | } 104 | 105 | // api file域 106 | public static class ApiFile extends ApiLiveTemplateContextType { 107 | protected ApiFile() { 108 | super("API_FILE", "ApiFile", ApiEverywhereContextType.class); 109 | } 110 | 111 | @Override 112 | protected boolean isInContext(@NotNull PsiElement element) { 113 | ASTNode node = element.getNode(); 114 | if (node == null) { 115 | return false; 116 | } 117 | ASTNode treeParent = node.getTreeParent(); 118 | if (treeParent == null) { 119 | return false; 120 | } 121 | 122 | return treeParent.getElementType().equals(ApiParserDefinition.rule(ApiParser.RULE_apiBody)) || treeParent.getElementType().equals(ApiParserDefinition.rule(ApiParser.RULE_api)); 123 | } 124 | } 125 | 126 | // service域 127 | public static class ApiService extends ApiLiveTemplateContextType { 128 | protected ApiService() { 129 | super("API_SERVICE", "ApiService", ApiEverywhereContextType.class); 130 | } 131 | 132 | @Override 133 | protected boolean isInContext(@NotNull PsiElement element) { 134 | ASTNode node = element.getNode(); 135 | while (true) { 136 | if (node == null) { 137 | return false; 138 | } 139 | if (node.getElementType().equals(ApiParserDefinition.rule(ApiParser.RULE_serviceStatement))) { 140 | return true; 141 | } 142 | if (node.getElementType().equals(ApiParserDefinition.rule(ApiParser.RULE_serviceSpec))) { 143 | return true; 144 | } 145 | if (node.getElementType().equals(ApiParserDefinition.rule(ApiParser.RULE_serviceBody))) { 146 | return true; 147 | } 148 | node = node.getTreeParent(); 149 | } 150 | } 151 | } 152 | 153 | 154 | public static class Tag extends ApiLiveTemplateContextType { 155 | protected Tag() { 156 | super("API_TAG", "Tag", ApiEverywhereContextType.class); 157 | } 158 | 159 | @Override 160 | protected boolean isInContext(@NotNull PsiElement element) { 161 | if (element.getNode().getElementType() == ApiParserDefinition.IDENTIFIER) { 162 | if (isInsideFieldTypeDeclaration(element)) { 163 | return true; 164 | } 165 | if (isInsideFieldTypeDeclaration(prevVisibleLeafOrNewLine(element))) { 166 | return true; 167 | } 168 | } 169 | return false; 170 | } 171 | 172 | private static boolean isInsideFieldTypeDeclaration(@Nullable PsiElement element) { 173 | if (element != null) { 174 | PsiElement parent = element.getParent(); 175 | ASTNode node = parent.getNode(); 176 | if (node == null) { 177 | return false; 178 | } 179 | ASTNode normalTypeNode = node.getTreeParent(); 180 | if (normalTypeNode == null) { 181 | return false; 182 | } 183 | ASTNode filedNode = normalTypeNode.getTreeParent(); 184 | if (filedNode == null) { 185 | return false; 186 | } 187 | IElementType elementType = filedNode.getElementType(); 188 | return elementType.equals(ApiParserDefinition.rule(ApiParser.RULE_typeFiled)); 189 | } 190 | return false; 191 | } 192 | 193 | @Override 194 | protected boolean acceptLeaf() { 195 | return true; 196 | } 197 | } 198 | 199 | public static class TagLiteral extends ApiLiveTemplateContextType { 200 | protected TagLiteral() { 201 | super("API_TAG_LITERAL", "Tag literal", ApiEverywhereContextType.class); 202 | } 203 | 204 | @Override 205 | protected boolean isInContext(@NotNull PsiElement element) { 206 | ASTNode node = element.getNode(); 207 | if (node == null) { 208 | return false; 209 | } 210 | return node.getElementType().equals(ApiParserDefinition.rule(ApiParser.RULE_tag)); 211 | } 212 | } 213 | 214 | } 215 | -------------------------------------------------------------------------------- /goland/src/main/java/cn/xiaoheiban/parser/ApiParserDefinition.java: -------------------------------------------------------------------------------- 1 | package cn.xiaoheiban.parser; 2 | 3 | import cn.xiaoheiban.antlr4.ApiLexer; 4 | import cn.xiaoheiban.antlr4.ApiParser; 5 | import cn.xiaoheiban.language.ApiLanguage; 6 | import cn.xiaoheiban.psi.ApiFile; 7 | import cn.xiaoheiban.psi.nodes.*; 8 | import com.intellij.lang.ASTNode; 9 | import com.intellij.lang.ParserDefinition; 10 | import com.intellij.lang.PsiParser; 11 | import com.intellij.lexer.Lexer; 12 | import com.intellij.openapi.project.Project; 13 | import com.intellij.psi.FileViewProvider; 14 | import com.intellij.psi.PsiElement; 15 | import com.intellij.psi.PsiFile; 16 | import com.intellij.psi.tree.IElementType; 17 | import com.intellij.psi.tree.IFileElementType; 18 | import com.intellij.psi.tree.TokenSet; 19 | import org.antlr.jetbrains.adapter.lexer.AntlrLexerAdapter; 20 | import org.antlr.jetbrains.adapter.lexer.PsiElementTypeFactory; 21 | import org.antlr.jetbrains.adapter.lexer.RuleIElementType; 22 | import org.antlr.jetbrains.adapter.lexer.TokenIElementType; 23 | import org.antlr.jetbrains.adapter.parser.AntlrParserAdapter; 24 | import org.antlr.jetbrains.adapter.psi.AntlrPsiNode; 25 | import org.antlr.v4.runtime.Parser; 26 | import org.antlr.v4.runtime.tree.ParseTree; 27 | import org.jetbrains.annotations.NotNull; 28 | 29 | import java.lang.reflect.Method; 30 | import java.util.*; 31 | import java.util.function.Function; 32 | 33 | import static cn.xiaoheiban.antlr4.ApiLexer.*; 34 | 35 | public class ApiParserDefinition implements ParserDefinition { 36 | public static final IFileElementType FILE = new IFileElementType(ApiLanguage.INSTANCE); 37 | public static final PsiElementTypeFactory ELEMENT_FACTORY = PsiElementTypeFactory 38 | .builder() 39 | .language(ApiLanguage.INSTANCE) 40 | .parser(new ApiParser(null)) 41 | .build(); 42 | 43 | private static final List tokenIElementTypes = ELEMENT_FACTORY.getTokenIElementTypes(); 44 | private static final List TOKEN_TYPES = ELEMENT_FACTORY.getTokenIElementTypes(); 45 | private static final List RULE_TYPES = ELEMENT_FACTORY.getRuleIElementTypes(); 46 | private final Map> elementFactories = new HashMap<>(); 47 | public static TokenIElementType LPAREN = tokenIElementTypes.get(ApiLexer.LPAREN); 48 | public static TokenIElementType RPAREN = tokenIElementTypes.get(ApiLexer.RPAREN); 49 | public static TokenIElementType LBRACE = tokenIElementTypes.get(ApiLexer.LBRACE); 50 | public static TokenIElementType RBRACE = tokenIElementTypes.get(ApiLexer.RBRACE); 51 | public static TokenIElementType LBRACK = tokenIElementTypes.get(ApiLexer.LBRACK); 52 | public static TokenIElementType RBRACK = tokenIElementTypes.get(ApiLexer.RBRACK); 53 | public static TokenIElementType IDENTIFIER = tokenIElementTypes.get(IDENT); 54 | 55 | public static final TokenSet COMMENTS = ELEMENT_FACTORY.createTokenSet(ApiLexer.COMMENT); 56 | public static final TokenSet WHITESPACE = ELEMENT_FACTORY.createTokenSet(ApiLexer.WS); 57 | public static final TokenSet STRING = ELEMENT_FACTORY.createTokenSet(ApiLexer.VALUE, ApiLexer.RAW_STRING); 58 | 59 | private final Map parserRuleMethods = createParserRuleMethods(); 60 | 61 | ApiParserDefinition() { 62 | register(ApiParser.RULE_api, ApiRootNode::new); 63 | register(ApiParser.RULE_apiBody, ApiBodyNode::new); 64 | register(ApiParser.RULE_importValue, ImportValueNode::new); 65 | register(ApiParser.RULE_structNameId, StructNameNode::new); 66 | register(ApiParser.RULE_structType, StructNode::new); 67 | register(ApiParser.RULE_typeFiled, FieldNode::new); 68 | register(ApiParser.RULE_anonymousField, AnonymousField::new); 69 | register(ApiParser.RULE_normalField, NormalField::new); 70 | register(ApiParser.RULE_referenceId, ReferenceIdNode::new); 71 | register(ApiParser.RULE_serviceSpec, ServiceNode::new); 72 | register(ApiParser.RULE_serviceRoute, ServiceRouteNode::new); 73 | register(ApiParser.RULE_httpRoute, HttpRouteNode::new); 74 | register(ApiParser.RULE_serviceName, ServiceNameNode::new); 75 | register(ApiParser.RULE_handlerValue, HandlerValueNode::new); 76 | } 77 | 78 | private void register(int rule, Function factory) { 79 | if (elementFactories.containsKey(rule)) { 80 | throw new IllegalStateException("Duplicate rule"); 81 | } 82 | elementFactories.put(rule, factory); 83 | } 84 | 85 | private Map createParserRuleMethods() { 86 | Map result = new HashMap<>(); 87 | for (String ruleName : ApiParser.ruleNames) { 88 | try { 89 | Method method = ApiParser.class.getMethod(ruleName); 90 | result.put(ruleName, method); 91 | } catch (NoSuchMethodException e) { 92 | e.printStackTrace(); 93 | } 94 | } 95 | return Collections.unmodifiableMap(result); 96 | } 97 | 98 | @Override 99 | public @NotNull Lexer createLexer(Project project) { 100 | ApiLexer lexer = new ApiLexer(null); 101 | return new AntlrLexerAdapter(ApiLanguage.INSTANCE, lexer, ELEMENT_FACTORY); 102 | } 103 | 104 | @Override 105 | public PsiParser createParser(Project project) { 106 | final ApiParser apiParser = new ApiParser(null); 107 | return new AntlrParserAdapter(ApiLanguage.INSTANCE, apiParser, ELEMENT_FACTORY) { 108 | 109 | @Override 110 | protected ParseTree parse(Parser parser, IElementType root) { 111 | if (root instanceof IFileElementType) { 112 | return ((ApiParser) parser).api(); 113 | } 114 | if (root instanceof RuleIElementType) { 115 | RuleIElementType type = (RuleIElementType) root; 116 | String ruleName = ruleNames[type.getRuleIndex()]; 117 | return parserRule(apiParser, ruleName); 118 | } 119 | throw new UnsupportedOperationException(String.valueOf(root.getIndex())); 120 | } 121 | }; 122 | } 123 | 124 | @NotNull 125 | private ParseTree parserRule(ApiParser parser, String ruleName) { 126 | try { 127 | Method method = parserRuleMethods.get(ruleName); 128 | if (method == null) { 129 | throw new IllegalStateException("Not a parser rule: " + ruleName); 130 | } 131 | return (ParseTree) method.invoke(parser); 132 | } catch (Exception e) { 133 | throw new IllegalStateException("Exception in parser rule: " + ruleName, e); 134 | } 135 | } 136 | 137 | @Override 138 | public IFileElementType getFileNodeType() { 139 | return FILE; 140 | } 141 | 142 | @Override 143 | public @NotNull TokenSet getWhitespaceTokens() { 144 | return WHITESPACE; 145 | } 146 | 147 | @Override 148 | public @NotNull TokenSet getCommentTokens() { 149 | return COMMENTS; 150 | } 151 | 152 | @Override 153 | public @NotNull TokenSet getStringLiteralElements() { 154 | return STRING; 155 | } 156 | 157 | @Override 158 | public SpaceRequirements spaceExistanceTypeBetweenTokens(ASTNode left, ASTNode right) { 159 | return SpaceRequirements.MAY; 160 | } 161 | 162 | @Override 163 | public @NotNull PsiElement createElement(ASTNode node) { 164 | IElementType elType = node.getElementType(); 165 | if (elType instanceof TokenIElementType) { 166 | return new AntlrPsiNode(node); 167 | } 168 | if (!(elType instanceof RuleIElementType)) { 169 | return new AntlrPsiNode(node); 170 | } 171 | RuleIElementType ruleElType = (RuleIElementType) elType; 172 | int ruleIndex = ruleElType.getRuleIndex(); 173 | if (elementFactories.containsKey(ruleIndex)) { 174 | Function factory = elementFactories.get(ruleIndex); 175 | return factory.apply(node); 176 | } 177 | return new AntlrPsiNode(node); 178 | } 179 | 180 | @Override 181 | public PsiFile createFile(FileViewProvider viewProvider) { 182 | return new ApiFile(viewProvider); 183 | } 184 | 185 | public static IElementType rule(int rule) { 186 | return (IElementType) RULE_TYPES.get(rule); 187 | } 188 | 189 | public static TokenIElementType token(int token) { 190 | return TOKEN_TYPES.get(token); 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /vscode/syntaxes/goctl.tmLanguage.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://raw.githubusercontent.com/martinring/tmlanguage/master/tmlanguage.json", 3 | "name": "goctl", 4 | "scopeName": "source.goctl", 5 | "patterns": [ 6 | { 7 | "include": "#comments" 8 | }, 9 | { 10 | "include": "#service" 11 | }, 12 | { 13 | "comment": "Interpreted string literals", 14 | "begin": "\"", 15 | "beginCaptures": { 16 | "0": { 17 | "name": "punctuation.definition.string.begin.go" 18 | } 19 | }, 20 | "end": "\"", 21 | "endCaptures": { 22 | "0": { 23 | "name": "punctuation.definition.string.end.go" 24 | } 25 | }, 26 | "name": "string.quoted.double.go", 27 | "patterns": [ 28 | { 29 | "include": "#string_escaped_char" 30 | }, 31 | { 32 | "include": "#string_placeholder" 33 | } 34 | ] 35 | }, 36 | { 37 | "comment": "Raw string literals", 38 | "begin": "`", 39 | "beginCaptures": { 40 | "0": { 41 | "name": "punctuation.definition.string.begin.go" 42 | } 43 | }, 44 | "end": "`", 45 | "endCaptures": { 46 | "0": { 47 | "name": "punctuation.definition.string.end.go" 48 | } 49 | }, 50 | "name": "string.quoted.raw.go", 51 | "patterns": [ 52 | { 53 | "include": "#string_placeholder" 54 | } 55 | ] 56 | }, 57 | { 58 | "comment": "Syntax error receiving channels", 59 | "match": "<\\-([\\t ]+)chan\\b", 60 | "captures": { 61 | "1": { 62 | "name": "invalid.illegal.receive-channel.go" 63 | } 64 | } 65 | }, 66 | { 67 | "comment": "Syntax error sending channels", 68 | "match": "\\bchan([\\t ]+)<-", 69 | "captures": { 70 | "1": { 71 | "name": "invalid.illegal.send-channel.go" 72 | } 73 | } 74 | }, 75 | { 76 | "comment": "Syntax error using slices", 77 | "match": "\\[\\](\\s+)", 78 | "captures": { 79 | "1": { 80 | "name": "invalid.illegal.slice.go" 81 | } 82 | } 83 | }, 84 | { 85 | "comment": "Syntax error numeric literals", 86 | "match": "\\b0[0-7]*[89]\\d*\\b", 87 | "name": "invalid.illegal.numeric.go" 88 | }, 89 | { 90 | "comment": "Floating-point literals", 91 | "match": "(\\.\\d+([Ee][-+]\\d+)?i?)\\b|\\b\\d+\\.\\d*(([Ee][-+]\\d+)?i?\\b)?", 92 | "name": "constant.numeric.floating-point.go" 93 | }, 94 | { 95 | "comment": "Integers", 96 | "match": "\\b((0x[0-9a-fA-F]+)|(0[0-7]+i?)|(\\d+([Ee]\\d+)?i?)|(\\d+[Ee][-+]\\d+i?))\\b", 97 | "name": "constant.numeric.integer.go" 98 | }, 99 | { 100 | "comment": "Language constants", 101 | "match": "\\b(true|false|nil|iota)\\b", 102 | "name": "constant.language.go" 103 | }, 104 | { 105 | "begin": "\\b(type)\\s+", 106 | "beginCaptures": { 107 | "1": { 108 | "name": "keyword.type.go" 109 | } 110 | }, 111 | "end": "(?!\\G)", 112 | "patterns": [ 113 | { 114 | "match": "\\d\\w*", 115 | "name": "invalid.illegal.identifier.go" 116 | }, 117 | { 118 | "match": "\\w+", 119 | "name": "entity.name.type.go" 120 | } 121 | ] 122 | }, 123 | { 124 | "comment": "Terminators", 125 | "match": ";", 126 | "name": "punctuation.terminator.go" 127 | }, 128 | { 129 | "include": "#brackets" 130 | }, 131 | { 132 | "include": "#delimiters" 133 | }, 134 | { 135 | "include": "#keywords" 136 | }, 137 | { 138 | "include": "#operators" 139 | }, 140 | { 141 | "include": "#runes" 142 | }, 143 | { 144 | "include": "#storage_types" 145 | } 146 | ], 147 | "repository": { 148 | "brackets": { 149 | "patterns": [ 150 | { 151 | "begin": "{", 152 | "beginCaptures": { 153 | "0": { 154 | "name": "punctuation.definition.begin.bracket.curly.go" 155 | } 156 | }, 157 | "end": "}", 158 | "endCaptures": { 159 | "0": { 160 | "name": "punctuation.definition.end.bracket.curly.go" 161 | } 162 | }, 163 | "patterns": [ 164 | { 165 | "include": "$self" 166 | } 167 | ] 168 | }, 169 | { 170 | "begin": "\\(", 171 | "beginCaptures": { 172 | "0": { 173 | "name": "punctuation.definition.begin.bracket.round.go" 174 | } 175 | }, 176 | "end": "\\)", 177 | "endCaptures": { 178 | "0": { 179 | "name": "punctuation.definition.end.bracket.round.go" 180 | } 181 | }, 182 | "patterns": [ 183 | { 184 | "include": "$self" 185 | } 186 | ] 187 | }, 188 | { 189 | "match": "\\[|\\]", 190 | "name": "punctuation.definition.bracket.square.go" 191 | } 192 | ] 193 | }, 194 | "comments": { 195 | "patterns": [ 196 | { 197 | "begin": "/\\*", 198 | "end": "\\*/", 199 | "captures": { 200 | "0": { 201 | "name": "punctuation.definition.comment.go" 202 | } 203 | }, 204 | "name": "comment.block.go" 205 | }, 206 | { 207 | "begin": "//", 208 | "beginCaptures": { 209 | "0": { 210 | "name": "punctuation.definition.comment.go" 211 | } 212 | }, 213 | "end": "$", 214 | "name": "comment.line.double-slash.go" 215 | } 216 | ] 217 | }, 218 | "delimiters": { 219 | "patterns": [ 220 | { 221 | "match": ",", 222 | "name": "punctuation.other.comma.go" 223 | }, 224 | { 225 | "match": "\\.(?!\\.\\.)", 226 | "name": "punctuation.other.period.go" 227 | }, 228 | { 229 | "match": ":(?!=)", 230 | "name": "punctuation.other.colon.go" 231 | } 232 | ] 233 | }, 234 | "keywords": { 235 | "patterns": [ 236 | { 237 | "comment": "Flow control keywords", 238 | "match": "\\b(break|case|continue|default|defer|else|fallthrough|for|go|goto|if|range|return|select|switch)\\b", 239 | "name": "keyword.control.go" 240 | }, 241 | { 242 | "match": "\\bchan\\b", 243 | "name": "keyword.channel.go" 244 | }, 245 | { 246 | "match": "\\bconst\\b", 247 | "name": "keyword.const.go" 248 | }, 249 | { 250 | "match": "\\bfunc\\b", 251 | "name": "keyword.function.go" 252 | }, 253 | { 254 | "match": "\\binterface\\b", 255 | "name": "keyword.interface.go" 256 | }, 257 | { 258 | "match": "\\bmap\\b", 259 | "name": "keyword.map.go" 260 | }, 261 | { 262 | "match": "\\bstruct\\b", 263 | "name": "keyword.struct.go" 264 | } 265 | ] 266 | }, 267 | "operators": { 268 | "comment": "Note that the order here is very important!", 269 | "patterns": [ 270 | { 271 | "match": "(\\*|&)(?=\\w)", 272 | "name": "keyword.operator.address.go" 273 | }, 274 | { 275 | "match": "<\\-", 276 | "name": "keyword.operator.channel.go" 277 | }, 278 | { 279 | "match": "\\-\\-", 280 | "name": "keyword.operator.decrement.go" 281 | }, 282 | { 283 | "match": "\\+\\+", 284 | "name": "keyword.operator.increment.go" 285 | }, 286 | { 287 | "match": "(==|!=|<=|>=|<(?!<)|>(?!>))", 288 | "name": "keyword.operator.comparison.go" 289 | }, 290 | { 291 | "match": "(&&|\\|\\||!)", 292 | "name": "keyword.operator.logical.go" 293 | }, 294 | { 295 | "match": "(=|\\+=|\\-=|\\|=|\\^=|\\*=|/=|:=|%=|<<=|>>=|&\\^=|&=)", 296 | "name": "keyword.operator.assignment.go" 297 | }, 298 | { 299 | "match": "(\\+|\\-|\\*|/|%)", 300 | "name": "keyword.operator.arithmetic.go" 301 | }, 302 | { 303 | "match": "(&(?!\\^)|\\||\\^|&\\^|<<|>>)", 304 | "name": "keyword.operator.arithmetic.bitwise.go" 305 | }, 306 | { 307 | "match": "\\.\\.\\.", 308 | "name": "keyword.operator.ellipsis.go" 309 | } 310 | ] 311 | }, 312 | "runes": { 313 | "patterns": [ 314 | { 315 | "begin": "'", 316 | "beginCaptures": { 317 | "0": { 318 | "name": "punctuation.definition.string.begin.go" 319 | } 320 | }, 321 | "end": "'", 322 | "endCaptures": { 323 | "0": { 324 | "name": "punctuation.definition.string.end.go" 325 | } 326 | }, 327 | "name": "string.quoted.rune.go", 328 | "patterns": [ 329 | { 330 | "match": "\\G(\\\\([0-7]{3}|[abfnrtv\\\\'\"]|x[0-9a-fA-F]{2}|u[0-9a-fA-F]{4}|U[0-9a-fA-F]{8})|.)(?=')", 331 | "name": "constant.other.rune.go" 332 | }, 333 | { 334 | "match": "[^']+", 335 | "name": "invalid.illegal.unknown-rune.go" 336 | } 337 | ] 338 | } 339 | ] 340 | }, 341 | "storage_types": { 342 | "patterns": [ 343 | { 344 | "match": "\\bbool\\b", 345 | "name": "storage.type.boolean.go" 346 | }, 347 | { 348 | "match": "\\bbyte\\b", 349 | "name": "storage.type.byte.go" 350 | }, 351 | { 352 | "match": "\\berror\\b", 353 | "name": "storage.type.error.go" 354 | }, 355 | { 356 | "match": "\\b(complex(64|128)|float(32|64)|u?int(8|16|32|64)?)\\b", 357 | "name": "storage.type.numeric.go" 358 | }, 359 | { 360 | "match": "\\brune\\b", 361 | "name": "storage.type.rune.go" 362 | }, 363 | { 364 | "match": "\\bstring\\b", 365 | "name": "storage.type.string.go" 366 | }, 367 | { 368 | "match": "\\buintptr\\b", 369 | "name": "storage.type.uintptr.go" 370 | } 371 | ] 372 | }, 373 | "string_escaped_char": { 374 | "patterns": [ 375 | { 376 | "match": "\\\\([0-7]{3}|[abfnrtv\\\\'\"]|x[0-9a-fA-F]{2}|u[0-9a-fA-F]{4}|U[0-9a-fA-F]{8})", 377 | "name": "constant.character.escape.go" 378 | }, 379 | { 380 | "match": "\\\\[^0-7xuUabfnrtv\\'\"]", 381 | "name": "invalid.illegal.unknown-escape.go" 382 | } 383 | ] 384 | }, 385 | "string_placeholder": { 386 | "patterns": [ 387 | { 388 | "match": "%(\\[\\d+\\])?([\\+#\\-0\\x20]{,2}((\\d+|\\*)?(\\.?(\\d+|\\*|(\\[\\d+\\])\\*?)?(\\[\\d+\\])?)?))?[vT%tbcdoqxXUbeEfFgGsp]", 389 | "name": "constant.other.placeholder.go" 390 | } 391 | ] 392 | }, 393 | "variables": { 394 | "patterns": [ 395 | { 396 | "match": "(\\w+(?:,\\s*\\w+)*)(\\s+\\*?\\w+(?:\\.\\w+)?\\s*)?(?=\\s*=)", 397 | "captures": { 398 | "1": { 399 | "patterns": [ 400 | { 401 | "match": "\\d\\w*", 402 | "name": "invalid.illegal.identifier.go" 403 | }, 404 | { 405 | "match": "\\w+", 406 | "name": "variable.other.assignment.go" 407 | }, 408 | { 409 | "include": "#delimiters" 410 | } 411 | ] 412 | }, 413 | "2": { 414 | "patterns": [ 415 | { 416 | "include": "$self" 417 | } 418 | ] 419 | } 420 | } 421 | }, 422 | { 423 | "match": "(\\w+(?:,\\s*\\w+)*)(\\s+(\\[(\\d*|\\.\\.\\.)\\])*\\*?(<-)?\\w+(?:\\.\\w+)?\\s*[^=].*)", 424 | "captures": { 425 | "1": { 426 | "patterns": [ 427 | { 428 | "match": "\\d\\w*", 429 | "name": "invalid.illegal.identifier.go" 430 | }, 431 | { 432 | "match": "\\w+", 433 | "name": "variable.other.declaration.go" 434 | }, 435 | { 436 | "include": "#delimiters" 437 | } 438 | ] 439 | }, 440 | "2": { 441 | "patterns": [ 442 | { 443 | "include": "$self" 444 | } 445 | ] 446 | } 447 | } 448 | }, 449 | { 450 | "begin": "\\(", 451 | "beginCaptures": { 452 | "0": { 453 | "name": "punctuation.definition.variables.begin.bracket.round.go" 454 | } 455 | }, 456 | "end": "\\)", 457 | "endCaptures": { 458 | "0": { 459 | "name": "punctuation.definition.variables.end.bracket.round.go" 460 | } 461 | }, 462 | "patterns": [ 463 | { 464 | "include": "$self" 465 | }, 466 | { 467 | "include": "#variables" 468 | } 469 | ] 470 | } 471 | ] 472 | }, 473 | "service": { 474 | "name": "service.goctl", 475 | "begin": "(service)\\s+([A-Za-z][A-Za-z0-9_.-]*)\\s*\\{?", 476 | "beginCaptures": { 477 | "1": { 478 | "name": "keyword.other.goctl" 479 | }, 480 | "2": { 481 | "name": "entity.name.class.message.goctl" 482 | } 483 | }, 484 | "end": "\\}", 485 | "patterns": [ 486 | { 487 | "include": "#comments" 488 | }, 489 | { 490 | "include": "#optionStmt" 491 | }, 492 | { 493 | "include": "#method" 494 | }, 495 | { 496 | "include": "#methodReturn" 497 | }, 498 | { 499 | "include": "#doc" 500 | }, 501 | { 502 | "include": "#server" 503 | }, 504 | { 505 | "include": "#handler" 506 | } 507 | ] 508 | }, 509 | "method": { 510 | "name": "method.service.goctl", 511 | "begin": "(GET|HEAD|POST|PUT|DELETE|CONNECT|OPTIONS|TRACE|PATCH|get|head|post|put|delete|connect|options|trace|patch)\\s*((/[A-Za-z][A-Za-z0-9_/:-]*)|\\s|\\n)", 512 | "beginCaptures": { 513 | "1": { 514 | "name": "keyword.other.goctl" 515 | }, 516 | "2": { 517 | "name": "entity.name.function" 518 | } 519 | }, 520 | "end": "(\\)|\\n|\\s)", 521 | "patterns": [ 522 | { 523 | "include": "#comments" 524 | }, 525 | { 526 | "include": "#optionStmt" 527 | }, 528 | { 529 | "include": "#rpcKeywords" 530 | }, 531 | { 532 | "include": "#ident" 533 | } 534 | ] 535 | }, 536 | "methodReturn": { 537 | "name": "method.service.goctl", 538 | "begin": "returns", 539 | "beginCaptures": { 540 | "1": { 541 | "name": "keyword.other.goctl" 542 | }, 543 | "2": { 544 | "name": "entity.name.function" 545 | } 546 | }, 547 | "end": "(\\)|\\n)", 548 | "patterns": [ 549 | { 550 | "include": "#comments" 551 | }, 552 | { 553 | "include": "#optionStmt" 554 | }, 555 | { 556 | "include": "#ident" 557 | } 558 | ] 559 | }, 560 | "doc": { 561 | "name": "comment.doc.service.goctl", 562 | "begin": "@doc", 563 | "end": "\\)" 564 | }, 565 | "server": { 566 | "name": "method.server.service.goctl", 567 | "begin": "@server\\(", 568 | "end": "\\)", 569 | "patterns": [ 570 | { 571 | "include": "#handlerKeywords" 572 | }, 573 | { 574 | "include": "#ident" 575 | } 576 | ] 577 | }, 578 | "handler": { 579 | "name": "method.handle.service.goctl", 580 | "begin": "@handler", 581 | "end": "\\n", 582 | "patterns": [ 583 | { 584 | "include": "#ident" 585 | } 586 | ] 587 | }, 588 | "handlerKeywords": { 589 | "match": "\\bhandler\\b", 590 | "name": "keyword.other.goctl" 591 | }, 592 | "ident": { 593 | "match": "[A-Za-z][A-Za-z0-9_]*", 594 | "name": "entity.name.class.goctl" 595 | }, 596 | "constants": { 597 | "match": "\\b(true|false|max|[A-Z_]+)\\b", 598 | "name": "constant.language.goctl" 599 | }, 600 | "storagetypes": { 601 | "match": "\\b(double|float|int32|int64|uint32|uint64|sint32|sint64|fixed32|fixed64|sfixed32|sfixed64|bool|string|bytes)\\b", 602 | "name": "storage.type.goctl" 603 | }, 604 | "string": { 605 | "match": "('([^']|\\')*')|(\"([^\"]|\\\")*\")", 606 | "name": "string.quoted.double.goctl" 607 | }, 608 | "number": { 609 | "name": "constant.numeric.goctl", 610 | "match": "\\b((0(x|X)[0-9a-fA-F]*)|(([0-9]+\\.?[0-9]*)|(\\.[0-9]+))((e|E)(\\+|-)?[0-9]+)?)\\b" 611 | } 612 | } 613 | } --------------------------------------------------------------------------------