├── .github
└── workflows
│ ├── build.yml
│ └── release.yml
├── .gitignore
├── CHANGELOG.md
├── README.md
├── build.gradle.kts
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── settings.gradle.kts
└── src
└── main
├── java
└── com
│ └── github
│ └── anonfunc
│ └── vcidea
│ ├── VoicecodePlugin.java
│ └── commands
│ ├── CloneLineCommand.java
│ ├── ExtendCommand.java
│ ├── FindCommand.java
│ ├── GivenActionCommand.java
│ ├── GotoCommand.java
│ ├── LocationCommand.java
│ ├── RangeCommand.java
│ ├── StructureCommand.java
│ └── VcCommand.java
└── resources
└── META-INF
└── plugin.xml
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | # GitHub Actions Workflow created for testing and preparing the plugin release in following steps:
2 | # - validate Gradle Wrapper,
3 | # - run 'test' and 'verifyPlugin' tasks,
4 | # - run 'buildPlugin' task and prepare artifact for the further tests,
5 | # - run 'runPluginVerifier' task,
6 | # - create a draft release.
7 | #
8 | # Workflow is triggered on push and pull_request events.
9 | #
10 | # GitHub Actions reference: https://help.github.com/en/actions
11 | #
12 | ## JBIJPPTPL
13 |
14 | name: Build
15 | on:
16 | # Trigger the workflow on pushes to only the 'main' branch (this avoids duplicate checks being run e.g. for dependabot pull requests)
17 | push:
18 | branches: [master]
19 | # Trigger the workflow on any pull request
20 | pull_request:
21 |
22 | jobs:
23 |
24 | # Run Gradle Wrapper Validation Action to verify the wrapper's checksum
25 | # Run verifyPlugin, IntelliJ Plugin Verifier, and test Gradle tasks
26 | # Build plugin and provide the artifact for the next workflow jobs
27 | build:
28 | name: Build
29 | runs-on: ubuntu-latest
30 | outputs:
31 | version: ${{ steps.properties.outputs.version }}
32 | changelog: ${{ steps.properties.outputs.changelog }}
33 | steps:
34 |
35 | # Check out current repository
36 | - name: Fetch Sources
37 | uses: actions/checkout@v2.4.0
38 |
39 | # Validate wrapper
40 | - name: Gradle Wrapper Validation
41 | uses: gradle/wrapper-validation-action@v1.0.4
42 |
43 | # Setup Java 17 environment for the next steps
44 | - name: Setup Java
45 | uses: actions/setup-java@v2
46 | with:
47 | distribution: zulu
48 | java-version: 17
49 | cache: gradle
50 |
51 | # Set environment variables
52 | - name: Export Properties
53 | id: properties
54 | shell: bash
55 | run: |
56 | PROPERTIES="$(./gradlew properties --console=plain -q)"
57 | VERSION="$(echo "$PROPERTIES" | grep "^version:" | cut -f2- -d ' ')"
58 | NAME="$(echo "$PROPERTIES" | grep "^pluginName:" | cut -f2- -d ' ')"
59 | CHANGELOG="$(./gradlew getChangelog --unreleased --no-header --console=plain -q)"
60 | CHANGELOG="${CHANGELOG//'%'/'%25'}"
61 | CHANGELOG="${CHANGELOG//$'\n'/'%0A'}"
62 | CHANGELOG="${CHANGELOG//$'\r'/'%0D'}"
63 |
64 | echo "::set-output name=version::$VERSION"
65 | echo "::set-output name=name::$NAME"
66 | echo "::set-output name=changelog::$CHANGELOG"
67 | echo "::set-output name=pluginVerifierHomeDir::~/.pluginVerifier"
68 |
69 | ./gradlew listProductsReleases # prepare list of IDEs for Plugin Verifier
70 |
71 | # Run tests
72 | - name: Run Tests
73 | run: ./gradlew test
74 |
75 | # Collect Tests Result of failed tests
76 | - name: Collect Tests Result
77 | if: ${{ failure() }}
78 | uses: actions/upload-artifact@v2
79 | with:
80 | name: tests-result
81 | path: ${{ github.workspace }}/build/reports/tests
82 | #
83 | # # Cache Plugin Verifier IDEs
84 | # - name: Setup Plugin Verifier IDEs Cache
85 | # uses: actions/cache@v2.1.6
86 | # with:
87 | # path: ${{ steps.properties.outputs.pluginVerifierHomeDir }}/ides
88 | # key: plugin-verifier-${{ hashFiles('build/listProductsReleases.txt') }}
89 | #
90 | # # Run Verify Plugin task and IntelliJ Plugin Verifier tool
91 | # - name: Run Plugin Verification tasks
92 | # run: ./gradlew runPluginVerifier -Pplugin.verifier.home.dir=${{ steps.properties.outputs.pluginVerifierHomeDir }}
93 | #
94 | # # Collect Plugin Verifier Result
95 | # - name: Collect Plugin Verifier Result
96 | # if: ${{ always() }}
97 | # uses: actions/upload-artifact@v2
98 | # with:
99 | # name: pluginVerifier-result
100 | # path: ${{ github.workspace }}/build/reports/pluginVerifier
101 |
102 | - name: Build Plugin Artifact
103 | shell: bash
104 | run: ./gradlew buildPlugin
105 | # Prepare plugin archive content for creating artifact
106 | - name: Prepare Plugin Artifact
107 | id: artifact
108 | shell: bash
109 | run: |
110 | cd ${{ github.workspace }}/build/distributions
111 | FILENAME=`ls *.zip`
112 | unzip "$FILENAME" -d content
113 |
114 | echo "::set-output name=filename::$FILENAME"
115 |
116 | # Store already-built plugin as an artifact for downloading
117 | - name: Upload artifact
118 | uses: actions/upload-artifact@v2.2.4
119 | with:
120 | name: ${{ steps.artifact.outputs.filename }}
121 | path: ./build/distributions/content/*/*
122 |
123 | # Prepare a draft release for GitHub Releases page for the manual verification
124 | # If accepted and published, release workflow would be triggered
125 | releaseDraft:
126 | name: Release Draft
127 | if: github.event_name != 'pull_request'
128 | needs: build
129 | runs-on: ubuntu-latest
130 | steps:
131 |
132 | # Check out current repository
133 | - name: Fetch Sources
134 | uses: actions/checkout@v2.4.0
135 |
136 | # Remove old release drafts by using the curl request for the available releases with draft flag
137 | - name: Remove Old Release Drafts
138 | env:
139 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
140 | run: |
141 | gh api repos/{owner}/{repo}/releases \
142 | --jq '.[] | select(.draft == true) | .id' \
143 | | xargs -I '{}' gh api -X DELETE repos/{owner}/{repo}/releases/{}
144 |
145 | # Create new release draft - which is not publicly visible and requires manual acceptance
146 | - name: Create Release Draft
147 | env:
148 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
149 | run: |
150 | gh release create v${{ needs.build.outputs.version }} \
151 | --draft \
152 | --title "v${{ needs.build.outputs.version }}" \
153 | --notes "$(cat << 'EOM'
154 | ${{ needs.build.outputs.changelog }}
155 | EOM
156 | )"
157 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | # GitHub Actions Workflow created for handling the release process based on the draft release prepared
2 | # with the Build workflow. Running the publishPlugin task requires the PUBLISH_TOKEN secret provided.
3 |
4 | name: Release
5 | on:
6 | release:
7 | types: [prereleased, released]
8 |
9 | jobs:
10 |
11 | # Prepare and publish the plugin to the Marketplace repository
12 | release:
13 | name: Publish Plugin
14 | runs-on: ubuntu-latest
15 | steps:
16 |
17 | # Check out current repository
18 | - name: Fetch Sources
19 | uses: actions/checkout@v2.4.0
20 | with:
21 | ref: ${{ github.event.release.tag_name }}
22 |
23 | # Setup Java 17 environment for the next steps
24 | - name: Setup Java
25 | uses: actions/setup-java@v2
26 | with:
27 | distribution: zulu
28 | java-version: 17
29 | cache: gradle
30 |
31 | # Set environment variables
32 | - name: Export Properties
33 | id: properties
34 | shell: bash
35 | run: |
36 | CHANGELOG="$(cat << 'EOM' | sed -e 's/^[[:space:]]*$//g' -e '/./,$!d'
37 | ${{ github.event.release.body }}
38 | EOM
39 | )"
40 |
41 | CHANGELOG="${CHANGELOG//'%'/'%25'}"
42 | CHANGELOG="${CHANGELOG//$'\n'/'%0A'}"
43 | CHANGELOG="${CHANGELOG//$'\r'/'%0D'}"
44 |
45 | echo "::set-output name=changelog::$CHANGELOG"
46 |
47 | # Update Unreleased section with the current release note
48 | - name: Patch Changelog
49 | if: ${{ steps.properties.outputs.changelog != '' }}
50 | env:
51 | CHANGELOG: ${{ steps.properties.outputs.changelog }}
52 | run: |
53 | ./gradlew patchChangelog --release-note="$CHANGELOG"
54 |
55 | # Publish the plugin to the Marketplace
56 | - name: Publish Plugin
57 | env:
58 | PUBLISH_TOKEN: ${{ secrets.PUBLISH_TOKEN }}
59 | run: ./gradlew publishPlugin
60 |
61 | # Upload artifact as a release asset
62 | - name: Upload Release Asset
63 | env:
64 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
65 | run: gh release upload ${{ github.event.release.tag_name }} ./build/distributions/*
66 |
67 | # Create pull request
68 | - name: Create Pull Request
69 | if: ${{ steps.properties.outputs.changelog != '' }}
70 | env:
71 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
72 | run: |
73 | VERSION="${{ github.event.release.tag_name }}"
74 | BRANCH="changelog-update-$VERSION"
75 |
76 | git config user.email "action@github.com"
77 | git config user.name "GitHub Action"
78 |
79 | git checkout -b $BRANCH
80 | git commit -am "Changelog update - $VERSION"
81 | git push --set-upstream origin $BRANCH
82 |
83 | gh pr create \
84 | --title "Changelog update - \`$VERSION\`" \
85 | --body "Current pull request contains patched \`CHANGELOG.md\` file for the \`$VERSION\` version." \
86 | --base master \
87 | --head $BRANCH
88 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /.gradle
2 | /.idea/
3 | /build/
4 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # Changelog
4 |
5 | ## Unreleased
6 |
7 | ## [0.0.17]
8 | - Update pluginUntilBuild to support all future builds
9 |
10 | ## 0.0.16 - 2023-05-06
11 | - Update pluginUntilBuild to support 2022.1
12 |
13 | ## 0.0.13
14 | - Bump platformVersion to 2022.1 to support latest intellij
15 |
16 | ## 0.0.12
17 |
18 | ### Added
19 | - No longer modifies setting for whole-line copy on empty selection.
20 | - Recreated build from JetBrains/intellij-plugin-template
21 |
22 | ## 0.0.11
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # IntelliJ support for VoiceCode (IDE plugin)
2 |
3 | This plugin adds a small web server which supports a Talon module, but can be used generically as an
4 | HTTP based RPC driven by any system on the same machine.t
5 |
6 |
7 | This adds [VoiceCode](https://voicecode.io) support for IntelliJ, in tandem with [this VoiceCode package](https://github.com/anonfunc/voicecode-intellij).
8 |
9 | This should support:
10 |
11 | - Android Studio
12 | - AppCode
13 | - CLion
14 | - DataGrip
15 | - GoLand
16 | - MPS
17 | - PhpStorm
18 | - PyCharm (Professional and Community editions)
19 | - RubyMine
20 | - WebStorm
21 |
22 | However, my primary use case is currently Java and IntelliJ CE. I will not be able to debug or reproduce issues occuring outside of the freely available IDEs. (Pull requests are welcome. :smile:) Support for these IDEs is contingent on their menu items remaining very similar, see *Limitations*.
23 |
24 | ## Manual Installation
25 |
26 | ### Download release zip, install plugin from zip.
27 |
28 | ## Testing uncommitted changes
29 |
30 | ./gradlew runIde
--------------------------------------------------------------------------------
/build.gradle.kts:
--------------------------------------------------------------------------------
1 | import org.jetbrains.changelog.markdownToHTML
2 |
3 | fun properties(key: String) = project.findProperty(key).toString()
4 |
5 | plugins {
6 | // Java support
7 | id("java")
8 | // Gradle IntelliJ Plugin
9 | id("org.jetbrains.intellij") version "1.15.0"
10 | // Gradle Changelog Plugin
11 | id("org.jetbrains.changelog") version "2.0.0"
12 | }
13 |
14 | group = properties("pluginGroup")
15 | version = properties("pluginVersion")
16 |
17 | // Configure project's dependencies
18 | repositories {
19 | mavenCentral()
20 | }
21 |
22 | // Configure Gradle IntelliJ Plugin - read more: https://github.com/JetBrains/gradle-intellij-plugin
23 | intellij {
24 | pluginName.set(properties("pluginName"))
25 | version.set(properties("platformVersion"))
26 | type.set(properties("platformType"))
27 |
28 | // Plugin Dependencies. Uses `platformPlugins` property from the gradle.properties file.
29 | plugins.set(properties("platformPlugins").split(',').map(String::trim).filter(String::isNotEmpty))
30 | }
31 |
32 | // Configure Gradle Changelog Plugin - read more: https://github.com/JetBrains/gradle-changelog-plugin
33 | changelog {
34 | version.set(properties("pluginVersion"))
35 | groups.set(emptyList())
36 | }
37 |
38 |
39 | tasks {
40 | // Set the JVM compatibility versions
41 | properties("javaVersion").let {
42 | withType {
43 | sourceCompatibility = it
44 | targetCompatibility = it
45 | }
46 | }
47 |
48 | buildSearchableOptions {
49 | enabled = false
50 | }
51 |
52 | wrapper {
53 | gradleVersion = properties("gradleVersion")
54 | }
55 |
56 | intellij.updateSinceUntilBuild.set(false)
57 |
58 | patchPluginXml {
59 | version.set(properties("pluginVersion"))
60 | sinceBuild.set(properties("pluginSinceBuild"))
61 |
62 | // Extract the section from README.md and provide for the plugin's manifest
63 | pluginDescription.set(
64 | projectDir.resolve("README.md").readText().lines().run {
65 | val start = ""
66 | val end = ""
67 |
68 | if (!containsAll(listOf(start, end))) {
69 | throw GradleException("Plugin description section not found in README.md:\n$start ... $end")
70 | }
71 | subList(indexOf(start) + 1, indexOf(end))
72 | }.joinToString("\n").run { markdownToHTML(this) }
73 | )
74 |
75 | // Get the latest available change notes from the changelog file
76 | changeNotes.set(provider {
77 | changelog.run {
78 | getOrNull(properties("pluginVersion")) ?: getLatest()
79 | }.toHTML()
80 | })
81 | }
82 |
83 | // Configure UI tests plugin
84 | // Read more: https://github.com/JetBrains/intellij-ui-test-robot
85 | runIdeForUiTests {
86 | systemProperty("robot-server.port", "8082")
87 | systemProperty("ide.mac.message.dialogs.as.sheets", "false")
88 | systemProperty("jb.privacy.policy.text", "")
89 | systemProperty("jb.consents.confirmation.enabled", "false")
90 | }
91 |
92 | signPlugin {
93 | certificateChain.set(System.getenv("CERTIFICATE_CHAIN"))
94 | privateKey.set(System.getenv("PRIVATE_KEY"))
95 | password.set(System.getenv("PRIVATE_KEY_PASSWORD"))
96 | }
97 |
98 | publishPlugin {
99 | dependsOn("patchChangelog")
100 | token.set(System.getenv("PUBLISH_TOKEN"))
101 | // pluginVersion is based on the SemVer (https://semver.org) and supports pre-release labels, like 2.1.7-alpha.3
102 | // Specify pre-release label to publish the plugin in a custom Release Channel automatically. Read more:
103 | // https://plugins.jetbrains.com/docs/intellij/deployment.html#specifying-a-release-channel
104 | channels.set(listOf(properties("pluginVersion").split('-').getOrElse(1) { "default" }.split('.').first()))
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # IntelliJ Platform Artifacts Repositories
2 | # -> https://plugins.jetbrains.com/docs/intellij/intellij-artifacts.html
3 |
4 | pluginGroup = com.github.anonfunc.vcidea
5 | pluginName = Voice Code IDEA
6 | # SemVer format -> https://semver.org
7 | pluginVersion = 0.0.17
8 |
9 | # See https://plugins.jetbrains.com/docs/intellij/build-number-ranges.html
10 | # for insight into build numbers and IntelliJ Platform versions.
11 | pluginSinceBuild = 222
12 |
13 | # IntelliJ Platform Properties -> https://github.com/JetBrains/gradle-intellij-plugin#intellij-platform-properties
14 | platformType = IC
15 | platformVersion = 2022.2
16 |
17 | # Plugin Dependencies -> https://plugins.jetbrains.com/docs/intellij/plugin-dependencies.html
18 | # Example: platformPlugins = com.intellij.java, com.jetbrains.php:203.4449.22
19 | platformPlugins =
20 |
21 | # Java language level used to compile sources and to generate the files for - Java 11 is required since 2020.3
22 | javaVersion = 17
23 |
24 | # Gradle Releases -> https://github.com/gradle/gradle/releases
25 | gradleVersion = 8.1.1
26 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anonfunc/intellij-voicecode/93da702842e16701f970f49ce78f90cac968fbf1/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip
4 | networkTimeout=10000
5 | zipStoreBase=GRADLE_USER_HOME
6 | zipStorePath=wrapper/dists
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | #
4 | # Copyright © 2015-2021 the original 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 POSIX generated by Gradle.
22 | #
23 | # Important for running:
24 | #
25 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
26 | # noncompliant, but you have some other compliant shell such as ksh or
27 | # bash, then to run this script, type that shell name before the whole
28 | # command line, like:
29 | #
30 | # ksh Gradle
31 | #
32 | # Busybox and similar reduced shells will NOT work, because this script
33 | # requires all of these POSIX shell features:
34 | # * functions;
35 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
36 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»;
37 | # * compound commands having a testable exit status, especially «case»;
38 | # * various built-in commands including «command», «set», and «ulimit».
39 | #
40 | # Important for patching:
41 | #
42 | # (2) This script targets any POSIX shell, so it avoids extensions provided
43 | # by Bash, Ksh, etc; in particular arrays are avoided.
44 | #
45 | # The "traditional" practice of packing multiple parameters into a
46 | # space-separated string is a well documented source of bugs and security
47 | # problems, so this is (mostly) avoided, by progressively accumulating
48 | # options in "$@", and eventually passing that to Java.
49 | #
50 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
51 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
52 | # see the in-line comments for details.
53 | #
54 | # There are tweaks for specific operating systems such as AIX, CygWin,
55 | # Darwin, MinGW, and NonStop.
56 | #
57 | # (3) This script is generated from the Groovy template
58 | # https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
59 | # within the Gradle project.
60 | #
61 | # You can find Gradle at https://github.com/gradle/gradle/.
62 | #
63 | ##############################################################################
64 |
65 | # Attempt to set APP_HOME
66 |
67 | # Resolve links: $0 may be a link
68 | app_path=$0
69 |
70 | # Need this for daisy-chained symlinks.
71 | while
72 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
73 | [ -h "$app_path" ]
74 | do
75 | ls=$( ls -ld "$app_path" )
76 | link=${ls#*' -> '}
77 | case $link in #(
78 | /*) app_path=$link ;; #(
79 | *) app_path=$APP_HOME$link ;;
80 | esac
81 | done
82 |
83 | # This is normally unused
84 | # shellcheck disable=SC2034
85 | APP_BASE_NAME=${0##*/}
86 | APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
87 |
88 | # Use the maximum available, or set MAX_FD != -1 to use that value.
89 | MAX_FD=maximum
90 |
91 | warn () {
92 | echo "$*"
93 | } >&2
94 |
95 | die () {
96 | echo
97 | echo "$*"
98 | echo
99 | exit 1
100 | } >&2
101 |
102 | # OS specific support (must be 'true' or 'false').
103 | cygwin=false
104 | msys=false
105 | darwin=false
106 | nonstop=false
107 | case "$( uname )" in #(
108 | CYGWIN* ) cygwin=true ;; #(
109 | Darwin* ) darwin=true ;; #(
110 | MSYS* | MINGW* ) msys=true ;; #(
111 | NONSTOP* ) nonstop=true ;;
112 | esac
113 |
114 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
115 |
116 |
117 | # Determine the Java command to use to start the JVM.
118 | if [ -n "$JAVA_HOME" ] ; then
119 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
120 | # IBM's JDK on AIX uses strange locations for the executables
121 | JAVACMD=$JAVA_HOME/jre/sh/java
122 | else
123 | JAVACMD=$JAVA_HOME/bin/java
124 | fi
125 | if [ ! -x "$JAVACMD" ] ; then
126 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
127 |
128 | Please set the JAVA_HOME variable in your environment to match the
129 | location of your Java installation."
130 | fi
131 | else
132 | JAVACMD=java
133 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
134 |
135 | Please set the JAVA_HOME variable in your environment to match the
136 | location of your Java installation."
137 | fi
138 |
139 | # Increase the maximum file descriptors if we can.
140 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
141 | case $MAX_FD in #(
142 | max*)
143 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
144 | # shellcheck disable=SC3045
145 | MAX_FD=$( ulimit -H -n ) ||
146 | warn "Could not query maximum file descriptor limit"
147 | esac
148 | case $MAX_FD in #(
149 | '' | soft) :;; #(
150 | *)
151 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
152 | # shellcheck disable=SC3045
153 | ulimit -n "$MAX_FD" ||
154 | warn "Could not set maximum file descriptor limit to $MAX_FD"
155 | esac
156 | fi
157 |
158 | # Collect all arguments for the java command, stacking in reverse order:
159 | # * args from the command line
160 | # * the main class name
161 | # * -classpath
162 | # * -D...appname settings
163 | # * --module-path (only if needed)
164 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
165 |
166 | # For Cygwin or MSYS, switch paths to Windows format before running java
167 | if "$cygwin" || "$msys" ; then
168 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
169 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
170 |
171 | JAVACMD=$( cygpath --unix "$JAVACMD" )
172 |
173 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
174 | for arg do
175 | if
176 | case $arg in #(
177 | -*) false ;; # don't mess with options #(
178 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
179 | [ -e "$t" ] ;; #(
180 | *) false ;;
181 | esac
182 | then
183 | arg=$( cygpath --path --ignore --mixed "$arg" )
184 | fi
185 | # Roll the args list around exactly as many times as the number of
186 | # args, so each arg winds up back in the position where it started, but
187 | # possibly modified.
188 | #
189 | # NB: a `for` loop captures its iteration list before it begins, so
190 | # changing the positional parameters here affects neither the number of
191 | # iterations, nor the values presented in `arg`.
192 | shift # remove old arg
193 | set -- "$@" "$arg" # push replacement arg
194 | done
195 | fi
196 |
197 |
198 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
199 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
200 |
201 | # Collect all arguments for the java command;
202 | # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
203 | # shell script including quotes and variable substitutions, so put them in
204 | # double quotes to make sure that they get re-expanded; and
205 | # * put everything else in single quotes, so that it's not re-expanded.
206 |
207 | set -- \
208 | "-Dorg.gradle.appname=$APP_BASE_NAME" \
209 | -classpath "$CLASSPATH" \
210 | org.gradle.wrapper.GradleWrapperMain \
211 | "$@"
212 |
213 | # Stop when "xargs" is not available.
214 | if ! command -v xargs >/dev/null 2>&1
215 | then
216 | die "xargs is not available"
217 | fi
218 |
219 | # Use "xargs" to parse quoted args.
220 | #
221 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed.
222 | #
223 | # In Bash we could simply go:
224 | #
225 | # readarray ARGS < <( xargs -n1 <<<"$var" ) &&
226 | # set -- "${ARGS[@]}" "$@"
227 | #
228 | # but POSIX shell has neither arrays nor command substitution, so instead we
229 | # post-process each arg (as a line of input to sed) to backslash-escape any
230 | # character that might be a shell metacharacter, then use eval to reverse
231 | # that process (while maintaining the separation between arguments), and wrap
232 | # the whole thing up as a single "set" statement.
233 | #
234 | # This will of course break if any of these variables contains a newline or
235 | # an unmatched quote.
236 | #
237 |
238 | eval "set -- $(
239 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
240 | xargs -n1 |
241 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
242 | tr '\n' ' '
243 | )" '"$@"'
244 |
245 | exec "$JAVACMD" "$@"
246 |
--------------------------------------------------------------------------------
/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 | @rem This is normally unused
30 | set APP_BASE_NAME=%~n0
31 | set APP_HOME=%DIRNAME%
32 |
33 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
34 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
35 |
36 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
37 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
38 |
39 | @rem Find java.exe
40 | if defined JAVA_HOME goto findJavaFromJavaHome
41 |
42 | set JAVA_EXE=java.exe
43 | %JAVA_EXE% -version >NUL 2>&1
44 | if %ERRORLEVEL% equ 0 goto execute
45 |
46 | echo.
47 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
48 | echo.
49 | echo Please set the JAVA_HOME variable in your environment to match the
50 | echo location of your Java installation.
51 |
52 | goto fail
53 |
54 | :findJavaFromJavaHome
55 | set JAVA_HOME=%JAVA_HOME:"=%
56 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
57 |
58 | if exist "%JAVA_EXE%" goto execute
59 |
60 | echo.
61 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
62 | echo.
63 | echo Please set the JAVA_HOME variable in your environment to match the
64 | echo location of your Java installation.
65 |
66 | goto fail
67 |
68 | :execute
69 | @rem Setup the command line
70 |
71 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
72 |
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if %ERRORLEVEL% equ 0 goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | set EXIT_CODE=%ERRORLEVEL%
85 | if %EXIT_CODE% equ 0 set EXIT_CODE=1
86 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
87 | exit /b %EXIT_CODE%
88 |
89 | :mainEnd
90 | if "%OS%"=="Windows_NT" endlocal
91 |
92 | :omega
93 |
--------------------------------------------------------------------------------
/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | rootProject.name = "IntelliJ Platform Plugin Template"
2 |
--------------------------------------------------------------------------------
/src/main/java/com/github/anonfunc/vcidea/VoicecodePlugin.java:
--------------------------------------------------------------------------------
1 | package com.github.anonfunc.vcidea;
2 |
3 | import com.github.anonfunc.vcidea.commands.VcCommand;
4 | import com.intellij.ide.AppLifecycleListener;
5 | import com.intellij.notification.Notification;
6 | import com.intellij.notification.NotificationType;
7 | import com.intellij.notification.Notifications;
8 | import com.intellij.openapi.diagnostic.Logger;
9 | import com.intellij.util.PlatformUtils;
10 | import com.sun.net.httpserver.HttpExchange;
11 | import com.sun.net.httpserver.HttpHandler;
12 | import com.sun.net.httpserver.HttpServer;
13 |
14 | import java.io.IOException;
15 | import java.io.InputStream;
16 | import java.io.OutputStream;
17 | import java.net.InetAddress;
18 | import java.net.InetSocketAddress;
19 | import java.nio.file.FileSystems;
20 | import java.nio.file.Files;
21 | import java.nio.file.Path;
22 | import java.security.SecureRandom;
23 | import java.util.*;
24 |
25 |
26 | public class VoicecodePlugin implements HttpHandler, AppLifecycleListener {
27 |
28 | public static final int DEFAULT_PORT = 8652;
29 |
30 | private static final Map PLATFORM_TO_PORT = new HashMap<>();
31 | private static final Logger LOG = Logger.getInstance(VoicecodePlugin.class);
32 |
33 | private Path pathToNonce;
34 | private HttpServer server;
35 |
36 | static {
37 | PLATFORM_TO_PORT.put(PlatformUtils.IDEA_PREFIX, 8653);
38 | PLATFORM_TO_PORT.put(PlatformUtils.IDEA_CE_PREFIX, 8654);
39 | PLATFORM_TO_PORT.put(PlatformUtils.APPCODE_PREFIX, 8655);
40 | PLATFORM_TO_PORT.put(PlatformUtils.CLION_PREFIX, 8657);
41 | PLATFORM_TO_PORT.put(PlatformUtils.PYCHARM_PREFIX, 8658);
42 | PLATFORM_TO_PORT.put(PlatformUtils.PYCHARM_CE_PREFIX, 8658);
43 | PLATFORM_TO_PORT.put(PlatformUtils.PYCHARM_EDU_PREFIX, 8658);
44 | PLATFORM_TO_PORT.put(PlatformUtils.RUBY_PREFIX, 8661);
45 | PLATFORM_TO_PORT.put(PlatformUtils.PHP_PREFIX, 8662);
46 | PLATFORM_TO_PORT.put(PlatformUtils.WEB_PREFIX, 8663);
47 | PLATFORM_TO_PORT.put(PlatformUtils.DBE_PREFIX, 8664);
48 | PLATFORM_TO_PORT.put(PlatformUtils.RIDER_PREFIX, 8660);
49 | PLATFORM_TO_PORT.put(PlatformUtils.GOIDE_PREFIX, 8659);
50 | }
51 |
52 | @Override
53 | public void appStarted() {
54 | LOG.info("Starting Voicecode plugin...");
55 | SecureRandom random = new SecureRandom();
56 | byte[] bytes = new byte[20];
57 | random.nextBytes(bytes);
58 | String nonce = new String(Base64.getUrlEncoder().encode(bytes));
59 | // String nonce = "localdev";
60 | Integer port = PLATFORM_TO_PORT.getOrDefault(PlatformUtils.getPlatformPrefix(), DEFAULT_PORT);
61 | try {
62 | pathToNonce = FileSystems.getDefault().getPath(System.getProperty("java.io.tmpdir"), "vcidea_" + port.toString());
63 | Files.write(pathToNonce, nonce.getBytes());
64 | } catch (IOException e) {
65 | LOG.error("Failed to write nonce file", e);
66 | }
67 |
68 | Notification notification = new Notification("vc-idea",
69 | "Voicecode Plugin","Listening on http://localhost:" + port + "/" + nonce,
70 | NotificationType.INFORMATION);
71 | Notifications.Bus.notify(notification);
72 |
73 | // https://stackoverflow.com/questions/3732109/simple-http-server-in-java-using-only-java-se-api#3732328
74 | final InetSocketAddress loopbackSocket = new InetSocketAddress(InetAddress.getLoopbackAddress(), port);
75 | try {
76 | server = HttpServer.create(loopbackSocket, -1);
77 | } catch (IOException e) {
78 | LOG.error("Failed to start server to listen for commands", e);
79 | return;
80 | }
81 | server.createContext("/" + nonce, this);
82 | server.setExecutor(null); // creates a default executor
83 | server.start();
84 | }
85 |
86 | @Override
87 | public void appWillBeClosed(boolean isRestart) {
88 | try {
89 | if(pathToNonce != null) {
90 | Files.delete(pathToNonce);
91 | }
92 | } catch (IOException e) {
93 | LOG.error("Failed to cleanup nonce file", e);
94 | }
95 |
96 | if(server != null) {
97 | server.stop(1);
98 | }
99 | LOG.info("Completed cleanup of Voicecode plugin");
100 | }
101 |
102 | @Override
103 | public void handle(final HttpExchange httpExchange) throws IOException {
104 | try {
105 | LOG.debug("Handling " + httpExchange.getRequestURI().toString() + httpExchange.getRequestMethod());
106 | final InputStream is = httpExchange.getRequestBody();
107 | final Scanner s = new Scanner(is).useDelimiter("\\A");
108 | var response = VcCommand.fromRequestUri(httpExchange.getRequestURI())
109 | .map(VcCommand::run)
110 | .map(resp -> new VoicePluginResponse(200, resp))
111 | .orElse(new VoicePluginResponse(502, "BAD"));
112 | httpExchange.sendResponseHeaders(response.responseCode(), response.response().length());
113 | OutputStream os = httpExchange.getResponseBody();
114 | os.write(response.response().getBytes());
115 | os.close();
116 | } catch (Exception e) {
117 | LOG.error("Failed to process command... ", e);
118 | final String response = e.toString();
119 | httpExchange.sendResponseHeaders(500, response.length());
120 | OutputStream os = httpExchange.getResponseBody();
121 | os.write(response.getBytes());
122 | os.close();
123 | }
124 | }
125 |
126 | record VoicePluginResponse(int responseCode, String response) {
127 | }
128 | }
129 |
--------------------------------------------------------------------------------
/src/main/java/com/github/anonfunc/vcidea/commands/CloneLineCommand.java:
--------------------------------------------------------------------------------
1 | package com.github.anonfunc.vcidea.commands;
2 |
3 | import com.intellij.openapi.application.Application;
4 | import com.intellij.openapi.application.ApplicationManager;
5 | import com.intellij.openapi.command.CommandProcessor;
6 | import com.intellij.openapi.diagnostic.Logger;
7 | import com.intellij.openapi.editor.Document;
8 | import com.intellij.openapi.editor.Editor;
9 | import com.intellij.openapi.project.Project;
10 | import com.intellij.openapi.util.TextRange;
11 |
12 |
13 |
14 | public class CloneLineCommand extends VcCommand {
15 |
16 | private static final Logger LOG = Logger.getInstance(StructureCommand.class);
17 | private int sourceLine;
18 |
19 | public CloneLineCommand(final int sourceLine) {
20 | this.sourceLine = sourceLine - 1;
21 | }
22 |
23 | @Override
24 | public String run() {
25 | final Application application = ApplicationManager.getApplication();
26 | final CommandProcessor cp = CommandProcessor.getInstance();
27 | final Project p = VcCommand.getProject();
28 | try {
29 | application.invokeAndWait(() -> {
30 | final Editor e = VcCommand.getEditor();
31 | final Document document = e.getDocument();
32 | document.setReadOnly(false);
33 | final int startOffset = document.getLineStartOffset(sourceLine);
34 | final int endOffset = document.getLineEndOffset(sourceLine);
35 | final String text = document.getText(new TextRange(startOffset, endOffset)).trim();
36 |
37 | application.runWriteAction(() -> {
38 | final int originalOffset = e.getCaretModel().getOffset();
39 | cp.executeCommand(p, () -> document.insertString(originalOffset, text), "clone", "cloneGroup");
40 | });
41 | });
42 | } catch (Exception ex) {
43 | LOG.error("Failed to run clone line command", ex);
44 | return null;
45 | }
46 | return "OK";
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/main/java/com/github/anonfunc/vcidea/commands/ExtendCommand.java:
--------------------------------------------------------------------------------
1 | package com.github.anonfunc.vcidea.commands;
2 |
3 | import com.intellij.openapi.application.ApplicationManager;
4 | import com.intellij.openapi.editor.Editor;
5 | import com.intellij.openapi.editor.LogicalPosition;
6 | import com.intellij.openapi.editor.ScrollType;
7 | import com.intellij.openapi.editor.SelectionModel;
8 | import com.intellij.openapi.wm.IdeFocusManager;
9 |
10 | public class ExtendCommand extends VcCommand {
11 | private int targetLine;
12 |
13 | public ExtendCommand(int line) {
14 | this.targetLine = line - 1;
15 | }
16 |
17 | @Override
18 | public String run() {
19 |
20 | ApplicationManager.getApplication().invokeAndWait(() -> {
21 | final Editor e = VcCommand.getEditor();
22 | final SelectionModel selection = e.getSelectionModel();
23 | final LogicalPosition current = e.getCaretModel().getLogicalPosition();
24 |
25 | int startLine = Math.min(current.line, targetLine);
26 | int endLine = Math.max(current.line, targetLine);
27 |
28 | e.getCaretModel().moveToLogicalPosition(new LogicalPosition(startLine, 0));
29 | int startOffset = e.getCaretModel().getOffset();
30 | e.getCaretModel().moveToLogicalPosition(new LogicalPosition(endLine + 1, 0));
31 | int endOffset = e.getCaretModel().getOffset() - 1;
32 | selection.setSelection(startOffset, endOffset);
33 | e.getScrollingModel().scrollToCaret(ScrollType.CENTER);
34 | IdeFocusManager.getGlobalInstance().requestFocus(e.getContentComponent(), true);
35 | });
36 |
37 | return "OK";
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/main/java/com/github/anonfunc/vcidea/commands/FindCommand.java:
--------------------------------------------------------------------------------
1 | package com.github.anonfunc.vcidea.commands;
2 |
3 | import com.intellij.find.FindManager;
4 | import com.intellij.find.FindModel;
5 | import com.intellij.find.FindResult;
6 | import com.intellij.openapi.application.ApplicationManager;
7 | import com.intellij.openapi.editor.Document;
8 | import com.intellij.openapi.editor.Editor;
9 | import com.intellij.openapi.editor.ScrollType;
10 | import com.intellij.openapi.editor.SelectionModel;
11 | import com.intellij.openapi.project.Project;
12 | import com.intellij.openapi.project.ProjectManager;
13 | import com.intellij.openapi.wm.IdeFocusManager;
14 |
15 | public class FindCommand extends VcCommand {
16 | private String direction;
17 | private String searchTerm;
18 |
19 | public FindCommand(final String direction, final String searchTerm) {
20 | this.direction = direction;
21 | this.searchTerm = searchTerm;
22 | }
23 |
24 | @Override
25 |
26 | public String run() {
27 | ApplicationManager.getApplication().invokeAndWait(() -> {
28 | final Editor e = VcCommand.getEditor();
29 | final Document document = e.getDocument();
30 | final SelectionModel selection = e.getSelectionModel();
31 | final Project project = ProjectManager.getInstance().getOpenProjects()[0];
32 | FindManager findManager = FindManager.getInstance(project);
33 | final FindModel findModel = new FindModel();
34 | findModel.setStringToFind(searchTerm);
35 | findModel.setCaseSensitive(false);
36 | findModel.setRegularExpressions(true);
37 | findModel.setForward(direction.equals("next"));
38 | final FindResult result = findManager.findString(
39 | document.getCharsSequence(),
40 | e.getCaretModel().getOffset(),
41 | findModel);
42 |
43 | if (result.isStringFound()) {
44 | if (direction.equals("next")) {
45 | e.getCaretModel().moveToOffset(result.getEndOffset());
46 | } else {
47 | e.getCaretModel().moveToOffset(result.getStartOffset());
48 | }
49 | selection.setSelection(result.getStartOffset(), result.getEndOffset());
50 | e.getScrollingModel().scrollToCaret(ScrollType.CENTER);
51 | IdeFocusManager.getGlobalInstance().requestFocus(e.getContentComponent(), true);
52 | }
53 | });
54 | return "OK";
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/main/java/com/github/anonfunc/vcidea/commands/GivenActionCommand.java:
--------------------------------------------------------------------------------
1 | package com.github.anonfunc.vcidea.commands;
2 |
3 | import com.intellij.openapi.actionSystem.ActionManager;
4 | import com.intellij.openapi.actionSystem.AnAction;
5 | import com.intellij.openapi.application.ApplicationManager;
6 | import com.intellij.openapi.ui.playback.commands.ActionCommand;
7 | import com.intellij.openapi.wm.ToolWindow;
8 |
9 | import java.awt.Component;
10 | import java.awt.event.InputEvent;
11 |
12 | // Not sure if this works. XXX
13 | public class GivenActionCommand extends VcCommand {
14 | private String actionId;
15 |
16 | public GivenActionCommand(final String actionId) {
17 | this.actionId = actionId;
18 | }
19 |
20 | @Override
21 | public String run() {
22 | ApplicationManager.getApplication().invokeAndWait(() -> {
23 | AnAction action = ActionManager.getInstance().getAction(actionId);
24 | InputEvent event = ActionCommand.getInputEvent(actionId);
25 | final ToolWindow e = VcCommand.getToolWindow();
26 | Component component = null;
27 | if (e != null) {
28 | component = e.getComponent();
29 | }
30 | ActionManager.getInstance().tryToExecute(action, event, component, null, true);
31 | });
32 | return "OK";
33 |
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/main/java/com/github/anonfunc/vcidea/commands/GotoCommand.java:
--------------------------------------------------------------------------------
1 | package com.github.anonfunc.vcidea.commands;
2 |
3 | import com.intellij.openapi.application.ApplicationManager;
4 | import com.intellij.openapi.editor.Editor;
5 | import com.intellij.openapi.editor.LogicalPosition;
6 | import com.intellij.openapi.editor.ScrollType;
7 | import com.intellij.openapi.wm.IdeFocusManager;
8 |
9 | public class GotoCommand extends VcCommand {
10 | private int line;
11 | private int column;
12 |
13 | public GotoCommand(final int line, final int column) {
14 | // Both count from 0, so adjust.
15 | this.line = Math.max(line - 1, 0);
16 | this.column = Math.max(column - 1, 0);
17 | }
18 |
19 | @Override
20 | public String run() {
21 | final LogicalPosition pos = new LogicalPosition(line, column);
22 | ApplicationManager.getApplication().invokeAndWait(() -> {
23 | final Editor e = VcCommand.getEditor();
24 | e.getCaretModel().removeSecondaryCarets();
25 | e.getCaretModel().moveToLogicalPosition(pos);
26 | e.getScrollingModel().scrollToCaret(ScrollType.CENTER);
27 | e.getSelectionModel().removeSelection();
28 | IdeFocusManager.getGlobalInstance().requestFocus(e.getContentComponent(), true);
29 | });
30 | return "OK";
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/main/java/com/github/anonfunc/vcidea/commands/LocationCommand.java:
--------------------------------------------------------------------------------
1 | package com.github.anonfunc.vcidea.commands;
2 |
3 | import com.intellij.openapi.application.ApplicationManager;
4 | import com.intellij.openapi.editor.Editor;
5 | import com.intellij.openapi.editor.LogicalPosition;
6 |
7 | public class LocationCommand extends VcCommand {
8 |
9 | public LocationCommand() {
10 | }
11 |
12 | @Override
13 | public String run() {
14 | final Editor e[] = new Editor[1];
15 | ApplicationManager.getApplication().invokeAndWait(() -> {
16 | e[0] = VcCommand.getEditor();
17 | });
18 | LogicalPosition logicalPosition = e[0].getCaretModel().getLogicalPosition();
19 | return String.format("%d %d", logicalPosition.line + 1, logicalPosition.column + 1);
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/main/java/com/github/anonfunc/vcidea/commands/RangeCommand.java:
--------------------------------------------------------------------------------
1 | package com.github.anonfunc.vcidea.commands;
2 |
3 | import com.intellij.openapi.application.ApplicationManager;
4 | import com.intellij.openapi.editor.Editor;
5 | import com.intellij.openapi.editor.LogicalPosition;
6 | import com.intellij.openapi.editor.ScrollType;
7 | import com.intellij.openapi.editor.SelectionModel;
8 | import com.intellij.openapi.wm.IdeFocusManager;
9 |
10 | public class RangeCommand extends VcCommand {
11 |
12 | private int startLine;
13 | private int endLine;
14 |
15 | public RangeCommand(int startLine, int endLine) {
16 | this.startLine = startLine - 1;
17 | this.endLine = endLine - 1;
18 | }
19 |
20 | @Override
21 | public String run() {
22 | ApplicationManager.getApplication().invokeAndWait(() -> {
23 | final Editor e = VcCommand.getEditor();
24 | final SelectionModel selection = e.getSelectionModel();
25 | e.getCaretModel().moveToLogicalPosition(new LogicalPosition(startLine, 0));
26 | int startOffset = e.getCaretModel().getOffset();
27 | e.getCaretModel().moveToLogicalPosition(new LogicalPosition(endLine + 1, 0));
28 | int endOffset = e.getCaretModel().getOffset() - 1;
29 | selection.setSelection(startOffset, endOffset);
30 | e.getScrollingModel().scrollToCaret(ScrollType.CENTER);
31 | IdeFocusManager.getGlobalInstance().requestFocus(e.getContentComponent(), true);
32 | });
33 |
34 | return "OK";
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/main/java/com/github/anonfunc/vcidea/commands/StructureCommand.java:
--------------------------------------------------------------------------------
1 | package com.github.anonfunc.vcidea.commands;
2 |
3 | import com.intellij.openapi.application.ApplicationManager;
4 | import com.intellij.openapi.diagnostic.Logger;
5 | import com.intellij.openapi.editor.Editor;
6 | import com.intellij.openapi.editor.ScrollType;
7 | import com.intellij.openapi.util.TextRange;
8 | import com.intellij.openapi.wm.IdeFocusManager;
9 | import com.intellij.psi.PsiElement;
10 | import com.intellij.psi.PsiFile;
11 | import com.intellij.psi.util.PsiUtilBase;
12 | import java.util.ArrayDeque;
13 | import java.util.ArrayList;
14 | import java.util.Arrays;
15 | import java.util.Comparator;
16 | import java.util.HashSet;
17 | import java.util.List;
18 | import java.util.Queue;
19 | import java.util.Set;
20 |
21 | public class StructureCommand extends VcCommand {
22 |
23 | private static final Logger LOG = Logger.getInstance(StructureCommand.class);
24 |
25 | private final String cursorMovement;
26 | private final String startNavType;
27 | private final String[] classes;
28 |
29 | public StructureCommand(String cursorMovement, String[] classes) {
30 | // psi start/end/range containing/previous/next Psi Classes...
31 | this.cursorMovement = cursorMovement;
32 | this.startNavType = classes[0];
33 | this.classes = Arrays.copyOfRange(classes, 1, classes.length);
34 | }
35 |
36 | @Override
37 | public String run() {
38 | LOG.debug("psi " + this.cursorMovement + " " + this.startNavType + " " + String
39 | .join(" ", this.classes));
40 | try {
41 | ApplicationManager.getApplication().invokeAndWait(() -> {
42 | final Editor e = VcCommand.getEditor();
43 | final PsiFile psiFile = VcCommand.getPsiFile();
44 | final int startingOffset = e.getCaretModel().getOffset();
45 | PsiElement currentElement = PsiUtilBase
46 | .getElementAtOffset(psiFile, startingOffset);
47 | printHierarchy(currentElement);
48 | currentElement = parentElementOfNavType(currentElement, startNavType);
49 | LOG.debug("Containing element? " + (currentElement != null));
50 |
51 | if (currentElement == null) {
52 | return;
53 | }
54 |
55 | for (String clazz : this.classes) {
56 | currentElement = childElementOfNavType(currentElement, clazz, startingOffset);
57 | LOG.debug("Next element(" + clazz + ") found? " + (currentElement != null));
58 | if (currentElement == null) {
59 | return;
60 | }
61 | }
62 | // CurrentElement is where we want to go
63 |
64 | final TextRange result = currentElement.getTextRange();
65 | if (cursorMovement.equals("start")) {
66 | e.getCaretModel().moveToOffset(result.getStartOffset());
67 | } else if (cursorMovement.equals("end")) {
68 | e.getCaretModel().moveToOffset(result.getEndOffset());
69 | } else {
70 | e.getCaretModel().moveToOffset(result.getStartOffset());
71 | e.getSelectionModel().setSelection(result.getStartOffset(), result.getEndOffset());
72 | }
73 | e.getScrollingModel().scrollToCaret(ScrollType.CENTER);
74 | IdeFocusManager.getGlobalInstance().requestFocus(e.getContentComponent(), true);
75 |
76 | });
77 | } catch (Exception e) {
78 | LOG.error("Structure command failed", e);
79 | return null;
80 | }
81 |
82 | return "OK";
83 | }
84 |
85 | private void printHierarchy(PsiElement element) {
86 | while (element != null && element.getNode() != null) {
87 | LOG.debug(element.toString() + " " + element.getClass().getName() + " " + element
88 | .getNavigationElement().toString() + " " + element.getNode().getElementType().toString());
89 |
90 | element = element.getParent();
91 | }
92 | }
93 |
94 | private PsiElement parentElementOfNavType(PsiElement element, String specifier) {
95 | final String navType = specifier.split("#")[0];
96 | while (element != null && element.getNode() != null) {
97 | // LOG.debug(element.toString() + " " + element.getClass().getName() + " " + element
98 | // .getNavigationElement().toString() + " " + element.getNode().getElementType().toString());
99 | if (matches(navType, element.getNode().getElementType().toString())) {
100 | return element;
101 | }
102 | element = element.getParent();
103 | }
104 | return null;
105 | }
106 |
107 | private PsiElement childElementOfNavType(PsiElement element, String specifier, int offset) {
108 | Queue toSearch = new ArrayDeque<>();
109 | final String[] split = specifier.split("#");
110 | final String navType;
111 | String direction = null;
112 | int index;
113 | if (split.length == 2) {
114 | navType = split[0];
115 | try {
116 | index = Integer.parseInt(split[1]);
117 | } catch (NumberFormatException e) {
118 | direction = split[1];
119 | index = 0;
120 | }
121 | } else {
122 | navType = specifier;
123 | index = 0;
124 | }
125 | List results = new ArrayList<>();
126 | Set seen = new HashSet<>();
127 | toSearch.add(element);
128 | while (toSearch.peek() != null) {
129 | final PsiElement current = toSearch.remove();
130 | if (current.getNode() != null && matches(navType,
131 | current.getNode().getElementType().toString())) {
132 | results.add(current);
133 | // continue;
134 | }
135 | PsiElement child = current.getFirstChild();
136 | while (child != null) {
137 | if (!seen.contains(child)) {
138 | seen.add(child);
139 | toSearch.add(child);
140 | }
141 | child = child.getNextSibling();
142 | }
143 | }
144 | if (results.size() == 0) {
145 | return null;
146 | }
147 | LOG.debug("Results " + results.toString());
148 | if (direction == null) {
149 | if (index < 0) {
150 | // LOG.debug("Negative index bump " + index);
151 | index += results.size();
152 | // LOG.debug("Negative index bump " + index);
153 | }
154 | return results.get(index);
155 | } else if (direction.equals("next")) {
156 | results.sort(Comparator.comparingInt(PsiElement::getTextOffset));
157 | PsiElement best = null;
158 | int distance = 9999;
159 | for (PsiElement result : results) {
160 | LOG.debug("Result " + result + " offset: " + result.getTextRange().getStartOffset() + " > " + offset);
161 | final int dist = result.getTextRange().getStartOffset() - offset;
162 | if (dist > 0 && dist <= distance) {
163 | best = result;
164 | distance = dist;
165 | }
166 | }
167 | return best;
168 | } else if (direction.equals("last")) {
169 | results.sort((o1, o2) -> -Integer.compare(o1.getTextRange().getEndOffset(), o2.getTextRange().getEndOffset()));
170 | PsiElement best = null;
171 | int distance = 9999;
172 | for (PsiElement result : results) {
173 | LOG.debug(
174 | "Result " + result + " offset: " + result.getTextRange().getEndOffset() + " < "
175 | + offset);
176 | final int dist = offset - result.getTextRange().getEndOffset();
177 | if (dist > 0 && dist <= distance) {
178 | best = result;
179 | distance = dist;
180 | }
181 | }
182 | return best;
183 | } else if (direction.equals("this")) {
184 | PsiElement best = null;
185 | int size = 999999;
186 | for (PsiElement result : results) {
187 |
188 | if (result.getTextRange().contains(offset) && result.getTextLength() < size) {
189 | best = result;
190 | size = result.getTextLength();
191 | }
192 | }
193 | return best;
194 | }
195 | return null;
196 | }
197 |
198 | private boolean matches(String navType, String type) {
199 | final String[] matchingTypes = navType.split("\\|");
200 | for (String matchingType : matchingTypes) {
201 | // LOG.debug("type "+ type +" matches " + matchingType + "? " + type.matches(matchingType));
202 | if (!matchingType.startsWith("^")) {
203 | matchingType = ".*"+ matchingType;
204 | }
205 | if (type.matches(matchingType)) {
206 | return true;
207 | }
208 | }
209 | return false;
210 | }
211 | }
212 |
--------------------------------------------------------------------------------
/src/main/java/com/github/anonfunc/vcidea/commands/VcCommand.java:
--------------------------------------------------------------------------------
1 | package com.github.anonfunc.vcidea.commands;
2 |
3 | import com.intellij.openapi.diagnostic.Logger;
4 | import com.intellij.openapi.editor.Editor;
5 | import com.intellij.openapi.fileEditor.FileEditorManager;
6 | import com.intellij.openapi.project.Project;
7 | import com.intellij.openapi.wm.IdeFocusManager;
8 | import com.intellij.openapi.wm.ToolWindow;
9 | import com.intellij.openapi.wm.ToolWindowManager;
10 | import com.intellij.psi.PsiDocumentManager;
11 | import com.intellij.psi.PsiFile;
12 | import java.io.UnsupportedEncodingException;
13 | import java.net.URI;
14 | import java.net.URLDecoder;
15 | import java.util.Arrays;
16 | import java.util.Optional;
17 |
18 | public abstract class VcCommand {
19 |
20 | private static final Logger LOG = Logger.getInstance(VcCommand.class);
21 |
22 | public static Optional fromRequestUri(URI requestURI) {
23 |
24 | String[] split;
25 | try {
26 | String decode = URLDecoder.decode(requestURI.toString().substring(1), "UTF-8");
27 | split = decode.split("/");
28 | // XXX For debugging
29 | // Notification notification =new Notification("vc-idea", "Voicecode Plugin", decode,
30 | // NotificationType.INFORMATION);
31 | // Notifications.Bus.notify(notification);
32 | split = split[1].split(" ");
33 | } catch (UnsupportedEncodingException e) {
34 | LOG.error("Failed to parse request URI", e);
35 | return Optional.empty();
36 |
37 | }
38 |
39 | String command = split[0];
40 | if (command.equals("goto")) {
41 | return Optional.of(new GotoCommand(Integer.parseInt(split[1]), Integer.parseInt(split[2])));
42 | }
43 | if (command.equals("range")) {
44 | return Optional.of(new RangeCommand(Integer.parseInt(split[1]), Integer.parseInt(split[2])));
45 | }
46 | if (command.equals("extend")) {
47 | return Optional.of(new ExtendCommand(Integer.parseInt(split[1])));
48 | }
49 | if (command.equals("clone")) {
50 | return Optional.of(new CloneLineCommand(Integer.parseInt(split[1])));
51 | }
52 | if (command.equals("action")) {
53 | return Optional.of(new GivenActionCommand(split[1]));
54 | }
55 | if (command.equals("location")) {
56 | return Optional.of(new LocationCommand());
57 | }
58 | if (command.equals("find")) {
59 | return Optional.of(new FindCommand(split[1], String.join(" ", Arrays.copyOfRange(split, 2, split.length))));
60 | }
61 | if (command.equals("psi")) {
62 | return Optional.of(new StructureCommand(split[1], String.join(" ", Arrays.copyOfRange(split, 2, split.length)).split(",")));
63 | }
64 | return Optional.empty();
65 | }
66 |
67 | static Editor getEditor() {
68 | Project currentProject = getProject();
69 | Editor e = FileEditorManager.getInstance(currentProject).getSelectedTextEditor();
70 | if (e == null) {
71 | LOG.debug("No selected editor?");
72 | }
73 | return e;
74 | }
75 |
76 | static ToolWindow getToolWindow() {
77 | Project currentProject = getProject();
78 | ToolWindowManager twm = ToolWindowManager.getInstance(currentProject);
79 | ToolWindow tw = twm.getToolWindow(twm.getActiveToolWindowId());
80 | if (tw == null) {
81 | LOG.debug("No selected tool window?");
82 | }
83 | return tw;
84 | }
85 |
86 | static PsiFile getPsiFile() {
87 | Project currentProject = getProject();
88 | Editor e = FileEditorManager.getInstance(currentProject).getSelectedTextEditor();
89 | final PsiFile psiFile = PsiDocumentManager.getInstance(currentProject)
90 | .getPsiFile(e.getDocument());
91 | return psiFile;
92 | }
93 |
94 | static Project getProject() {
95 | return IdeFocusManager.findInstance().getLastFocusedFrame().getProject();
96 | }
97 |
98 | public abstract String run();
99 | }
100 |
--------------------------------------------------------------------------------
/src/main/resources/META-INF/plugin.xml:
--------------------------------------------------------------------------------
1 |
2 | com.github.anonfunc.vcidea
3 | Voice Code Idea
4 | dummy
5 | anonfunc
6 |
7 | This plugin adds a small web server which supports a Talon module, but can be used generically as an
9 | HTTP based RPC driven by any system on the same machine.
10 |
11 | For an example integration, see the author's Talon files.
12 |
13 | Support, if any, provided in the #jetbrains channel of the Talon slack. See the Talon homepage for
14 | more information about Talon.
15 | ]]>
16 |
17 |
20 |
21 |
22 |
23 |
24 |
25 |
27 |
28 | com.intellij.modules.platform
29 | com.intellij.modules.lang
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
42 |
43 |
44 |
--------------------------------------------------------------------------------