├── .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 | 
23 |
24 | ### 代码格式化
25 |
26 | 调用 goctl 命令行格式化工具,使用前请确认 goctl 已加入 `$PATH` 且有可执行权限
27 |
28 | ### 代码块提示
29 |
30 | #### info 代码块
31 |
32 | 
33 |
34 | #### type 代码块
35 |
36 | 
37 |
38 | #### service 代码块
39 |
40 | 
41 |
42 | #### handler 代码块
43 |
44 | 
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 extends PsiElement> 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 |
46 | - Remove fixed grammatical order
47 | - Modify the key of kv key value grammar to id type
48 |
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 |
6 |
7 |
8 |
9 |
10 |
11 |
13 |
14 |
15 |
16 |
17 |
18 |
20 |
21 |
22 |
23 |
24 |
25 |
27 |
28 |
29 |
30 |
31 |
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 extends PsiElement> 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 | 
31 |
32 | # 新建 Api file
33 |
34 | 在工程区域目标文件夹`右键->New-> New Api File ->Empty File/Api Template`,如图:
35 |
36 | 
37 |
38 | # 新建 Proto File
39 | 在工程区目标文件夹`右键->New-> New Proto File -> Empty File/Proto Template`,如图:
40 |
41 | 
42 |
43 | # 快速生成api/rpc服务
44 | 在目标文件夹`右键->New->Go Zero -> Api Greet Service/Rpc Greet Service`
45 |
46 | 
47 |
48 | # Api/Rpc/Model Code生成
49 |
50 | ## 方法一(工程区域)
51 |
52 | 对应文件(api、proto、sql)`右键->New->Go Zero-> Api/Rpc/Model Code`,如图:
53 |
54 | 
55 |
56 | ## 方法二(编辑区域)
57 | 对应文件(api、proto、sql)`右键-> Generate-> Api/Rpc/Model Code`
58 |
59 |
60 | # 错误提示
61 | 
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 | 
182 |
183 | 下面就进入今天api语法中的模板使用说明吧,我们先来看看service模板的效果
184 | 
185 |
186 | 首先上一张图了解一下api文件中几个模板生效区域(psiTree元素区域)
187 | 
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 | 
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 |
6 |
7 |
8 |
9 |
10 |
11 |
13 |
14 |
15 |
16 |
17 |
18 |
20 |
21 |
22 |
23 |
24 |
25 |
27 |
28 |
29 |
30 |
31 |
32 |
34 |
35 |
36 |
37 |
38 |
40 |
41 |
42 |
43 |
44 |
46 |
47 |
48 |
49 |
50 |
52 |
53 |
54 |
55 |
56 |
58 |
59 |
60 |
61 |
62 |
64 |
65 |
66 |
67 |
68 |
70 |
71 |
72 |
73 |
74 |
76 |
77 |
78 |
79 |
80 |
81 |
83 |
84 |
85 |
86 |
87 |
88 |
90 |
91 |
92 |
93 |
94 |
95 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
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 extends TemplateContextType> 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 | }
--------------------------------------------------------------------------------