├── .gitignore
├── LICENSE
├── README.md
├── build.gradle
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── mypy-mypy.png
├── resources
├── META-INF
│ └── plugin.xml
└── icons
│ ├── mypy.png
│ ├── mypy@2x.png
│ ├── mypy@2x_dark.png
│ ├── mypy_dark.png
│ ├── mypybig.png
│ ├── mypybig@2x.png
│ ├── mypybig@2x_dark.png
│ └── mypybig_dark.png
├── settings.gradle
└── src
├── com
└── dropbox
│ └── plugins
│ └── mypy_plugin
│ ├── MypyCellRenderer.java
│ ├── MypyConfigDialog.form
│ ├── MypyConfigDialog.java
│ ├── MypyConfigLoader.java
│ ├── MypyConfigService.java
│ ├── MypyHelp.form
│ ├── MypyHelp.java
│ ├── MypyRunner.java
│ ├── MypyTerminal.form
│ ├── MypyTerminal.java
│ ├── MypyToolWindowFactory.java
│ ├── actions
│ ├── AskSuggestion.java
│ ├── CopyError.java
│ ├── ExpandErrors.java
│ ├── NextError.java
│ ├── PrevError.java
│ ├── RunMypyDaemon.java
│ └── ShowMypyWindow.java
│ └── model
│ ├── MypyConfig.java
│ ├── MypyError.java
│ └── MypyResult.java
└── icons
└── MypyIcons.java
/.gitignore:
--------------------------------------------------------------------------------
1 | .gradle
2 | .idea
3 | build
4 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # mypy-PyCharm-plugin
4 |
5 | The plugin provides a simple terminal to run fast mypy daemon
6 | from PyCharm with a single click or hotkey and easily navigate
7 | through type checking results. The idea of the mypy terminal is
8 | different from the normal PyCharm type checking that highlights
9 | the errors in a current file. The mypy terminal shows errors in
10 | all files in your project (even in those not currently open).
11 | Also mypy provides a bit stricter type checking and is tunable
12 | by various flags and config settings.
13 |
14 | 
15 |
16 | ## Installation
17 |
18 | The plugin can be installed directly from
19 | [JetBrains plugin repository](https://plugins.jetbrains.com/plugin/13348-mypy-official-/)
20 |
21 | ## Installing developer build
22 |
23 | Requirements for building the plugin:
24 |
25 | * [Oracle JDK 8](https://www.oracle.com/javadownload)
26 | * Either `javac` should be available on your `PATH` or `JAVA_HOME`
27 | environment variable should contain your JDK installation path
28 |
29 | Requirements for running the plugin:
30 |
31 | * [Mypy](https://github.com/python/mypy)
32 | * The plugin runs the `mypy` executable to check types
33 |
34 | Installation steps:
35 |
36 | 1. Clone the GitHub repository.
37 |
38 | 2. Open the cloned directory in your terminal and build it using this
39 | shell command:
40 |
41 | ./gradlew clean buildPlugin
42 |
43 | or on Windows:
44 |
45 | gradlew clean buildPlugin
46 |
47 | The plugin file `mypy-PyCharm-plugin.zip` will be built in
48 | `build/distributions`.
49 |
50 | 3. In PyCharm go to Preferences -> Plugins -> Install plugins from disk
51 | -> Select the plugin file -> Restart PyCharm when prompted.
52 |
53 | 4. After restart you should find the plugin in View -> Tool windows
54 | -> Mypy terminal.
55 |
56 | ## Configuration
57 |
58 | Normally, plugin should not require any configuration steps. However,
59 | sometimes plugin cannot find `dmypy` command because it doesn't have
60 | the full environment. If the plugin says something like `dmypy command not found`
61 | when you try to run mypy, then this is likely the cause. In this case right click
62 | in mypy terminal in PyCharm -> Configure plugin. Then enter the path where
63 | mypy is installed as PATH suffix. If you are using a virtual environment, this
64 | will look like `/my/project/bin` (or `C:\my\project\Scripts` if you are on Windows).
65 | If necessary, you can also configure mypy command to use your custom `.ini` file
66 | and flags.
67 |
68 | ## Usage
69 |
70 | You can pin the terminal to either side of PyCharm window: click
71 | on window toolbar → Move. The current default is bottom, which
72 | works best if you typically have only a few errors. If you are
73 | working on legacy code with many mypy errors, you may want to use
74 | the ‘left’ or ‘right’ setting. Finally, if you have multiple
75 | monitors you might find the floating mode convenient.
76 |
77 | Currently supported features and keyboard shortcuts:
78 |
79 | - Show/hide mypy terminal: `Ctrl + Shift + X`
80 | - Run mypy type checking: `Ctrl + Shift + M` or click Run
81 | - Go to error: click on error line, or use `Ctrl + Shift + `
82 | to navigate between errors
83 | - Copy current error: right click → Copy error text,
84 | or `Ctrl + Shift + C`
85 | - Collapse/expand errors: click on file name in the mypy terminal,
86 | or `Ctrl + Shift + Enter` when a file name is selected
87 | - Sometimes mypy shows links to online documentation; to follow
88 | links use `Alt + `
89 |
90 | ## Contributing
91 |
92 | External contributions to the project should be subject to
93 | Dropbox Contributor License Agreement (CLA).
94 |
95 | 1. Open the repository in IntelliJ 2019.1 or newer via
96 | File -> Open. IntelliJ will import it as a Gradle project.
97 |
98 | 2. Set up a project JDK via File -> Project Structure -> Project
99 | -> Project SDK. JDK 8 or newer is required.
100 |
101 | 3. Build and run the plugin via a Gradle task `runIde` available
102 | as View -> Tool Windows -> Gradle -> Tasks -> intellij -> runIde.
103 |
104 | --------------------------------
105 | Copyright (c) 2018 Dropbox, Inc.
106 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'org.jetbrains.intellij' version '0.4.8'
3 | }
4 |
5 | sourceCompatibility = 1.8
6 |
7 | repositories {
8 | mavenCentral()
9 | }
10 |
11 | sourceSets {
12 | main {
13 | java.srcDir 'src'
14 | resources.srcDir 'resources'
15 | }
16 | }
17 |
18 | intellij {
19 | pluginName 'mypy-plugin'
20 | // Build for IntelliJ Community with the Python plugin
21 | // Building for PyCharm will be directly supported soon by https://github.com/JetBrains/gradle-intellij-plugin
22 | version 'IC-2019.1.3'
23 | plugins = ['PythonCore:2019.1.191.7479.7']
24 | updateSinceUntilBuild false
25 | downloadSources true
26 | buildSearchableOptions.enabled = false
27 | }
28 |
29 | dependencies {
30 | implementation 'org.ini4j:ini4j:0.5.4'
31 | }
32 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dropbox/mypy-PyCharm-plugin/49354e6263f50a3d985931384948b2a78af0f0d1/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Thu May 30 01:09:45 MSK 2019
2 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip
3 | distributionBase=GRADLE_USER_HOME
4 | distributionPath=wrapper/dists
5 | zipStorePath=wrapper/dists
6 | zipStoreBase=GRADLE_USER_HOME
7 |
--------------------------------------------------------------------------------
/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 | # http://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, switch paths to Windows format before running java
129 | if $cygwin ; 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=$((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 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
184 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
185 | cd "$(dirname "$0")"
186 | fi
187 |
188 | exec "$JAVACMD" "$@"
189 |
--------------------------------------------------------------------------------
/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 http://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 |
--------------------------------------------------------------------------------
/mypy-mypy.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dropbox/mypy-PyCharm-plugin/49354e6263f50a3d985931384948b2a78af0f0d1/mypy-mypy.png
--------------------------------------------------------------------------------
/resources/META-INF/plugin.xml:
--------------------------------------------------------------------------------
1 |
2 | com.dropbox.plugins.mypy_plugin
3 | Mypy (Official)
4 | 0.3.2
5 | Dropbox Mypy Team
6 |
7 | mypy
9 | from PyCharm and navigate between errors.
10 | ]]>
11 |
12 |
14 | Version 0.3.2 (November 2019) — Replace deprecated icon.
15 | Version 0.3.1 (November 2019) — Add basic Windows support. Update plugin display name.
16 | Switch default command to dmypy run.
17 | Version 0.3.0 (October 2019) — Minor bug fixes. Support reading configuration from mypy.ini.
18 | Experimental support of dmypy suggest. Convert plugin to Gradle.
19 | Version 0.2.0 (November 2018) — Track error locations after editing.
20 | Avoid NullPointerException on empty output. Show output if there are only notes.
21 | Show notification in editor when type checking completed while plugin window is closed.
22 | Version 0.1.1 (April 2018) — Minor bug fixes. Simple configuration.
23 | Version 0.1.0 (April 2018) — Initial prototype. Show errors and navigate between them.
24 |
25 | ]]>
26 |
27 |
28 |
29 |
30 |
31 |
33 |
34 | com.intellij.modules.lang
35 | com.intellij.modules.python
36 |
37 |
38 |
39 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
--------------------------------------------------------------------------------
/resources/icons/mypy.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dropbox/mypy-PyCharm-plugin/49354e6263f50a3d985931384948b2a78af0f0d1/resources/icons/mypy.png
--------------------------------------------------------------------------------
/resources/icons/mypy@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dropbox/mypy-PyCharm-plugin/49354e6263f50a3d985931384948b2a78af0f0d1/resources/icons/mypy@2x.png
--------------------------------------------------------------------------------
/resources/icons/mypy@2x_dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dropbox/mypy-PyCharm-plugin/49354e6263f50a3d985931384948b2a78af0f0d1/resources/icons/mypy@2x_dark.png
--------------------------------------------------------------------------------
/resources/icons/mypy_dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dropbox/mypy-PyCharm-plugin/49354e6263f50a3d985931384948b2a78af0f0d1/resources/icons/mypy_dark.png
--------------------------------------------------------------------------------
/resources/icons/mypybig.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dropbox/mypy-PyCharm-plugin/49354e6263f50a3d985931384948b2a78af0f0d1/resources/icons/mypybig.png
--------------------------------------------------------------------------------
/resources/icons/mypybig@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dropbox/mypy-PyCharm-plugin/49354e6263f50a3d985931384948b2a78af0f0d1/resources/icons/mypybig@2x.png
--------------------------------------------------------------------------------
/resources/icons/mypybig@2x_dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dropbox/mypy-PyCharm-plugin/49354e6263f50a3d985931384948b2a78af0f0d1/resources/icons/mypybig@2x_dark.png
--------------------------------------------------------------------------------
/resources/icons/mypybig_dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dropbox/mypy-PyCharm-plugin/49354e6263f50a3d985931384948b2a78af0f0d1/resources/icons/mypybig_dark.png
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | /*
2 | * This file was generated by the Gradle 'init' task.
3 | *
4 | * The settings file is used to specify which projects to include in your build.
5 | *
6 | * Detailed information about configuring a multi-project build in Gradle can be found
7 | * in the user manual at https://docs.gradle.org/5.4.1/userguide/multi_project_builds.html
8 | */
9 |
10 | rootProject.name = 'mypy-PyCharm-plugin'
11 |
--------------------------------------------------------------------------------
/src/com/dropbox/plugins/mypy_plugin/MypyCellRenderer.java:
--------------------------------------------------------------------------------
1 | package com.dropbox.plugins.mypy_plugin;
2 |
3 | import com.dropbox.plugins.mypy_plugin.model.MypyError;
4 | import com.intellij.ui.ColoredListCellRenderer;
5 | import com.intellij.ui.JBColor;
6 | import com.intellij.ui.SimpleTextAttributes;
7 | import org.jetbrains.annotations.NotNull;
8 |
9 | import javax.swing.*;
10 | import java.awt.*;
11 |
12 | final class MypyCellRenderer extends ColoredListCellRenderer {
13 | MypyCellRenderer() {
14 | setOpaque(true);
15 | }
16 |
17 | @Override
18 | protected void customizeCellRenderer(@NotNull JList extends MypyError> list, MypyError value, int index, boolean selected, boolean hasFocus) {
19 | int LINE_WIDTH = 5;
20 | int GAP = 10;
21 | String TAB = " ";
22 | setFont(list.getFont());
23 | setToolTipText(null);
24 | boolean isError = value.getLevel() == MypyError.ERROR;
25 | boolean isNote = value.getLevel() == MypyError.NOTE;
26 | boolean collapsed = value.isCollapsed();
27 | setPreferredSize(new Dimension(-1, 5));
28 | if (value.getLevel() == MypyError.HEADER) {
29 | if (collapsed) {
30 | setIcon(UIManager.getIcon("Tree.collapsedIcon"));
31 | setIconTextGap(GAP);
32 | } else {
33 | setIcon(UIManager.getIcon("Tree.expandedIcon"));
34 | setIconTextGap(GAP);
35 | }
36 | } else {
37 | setIcon(null);
38 | }
39 | if (value.getLevel() == MypyError.HEADER) {
40 | String file = value.getFile();
41 | String suffix = value.getErrCount() != 1 ? "s" : "";
42 | String cnt = String.format("(%d error%s)", value.getErrCount(), suffix);
43 | append(file + " ");
44 | append(cnt, new SimpleTextAttributes(SimpleTextAttributes.STYLE_PLAIN,
45 | new JBColor(new Color(255, 100, 100), new Color(255, 100, 100))));
46 | } else if (isError | isNote) {
47 | String line;
48 | if (value.getLine() > 0) {
49 | line = String.format("%d", value.getLine());
50 | } else {
51 | line = "";
52 | }
53 | append(TAB + String.format("%1$-" + LINE_WIDTH + "s", isNote ? "" : line),
54 | new SimpleTextAttributes(SimpleTextAttributes.STYLE_PLAIN,
55 | new JBColor(new Color(MypyTerminal.GRAY), new Color(MypyTerminal.DARK_GRAY))));
56 | if (isNote) {
57 | String[] chunks = value.getMessage().split("\"");
58 | boolean italic = false;
59 | for (int i = 0; i < chunks.length; i++) {
60 | if (i == chunks.length - 1) { // TODO: replace with real HTTP regex treatment.
61 | if (chunks[i].matches(".+ http://.+")) {
62 | String[] subChunks = chunks[i].split("http://");
63 | append(subChunks[0], new SimpleTextAttributes(SimpleTextAttributes.STYLE_PLAIN,
64 | new JBColor(new Color(MypyTerminal.GRAY), new Color(MypyTerminal.DARK_GRAY))));
65 | append(subChunks[1], SimpleTextAttributes.LINK_ATTRIBUTES);
66 | setToolTipText("Alt + click to follow link");
67 | break;
68 | }
69 | }
70 | if (italic) {
71 | append(chunks[i], new SimpleTextAttributes(SimpleTextAttributes.STYLE_ITALIC,
72 | new JBColor(new Color(MypyTerminal.GRAY), new Color(MypyTerminal.DARK_GRAY))));
73 | } else {
74 | append(chunks[i], new SimpleTextAttributes(SimpleTextAttributes.STYLE_PLAIN,
75 | new JBColor(new Color(MypyTerminal.GRAY), new Color(MypyTerminal.DARK_GRAY))));
76 | }
77 | italic = !italic;
78 | }
79 | } else {
80 | String[] chunks = value.getMessage().split("\"");
81 | boolean italic = false;
82 | for (String chunk : chunks) {
83 | if (italic) {
84 | append(chunk, SimpleTextAttributes.REGULAR_ITALIC_ATTRIBUTES);
85 | } else {
86 | append(chunk);
87 | }
88 | italic = !italic;
89 | }
90 | }
91 | } else {
92 | // something ill-formatted
93 | append(value.getRaw());
94 | }
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/src/com/dropbox/plugins/mypy_plugin/MypyConfigDialog.form:
--------------------------------------------------------------------------------
1 |
2 |
67 |
--------------------------------------------------------------------------------
/src/com/dropbox/plugins/mypy_plugin/MypyConfigDialog.java:
--------------------------------------------------------------------------------
1 | package com.dropbox.plugins.mypy_plugin;
2 |
3 | import com.dropbox.plugins.mypy_plugin.model.MypyConfig;
4 | import com.intellij.openapi.project.Project;
5 | import com.intellij.openapi.ui.DialogWrapper;
6 | import com.intellij.openapi.ui.ValidationInfo;
7 | import icons.MypyIcons;
8 |
9 | import javax.swing.*;
10 |
11 | public final class MypyConfigDialog extends DialogWrapper {
12 | private JPanel contentPane;
13 | private JLabel logo;
14 | private JTextField command;
15 | private JTextField path;
16 | private final MypyConfigService configService;
17 |
18 | MypyConfigDialog(Project project) {
19 | super(project);
20 | setModal(true);
21 | init();
22 | setTitle("Mypy Plugin Configuration");
23 | configService = MypyConfigService.getInstance(project);
24 | MypyConfig config = MypyConfigLoader.findMypyConfig(project);
25 | this.command.setText(config.getExecutableName());
26 | this.path.setText(config.getPathSuffix());
27 | logo.setIcon(MypyIcons.MYPY_BIG);
28 | command.setCaretPosition(0);
29 | path.setCaretPosition(0);
30 | }
31 |
32 | @Override
33 | protected void doOKAction() {
34 | configService.loadState(new MypyConfig(command.getText(), path.getText()));
35 | super.doOKAction();
36 | }
37 |
38 | public JComponent createCenterPanel() {
39 | return this.contentPane;
40 | }
41 |
42 | public ValidationInfo doValidate() {
43 | if(this.command.getText().equals("")) {
44 | return new ValidationInfo("Command cannot be empty", this.command);
45 | }
46 | return null;
47 | }
48 |
49 | }
50 |
--------------------------------------------------------------------------------
/src/com/dropbox/plugins/mypy_plugin/MypyConfigLoader.java:
--------------------------------------------------------------------------------
1 | package com.dropbox.plugins.mypy_plugin;
2 |
3 | import com.dropbox.plugins.mypy_plugin.model.MypyConfig;
4 | import com.intellij.openapi.project.Project;
5 | import org.ini4j.Ini;
6 |
7 | import java.io.File;
8 | import java.io.IOException;
9 |
10 | public class MypyConfigLoader {
11 | final static String DEFAULT_MYPY_COMMAND = "dmypy run -- --follow-imports=error .";
12 | final static String DEFAULT_MYPY_PATH_SUFFIX = "";
13 |
14 | public static MypyConfig findMypyConfig(Project project) {
15 | MypyConfigService configService = MypyConfigService.getInstance(project);
16 | if (configService != null && configService.getState() != null) {
17 | return configService.getState();
18 | }
19 | MypyConfig iniConfig = loadConfigFromIni(project);
20 | if (iniConfig != null) {
21 | return iniConfig;
22 | }
23 | return new MypyConfig(DEFAULT_MYPY_COMMAND, DEFAULT_MYPY_PATH_SUFFIX);
24 | }
25 |
26 | public static MypyConfig loadConfigFromIni(Project project) {
27 | String directory = project.getBaseDir().getPath();
28 | File ini_file = new File(directory, "mypy.ini");
29 | Ini ini = null;
30 | try {
31 | ini = new Ini(ini_file);
32 | } catch (IOException e) {
33 | return null;
34 | }
35 | String command = ini.get("mypy", "x_pycharm_command");
36 | String path_suffix = ini.get("mypy", "x_pycharm_PATH");
37 | return new MypyConfig(
38 | command != null ? command : DEFAULT_MYPY_COMMAND,
39 | path_suffix != null ? path_suffix : DEFAULT_MYPY_PATH_SUFFIX);
40 |
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/com/dropbox/plugins/mypy_plugin/MypyConfigService.java:
--------------------------------------------------------------------------------
1 | package com.dropbox.plugins.mypy_plugin;
2 |
3 | import com.dropbox.plugins.mypy_plugin.model.MypyConfig;
4 | import com.intellij.openapi.components.PersistentStateComponent;
5 | import com.intellij.openapi.components.ServiceManager;
6 | import com.intellij.openapi.components.State;
7 | import com.intellij.openapi.components.Storage;
8 | import com.intellij.openapi.project.Project;
9 | import org.jetbrains.annotations.NotNull;
10 | import org.jetbrains.annotations.Nullable;
11 |
12 | @State(
13 | name = "MypyConfigService",
14 | storages = {
15 | @Storage("MypyConfig.xml")}
16 | )
17 | public final class MypyConfigService implements PersistentStateComponent {
18 | private MypyConfig config;
19 |
20 | @Nullable
21 | @Override
22 | public MypyConfig getState() {
23 | return config;
24 | }
25 |
26 | @Override
27 | public void loadState(@NotNull MypyConfig config) {
28 | this.config = config;
29 | }
30 |
31 | @Nullable
32 | public static MypyConfigService getInstance(Project project) {
33 | return ServiceManager.getService(project, MypyConfigService.class);
34 | }
35 | }
--------------------------------------------------------------------------------
/src/com/dropbox/plugins/mypy_plugin/MypyHelp.form:
--------------------------------------------------------------------------------
1 |
2 |
80 |
--------------------------------------------------------------------------------
/src/com/dropbox/plugins/mypy_plugin/MypyHelp.java:
--------------------------------------------------------------------------------
1 | package com.dropbox.plugins.mypy_plugin;
2 |
3 | import com.intellij.openapi.project.Project;
4 | import com.intellij.openapi.wm.WindowManager;
5 | import com.intellij.ui.JBColor;
6 |
7 | import javax.swing.*;
8 | import java.awt.*;
9 |
10 | public final class MypyHelp extends JDialog {
11 | private JPanel contentPane;
12 | private JButton buttonOK;
13 | private JTextPane textPane;
14 |
15 | private MypyHelp() {
16 | setContentPane(contentPane);
17 | setModal(false);
18 | getRootPane().setDefaultButton(buttonOK);
19 |
20 | buttonOK.addActionListener(e -> dispose());
21 | }
22 |
23 | static void show(Project project) {
24 | MypyHelp dialog = new MypyHelp();
25 | dialog.pack();
26 | dialog.setSize(600, 400);
27 | JFrame frame = WindowManager.getInstance().getFrame(project);
28 | dialog.setLocationRelativeTo(frame);
29 | dialog.textPane.setCaretPosition(0);
30 | dialog.textPane.setForeground(new JBColor(MypyTerminal.BLACK, MypyTerminal.GRAY));
31 | dialog.textPane.setBackground(new JBColor(new Color(MypyTerminal.WHITE),
32 | dialog.contentPane.getBackground()));
33 | dialog.setVisible(true);
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/com/dropbox/plugins/mypy_plugin/MypyRunner.java:
--------------------------------------------------------------------------------
1 | package com.dropbox.plugins.mypy_plugin;
2 |
3 | import com.dropbox.plugins.mypy_plugin.model.MypyConfig;
4 | import com.dropbox.plugins.mypy_plugin.model.MypyError;
5 | import com.dropbox.plugins.mypy_plugin.model.MypyResult;
6 | import com.intellij.openapi.application.ApplicationManager;
7 | import com.intellij.openapi.project.Project;
8 | import com.intellij.openapi.ui.Messages;
9 | import com.intellij.openapi.vfs.VirtualFile;
10 | import org.jetbrains.annotations.Nullable;
11 |
12 | import javax.swing.*;
13 | import java.io.BufferedReader;
14 | import java.io.File;
15 | import java.io.IOException;
16 | import java.io.InputStreamReader;
17 | import java.util.*;
18 |
19 | public final class MypyRunner {
20 | private final JList display;
21 | private final Project project;
22 | private boolean isRunning;
23 |
24 | MypyRunner(JList display, Project project) {
25 | this.display = display;
26 | this.project = project;
27 | this.isRunning = false;
28 | }
29 |
30 | @Nullable
31 | MypyResult runMypyDaemon(@Nullable String command, @Nullable VirtualFile vf) {
32 | Process process;
33 | String directory = project.getBaseDir().getPath();
34 | MypyConfig config = MypyConfigLoader.findMypyConfig(project);
35 |
36 | ProcessBuilder processBuilder = new ProcessBuilder();
37 | Map envProcess = processBuilder.environment();
38 | Map env = System.getenv();
39 |
40 | boolean isWindows = System.getProperty("os.name").startsWith("Windows");
41 | String pathVar = isWindows ? "Path" : "PATH";
42 |
43 | envProcess.putAll(env);
44 | String extraPath = config.getPathSuffix();
45 | if (!extraPath.equals("")) {
46 | envProcess.put(pathVar, envProcess.get(pathVar) + File.pathSeparator + extraPath);
47 | }
48 | String mypyCommand;
49 | if (command != null) {
50 | mypyCommand = command;
51 | }
52 | else {
53 | mypyCommand = config.getExecutableName();
54 | }
55 | if (isWindows) {
56 | processBuilder.command("cmd.exe", "/C", mypyCommand);
57 | }
58 | else {
59 | processBuilder.command("/bin/bash", "-c", mypyCommand);
60 | }
61 | processBuilder.redirectErrorStream(true);
62 | if (!isWindows) {
63 | processBuilder.redirectInput(new File("/dev/null"));
64 | }
65 | this.isRunning = true;
66 | try {
67 | process = processBuilder.directory(new File(directory)).start();
68 | } catch (IOException e) {
69 | ApplicationManager.getApplication().invokeLater(() -> Messages.showMessageDialog(project, e.getMessage(),
70 | "Plugin Exception:", Messages.getErrorIcon()));
71 | this.isRunning = false;
72 | return null;
73 | }
74 | ArrayList lines = new ArrayList<>();
75 | ArrayList debug = new ArrayList<>();
76 | BufferedReader br=new BufferedReader(
77 | new InputStreamReader(
78 | process.getInputStream()));
79 | MypyError[] data;
80 | int errCount = 0;
81 | int noteCount = 0;
82 | try {
83 | String line;
84 | while((line=br.readLine()) != null) {
85 | if (line.matches(MypyTerminal.ERROR_RE) | line.matches(MypyTerminal.NOTE_RE)) {
86 | lines.add(new MypyError(line, line.matches(MypyTerminal.ERROR_RE) ? MypyError.ERROR : MypyError.NOTE));
87 | if (line.matches(MypyTerminal.ERROR_RE)) {
88 | errCount++;
89 | }
90 | if (line.matches(MypyTerminal.NOTE_RE)) {
91 | noteCount++;
92 | }
93 | } else if (line.matches("PASSED") | line.matches("FAILED")) {
94 | // these will be shown in status line anyway
95 | break;
96 | } else {
97 | debug.add(new MypyError(line, MypyError.DEBUG));
98 | }
99 | data = new MypyError[debug.size()];
100 | data = debug.toArray(data);
101 | this.display.setListData(data);
102 | int max = this.display.getModel().getSize();
103 | if (max > 0) {
104 | this.display.scrollRectToVisible(this.display.getCellBounds(max - 1, max));
105 | }
106 | }
107 | process.waitFor();
108 | if (vf != null) {
109 | vf.refresh(false, false);
110 | }
111 | } catch (IOException | InterruptedException e) {
112 | ApplicationManager.getApplication().invokeLater(() -> Messages.showMessageDialog(project, e.getMessage(),
113 | "Plugin Exception:", Messages.getErrorIcon()));
114 | this.isRunning = false;
115 | return null;
116 | }
117 | lines.sort(Comparator.comparing(MypyError::getFile));
118 | this.isRunning = false;
119 | return new MypyResult(process.exitValue(), errCount, noteCount, lines);
120 | }
121 |
122 | public boolean isRunning() {
123 | return isRunning;
124 | }
125 | }
126 |
--------------------------------------------------------------------------------
/src/com/dropbox/plugins/mypy_plugin/MypyTerminal.form:
--------------------------------------------------------------------------------
1 |
2 |
71 |
--------------------------------------------------------------------------------
/src/com/dropbox/plugins/mypy_plugin/MypyTerminal.java:
--------------------------------------------------------------------------------
1 | package com.dropbox.plugins.mypy_plugin;
2 |
3 | import com.dropbox.plugins.mypy_plugin.model.MypyError;
4 | import com.dropbox.plugins.mypy_plugin.model.MypyResult;
5 | import com.intellij.icons.AllIcons;
6 | import com.intellij.notification.Notification;
7 | import com.intellij.notification.NotificationType;
8 | import com.intellij.notification.Notifications;
9 | import com.intellij.openapi.application.ApplicationManager;
10 | import com.intellij.openapi.editor.Document;
11 | import com.intellij.openapi.editor.Editor;
12 | import com.intellij.openapi.editor.LogicalPosition;
13 | import com.intellij.openapi.editor.ScrollType;
14 | import com.intellij.openapi.fileEditor.FileDocumentManager;
15 | import com.intellij.openapi.fileEditor.FileEditor;
16 | import com.intellij.openapi.fileEditor.TextEditor;
17 | import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx;
18 | import com.intellij.openapi.project.Project;
19 | import com.intellij.openapi.ui.JBMenuItem;
20 | import com.intellij.openapi.ui.JBPopupMenu;
21 | import com.intellij.openapi.ui.Messages;
22 | import com.intellij.openapi.vfs.LocalFileSystem;
23 | import com.intellij.openapi.vfs.VirtualFile;
24 | import com.intellij.openapi.wm.ToolWindow;
25 | import com.intellij.openapi.wm.ToolWindowManager;
26 | import com.intellij.ui.JBColor;
27 | import com.intellij.ui.components.JBList;
28 | import com.intellij.ui.content.Content;
29 | import com.intellij.ui.content.ContentFactory;
30 | import icons.MypyIcons;
31 | import org.jetbrains.annotations.Nullable;
32 |
33 | import javax.swing.*;
34 | import java.awt.*;
35 | import java.awt.datatransfer.Clipboard;
36 | import java.awt.datatransfer.StringSelection;
37 | import java.awt.event.MouseAdapter;
38 | import java.awt.event.MouseEvent;
39 | import java.io.File;
40 | import java.io.IOException;
41 | import java.net.URISyntaxException;
42 | import java.net.URL;
43 | import java.util.ArrayList;
44 | import java.util.HashMap;
45 | import java.util.HashSet;
46 | import java.util.Set;
47 | import java.util.concurrent.Executors;
48 | import java.util.regex.Matcher;
49 | import java.util.regex.Pattern;
50 |
51 | import static java.lang.Integer.max;
52 |
53 | public final class MypyTerminal {
54 | private JPanel mypyToolWindowContent;
55 | private JBList errorsList;
56 | private JTextField mypyStatus;
57 | private JButton mypyRun;
58 | @SuppressWarnings("unused")
59 | private JScrollPane scroll;
60 | private int rightIndex;
61 | private final Project project;
62 | private ListCellRenderer super MypyError> defaultRenderer;
63 | private ListCellRenderer mypyRenderer;
64 | private MypyRunner runner;
65 | private ArrayList errorFiles;
66 | private Set collapsed;
67 | private HashMap> errorMap;
68 |
69 | final static int GRAY = 11579568;
70 | final static int DARK_GRAY = 7368816;
71 | private final static int LIGHT_GREEN = 13500365;
72 | private final static int LIGHT_RED = 16764365;
73 | final static int BLACK = 0;
74 | final static int WHITE = 16777215;
75 | final public static String ERROR_MARK = ": error:";
76 | final public static String NOTE_MARK = ": note:";
77 | final static String ERROR_RE = ".+" + ERROR_MARK + ".+";
78 | final static String NOTE_RE = ".+" + NOTE_MARK + ".+";
79 |
80 | public MypyTerminal (Project project) {
81 | this.project = project;
82 | }
83 |
84 | public MypyRunner getRunner() {
85 | return runner;
86 | }
87 |
88 | public JBList getErrorsList() {
89 | return errorsList;
90 | }
91 |
92 | public void toggleExpand(MypyError error) {
93 | String file = error.getFile();
94 | if (collapsed.contains(file)) {
95 | collapsed.remove(file);
96 | } else {
97 | collapsed.add(file);
98 | }
99 | }
100 |
101 | void initUI(ToolWindow toolWindow) {
102 | errorsList.getEmptyText().setText("");
103 | errorsList.setListData(new MypyError[] {});
104 | runner = new MypyRunner(errorsList, project);
105 | rightIndex = 0;
106 |
107 | // List popup menu.
108 |
109 | JBPopupMenu popup = new JBPopupMenu();
110 | JBMenuItem gotoItem = new JBMenuItem("Go to error");
111 | gotoItem.addActionListener(e -> {
112 | int tot = MypyTerminal.this.errorsList.getModel().getSize();
113 | int right = MypyTerminal.this.rightIndex;
114 | int index = MypyTerminal.this.errorsList.getSelectedIndex();
115 | if ((right >= 0) & (right < tot)) {
116 | MypyTerminal.this.errorsList.setSelectedIndex(right);
117 | // If it was already selected, we need to trigger this manually.
118 | if (right == index) {
119 | MypyTerminal.this.openError(index);
120 | }
121 | }
122 | });
123 | gotoItem.setIcon(AllIcons.Actions.RunToCursor);
124 | gotoItem.setDisabledIcon(AllIcons.Actions.RunToCursor);
125 | popup.add(gotoItem);
126 | JBMenuItem copyItem = new JBMenuItem("Copy error text");
127 | copyItem.addActionListener(e -> {
128 | MypyError error = MypyTerminal.this.errorsList.getModel().getElementAt(
129 | MypyTerminal.this.rightIndex);
130 | StringSelection selection = new StringSelection(error.getRaw());
131 | Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
132 | clipboard.setContents(selection, selection);
133 | });
134 | copyItem.setIcon(AllIcons.Actions.Copy);
135 | copyItem.setDisabledIcon(AllIcons.Actions.Copy);
136 | popup.add(copyItem);
137 | JBMenuItem copyAllItem = new JBMenuItem("Copy all errors");
138 | copyAllItem.addActionListener(e -> {
139 | ArrayList allErrors = new ArrayList<>();
140 | int size = MypyTerminal.this.errorsList.getModel().getSize();
141 | if (size == 0) {
142 | return;
143 | }
144 | for (int i = 0; i < size; i++) {
145 | MypyError err = MypyTerminal.this.errorsList.getModel().getElementAt(i);
146 | if (err.getLevel() == MypyError.HEADER) {
147 | continue;
148 | }
149 | allErrors.add(err.getRaw());
150 | }
151 | String error = String.join("\n", allErrors);
152 | StringSelection selection = new StringSelection(error);
153 | Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
154 | clipboard.setContents(selection, selection);
155 | });
156 | popup.add(copyAllItem);
157 | JBMenuItem expandItem = new JBMenuItem("Expand");
158 | expandItem.addActionListener(e -> {
159 | MypyError error = MypyTerminal.this.errorsList.getModel().getElementAt(
160 | MypyTerminal.this.rightIndex);
161 | if (error.getLevel() == MypyError.HEADER) {
162 | MypyTerminal.this.toggleExpand(error);
163 | MypyTerminal.this.renderList();
164 | MypyTerminal.this.errorsList.setSelectedIndex(MypyTerminal.this.rightIndex);
165 | }
166 | });
167 | popup.add(expandItem);
168 | JBMenuItem helpItem = new JBMenuItem("Help");
169 | helpItem.addActionListener(e -> MypyHelp.show(project));
170 | popup.add(helpItem);
171 | JSeparator sep = new JSeparator();
172 | popup.add(sep);
173 | JBMenuItem configItem = new JBMenuItem("Configure plugin...");
174 | configItem.addActionListener(e -> {
175 | MypyConfigDialog dialog = new MypyConfigDialog(project);
176 | dialog.show();
177 | });
178 | popup.add(configItem);
179 |
180 | // List selection listener.
181 |
182 | errorsList.addListSelectionListener(e -> {
183 | int index = MypyTerminal.this.errorsList.getSelectedIndex();
184 | Rectangle rect = MypyTerminal.this.errorsList.getCellBounds(index, index);
185 | if (rect != null) {
186 | MypyTerminal.this.errorsList.scrollRectToVisible(rect);
187 | }
188 | MypyTerminal.this.openError(index);
189 | });
190 |
191 | // List mouse listener.
192 |
193 | errorsList.addMouseListener(new MouseAdapter() {
194 | public void mouseClicked(MouseEvent e) {
195 | int index = MypyTerminal.this.errorsList.locationToIndex(e.getPoint());
196 | if ((e.getButton() == MouseEvent.BUTTON2) |
197 | (e.getButton() == MouseEvent.BUTTON3) | (e.isControlDown())) {
198 | boolean active = !(MypyTerminal.this.runner.isRunning());
199 | boolean isError = false;
200 | boolean isExpanded = false;
201 | boolean isHeader = false;
202 | if (index >= 0) {
203 | MypyError error = MypyTerminal.this.errorsList.getModel().getElementAt(index);
204 | isError = error.isError();
205 | isExpanded = !error.isCollapsed();
206 | isHeader = error.getLevel() == MypyError.HEADER;
207 | }
208 | gotoItem.setEnabled(active & isError);
209 | gotoItem.updateUI();
210 | copyItem.setEnabled(active & !isHeader & (index >= 0));
211 | copyItem.updateUI();
212 | copyAllItem.setEnabled(active);
213 | copyAllItem.updateUI();
214 | expandItem.setEnabled(active & isHeader);
215 | expandItem.setText(isExpanded ? "Collapse" : "Expand");
216 | expandItem.updateUI();
217 | configItem.updateUI();
218 | helpItem.updateUI();
219 | MypyTerminal.this.rightIndex = index;
220 | popup.updateUI();
221 | popup.show(e.getComponent(), e.getX(), e.getY());
222 | return;
223 | }
224 | if (e.isAltDown()) {
225 | String error = MypyTerminal.this.errorsList.getModel()
226 | .getElementAt(index).getMessage();
227 | Pattern http = Pattern.compile("http://\\S+"); // TODO: Use better regex.
228 | Matcher matcher = http.matcher(error);
229 | if (matcher.find()) {
230 | String link = error.substring(matcher.start(0), matcher.end(0));
231 | try {
232 | Desktop.getDesktop().browse(new URL(link).toURI());
233 | } catch (URISyntaxException | IOException exc) {
234 | Messages.showMessageDialog(project, exc.getMessage(),
235 | "Plugin Exception:", Messages.getErrorIcon());
236 | }
237 | }
238 | return;
239 | }
240 | if (e.getClickCount() >= 1) {
241 | if (index >= 0) {
242 | MypyError error = MypyTerminal.this.errorsList.getModel().getElementAt(index);
243 | boolean expandable = (error.getLevel() == MypyError.HEADER);
244 | if (expandable) {
245 | MypyTerminal.this.toggleExpand(error);
246 | MypyTerminal.this.renderList();
247 | MypyTerminal.this.errorsList.setSelectedIndex(index);
248 | } else {
249 | int old = MypyTerminal.this.errorsList.getSelectedIndex();
250 | if (old == index) {
251 | // manually trigger if selection didn't change
252 | MypyTerminal.this.openError(index);
253 | } else {
254 | MypyTerminal.this.errorsList.setSelectedIndex(index);
255 | }
256 | }
257 | }
258 |
259 | }
260 | }
261 | });
262 |
263 | // Final strokes.
264 |
265 | mypyRun.addActionListener(e -> MypyTerminal.this.runMypyDaemonUIWrapper());
266 | mypyRun.setIcon(MypyIcons.MYPY_SMALL);
267 | mypyRenderer = new MypyCellRenderer();
268 | defaultRenderer = errorsList.getCellRenderer();
269 |
270 | ContentFactory contentFactory = ContentFactory.SERVICE.getInstance();
271 | Content content = contentFactory.createContent(mypyToolWindowContent, "", false);
272 | toolWindow.getContentManager().addContent(content);
273 | }
274 |
275 | public void runMypyDaemonUIWrapper() {
276 | runMypyDaemonUIWrapper(null, null);
277 | }
278 |
279 | public void runMypyDaemonUIWrapper(@Nullable String command, @Nullable VirtualFile vf) {
280 |
281 | setWaiting();
282 | FileDocumentManager.getInstance().saveAllDocuments();
283 | // Invoke mypy daemon runner script in a sub-thread,
284 | // it looks like UI is blocked on it otherwise.
285 | Executors.newSingleThreadExecutor().execute(() -> {
286 | Thread.currentThread().setName("MypyRunnerThread");
287 | MypyResult result = MypyTerminal.this.runner.runMypyDaemon(command, vf);
288 | if (result == null) return;
289 | // Access UI is prohibited from non-dispatch thread.
290 | ApplicationManager.getApplication().invokeLater(() -> {
291 | MypyTerminal.this.setReady(result);
292 | ToolWindow tw = ToolWindowManager.getInstance(project).getToolWindow(
293 | MypyToolWindowFactory.MYPY_PLUGIN_ID);
294 | if (!tw.isVisible()) {
295 | String suffix = result.getErrCount() != 1 ? "s" : "";
296 | NotificationType n_type =
297 | result.getRetCode() != 0 ? NotificationType.WARNING : NotificationType.INFORMATION;
298 | Notification completed = new Notification("Indexing", "Mypy Daemon",
299 | String.format("Type checking completed: %d error%s found",
300 | result.getErrCount(), suffix),
301 | n_type);
302 | Notifications.Bus.notify(completed);
303 | }
304 | if (result.getErrCount() == 0 & result.getNoteCount() == 0) {
305 | return;
306 | }
307 | if (result.getRetCode() != 0) {
308 | MypyTerminal.this.makeErrorMap(result);
309 | MypyTerminal.this.generateMarkers(result);
310 | MypyTerminal.this.collapsed = new HashSet<>();
311 | MypyTerminal.this.renderList();
312 | MypyTerminal.this.errorsList.setSelectedIndex(0);
313 | }
314 | });
315 | });
316 | }
317 |
318 | private void setWaiting() {
319 | errorsList.setForeground(new JBColor(new Color(GRAY), new Color(DARK_GRAY)));
320 | errorsList.setCellRenderer(defaultRenderer);
321 | mypyStatus.setText("Running...");
322 | mypyStatus.setForeground(new JBColor(new Color(BLACK), new Color(GRAY)));
323 | mypyStatus.setBackground(new JBColor(new Color(WHITE), new Color(BLACK)));
324 | errorsList.setListData(new MypyError[] {});
325 | errorsList.setPaintBusy(true);
326 | mypyRun.setText("Wait...");
327 | mypyRun.setEnabled(false);
328 | }
329 |
330 | private void setReady(MypyResult result) {
331 | mypyRun.setText("Run");
332 | mypyRun.setEnabled(true);
333 | errorsList.setPaintBusy(false);
334 | if (result == null) { // IO exception happened
335 | mypyStatus.setText("Internal problem...");
336 | return;
337 | }
338 | if (result.getRetCode() == 0) {
339 | mypyStatus.setText("PASSED");
340 | mypyStatus.setForeground(new JBColor(new Color(BLACK), new Color(100, 255, 100)));
341 | mypyStatus.setBackground(new JBColor(new Color(LIGHT_GREEN), new Color(BLACK)));
342 | } else {
343 | String suffix = result.getErrCount() != 1 ? "s" : "";
344 | mypyStatus.setText(String.format("FAILED: %d error%s", result.getErrCount(), suffix));
345 | mypyStatus.setForeground(new JBColor(new Color(BLACK), new Color(255, 100, 100)));
346 | mypyStatus.setBackground(new JBColor(new Color(LIGHT_RED), new Color(BLACK)));
347 | if (result.getErrCount() == 0 & result.getNoteCount() == 0) {
348 | // keep debug output
349 | return;
350 | }
351 | }
352 | // clear debug output
353 | errorsList.setListData(new MypyError[] {});
354 | errorsList.setForeground(new JBColor(new Color(BLACK), new Color(GRAY)));
355 | errorsList.setCellRenderer(mypyRenderer);
356 | }
357 |
358 | private void makeErrorMap(MypyResult result) {
359 | HashMap> map = new HashMap<>();
360 | ArrayList errors = result.getErrors();
361 | ArrayList files = new ArrayList<>();
362 | for (MypyError next: errors) {
363 | String file = next.getFile();
364 | if (!map.containsKey(file)) {
365 | map.put(file, new ArrayList<>());
366 | }
367 | map.get(file).add(next);
368 | if (!files.contains(file)) {
369 | files.add(file);
370 | }
371 | }
372 | errorMap = map;
373 | errorFiles = files;
374 | }
375 |
376 | private void generateMarkers(MypyResult result) {
377 | for (MypyError error: result.getErrors()) {
378 | if (error.isError()) {
379 | String directory = project.getBaseDir().getPath();
380 | String file = error.getFile();
381 | int line = max(error.getLine() - 1, 0);
382 | VirtualFile vf = LocalFileSystem.getInstance().findFileByPath(directory + File.separator + file);
383 | if (vf != null) {
384 | Document document = FileDocumentManager.getInstance().getCachedDocument(vf);
385 | if (document != null) {
386 | error.marker = document.createRangeMarker(document.getLineStartOffset(line),
387 | document.getLineEndOffset(line));
388 | }
389 | }
390 | }
391 | }
392 | }
393 |
394 | public void renderList() {
395 | ArrayList lines = new ArrayList<>();
396 | for (String file: errorFiles) {
397 | boolean toggle = collapsed.contains(file);
398 | int errs = 0;
399 | for (MypyError error: errorMap.get(file)) {
400 | if (error.getLevel() == MypyError.ERROR) {
401 | errs++;
402 | }
403 | }
404 | MypyError title = new MypyError(file, MypyError.HEADER, errs);
405 | if (toggle) {
406 | title.toggle();
407 | }
408 | lines.add(title);
409 | if (!collapsed.contains(file)) {
410 | lines.addAll(errorMap.get(file));
411 | }
412 | }
413 | MypyError[] data = new MypyError[lines.size()];
414 | data = lines.toArray(data);
415 | errorsList.setListData(data);
416 | }
417 |
418 | private void openError(int index) {
419 | if ((index >= errorsList.getModel().getSize()) | (index < 0)) {
420 | return;
421 | }
422 | if (runner.isRunning()) {
423 | return;
424 | }
425 | MypyError error = errorsList.getModel().getElementAt(index);
426 | String directory = project.getBaseDir().getPath();
427 | if (error.isError()) {
428 | String file = error.getFile();
429 | int line = max(error.getLine() - 1, 0);
430 | int column = max(error.getColumn() - 1, 0);
431 | VirtualFile vf = LocalFileSystem.getInstance().findFileByPath(directory + File.separator + file);
432 | // May be null if an error is shown in a file beyond repository
433 | // (e.g. typeshed or a deleted file because of a bug).
434 | if (vf != null) {
435 | FileEditor[] editors = FileEditorManagerEx.getInstanceEx(project).openFile(vf, true);
436 | if (editors[0] instanceof TextEditor) {
437 | Editor editor = ((TextEditor) editors[0]).getEditor();
438 | if (error.marker == null) {
439 | // Try re-creating markers, likely the file was not in cache after the type check.
440 | // TODO: do this on document opening for all documents?
441 | Document document = FileDocumentManager.getInstance().getCachedDocument(vf);
442 | if (document != null) {
443 | for (MypyError e: errorMap.get(error.getFile())) {
444 | int errorLine = max(e.getLine() - 1, 0);
445 | e.marker = document.createRangeMarker(document.getLineStartOffset(errorLine),
446 | document.getLineEndOffset(errorLine));
447 | }
448 | }
449 | }
450 | if (error.marker != null && error.marker.isValid()) {
451 | editor.getCaretModel().getPrimaryCaret().moveToOffset(error.marker.getStartOffset());
452 | }
453 | else {
454 | LogicalPosition pos = new LogicalPosition(line, column);
455 | editor.getCaretModel().getPrimaryCaret().moveToLogicalPosition(pos);
456 | }
457 | editor.getScrollingModel().scrollToCaret(ScrollType.CENTER);
458 | if (error.marker != null && error.marker.isValid()) {
459 | editor.getSelectionModel().selectLineAtCaret();
460 | }
461 | }
462 | }
463 | }
464 | }
465 | }
466 |
--------------------------------------------------------------------------------
/src/com/dropbox/plugins/mypy_plugin/MypyToolWindowFactory.java:
--------------------------------------------------------------------------------
1 | package com.dropbox.plugins.mypy_plugin;
2 |
3 | import com.intellij.openapi.project.DumbAware;
4 | import com.intellij.openapi.project.Project;
5 | import com.intellij.openapi.wm.ToolWindow;
6 | import com.intellij.openapi.wm.ToolWindowFactory;
7 |
8 | import org.jetbrains.annotations.NotNull;
9 |
10 | import java.util.HashMap;
11 |
12 |
13 | public final class MypyToolWindowFactory implements ToolWindowFactory, DumbAware {
14 | final public static String MYPY_PLUGIN_ID = "Mypy Terminal";
15 | final public static boolean DEBUG_BUILD = false;
16 | private final static HashMap instances = new HashMap<>();
17 |
18 | public void createToolWindowContent(@NotNull Project project, @NotNull ToolWindow toolWindow) {
19 | MypyTerminal terminal = new MypyTerminal(project);
20 | terminal.initUI(toolWindow);
21 | instances.put(project, terminal);
22 | }
23 |
24 | public static MypyTerminal getMypyTerminal(Project project) {
25 | return instances.get(project);
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/com/dropbox/plugins/mypy_plugin/actions/AskSuggestion.java:
--------------------------------------------------------------------------------
1 | package com.dropbox.plugins.mypy_plugin.actions;
2 |
3 | import com.dropbox.plugins.mypy_plugin.MypyTerminal;
4 | import com.dropbox.plugins.mypy_plugin.MypyToolWindowFactory;
5 | import com.intellij.openapi.actionSystem.AnAction;
6 | import com.intellij.openapi.actionSystem.AnActionEvent;
7 | import com.intellij.openapi.actionSystem.PlatformDataKeys;
8 | import com.intellij.openapi.editor.Editor;
9 | import com.intellij.openapi.editor.LogicalPosition;
10 | import com.intellij.openapi.fileEditor.FileDocumentManager;
11 | import com.intellij.openapi.project.DumbAware;
12 | import com.intellij.openapi.project.Project;
13 | import com.intellij.openapi.vfs.VirtualFile;
14 | import com.intellij.openapi.wm.ToolWindow;
15 | import com.intellij.openapi.wm.ToolWindowManager;
16 | import org.jetbrains.annotations.NotNull;
17 |
18 |
19 | final class AskSuggestion extends AnAction implements DumbAware {
20 | @Override
21 | public void actionPerformed(@NotNull AnActionEvent e) {
22 | Editor editor = e.getData(PlatformDataKeys.EDITOR);
23 | if (editor == null)
24 | return;
25 | LogicalPosition pos = editor.getCaretModel().getPrimaryCaret().getLogicalPosition();
26 | int line = pos.line;
27 | FileDocumentManager.getInstance().saveAllDocuments();
28 | VirtualFile vf = e.getData(PlatformDataKeys.VIRTUAL_FILE);
29 | if (vf == null)
30 | return;
31 | String command = "./mypy/mypy-suggest " + vf.getPath() + " " + String.valueOf(line + 1);
32 |
33 | Project project = e.getData(PlatformDataKeys.PROJECT);
34 | if (project == null) {
35 | return;
36 | }
37 | ToolWindow tw = ToolWindowManager.getInstance(project).getToolWindow(
38 | MypyToolWindowFactory.MYPY_PLUGIN_ID);
39 | if (!tw.isVisible()) {
40 | tw.show(null);
41 | }
42 | MypyTerminal terminal = MypyToolWindowFactory.getMypyTerminal(project);
43 | if (terminal == null) {
44 | return;
45 | }
46 | if (terminal.getRunner().isRunning()) {
47 | return;
48 | }
49 | terminal.runMypyDaemonUIWrapper(command, vf);
50 | vf.refresh(false, false);
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/src/com/dropbox/plugins/mypy_plugin/actions/CopyError.java:
--------------------------------------------------------------------------------
1 | package com.dropbox.plugins.mypy_plugin.actions;
2 |
3 | import com.dropbox.plugins.mypy_plugin.MypyTerminal;
4 | import com.dropbox.plugins.mypy_plugin.MypyToolWindowFactory;
5 | import com.dropbox.plugins.mypy_plugin.model.MypyError;
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.project.DumbAware;
10 | import com.intellij.openapi.project.Project;
11 | import com.intellij.openapi.wm.ToolWindow;
12 | import com.intellij.openapi.wm.ToolWindowManager;
13 | import org.jetbrains.annotations.NotNull;
14 |
15 | import java.awt.*;
16 | import java.awt.datatransfer.Clipboard;
17 | import java.awt.datatransfer.StringSelection;
18 |
19 | final class CopyError extends AnAction implements DumbAware {
20 | @Override
21 | public void actionPerformed(@NotNull AnActionEvent e) {
22 | Project project = e.getData(PlatformDataKeys.PROJECT);
23 | if (project == null) {
24 | return;
25 | }
26 | ToolWindow tw = ToolWindowManager.getInstance(project).getToolWindow(
27 | MypyToolWindowFactory.MYPY_PLUGIN_ID);
28 | if (!tw.isVisible()) {
29 | return;
30 | }
31 | MypyTerminal terminal = MypyToolWindowFactory.getMypyTerminal(project);
32 | if (terminal == null) {
33 | return;
34 | }
35 | if (terminal.getRunner().isRunning()) {
36 | return;
37 | }
38 | MypyError error = terminal.getErrorsList().getSelectedValue();
39 | if (error == null) { // no errors
40 | return;
41 | }
42 | if (error.getLevel() == MypyError.HEADER) {
43 | return;
44 | }
45 | StringSelection selection = new StringSelection(error.getRaw());
46 | Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
47 | clipboard.setContents(selection, selection);
48 | }
49 | }
--------------------------------------------------------------------------------
/src/com/dropbox/plugins/mypy_plugin/actions/ExpandErrors.java:
--------------------------------------------------------------------------------
1 | package com.dropbox.plugins.mypy_plugin.actions;
2 |
3 | import com.dropbox.plugins.mypy_plugin.MypyTerminal;
4 | import com.dropbox.plugins.mypy_plugin.MypyToolWindowFactory;
5 | import com.dropbox.plugins.mypy_plugin.model.MypyError;
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.project.DumbAware;
10 | import com.intellij.openapi.project.Project;
11 | import com.intellij.openapi.wm.ToolWindow;
12 | import com.intellij.openapi.wm.ToolWindowManager;
13 | import org.jetbrains.annotations.NotNull;
14 |
15 | final class ExpandErrors extends AnAction implements DumbAware {
16 | @Override
17 | public void actionPerformed(@NotNull AnActionEvent e) {
18 | Project project = e.getData(PlatformDataKeys.PROJECT);
19 | if (project == null) {
20 | return;
21 | }
22 | ToolWindow tw = ToolWindowManager.getInstance(project).getToolWindow(
23 | MypyToolWindowFactory.MYPY_PLUGIN_ID);
24 | if (!tw.isVisible()) {
25 | return;
26 | }
27 | MypyTerminal terminal = MypyToolWindowFactory.getMypyTerminal(project);
28 | if (terminal == null) {
29 | return;
30 | }
31 | if (terminal.getRunner().isRunning()) {
32 | return;
33 | }
34 | MypyError error = terminal.getErrorsList().getSelectedValue();
35 | if (error == null) { // no errors
36 | return;
37 | }
38 | if (error.getLevel() == MypyError.HEADER) {
39 | terminal.toggleExpand(error);
40 | int index = terminal.getErrorsList().getSelectedIndex();
41 | terminal.renderList();
42 | terminal.getErrorsList().setSelectedIndex(index);
43 | }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/com/dropbox/plugins/mypy_plugin/actions/NextError.java:
--------------------------------------------------------------------------------
1 | package com.dropbox.plugins.mypy_plugin.actions;
2 |
3 | import com.dropbox.plugins.mypy_plugin.MypyTerminal;
4 | import com.dropbox.plugins.mypy_plugin.MypyToolWindowFactory;
5 | import com.intellij.openapi.actionSystem.AnAction;
6 | import com.intellij.openapi.actionSystem.AnActionEvent;
7 | import com.intellij.openapi.actionSystem.PlatformDataKeys;
8 | import com.intellij.openapi.project.DumbAware;
9 | import com.intellij.openapi.project.Project;
10 | import com.intellij.openapi.wm.ToolWindow;
11 | import com.intellij.openapi.wm.ToolWindowManager;
12 | import org.jetbrains.annotations.NotNull;
13 |
14 | final class NextError extends AnAction implements DumbAware {
15 | @Override
16 | public void actionPerformed(@NotNull AnActionEvent e) {
17 | Project project = e.getData(PlatformDataKeys.PROJECT);
18 | if (project == null) {
19 | return;
20 | }
21 | ToolWindow tw = ToolWindowManager.getInstance(project).getToolWindow(
22 | MypyToolWindowFactory.MYPY_PLUGIN_ID);
23 | if (!tw.isVisible()) {
24 | tw.show(null);
25 | }
26 | MypyTerminal terminal = MypyToolWindowFactory.getMypyTerminal(project);
27 | if (terminal == null) {
28 | return;
29 | }
30 | if (terminal.getRunner().isRunning()) {
31 | return;
32 | }
33 | int total = terminal.getErrorsList().getItemsCount();
34 | int current = terminal.getErrorsList().getSelectedIndex();
35 | if (current < total) {
36 | terminal.getErrorsList().setSelectedIndex(current + 1);
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/com/dropbox/plugins/mypy_plugin/actions/PrevError.java:
--------------------------------------------------------------------------------
1 | package com.dropbox.plugins.mypy_plugin.actions;
2 |
3 | import com.dropbox.plugins.mypy_plugin.MypyTerminal;
4 | import com.dropbox.plugins.mypy_plugin.MypyToolWindowFactory;
5 | import com.intellij.openapi.actionSystem.AnAction;
6 | import com.intellij.openapi.actionSystem.AnActionEvent;
7 | import com.intellij.openapi.actionSystem.PlatformDataKeys;
8 | import com.intellij.openapi.project.DumbAware;
9 | import com.intellij.openapi.project.Project;
10 | import com.intellij.openapi.wm.ToolWindow;
11 | import com.intellij.openapi.wm.ToolWindowManager;
12 | import org.jetbrains.annotations.NotNull;
13 |
14 | final class PrevError extends AnAction implements DumbAware {
15 | @Override
16 | public void actionPerformed(@NotNull AnActionEvent e) {
17 | Project project = e.getData(PlatformDataKeys.PROJECT);
18 | if (project == null) {
19 | return;
20 | }
21 | ToolWindow tw = ToolWindowManager.getInstance(project).getToolWindow(
22 | MypyToolWindowFactory.MYPY_PLUGIN_ID);
23 | if (!tw.isVisible()) {
24 | tw.show(null);
25 | }
26 | MypyTerminal terminal = MypyToolWindowFactory.getMypyTerminal(project);
27 | if (terminal == null) {
28 | return;
29 | }
30 | if (terminal.getRunner().isRunning()) {
31 | return;
32 | }
33 | int current = terminal.getErrorsList().getSelectedIndex();
34 | if (current > 0) {
35 | terminal.getErrorsList().setSelectedIndex(current - 1);
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/com/dropbox/plugins/mypy_plugin/actions/RunMypyDaemon.java:
--------------------------------------------------------------------------------
1 | package com.dropbox.plugins.mypy_plugin.actions;
2 |
3 | import com.dropbox.plugins.mypy_plugin.MypyTerminal;
4 | import com.dropbox.plugins.mypy_plugin.MypyToolWindowFactory;
5 | import com.intellij.openapi.actionSystem.AnAction;
6 | import com.intellij.openapi.actionSystem.AnActionEvent;
7 | import com.intellij.openapi.actionSystem.PlatformDataKeys;
8 | import com.intellij.openapi.project.DumbAware;
9 | import com.intellij.openapi.project.Project;
10 | import com.intellij.openapi.wm.ToolWindow;
11 | import com.intellij.openapi.wm.ToolWindowManager;
12 | import org.jetbrains.annotations.NotNull;
13 |
14 | final class RunMypyDaemon extends AnAction implements DumbAware {
15 | @Override
16 | public void actionPerformed(@NotNull AnActionEvent e) {
17 | Project project = e.getData(PlatformDataKeys.PROJECT);
18 | if (project == null) {
19 | return;
20 | }
21 | ToolWindow tw = ToolWindowManager.getInstance(project).getToolWindow(
22 | MypyToolWindowFactory.MYPY_PLUGIN_ID);
23 | if (!tw.isVisible()) {
24 | tw.show(null);
25 | }
26 | MypyTerminal terminal = MypyToolWindowFactory.getMypyTerminal(project);
27 | if (terminal == null) {
28 | return;
29 | }
30 | if (terminal.getRunner().isRunning()) {
31 | return;
32 | }
33 | terminal.runMypyDaemonUIWrapper();
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/com/dropbox/plugins/mypy_plugin/actions/ShowMypyWindow.java:
--------------------------------------------------------------------------------
1 | package com.dropbox.plugins.mypy_plugin.actions;
2 |
3 | import com.dropbox.plugins.mypy_plugin.MypyToolWindowFactory;
4 | import com.intellij.openapi.actionSystem.AnAction;
5 | import com.intellij.openapi.actionSystem.AnActionEvent;
6 | import com.intellij.openapi.actionSystem.PlatformDataKeys;
7 | import com.intellij.openapi.project.DumbAware;
8 | import com.intellij.openapi.project.Project;
9 | import com.intellij.openapi.wm.ToolWindow;
10 | import com.intellij.openapi.wm.ToolWindowManager;
11 | import org.jetbrains.annotations.NotNull;
12 |
13 | final class ShowMypyWindow extends AnAction implements DumbAware {
14 | @Override
15 | public void actionPerformed(@NotNull AnActionEvent e) {
16 | Project project = e.getData(PlatformDataKeys.PROJECT);
17 | if (project == null) {
18 | return;
19 | }
20 | ToolWindow tw = ToolWindowManager.getInstance(project).getToolWindow(
21 | MypyToolWindowFactory.MYPY_PLUGIN_ID);
22 | if (tw.isVisible()) {
23 | tw.hide(null);
24 | } else {
25 | tw.show(null);
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/com/dropbox/plugins/mypy_plugin/model/MypyConfig.java:
--------------------------------------------------------------------------------
1 | package com.dropbox.plugins.mypy_plugin.model;
2 |
3 | public class MypyConfig {
4 | public String executableName;
5 | public String pathSuffix;
6 |
7 | public MypyConfig() {
8 | // This is so the serialization system can handle this
9 | }
10 |
11 | public MypyConfig(String executableName, String pathSuffix) {
12 | this.executableName = executableName;
13 | this.pathSuffix = pathSuffix;
14 | }
15 |
16 | public String getExecutableName() {
17 | return executableName;
18 | }
19 |
20 | public String getPathSuffix() {
21 | return pathSuffix;
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/com/dropbox/plugins/mypy_plugin/model/MypyError.java:
--------------------------------------------------------------------------------
1 | package com.dropbox.plugins.mypy_plugin.model;
2 |
3 | import com.dropbox.plugins.mypy_plugin.MypyTerminal;
4 | import com.dropbox.plugins.mypy_plugin.MypyToolWindowFactory;
5 | import com.intellij.openapi.editor.RangeMarker;
6 |
7 | final public class MypyError {
8 | public final static int DEBUG = 0;
9 | public final static int NOTE = 1;
10 | public final static int ERROR = 2;
11 | public final static int HEADER = -1; // not a real error, just a separator
12 |
13 | private final int level;
14 | private String file;
15 | private int line;
16 | private int column;
17 | public RangeMarker marker;
18 | private String message;
19 | private String raw;
20 | // used only by headers
21 | private int errCount;
22 | private boolean collapsed;
23 |
24 | public MypyError(String raw, int level) {
25 | this.raw = raw;
26 | this.level = level;
27 | this.marker = null;
28 | assert((level == DEBUG) | (level == NOTE) | (level == ERROR));
29 | if (level == DEBUG) {
30 | return;
31 | }
32 | String loc;
33 | String[] pair;
34 | if (MypyToolWindowFactory.DEBUG_BUILD) {
35 | System.out.println(level);
36 | System.out.println(raw);
37 | }
38 | if (level == NOTE) {
39 | pair = raw.split(MypyTerminal.NOTE_MARK);
40 | } else {
41 | pair = raw.split(MypyTerminal.ERROR_MARK);
42 | }
43 | loc = pair[0];
44 | if (pair.length == 1) {
45 | message = "";
46 | } else {
47 | message = pair[1];
48 | }
49 | String[] parts = loc.split(":");
50 | file = parts[0];
51 | if (parts.length == 1) {
52 | line = 0;
53 | column = 0;
54 | } else {
55 | try {
56 | line = Integer.parseInt(parts[1]);
57 | } catch (NumberFormatException e) {
58 | line = 0;
59 | }
60 | if (parts.length == 2) {
61 | column = 0;
62 | } else {
63 | try {
64 | column = Integer.parseInt(parts[2]);
65 | } catch (NumberFormatException e) {
66 | column = 0;
67 | }
68 | }
69 | }
70 | }
71 |
72 | public MypyError(String file, int level, int errCount) {
73 | this.level = level;
74 | this.errCount = errCount;
75 | this.file = file;
76 | collapsed = false;
77 | assert (level == HEADER);
78 | }
79 |
80 | public String toString() {
81 | assert (level == DEBUG);
82 | // all other levels should be processed
83 | // by our custom renderer
84 | return raw;
85 | }
86 |
87 | public void toggle() {
88 | assert (level == HEADER);
89 | collapsed = !collapsed;
90 | }
91 |
92 | public int getLevel() {
93 | return level;
94 | }
95 |
96 | public String getFile() {
97 | return file;
98 | }
99 |
100 | public int getLine() {
101 | return line;
102 | }
103 |
104 | public int getColumn() {
105 | return column;
106 | }
107 |
108 | public String getMessage() {
109 | return message;
110 | }
111 |
112 | public String getRaw() {
113 | return raw;
114 | }
115 |
116 | public int getErrCount() {
117 | return errCount;
118 | }
119 |
120 | public boolean isCollapsed() {
121 | return collapsed;
122 | }
123 |
124 | public boolean isError() {
125 | return level == NOTE || level == ERROR;
126 | }
127 | }
128 |
--------------------------------------------------------------------------------
/src/com/dropbox/plugins/mypy_plugin/model/MypyResult.java:
--------------------------------------------------------------------------------
1 | package com.dropbox.plugins.mypy_plugin.model;
2 |
3 | import java.util.ArrayList;
4 |
5 | final public class MypyResult {
6 | private final int retCode;
7 | private final int errCount;
8 | private final int noteCount;
9 | private final ArrayList errors;
10 |
11 | public MypyResult(int code, int errCount, int noteCount, ArrayList errors) {
12 | this.retCode = code;
13 | this.errors = errors;
14 | this.errCount = errCount;
15 | this.noteCount = noteCount;
16 | }
17 |
18 | public int getRetCode() {
19 | return this.retCode;
20 | }
21 |
22 | public ArrayList getErrors() {
23 | return errors;
24 | }
25 |
26 | public int getErrCount() {
27 | return errCount;
28 | }
29 |
30 | public int getNoteCount() {
31 | return noteCount;
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/icons/MypyIcons.java:
--------------------------------------------------------------------------------
1 | package icons;
2 |
3 | import com.intellij.openapi.util.IconLoader;
4 |
5 | import javax.swing.*;
6 |
7 | public final class MypyIcons {
8 | private MypyIcons() {}
9 |
10 | public static final Icon MYPY_SMALL = IconLoader.getIcon("/icons/mypy.png");
11 | public static final Icon MYPY_BIG = IconLoader.getIcon("/icons/mypybig.png");
12 | }
13 |
--------------------------------------------------------------------------------