1
Help us understand the problem. What are the problem?

posted at

updated at

PyCharmでdocstringを書くのが面倒すぎたのでplugin作った

適当なpluginとかそもそもデフォルト機能であるはず。
と思ったのに見当たらなかったので。

結論

サンプルをちょっといじって使う

# サンプルコードのダウンロード
git clone https://github.com/JetBrains/intellij-sdk-code-samples.git

サンプルに添付のパッチを当てる

cd intellij-sdk-code-samples/
patch -p1 < xxxxx.txt

パッチ(適当な名前で保存してください)
diff --git a/editor_basics/build.gradle b/editor_basics/build.gradle
index 98436d90..ff5da234 100644
--- a/editor_basics/build.gradle
+++ b/editor_basics/build.gradle
@@ -3,8 +3,11 @@
 plugins {
   id 'java'
   id 'org.jetbrains.intellij' version '1.3.1'
+  id 'org.jetbrains.kotlin.jvm' version '1.3.41'
+}
+tasks.withType(JavaCompile) {
+  options.encoding = 'UTF-8'
 }
-
 group 'org.intellij.sdk'
 version '2.0.0'

diff --git a/editor_basics/src/main/java/org/intellij/sdk/editor/DocStringTemplate.kt b/editor_basics/src/main/java/org/intellij/sdk/editor/DocStringTemplate.kt
new file mode 100644
index 00000000..b0768526
--- /dev/null
+++ b/editor_basics/src/main/java/org/intellij/sdk/editor/DocStringTemplate.kt
@@ -0,0 +1,24 @@
+package org.intellij.sdk.editor
+
+object DocStringTemplate {
+    @JvmStatic
+    val s = """
+        \"\"\"
+        Parameters       
+        ----------
+        {%parameters}
+        
+        Returns
+        -------
+        :
+        \"\"\"
+        """.trimIndent().replace("\\","")
+
+}
+
+object ParameterTemplate {
+    @JvmStatic
+    val s = """
+        {%parameter} : {%type}{%default}
+        """.trimIndent()
+}
\ No newline at end of file
diff --git a/editor_basics/src/main/java/org/intellij/sdk/editor/EditorAreaIllustration.java b/editor_basics/src/main/java/org/intellij/sdk/editor/EditorAreaIllustration.java
index 5cfa1c5f..3deb8e2d 100644
--- a/editor_basics/src/main/java/org/intellij/sdk/editor/EditorAreaIllustration.java
+++ b/editor_basics/src/main/java/org/intellij/sdk/editor/EditorAreaIllustration.java
@@ -16,7 +16,6 @@ import org.jetbrains.annotations.NotNull;
  * @see AnAction
  */
 public class EditorAreaIllustration extends AnAction {
-
   /**
    * Displays a message with information about the current caret.
    *
@@ -24,6 +23,8 @@ public class EditorAreaIllustration extends AnAction {
    */
   @Override
   public void actionPerformed(@NotNull final AnActionEvent e) {
+    if (true) return;
+     e.getInputEvent().isAltDown();
     // Get access to the editor and caret model. update() validated editor's existence.
     final Editor editor = e.getRequiredData(CommonDataKeys.EDITOR);
     final CaretModel caretModel = editor.getCaretModel();
@@ -50,6 +51,7 @@ public class EditorAreaIllustration extends AnAction {
    */
   @Override
   public void update(@NotNull final AnActionEvent e) {
+    if (true) return;
     // Get required data keys
     final Project project = e.getProject();
     final Editor editor = e.getData(CommonDataKeys.EDITOR);
diff --git a/editor_basics/src/main/java/org/intellij/sdk/editor/EditorIllustrationAction.java b/editor_basics/src/main/java/org/intellij/sdk/editor/EditorIllustrationAction.java
index 1664cd02..d5609b0b 100644
--- a/editor_basics/src/main/java/org/intellij/sdk/editor/EditorIllustrationAction.java
+++ b/editor_basics/src/main/java/org/intellij/sdk/editor/EditorIllustrationAction.java
@@ -19,50 +19,51 @@ import org.jetbrains.annotations.NotNull;
  */
 public class EditorIllustrationAction extends AnAction {

-  /**
-   * Replaces the run of text selected by the primary caret with a fixed string.
-   *
-   * @param e Event related to this action
-   */
-  @Override
-  public void actionPerformed(@NotNull final AnActionEvent e) {
-    // Get all the required data from data keys
-    // Editor and Project were verified in update(), so they are not null.
-    final Editor editor = e.getRequiredData(CommonDataKeys.EDITOR);
-    final Project project = e.getRequiredData(CommonDataKeys.PROJECT);
-    final Document document = editor.getDocument();
-    // Work off of the primary caret to get the selection info
-    Caret primaryCaret = editor.getCaretModel().getPrimaryCaret();
-    int start = primaryCaret.getSelectionStart();
-    int end = primaryCaret.getSelectionEnd();
-    // Replace the selection with a fixed string.
-    // Must do this document change in a write action context.
-    WriteCommandAction.runWriteCommandAction(project, () ->
-            document.replaceString(start, end, "editor_basics")
-    );
-    // De-select the text range that was just replaced
-    primaryCaret.removeSelection();
-  }
+    /**
+     * Replaces the run of text selected by the primary caret with a fixed string.
+     *
+     * @param e Event related to this action
+     */
+    @Override
+    public void actionPerformed(@NotNull final AnActionEvent e) {
+        if (true) return;
+        // Get all the required data from data keys
+        // Editor and Project were verified in update(), so they are not null.
+        final Editor editor = e.getRequiredData(CommonDataKeys.EDITOR);
+        final Project project = e.getRequiredData(CommonDataKeys.PROJECT);
+        final Document document = editor.getDocument();
+        // Work off of the primary caret to get the selection info
+        Caret primaryCaret = editor.getCaretModel().getPrimaryCaret();
+        int start = primaryCaret.getSelectionStart();
+        int end = primaryCaret.getSelectionEnd();
+        // Replace the selection with a fixed string.
+        // Must do this document change in a write action context.
+        WriteCommandAction.runWriteCommandAction(project, () ->
+                document.replaceString(start, end, "editor_basics")
+        );
+        // De-select the text range that was just replaced
+        primaryCaret.removeSelection();
+    }

-  /**
-   * Sets visibility and enables this action menu item if:
-   * <ul>
-   *   <li>a project is open</li>
-   *   <li>an editor is active</li>
-   *   <li>some characters are selected</li>
-   * </ul>
-   *
-   * @param e Event related to this action
-   */
-  @Override
-  public void update(@NotNull final AnActionEvent e) {
-    // Get required data keys
-    final Project project = e.getProject();
-    final Editor editor = e.getData(CommonDataKeys.EDITOR);
-    // Set visibility and enable only in case of existing project and editor and if a selection exists
-    e.getPresentation().setEnabledAndVisible(
-            project != null && editor != null && editor.getSelectionModel().hasSelection()
-    );
-  }
+    /**
+     * Sets visibility and enables this action menu item if:
+     * <ul>
+     *   <li>a project is open</li>
+     *   <li>an editor is active</li>
+     *   <li>some characters are selected</li>
+     * </ul>
+     *
+     * @param e Event related to this action
+     */
+    @Override
+    public void update(@NotNull final AnActionEvent e) {
+        // Get required data keys
+        final Project project = e.getProject();
+        final Editor editor = e.getData(CommonDataKeys.EDITOR);
+        // Set visibility and enable only in case of existing project and editor and if a selection exists
+        e.getPresentation().setEnabledAndVisible(
+                project != null && editor != null && editor.getSelectionModel().hasSelection()
+        );
+    }

 }
diff --git a/editor_basics/src/main/java/org/intellij/sdk/editor/MyTypedHandler.java b/editor_basics/src/main/java/org/intellij/sdk/editor/MyTypedHandler.java
index defcc830..2316f9e7 100644
--- a/editor_basics/src/main/java/org/intellij/sdk/editor/MyTypedHandler.java
+++ b/editor_basics/src/main/java/org/intellij/sdk/editor/MyTypedHandler.java
@@ -4,29 +4,279 @@ package org.intellij.sdk.editor;

 import com.intellij.codeInsight.editorActions.TypedHandlerDelegate;
 import com.intellij.openapi.command.WriteCommandAction;
-import com.intellij.openapi.editor.Document;
-import com.intellij.openapi.editor.Editor;
+import com.intellij.openapi.editor.*;
 import com.intellij.openapi.project.Project;
 import com.intellij.psi.PsiFile;
 import org.jetbrains.annotations.NotNull;

+import java.io.BufferedReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.StringReader;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
 /**
  * This is a custom {@link TypedHandlerDelegate} that handles actions activated keystrokes in the editor.
  * The execute method inserts a fixed string at Offset 0 of the document.
  * Document changes are made in the context of a write action.
  */
 class MyTypedHandler extends TypedHandlerDelegate {
+    static long first = 0;
+    static long count = 0;
+
+    @NotNull
+    @Override
+    public Result charTyped(char c, @NotNull Project project, @NotNull Editor editor, @NotNull PsiFile file) {
+//        FileWriter writer;
+        int offset;
+        String contents;
+        String insert_str;
+        int curColumn;
+//        try {
+//            writer = new FileWriter("D:\\out.txt", StandardCharsets.UTF_8, true);
+//            writer.write("c:" + c + "\n");
+        if (c != ';') {
+//                writer.close();
+            return Result.CONTINUE;
+        }
+        long cur = System.currentTimeMillis();
+//            writer.write("cur:" + cur + " first:" + first);
+        if ((cur - first) > 1500) {
+            first = cur;
+            count = 1;
+//                writer.write("is first");
+//                writer.close();
+            return Result.STOP;
+        } else {
+            count++;
+//                writer.write("not first:" + count);
+            if (count != 3) {
+//                    writer.close();
+                return Result.STOP;
+            }
+        }
+//            writer.write("count is 3");
+        CaretModel caret = editor.getCaretModel();
+        VisualPosition position = caret.getVisualPosition();
+//            LogicalPosition position = caret.getLogicalPosition();
+//            offset = editor.logicalPositionToOffset(position);
+
+        offset = editor.visualPositionToOffset(position);
+        contents = file.getText();
+
+        String befLine = null;
+        String curLine = null;
+        try (BufferedReader reader = new BufferedReader(new StringReader(contents))) {
+            String line;
+            int cnt = 0;
+            while ((line = reader.readLine()) != null) {
+                if ((position.line - 1) == cnt) {
+                    befLine = line;
+                } else if (position.line == cnt) {
+                    curLine = line;
+                }
+                cnt++;
+            }
+        } catch (IOException ignored) {
+//                writer.write("exception\n");
+//                writer.close();
+            return Result.CONTINUE;
+        }
+        if ((befLine == null) || (curLine == null)) {
+//                writer.write("line check ng");
+//                writer.close();
+            return Result.CONTINUE;
+        }
+
+        // 上が空行は無視
+        if (befLine.matches("^\\s*$")) {
+//                writer.write("above is null");
+//                writer.close();
+            return Result.CONTINUE;
+        }
+
+        // 他に入力文字が有ったら無視
+        if (!curLine.matches("^\\s*;;\\s*$")) {
+//                writer.write("not ;;");
+//                writer.close();
+            return Result.CONTINUE;
+        }
+
+        curColumn = position.column;
+//            writer.write("ccc" + curColumn + "\n");
+//            writer.close();
+        insert_str = getInsertStr(befLine, curColumn - (curColumn % 4));
+//        } catch (IOException ignored) {
+//            return Result.CONTINUE;
+//        }
+
+
+        // Get the document and project
+        final Document document = editor.getDocument();
+        // Construct the runnable to substitute the string at offset 0 in the document
+        document.replaceString((offset - 3), offset, "");
+        String finalInsert_str = insert_str;
+//        try {
+//            writer = new FileWriter("D:\\out.txt", StandardCharsets.UTF_8, true);
+//            writer.write("insert:" + finalInsert_str + "\n");
+//            writer.close();
+//        } catch (IOException ignored) {
+//        }
+        Runnable runnable = () -> document.insertString(offset - 3, finalInsert_str + "\n");
+        // Make the document change in the context of a write action.
+        WriteCommandAction.runWriteCommandAction(project, runnable);
+        return Result.STOP;
+    }
+
+    /**
+     * 複数行の文字列の1行毎にインデントを追加する
+     *
+     * @param str    複数行の文字列
+     * @param indent 挿入するスペース数
+     * @return インデントを追加した文字列
+     */
+    String addIndent(String str, int indent) {
+        String indent_str = getIndentStr(indent);
+        StringBuilder retStr = new StringBuilder();
+        try (BufferedReader reader = new BufferedReader(new StringReader(str))) {
+            String line;
+            int cnt = 0;
+            while ((line = reader.readLine()) != null) {
+                if (cnt++ > 0) {
+                    retStr.append(indent_str);
+                }
+                retStr.append(line).append("\n");
+            }
+        } catch (IOException ignored) {
+        }
+
+        return retStr.toString();
+    }
+
+
+    /**
+     * 指定した数値に応じた連続スペースを返却する
+     *
+     * @param indent インデント位置
+     * @return 連続スペース
+     */
+    String getIndentStr(int indent) {
+        if (indent <= 0) return "";
+        return " ".repeat(indent);
+    }
+
+    /**
+     * すぐ上の行に応じたdocstringを返す
+     *
+     * @param befLine カーソルの上の行
+     * @return docstring
+     */
+    String getInsertStr(String befLine, int indent) {
+        String pattern = "^\\s*def\\s.*\\((.*)\\).*\\s*$";
+        Pattern p = Pattern.compile(pattern);
+        Matcher m = p.matcher(befLine);
+        String template = DocStringTemplate.getS();
+        String str = null;
+//        FileWriter writer;
+
+//        try {
+//            writer = new FileWriter("D:\\out.txt", StandardCharsets.UTF_8, true);
+            if (m.find()) {
+                // 関数の場合
+                String paramTemplate = ParameterTemplate.getS();
+
+                String args_str = m.group(1).replaceAll("\\s", "");
+//                writer.write("args_str:" + args_str + "\n");
+                String[] args_ary = args_str.split(",");
+                List<Arg> args = new ArrayList<>();
+//                writer.write("args_ary:" + args_ary.length);
+                for (String arg_str : args_ary) {
+                    if (arg_str.length() == 0) continue;
+                    String[] temp = arg_str.split("=");
+//                    writer.write("temp=:" + temp[0] + "\n");
+                    Arg arg = new Arg();
+                    if (temp.length > 1) {
+                        arg.def = temp[1];
+                    }
+                    temp = temp[0].split(":");
+//                    writer.write("temp::" + temp[0] + "\n");
+                    if (temp.length > 1) {
+                        arg.type = temp[1];
+                    }
+                    arg.name = temp[0];
+                    if (!"self".equals(arg.name))
+                        args.add(arg);
+                }

-  @NotNull
-  @Override
-  public Result charTyped(char c, @NotNull Project project, @NotNull Editor editor, @NotNull PsiFile file) {
-    // Get the document and project
-    final Document document = editor.getDocument();
-    // Construct the runnable to substitute the string at offset 0 in the document
-    Runnable runnable = () -> document.insertString(0, "editor_basics\n");
-    // Make the document change in the context of a write action.
-    WriteCommandAction.runWriteCommandAction(project, runnable);
-    return Result.STOP;
-  }
+                if (args.size() > 0) {
+//                    writer.write("args.size > 0\n");
+                    // 引数有りパターン
+                    StringBuilder strParams = new StringBuilder();
+                    for (Arg arg : args) {
+//                        writer.write("name:" + arg.name + " type:" + arg.type + " def:" + arg.type + "\n");
+                        String strParam = paramTemplate;
+                        strParam = strParam.replaceAll("\\{%parameter}", arg.name);
+//                        writer.write("pend:" + strParam +"\n");
+                        strParam = strParam.replaceAll("\\{%type}", arg.type);
+                        String def = arg.def.length() > 0 ? ", default " + arg.def : "";
+                        strParam = strParam.replaceAll("\\{%default}", def);
+//                        writer.write("comp:" + strParam +"\n");
+                        strParams.append(strParam).append("\n");
+                    }
+                    str = template.replaceAll("\\{%parameters}", strParams.toString());
+                } else {
+//                    writer.write("args.size == 0");
+                    // 引数なしパターン
+                    // java は複数行の部分置換が出来ない
+                    String p_str = "Parameters";
+                    String r_str = "Returns";
+                    int p_len = p_str.length();
+                    int r_len = r_str.length();
+                    int len = template.length();
+                    int d_start = 0;
+                    int d_end = 0;
+                    for (int i = 0; i < len - r_len; i++) {
+                        if (((len - i) > p_len) && (p_str.equals(template.substring(i, i + p_len)))) {
+                            d_start = i;
+                        } else if (((len - i) > r_len) && (r_str.equals(template.substring(i, i + r_len)))) {
+                            d_end = i;
+                            break;
+                        }
+                    }
+                    if (d_end > d_start) {
+                        str = template.substring(0, d_start) + template.substring(d_end);
+                    }
+                }
+            } else {
+                // 関数以外の場合
+                str = "\"\"\"\n" + "\"\"\"\n";
+            }
+//            writer.write("indent:" + getIndentStr(indent) + "\n");
+//            writer.close();
+//        } catch (IOException ignored) {
+//        }
+        return addIndent(str, indent);
+    }

+    /**
+     * python の 引数1つ分
+     */
+    static class Arg {
+        /**
+         * 引数名
+         */
+        String name = "";
+        /**
+         * 型ヒント
+         */
+        String type = "";
+        /**
+         * default値
+         */
+        String def = "";
+    }
 }

ビルドする

# JAVAのパスは環境に合わせる。
cd editor_basics
JAVA_HOME=/d/Java/jdk-11.0.12/ ./gradlew buildPlugin

editor_basics/build/distributions/editor-2.0.0.zip が作成される

PyCharmのpluginに設定する

PyCharmからFile -> Settings -> Plugins と選ぶ。
Installedの右の歯車からInstall Plugin from Diskを選択する。
作成されたzipファイルを選択する。
OKを押してSettings画面を閉じる。

使い方

;;;(関数定義の直下の行でセミコロンを1.5s以内に3連打するとコメントテンプレートが挿入される)

こんな感じ

image.png

1から作ろうとして断念した経緯

この辺とかこれとか
書いてあるのを参考にしようとしたけど

  • src 右クリでactionが出てこない
  • gradleとかxmlの編集とかよくわからない

とかの理由で誰かが作ってるの編集した方が速いのでは?と思いました。

はまったポイント

gradle実行時のjavaのversionが11(以上かな?)で無いと動かない
"(ダブルクォーテーション)は飛んでこない

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Sign upLogin
1
Help us understand the problem. What are the problem?