diff options
author | Tor Norbye <tnorbye@google.com> | 2014-05-28 17:06:51 -0700 |
---|---|---|
committer | Tor Norbye <tnorbye@google.com> | 2014-05-28 17:06:55 -0700 |
commit | c667c1f74abd96a2098520effdd5afdff7f0d34b (patch) | |
tree | ab650b0e69fb57d40350579d8796da74d8ea49cb /plugins/devkit/src | |
parent | 0f831a730c50607e2ffd95020875af6185e17734 (diff) | |
download | idea-c667c1f74abd96a2098520effdd5afdff7f0d34b.tar.gz |
Snapshot idea/138.343 from git://git.jetbrains.org/idea/community.git
363dc51: row color for byte code viewer under darcula
98900f0: external annotations markers: move to fast line markers
d5ee2ec: testdata fixed
2780ae5: ensure test class writable
b8cdda6: preserve read-only status for class to test
27c5938: test generation: accept same test class name; avoid same test names
55c4e7a: Merge remote-tracking branch 'origin/master'
81c6fb5: PY-11592 Django 1.6: TEMPLATE_DIRS variable in settings.py is not created properly on project creation
d4a3220: SQL: per-statement file structure grouping
3f7f1c1: IDEA-125397 Cyclic Expand Word no longer completes numbers
1443a9f: IDEA-125137 Click on failed unit test console output to navigate to test fails when using Groovy test name with '()' in test method name
bb1b109: Merge branch 'master' of git.labs.intellij.net:idea/community
d92b567: doesn't show empty langs in postfix completion tree
6252558: IDEA-88175 (wrong inspection Contents of collection 'x' are updated, but never queried)
5298c6b: HgCommandExecutor refactoring
d215ac2: identify command from WorkingCopy changed to local
3dcf8ce: Merge remote-tracking branch 'origin/master'
8199a13: CCE
1cd6faa: Merge branch 'master' of git.labs.intellij.net:idea/community
b2f594a: Merge remote-tracking branch 'origin/master'
c2ad3ae: IDEA-119640 Problem with goto declaration of XML attribute defined in XSD
701f9fb: Merge branch 'master' of git.labs.intellij.net:idea/community
fada6f1: Add temporary api to allow custom initialization of debug session data
a41ecf9: IDEA-123466 Sheet dialogs doesn't transfer focus back then closed
ea02814: Merge branch 'master' of git.labs.intellij.net:idea/community
c6be0ae: XBreakpointUtil.getShortText() shortens text now
740b6b1: [log] IDEA-125578 Handle empty permanent graph
d0891b3: IDEA-125580 - OpenShift: cloning application hangs if switch to another process
11202b9: DevKit: Goto EP declaration in plugin.xml (IDEA-86100)
8ebe9dd: IDEA-125258 ("Unnecessary boxing" inspection is triggered wrongly with overloaded constructors)
55ba38d: IDEA-121467 Quickfix inserts static imports instead of plain imports for inner classes
978a1dc: try with default pattern provider if selected one was not found (IDEA-125581)
8a80f67: leave foreign params untouched (IDEA-119700)
6c8c34c: avoid assert on IDE close with active debug session
c68b3d3: IDEA-125344 (Serializable class without 'read/writeObject()' inspection should ignore classes w/o additions fields)
ce6deee: IDEA-125558 new debugger: double result in evaluate expression
0315d6f: Merge branch 'svn_18_3'
7dcc19e: fix expression name for postfix template
2c9f426: [meo] restore backward compatibility in single-element CompositeFilter merge
a6f23d0: use NavigationGutterIconBuilder
4ab120e: Merge remote-tracking branch 'origin/master'
1c6bfe0: invoke 'install plugin from disk' action from ctrl+shit+a
a345e24: IDEA-125542 new debugger: 'rerun' failed tests are lost
53fd954: IDEA-125542 new debugger: 'rerun' failed tests are lost
c9716a4: IDEA-124891 Implementation icon in gutter overlaps preview for Icon class Get rid of icons from collapsed areas
f54e61d: ObjectsConvertor cleanup
05b53b7: IDEA-125421 diff tool opens wrong file if VCS file deleted via file system
854abe7: + retina variant
0d0dc51: add new icon
2e35935: broken plugins added
ad666ec: do not bother users with warnings from external annotation roots
32fe274: stream api: do not collapse loops when body is not throws compatible (IDEA-125541)
972c75d: Lazy load the icon in DependencyValidationManagerImpl. #188
775e4cc: IDEA-124609 Unexpected indentation of anonymous classes
41c5b88: save caret position in evaluation dialog
4ca7c2b: cleanup
5287678: cleanup
7984ec9: IDEA-122538 Implemented "repository supports merge tracking" detection (for both command line and SVNKit)
ffbcd16: fixed debugger tests
3d12351: disable postfix templates for coffeescript
bfd4b28: ExtendedBuildModel now exposes the project's build directory.
9786fa2: fixing debugger tests
13e4b01: make test light
ad7096e: IDEA-125361 (False positive 'Field may be final')
8ef32c8: missing dep
e62c3c5: Merge remote-tracking branch 'origin/master'
895353f: extract DevKitGutterTargetsChecker
5c120d4: goto actions: vk_up/vk_down fix [^vasya]
2750009: Merge remote-tracking branch 'origin/master'
5859ed9: IDEA-125486 (Comparison using == instead of .equals() should replace with Objects.equals() instead of the instance .equals() method)
1316de2: PyCharm 3.4 artwork.
03da78e: Merge remote-tracking branch 'origin/master'
0cb2b06: Merge remote-tracking branch 'origin/master'
a6d22de: fix testdata; restore again ExpectedHighlightingData
40420a0: quote parameters if win command processor launched with extra switches (e.g. /D)
b61f519: Merge remote-tracking branch 'origin/master'
f587cf6: require less stack in ResolveClassTest.testStaticImportNetwork
13441f9: Merge remote-tracking branch 'origin/master'
892e199: fix test
6c56888: Merge remote-tracking branch 'origin/master'
1d8d3f1: lambda: ignore results of assignment is used for expression lambda (IDEA-125473)
eaa9ce6: method refs: check if interface functional (IDEA-125511)
5e7d32c: edit sources for documentation component; F4
2cf09bb: include window class into diagnostics (met with documentation popup)
2853c95: cleanup code on commit option
d2c04b8: anonymous -> lambda: do not collapse to lambda functional interfaces with generics methods, accepted by method refs though
c20f9d2: Merge remote-tracking branch 'origin/master'
bfc443c: do not visit additional resource roots twice: additional indexed resource root providers can return intersecting paths sometimes
4b7dc58: a less gc-producing ImmutableText.subtext
2dc1af8: optimize ImmutableText memory usage
8dbda1f: [vcs] Log executable validation failure reason
9b3a535: [git] cleanup
52fb0b3: DebugLogConfigureAction: define the default focused component
47956e6: Merge remote-tracking branch 'origin/master'
ec369f5: Local History tests tuned for new VFS behavior
6c0132d: Platform: IDEA-125330 Warning about non-project file modification should be merged with Read-only warning and shown in a modal dialog
c611e59: Merge remote-tracking branch 'origin/master'
b3afb57: Platform: OS X scrollbars can be disabled for diagnostic purpose
d300533: Merge remote-tracking branch 'origin/master'
a1d5b1c: fix border in dbe table editors
72f2e89: cleanup
dccc79d: Merge remote-tracking branch 'origin/master'
5ffae2a: fix class with wrong qname sometimes returned from findClasses
813ab5f: [log] Don't schedule a refresh task if there is nothing to refresh
a40d619: [log] Fix IAE when jumping to row #0 in empty table
2d2cf45: [log] set bek synchronously when applying filters to avoid locks
80e3729: Merge remote-tracking branch 'origin/master'
c256133: fixed jdi objects leaks
c10a0ca: Merge remote-tracking branch 'origin/master'
354d205: Merge branch 'python-fixes'
99c6577: Fixed false positive for 'yield' expressions in lambdas (PY-11663)
5de561b: Fixed false negative for 'yield' expressions in default values of parameters
e23c4c0: Cleanup
222ecc2: hide execute in console action if not available
a9d038d: Merge remote-tracking branch 'origin/master'
6823565: Merge remote-tracking branch 'origin/master'
26a710e: Merge branch 'modulefileinex-service' of https://github.com/yole/intellij-community into yole-modulefileinex-service
4be8c9b: Convert ModuleFileIndex to a module-level service.
b37122b: merge hyperlink merging changes by meo (https://github.com/JetBrains/intellij-community/pull/185); cleanup
68f7fad: IDEA-125248 Incorrect simplify for float
c82e10f: Merge remote-tracking branch 'origin/master'
b7c5190: IDEA-125357 Throwable at com.intellij.openapi.wm.impl.WindowManagerImpl.isAlphaModeEnabled(WindowManagerImpl.java:359)
5e0039e: Merge remote-tracking branch 'origin/master'
e32d36a: path fixed
9989bd2: Merge remote-tracking branch 'origin/master'
ec32500: new "Serializable object implicitly stores non-Serializable object" inspection IDEA-62336
9db4420: - switching data buffering only once per state change - upon before file deletion event remove the file from myFilesToUpdate - rename inner class and other cleanup
438a3e2: final in "foreach" loop
0c86e1e: default message for postfix templates
e8cef21: Merge remote-tracking branch 'origin/master'
e2a7723: don't access disposed excluded entry (IDEA-120622 workaround)
33929c5: don't use checker.xml for dfa (but leave it there for a while, IDEA-125426)
d50f08a: Merge remote-tracking branch 'origin/master'
7f8eae7: unification example postifx templates
45a21b2: Merge remote-tracking branch 'origin/master'
0fcb1c3: introduced pycharm_load_entry_point instead of searching for executable through local file system
76c0e94: moved inspections to groovy-psi
7c078e9: path to standardDsls fixed
1e4a913: moved more stuff to groovy-psi
0c48aca: DevKit: ExtensionPointCandidate: target PsiElement, EP-Locator: performance, search without supers
9dd16ef: Merge remote-tracking branch 'origin/master'
1a0533b: Merge remote-tracking branch 'origin/master'
e617070: reverted 47fc79d09ef2793cd5fcc468d1fe7454bc5e4265
0375c72: correctly render more collections elements
a149338: rearrange actions and added separators in context menus
0e230ea: fixed PY-12970 Extract variable: disable refactoring for parts of comrehensions
4af130d: use IntObjectMap in DirectoryIndex to avoid holding VirtualFile objects
e41dedc: faster AdditionalIndexableFileSet.isInSet
bb13e6f: Merge remote-tracking branch 'origin/master'
f293b00: invalidate cashes stats
8173bf0: app start stats
1578ddf: JavaRearranger: proper processing of anonymous classes.
5580933: DevKit: extract ExtensionPointLocator
42702c9: moved to core
ee9fcba: moved ExpressionConverter to psi
8404532: add groovy-psi to the roots for standardDsls loading
e61091e: moved to psi
f84bdce: export dependency
8c3458e: moved to properties psi
8a342ab: moved to core
0cf35f7: resource/gen
ac478b1: moved resources to groovy-psi, regenerated icons
18ab45e: moved to core
1991ad8: moved to psi
dc90b9a: cleanup
cc0fbf8: test fix
9ed02a6: Platform: IDEA-125277 provide extension to allow non-project file modifications without notifications
817df5b: return code in django_manage_shell for old django versions
2355467: fix django console for PY-13031 remove old code with execute_manager and setup_environ fix django prompt
7b308ab: EA-54820 - assert: ClassMappingNameConverter.getVariants: fixed
fd94f78: EA-54820 - assert: ClassMappingNameConverter.getVariants: diagnostics
d0c1508: syncronize 'assert'
c1ec45d: toString() restored
8142475: replace assertSame with assertEquals when comparing virtualFiles
9ee6a48: IDEA-123484: generate stub type definition for implicit trait type mixing
a4094bf: IDEA-123484: multi-trait type
dff7733: IDEA-123484: as operator with traits
0eefe8c: IDEA-124932 "Analyze Backward Dependencies" on any class fails with AssertionError: diagnostics
d133496: javadoc
03ffdcf: IDEA-118036 Exception editing JSP file: diagnostics
49d5445: escaping redone, now only for double quotes
cdca783: GPUB: drop deprecated stuff, parseTokenSmart & cleanup
6aef47f: Merge remote-tracking branch 'origin/master'
d001816: PY-12734 Templates in app directories not found with implicit TEMPLATE_LOADERS
4fa70d5: do not write new setting to profile if it has not changed from the default value
d38e995: fix for private fields
6e3b903: cleanup ProjectView popup menu and EA-56883 on Java8
a8ca529: Merge remote-tracking branch 'origin/master'
f8b1755: revert: make too many mess in testdata to have everything escaped in error messages; or we won't have an ability just to accept actual error message and would have to rewrite it manually
6d3bf6d: IDEA-125464 Non-pinned and non-docked tool windows do not hide anymore
f6a8a5e: (IDEA-89870, IDEA-114058) hg4idea: java.lang.AssertionError: Invalid command
51a5628: make exception dialog appear faster for long traces
ea09e6e: don't fire UseVirtualFileEqualsInspection on comparisons with this
506a2cb: include anonym prefix in internal canonical text to avoid incompatibility messages with "same" types on both sides
5953b8f: accessibility of private method via anonymous classes fixed
e43dbe0: IDEA-122430 Search in class hierarchy: preselect class
b290c14: IDEA-125430 do not clear package view attributes
a4898d7: Utility method for text alignment
ee26a53: [log] Add registry key vcs.log.bek.sort=false.
2147279: [log] Add bek button.
02e5afb: [log] Add GitBekParentFixer for GitLogProvider.
9774870: [log] Create BekSorter.
e3177e8: [log] Fix DelegateGraphFacade when applying filters.
56c0dd0: [log] fix VcsLogColorManagerImpl.
313c4b1: [git] cleanup: move method to the only place in tests where it is used
a981a80: [git] IDEA-125328 Don't crash if commit/author time is empty
a0f6188: Merge branch 'python-fixes'
f0bc595: Specify HTTP URL scheme for proxy string passed to 'pip' (PY-12771)
843daaa: IDEA-125237 part II, the considering of static final fields
9213ca5: [log] IDEA-125309 One more fix for a possible deadlock
f4b288b: fix gray gap in ComboBox under Darcula and IntelliJ lafs
bdeea08: fix java.lang.IllegalArgumentException: Row index out of range when history is empty
b553f5c: check for plugin incompatibilities before update for a new version (IDEA-24014)
1fc11e7: cleanup
f6f7050: Merge remote-tracking branch 'origin/master'
dc5157d: Fix CME in terminal (IDEA-125398).
d8b5ecb: IDEA-125378 SQL: Implement keyword-based operators completion
d591ab2: IDEA-107350 EvaluateXPath dialog: input field height is too small, text is unreadable
033aed0: IDEA-91663 (Method can be variable arity method shall have an option to ignore methods with multiple array arguments)
2335b92: make test light
f0fdc59: IDEA-124590 Java Rearranger: Implement "section" support
61b6ca0: icon class regenerated
67b1070: Merge remote-tracking branch 'origin/master'
ce11bc5: Merge remote-tracking branch 'origin/master'
3501998: PY-13018 ForeignKey to model in different file brakes backward references if referenced as APP_NAME.Model
383494f: Package info with links to confluence added
961bf42: Start plugins wizard #29 move plugins saving to beforeOkAction
42c25f2: DevKit: resources, icons, DevKitBundle
36d20ee: fix wrong LAF shown in Settings/Appearance on first run
86cda23: do not show empty Refactoring popup group
aa68afd: IDEA-124119 [regression] Breakpoints dialog: checkbox for "condition" field is gone
544238b: Don't draw terminal content out of the terminal panel component (IDEA-125317).
fdf377f: Merge remote-tracking branch 'origin/master'
9f9e67e: Don't draw terminal content out of the terminal panel component (IDEA-125317).
635cfcc: show renderer's icon in variables view
a44cf2c: Merge remote-tracking branch 'origin/master'
70e83b9: Fixed scrolling issue in terminal (IDEA-125223).
947ef5c: show number of recurrent call in stack frames list
a092c29: optimization: avoid unnecessary disk access operations
09d7890: Revert "Fix list selection background when unfocused"
15b772c: rerun browser on performed fix
c7d2582: an internal inspection checking that virtual files are compared with "equals"
d763d57: PushedFilePropertiesUpdater: don't start dumb mode because of vfs changes outside the project
844d483: compare virtual files by equals, not ==
b9b3b32: cleanup
82c9f72: cosmetics
a8adf75: continue "auto expressions in Coffee" spec
00e17e3: borders for IntelliJ LaF
5857e85: removed another copy of processing files with given set of data keys
782f5eb: Merge remote-tracking branch 'origin/master'
164f48e: make customizable
9fe0fda: jediterm updated to fix IDEA-125385
421f658: selection border
356ac7a: default table render offset
5854fc8: support empty borders
92226c5: Merge remote-tracking branch 'origin/master'
e3e854a: IDEA-125287 [new Java debugger] trimmed string value in Variables view
f2f3a64: project file conversion: API, @NotNull and javadocs
96a6ad6: IDEA-121475 Row height in "Environment Variables" too low
19e6f54: IDEA-125369 (Error highlighting remains after expanding annotation to normal form)
3a80bec: IDEA-121475 Row height in "Environment Variables" too low After-review rollback
757fb8d: heads, parents and several other mercurial commands should be executed as local commands
6bb121c: IDEA-64250 ("simplifiable annotation" inspection could unwrap single-element array arguments)
54fdb56: [log] IDEA-125309 Fix deadlock. invokeLater instead of Wait
59630f4: Merge remote-tracking branch 'origin/master'
cc055ad: fixed PY-12405 --noinput option does not work when running django unit tests
22b7cde: IDEA-122962 - Clouds: perform connection test in background - fix UI breaking on long error message
eb65298: IDEA-125287 [new Java debugger] trimmed string value in Variables view
ee02141: sources roots without sources converted to resource roots
f2d9cda: @Nullable
2c2bc76: remove hardcoded RED color in tooltips
16d79c0: add module dependency [debugger-impl] -> [util]
2ff22a8: fix focus border around selected elements
fabedf6: for indexed refactoring
d4d6b60: sources roots without sources converted to resource roots
09c92d9: invalid source roots removed
d750e35: XDebugger: Added myCustomTopPropertiesPanelWrapper to XLightBreakpointPropertiesPanel
a1a393b: IDEA-124402 Exception during choosing input XML file
07e7838: IDEA-125289 Unexpected navigation after folding After-review fix
903857f: write action
268b90e: IDEA-124119 [regression] Breakpoints dialog: checkbox for "condition" field is gone - added mnemonic
2b3c1e1: notnull
2df4494: moved to psi
a708897: notnull
90859a3: moved to psi
a50eb7d: moved to psi, reduce dependencies
0a59e6f: notnull
76b18d8: moved to core
27e528f: continue "auto expressions in Coffee" spec fix NPE - absoluteLocalPathToSourceIndex
8a7ce9b: ensure fonts are not lost during collapse/expand, second popup shown, etc
0282f9e: IDEA-125047 Wrong error in XSLT injection
d8f9f2b: Merge remote-tracking branch 'origin/master'
3baeea4: try to highlight only wrong argument in inapplicable call
607732e: ensure scrolling finished before popup is shown
7e77395: include build target info in error message for unexpected errors
1676779: IDEA-123049 Rearrange Code is breaking code (Code Style > Java > Arrangement)
f91bf07: capitalization corrected
8f14954: IDEA-125302: The Analyze Stacktrace menu option remembers only one log file across multiple projects
7520a18: notnullification
94e42a8: don't warn on annotations containing errors
90a4b5c: make test light
b2fc5b1: allow xml escaping in test highlighting data
3794f56: Merge remote-tracking branch 'origin/master'
68565ae: PY-2980 Unresolved reference for ForeignKey defined as string
d923e07: IDEA-122956 Popups in Project Structure dialog have wrong initial size sometimes
202643b: init "auto expressions in Coffee" spec
a23319a: cleanup
552c2c5: cleanup
cb9b56b: cleanup
b719055: Merge remote-tracking branch 'origin/master'
67ec3fc: IDEA-125269 List: bad selection BG/FG color
cfa83df: use JBColor
cb3f677: use default color scheme if using darcula color scheme with default LAF
1b9cdd8: get rid of unused DirectoryInfo.orderEntries field
dd3c283: IDEA-124793 Editor gutter background not painted, gutter icons displaced
9f9a8b5: IDEA-121475 Row height in "Environment Variables" too low
98c8696: apply code cleanup after code generation, let's see how it would work on the long run (IDEA-124904)
7c15dce: cleanup tools extended
a915a2d: update editor notifications on roots changed
c21f106: check app directory first (IDEA-125294)
c731f05: bomb test
9424172: Merge remote-tracking branch 'origin/master'
61fd17b: IDEA-125189 new debugger: pause doesn't select thread in frames combo
c4f4f08: IDEA 123484: fix test
ba16a8c: move all header into starting script for django console (PY-11728)
dbe0fc8: IDEA-121687 New module->Ruby on Rails doesn't allow to specify sdk IDEA-121685 New module->Ruby has confusing UI
6db5e53: HgCommandExecutor refactoring (IDEA-119330 Reorganize mercurial command execution )
3066cd3: IDEA-124119 [regression] Breakpoints dialog: checkbox for "condition" field is gone
fbdaf43: foreach simplification + for in js postfix template
77e2c15: ignore 'winShellScriptingQuoting' test if not Windows
77df25e: AnnotateStackTraceAction in now reusable
a1b2bed: Merge remote-tracking branch 'origin/master'
cb8a9f0: add module dependencies: do not show duplicates (IDEA-125033)
f5656e8: gutter icon for external annotations (IDEA-39633)
bb5631c: action to change assignable var type (IDEA-125234)
ec9a61b: extract method: redundant cast treatments (IDEA-125259)
68f0af6: IDEA-125262 Can't create Django, AppEngine and other specific Python projects
6ebbcc6: cleanup
e11cf17: introduce process queue to auto-clean tossed resolve results
1a6a08f: compilation
2855b05: groovy sucks
11ba843: moved to psi
726502f: moved to psi
9fdcc12: removed static interface inheritance to reduce unnecessary dependencies
77522ae: inline GroovyFileType.GROOVY_LANGUAGE
a093f0d: introduce groovy-psi module
65b4b98: moved to ui-ex
4f7b33b: CopyPastePostProcessor interface converted to class so incompatible plugins won't break copy-paste functionality (and exception will be shown only on first invocation)
c946899: removed unused action to lessen dependency lang-impl on vcs-impl
0cb7117: removed unused code to get rid of dependencies from lang-impl to vcs-impl
68c8c32: some dependencies from lang-impl to vcs-impl removed
a61b41f: IDEA 123484: support for trait fields. Accessors and pack__name fields
abe983d: IDEA 123484: get rid of PsiImplUtil.isTrait(PsiClass)
98777dd: Gradle: nonclasspath finder updated to search psi classes in groovy sources
50c85d4: OC-9949 Breakpoint editing popup layout is broken
e7884cc: vcs specific classes moved from lang-impl to vcs-impl
47fc79d: PY-12810 "imported" urls are not supported by {% url %} tag ( "patterns +=" now supported)
535e2f3: svn: Refactored SvnConfigureProxiesDialog - execute "svn info" as "test connection" implementation (instead of direct SVNRepository usage)
4c22b04: svn: Refactored BranchMerger - get latest source revision using InfoClient (instead of direct SVNRepository usage)
2046a89: svn: Refactored SvnUpdateEnvironment - get latest repository revision using InfoClient (instead of direct SVNRepository usage)
f024c0c: svn: Refactored SvnUpdateEnvironment - removed unnecessary parameters, code simplified
39e5d54: svn: Fixed LatestExistentSearcher - ensure repository relative url starts with slash (for correct relative urls comparison)
65370cd: svn: Refactored LatestExistentSearcher - get latest repository revision using InfoClient (instead of direct SVNRepository usage)
6c0f1f1: svn: Refactored LatestExistentSearcher - fixed warnings, rewritten recursion with while loop
d4fd095: IDEA-125237 (Misordered 'assertEquals()' arguments: support non-primitives)
a11c2bd: svn: Refactored LatestExistentSearcher - check if item exists in given revision using InfoClient (instead of direct SVNRepository usage)
957aebe: svn: Refactored LatestExistentSearcher - use repository url passed as parameter (and not resolve repository url by itself)
a4fcdde: IDEA-125289 Unexpected navigation after folding
894bbbd: fixed no toString value
8168063: replace @Nullable findInstance with @NotNull utility with logging (EA-55734)
061c4b0: compilation fixed: use dependency on 'annotations' module instead of 'annotations.jar'
a454164: style
30287ac: IDEA-125285, EA-645852: AppCode exception on start up +review
c08b9bd: Merge remote-tracking branch 'origin/master'
546f5fd: IDEA-86100 DevKit: support "Go To Related File..." sort after/before template files
f4d0b25: svn: Refactored SvnUtil.remoteFolderIsEmpty - check folder content using BrowseClient (instead of direct SVNRepository usage)
12a5319: tweak insents
0e3c26e: [log] allow more results for the first request to get filtered results
06aebe0: Emmet: remove redundant borders from preview
2012b06: IDEA 123484: clashing methods from super-traits. Prefer the method from the last trait
1ba5924: IDEA 123484: access to super method with super. qualifier
205f80d: IDEA-124891 Implementation icon in gutter overlaps preview for Icon class
c17387a: IDEA-86100 DevKit: support "Go To Related File..."
298ee51: Merge remote-tracking branch 'origin/master'
3c638cb: show variable name for arg_x and slot_x variables
3280f4f: [log] LOG.info the ISE (Joiner failure), not LOG.error
f2e2a1b: [log] Joiner: use THashSet with much faster removeAll
a32815c: [log] clear IntStack in DfsUtil.
a5380f0: [log] Optimize IntTimestampGetter.
bba2e43: [log] Optimize PermanentGraphImpl#getAllCommits()
02daba7: [log] Move GraphCommit implementation to vcs-log-graph
13378bb: [log] Add vcs-log-graph-api to root build modules
057c82c: fix author width update
a1fe976: [log] Rewrite the process of refreshing log & building the DataPack
5cffaba: [log] Add sort type to PermanentGraph#createVisibleGraph.
081b82e: [log] Fix arrow.
fa93a67: [log] Fix edges sort problem.
42874b2: [log] Create SmartDeltaCompressor.
89d784b: [log] Create new PermanentListIntToIntMap. It is useful for IntDeltaCompressor.
a4141ad: [log] Fix NOT_LOAD_COMMIT.
82f85bd: [log] Fix color id.
de26777: [log] Extract GraphLayoutImpl from GraphLayoutBuilder.
1b43a23: [log] Add DelegatedPermanentGraphInfo.
50b2fc4: [log] Create PermanentGraphInfo & PermanentCommitsInfo.
3cd6eb1: [log] performance fix.
f5413a0: [log] PermanentGraphBuilderImpl - initial version
9210824: [log] Always request ordered recent commits
c4804c2: [log] move getFilteredDetails to VcsLogFilterer
63b4315: [log] use interface
2ca8eac: [log] Remove obsolete ContentRevisionFactory
ef6d65e: [log] Add new vcs-log-graph-api module in ultimate
dd97706: [log] Fix jump to not load commit.
0510209: [log] Use trove HashMap.
8476e99: [log] Optimize in PermanentCommitsInfo for case CommitId == Integer.
3b585f0: [log] optimize full graph build.
2441f57: [log] Generify VcsLogJoiner.
954a4c2: [log] Fix mouse over action.
44505e8: [log] add equals & hashcode for PrintElement.
5d94e56: [log] Collapse all.
64433f9: [log] Fix oneOfHead().
50388d4: [log] fix CurrentBranches.
24275af: [log] Fix CollapsedVisibleGraph.
e86fb65: [log] performance problem
20b21f2: [log] Fix with for graph cell.
108183c: [log] Fix EdgesInRowGenerator.
4cc1adc: [log] Fix underdone nodes.
e34b093: [log] Fix colorId.
af5b227: [log] Drop VcsCommit.
616274e: [log] COMPILED!
4211697: [log] fix DataPack
3cbb213: [log] Change signature of GraphFacade#getAllCommits();
545ef9f: [log] Rename getTime() -> getTimestamp().
6fbc6f6: [log] Rename getHash() -> getId().
33a7f03: [log] compilation fix.
4cb058c: ~~ [log] Add module dependence.
bc7a666: [log] Add DelegateGraphFacade.
b3d2206: [log] Add getVisibleRowIndex to VisibleGraph.
0a77ab3: [log] Make FilterVisibleGraph.
c9795ab: [log] Make CollapsedVisibleGraph.
5be555e: [log] optimize import.
6650399: [log] Add branchNodeIndexes in PermanentGraphImpl.
293ef9a: [log] Add constructor.
15dd5b1: [log] Add IdFlags.
34c687e: [log] Fix PermanentGraphImpl.
a4c2797: [log] Move method from CollapsedVisibleGraph to AbstractVisibleGraph.
e671eb1: Extract deprecation warnings only if they present in text.
b30e3f0: [log] In GraphColorManager JBColor -> int(colorId) && int -> CommitId
340ba68: [log] Fix EdgesInRowGenerator and add several tests.
1ec4cda: [log] Add LinearGraphWithElementsInfoParser.
e3369e2: [log] Remove unnecessary BufferedReader usage.
bd18165: [log] Remake test package structure.
4a67d5e: [log] Fix equals fox CommitId.
408d6878: [log] Remake tests.
62cad84: [log] Small fix.
88e5afd: [log] Remove newgraph package.
c3fbf99: [log] Add Flags#setAll().
b8c1375: [log] Move DfsUtil.
770aff8: [log] Remake image printer.
b49cea0: [log] Remake PrintElementsManagerImpl.
97c5f6d: [log] Add AbstractPrintElementsManager.
fe3a680: [log] Create new PrintElementWithGraphElement.
80d21e9: [log] Remake ContainingBranchesGetter.
5996a24: [log] GraphCellGeneratorImpl -> PrintElementGeneratorImpl.
6b56353: [log] Generate equals & hashcode methods.
55b4219: [log] Remake AbstractPrintElementGenerator.
938956f: [log] Add simple implementation of PrintElements.
3784b45: [log] Remake EdgesInRowGenerator.
ad7d357: [log] Remake FragmentGenerator.
1a1d238: [log] Add draft of CollapsedVisibleGraph.
95a51cd: [log] Add AbstractVisibleGraph.
b8a9f02: [log] Fix CurrentBranches.
8722519: [log] PrintedLinearGraph.getLayoutIndex() -> getGraphLayout().
f6467e4: [log] Add draft files for PrintElementGenerator.
6f8ff22: [log] Add FilterGraphWithHiddenNodes.
6563dea: [log] Some changes in CollapsedGraphWithHiddenNodes.
0994121: [log] Add constructor.
ad25cff: [log] Remake GraphWithElementsInfoImpl to CollapsedGraphWithHiddenNodes.
3abc13e: [log] Extract inner class.
c9a5fb2: [log] Add LinearGraphAsGraphWithHiddenNodes.
3b2baa1: [log] Fix update event in GraphWithHiddenNodesAsPrintedGraph.
a9bee1b: [log] graph.impl.visible.utils -> graph.impl.visible.adapters
2f4b46f: [log] Move all from vcs.log.facade.utils -> vcs.log.graph.utils.
579ee01c: [log] PermanentGraphLayout -> GraphLayout.
8e14dd2: [log] Add draft of PermanentGraphImpl.
6e5d35f: [log] Remake PermanentGraphBuilder to PermanentLinearGraphBuilder with CommitId.
313093e: [log] Remake PermanentCommitsInfo with CommitId.
8151d60: [log] Create new internal graph api.
797b59b: [log] Add PermanentCommitsInfo.
5e909f3: [log] Add IntTimestampGetter.
5049be6: [log] Drop unnecessary method from PermanentGraphLayout.
adfa4b0: [log] DfsUtil: use IntStack.
24768a4: [log-api] Update api.
b02fc19: [log-api] Update api.
d1fce17: GRAPH API
e7526b1: svn: Refactored BranchMerger - removed unused fields/parameters
65bab47: DevKit: refactor InspectionDescriptionNotFoundInspection
5a18a7e: svn: Refactored SelectLocationDialog - get repository root url using InfoClient (instead of direct SVNRepository usage)
caa8262: support ContinuationWebSocketFrame
cf56368: Calculate future feature only if it present in text.
1a79f0f: svn: Refactored SelectLocationDialog - removed unused code, removed duplication
445763e: do not calculate mirror file for each access to jar file
5cfdfeb: fix IllegalArgumentException: The file: doesn't exist.
49b2232: AC/C++: EA-56192 only allow directories in NullFileReferenceHelper roots +review CR-OC-1544
30d5ad4: js postfix template: parenthesized
3ce7f95: quote param containing ampersand for .bat files
0008feb: Based on comments for http://crucible.labs.intellij.net/cru/CR-IC-5400 * quote param containing ampersand for .bat exe files * improve test to actually execute .cmd/.bat files
2fb4ba8: reverted
69ea226: custom strategy bug fix, new Weak key soft value map
e7ed4bf: moved to ui-ex
bc74ad3: moved to core-impl
c2083de: [log] Handle the case when null value is passed to the renderer
86c197a: IDEA-125227 New debugger: please disable switch from expression mode to code fragment mode when evaluating multiline expressions
519d350: Gradle: discover local gradle sdk sources local gradle dependencies
911aa11: inline GroovyFileType.GROOVY_LANGUAGE
9668cc5: renamed
06ad15b: javadoc, IncorrectOperationException replaced with UnsupportedOperationException as more standard
940d174: javadoc, cleanup, removed bulk operations since they are not jdk8 compatible anyway
7e1f445: AC/C++: EA-56192 allow files in NullFileReferenceHelper roots +review CR-OC-1544
ce9a38c: fixed "unmute on stop" action
2ca53a3: Calculate dunderAll in stubs only if file text contains __all__.
80cb0da: fixed PY-12970 Extract variable: disable refactoring for parts of comrehensions
1fe1440: typo
317cd70: fixed tomcat tests
47060bb: Merge remote-tracking branch 'origin/master'
f343aa2: simplification postfix template
fb45e36: Gradle: nonclasspath finder updated to search psi classes in groovy sources
897595c: Gradle: EA-54734 - assert: JavaSourceFilterScope.<init>
58d3c58: Gradle: EA-56552 - assert: NonClasspathClassFinder.findClass
3003df2: change class signature: process diamond (IDEA-125236)
479578f: move edit settings down
0da1a20: ensure expression valid, initializer expression should be already replaced (IDEA-125231)
57fa2db: fix tests
30d1d10: select and scroll to most recent revision (IDEA-60345)
e54ffc6: scroll tree view file history to most recent change
c3d8951: make scroll to selection in file history work better
90de406: support content loading for resource files from output roots (needed by some annotation processors)
6faa3ff: remove unnecessary addClass() calls
113323e: doc test: assert HTML body text only
96d93a8: add new not expression test
c7ce438: cleanup
78a4030: Merge remote-tracking branch 'origin/master'
57f67f8: IDEA-125184 Mac launcher should not crash when IDEA_JDK env. var. does not correspond with JVMVersion from Info.plist.
e7a2262: js postfix templates: not expression
7877bab: IDEA-125129 I would like to reopen IDEA-117698
3b26929: move jql lexer to gen source root
6723047: postfix template: js introduce variable
be8ee4f: refactor expression postfix template with chooser
cae5144: cache ClasspathCache.transformName
4119688: javadoc typos
d12d8bd: [log] IDEA-116867 Use the same root renderer for author as for message
ee1a2cf: IDEA 123484: traits are supported only since 2.3 version
86ffe87: IDEA 123484: complete trait keyword and extends/implements lists in traits
91875b6: IDEA 123484: trait methods are not allowed to be protected
5462267: IDEA 123484: clena up utils and tests
4e540d4: use previously calculated data from PHM if no read right now is performed: calculate the value instead of wait for IO to complete
53e5113: cvs file history in Browse CVS Repository
727804a: AC/C++: EA-56794 - assert: FileReferenceSet.getAbsoluteTopLevelDirLocations +review CR-OC
3326c46: cache already checked plugins (IDEA-124828)
0f9f39d: auto expressions spec: done duplication check
5d4a66c: auto expressions spec: init duplication check
901d664: Merge branch 'github-theme'
f55b604: Updated JavaScript and CoffeeScript colors
9c26625: Updated Buildout, GQL, ReST and YAML colors
2bc7a23: Updated Django and Mako templates colors
a027a36: Updated CSS, LESS and SASS colors
f1a44bc: revert couple, 135 compatibility
8d2b706: change method return type on call site (IDEA-125166)
3316081: Updated HTML and XML colors
e4bbd52: move diff utility classes to Util module
54754f3: add evaluate expression action to variable's context menu
f915ea5: fixed NPE in evaluate handler
954f437: Updated default colors and set many Python styles to inherited
6c992e4: fix compilation
ae76055: Fix list selection background when unfocused
98f8e9f: don't highlight if psi was invalidated (EA-56385 - PIEAE: PsiUtilCore.ensureValid)
9c76516: prevent SOE when creating PIEAE (EA-56284, EA-56810, EA-56809)
968779e: IDEA-123484: override/implement actions are aware of traits
1f6e60a: IDEA-123484: trait -> type_definitions token set
c4764e6: IDEA-123484: use newHashSet(map()) instead of map2Set(), import static ContainerUtil.*
ae3fce2: Sorted options
08ce945: svn: When looking for cached credentials for given url/realm check also if there are cached credentials for its parent urls/realms (and use them if found)
ac6e1c9: IDEA-124077 Enum code reformat destroys enum
a92b91b: rearranger @Nullable replacement
1311963: [diff] Simplify the "gear" button implementation
e910c1c: fix Cut functionality and tests
9c75b87: [vcs] Simplify the gear action group definition
1dd998d: BlockSupportImpl: assertFileLength2->reportInconsistentLength
57b759f: IDEA-124712 ctrl-alt-space: duplicate completion variants
83a2697: IDEA-125194 Strange control-flow inspection
0f495ff: [git tests] cleanup
ce0259e: svn: Refactored RepositoryLoader - use common client factory model (instead of direct SVNRepository usage)
d7f2104: WEB-12199 Grunt tool window fails to load Gruntfile tasks, when an "&" is in the path
e718569: Merge remote-tracking branch 'origin/master'
c8485a5: Merge remote-tracking branch 'origin/master'
e79e97b: js potfix templates: notnull & null
2c935f8: [git tests] Fix expected-actual order
81c101c: simplify settings (CR-IC-5127)
c061e81: distinguish generated roots during rename/move warnings (IDEA-125147)
61460f7: combine all jdk jars into one marker in OutputChecker
b49b8fd: [git tests] Remove a useless blinking test
053f37f: [git tests] Log index.lock error
6616b1a: [git tests] Log debug GitHandler stuff if test failed
fed92bd: fixed PY-12991 Missing Added by user mark on paths added to the interpreter
abfedef: throw IllegalStateException if number of keys in node to split is different from expected
73218a0: platform: plugin descriptors loading optimized
a64c39b: EA-56305 (delegate FS used for parenting)
02521d2: [vcs] Add toolbar to the Shelf
d9c049b: [vcs] ShowHideRecycledAction: don't proceed if project is null, cleanup
db69de7: svn: Moved several classes to corresponding packages ("org.jetbrains.idea.svn.lowLevel" to "org.jetbrains.idea.svn.svnkit.lowLevel", diff related classes to "org.jetbrains.idea.svn.diff" package)
5546e02: Addede GitHub color scheme
fd96e01: fixed tests
9a1a122: Merge remote-tracking branch 'origin/master'
850f818: Revert start_new_thread override.
5c8997d: svn: Refactored RepositoryBrowserDialog.doUnifiedDiff - use common client factory model (instead of direct SVNDiffClient usage)
71d8ba1: svn: Implemented "unified diff" clients (both for command line and SVNKit)
e4dc06d: Merge remote-tracking branch 'origin/master'
02a4391: lazy scope initialization in case of big usage view open
fba79fe: Slim. Introduce variable test fixed
c816eb8: added javadoc
d8f4bdd: IDEA-99541 New run configuration type to run Ant get rid of ProcessHandler.waitFor()
e3f9e71: javafx: quick fix to switch to javafx css in property starts with -fx-; make css dialects configurable visible when file-mappings are available (IDEA-125020)
3cccccf: file templates: no NPE if exception doesn't contain message
5f0091f: refixed NPE in PasteHandler
ea62a1d: NPE in PasteHandler
9ee678f: flusher on low memory should work the same way as periodic memory flusher to avoid loosing updates [r=Eugene.Zhuravlev]
12a7479: Merge remote-tracking branch 'origin/master'
1e48abe: good code red: nested classes inheritance
e190be1: fix test
53b9222: processOverlapping should iterate in order
cf632d0: do not open API for abuse
7bae702: refactoring - create both RTF and HTML flavours in one copy-paste postprocessor
66d211f: cleanup
4c253dc: refactor API interface
71ea747: make rich copy work for documents with non-normalized line separators
c7f67f1: refactoring
dc95443: cleanup
74c97e0: remove impact of ADD_IMPORTS_ON_PASTE setting on other copy-paste-related functionality
897f2ba: unused/static imports
873cada: cleanup
5a50486: @Override
aa9f4b5: extracted GroovyRunnerPsiUtil
f256b8d: moved ErrorUtil to psi
6243ae7: moved GroovyImportHelper to psi
242a795: removed dependency of GrMethodBaseImpl to code style. The clients of GrMethod.setReturnType() should call GrReferenceAdjuster.shortenAllReferenceIn() if they need to
5bdb72f: merged PsiUtil and GroovyPsiUtil
f4c8ed4: moved to psi
939b86b: moved method to GroovyPsiUtil
9206109: moved to psi
532fdb0: notnull
b6594a3: moved to core
91d1a5b: moved org.jetbrains.plugins.groovy.intentions.utils package to psi
55bb9af: moved to psi
b2b65c2: removed dependency on platform
68910bc: moved to psi
bda7716: moved to psi
91e946e: moved to core
a95cbce: cleanup
7ca3394: cleanup
41b594c: notnull
00f6e5d: correctly identify class usage in dumps
28c6a3c: XML/HTML: move inspection descriptions to proper modules
47f5b9d: Revert "[vcs-log-graph] use Couple"
510ba46: fixed load project test
269e211: Code clean-up.
99f2434: fixed groovy debugger tests
a32c4c4: add new test xml map serialization
1b65256: EA-55646: debug output
00964e5: update according to changes in GotoActionModel
9ea4344: rollback
7de12bf: Mono<T> == Function<T, T>
1c4c11b: [svn4idea] use Couple
c159007: [testFramework] use Couple
00c700f: [vcs-log-graph] use Couple
b8aa01b: [vcs-impl] use Couple
2d2df3b: remove unnecessary static
27c06dd: [ui-designer] use Couple
891b65f: [lvcs-impl] use Couple
9f2118c: [vcs-api] use Couple
31bd437: [properties] use Couple
de97b7f: [platform-api] use Couple
bbc1d35: [platform-impl] use Couple
7347cd3: [extensions] use Couple
fb4d104: [idea-ui] use Couple
756e0ad: [hg4idea] use Couple
5bf1a92: [java-analysis-impl] use Couple
ccb6b7c: [java-impl] use Couple
c8b9199: [java-indexing-api] use Couple
c04909d: [java-psi-api] use Couple
d3278e6: [java-psi-impl] use Couple
70fcb07: [javaFx] use Couple
eaed3bf: [jetgroovy] use Couple
526441d: [gradle] use Couple
3e1c9d1: [lang-impl] use Couple
7afb583: [dom-openapi] use Couple
bffa11b: [debugger-impl] use Couple
e9254bf: [github] use Couple
8fc8555: [git4idea] use Couple
187dd78: [vcs-impl] use Couple
2985542: [platform-impl] use Couple
47797e5: [core-impl] use Couple
a583560: [core-api] use Couple
a8727c8: [compiler-openapi] use Couple
9d30723: [compiler-impl] use Couple
1a54d31: [common-javaFX-plugin] use Couple
e25eff2: Merge remote-tracking branch 'origin/master'
3974fdd: Fixed console debug for remote interpreter (PY-12959).
db0de10: vfs: .zip file cache reset fixed
e677a69: Remove double borders
d54e0b0: svn: Refactored RepositoryBrowserDialog.doGraphicalDiff - use correct client factory depending on settings (and not always factory for SVNKit)
c7313cf: Merge remote-tracking branch 'origin/master'
18844ff: PY-12810 "imported" urls are not supported by {% url %} tag (fixed quotes trouble)
eeab21a: svn: Implemented diff for "url + url" pair for command line
3c6cccf: Data Views specs
d47c2cd: EA-56596 - CCE: XmlLanguageInjector$.visitElement
7d729ca: platform: let out/err readers have independent policies
e829e8c: store vfs charset in user data
8f05759: IDEA-121961 Debugger: evaluate from variables doesn't include context
0829ee7: better fix for IDEA-125082
90d6990: new inference: separate caching
8148aa2: filter out elements not matching scope (IDEA-125178)
b1b6025: extract field from auto closable (IDEA-125141)
827e889: show the notification with a delay to avoid blinking when "too much output" ceases quickly
a38bfd3: calculate editor notifications in a single background thread
eae74d2: Cache enabling
b6b3618: EA-56566 Fix NPE in TaskItemProvider.dispose
c77cd7b: Merge remote-tracking branch 'origin/master'
714b3df: fixed PY-12698 Noticeable hang when selecting not yet added virtualenv as project interpreter
b44ffe6: EA-56384 Fix NPE in JiraRepository.discoverApiVersion
aef1f3e: do not show static node if empty
4c4b111: java-xdebugger: JRuby breakpoints handler
a49b8ff: java-xdebugger: fixed tests
31434a5: java-xdebugger: IDEA-125088 Debugger: Evaluate Code Fragment works like Evaluate Expression
11d6bce: java-xdebugger: IDEA-125081 Debugger: Force Step Into works like simple Step Into
7c6f28c: java-xdebugger: allow method calls in quick evaluation with alt
428a04e: use editor font in expression combobox
1b7a152: java-xdebugger: correct context for watches evaluation
e4d5f88: java-xdebugger: store java breakpoints in xbreakpoint's user data
3f3fdcd: java-xdebugger: fixes after review
a542026: java-xdebugger: use correct context for variables calculation
2a64442: java-xdebugger: IDEA-125035 Debugger: breakpoints: unmute on finish: inconsistencies between behavior and appearance
4f64e19: java-xdebugger: IDEA-125037 Debugger: "Watch method return values" setting label is changed on stop
f1de6f7: java-xdebugger: IDEA-125039 Debugger: Throwable at PsiDocumentManagerBase.commitAndRunReadAction()
dca55b4: java-xdebugger: IDEA-125038 Debugger: setting field breakpoint causes IAE at SimpleColoredComponent.append()
e1a926a: java-xdebugger: IDEA-125044 Debug: Run to Cursor: IAE at XDebugSessionImpl.breakpointReached()
c2dc88d: java-xdebugger: fixing tests
d369a04: java-xdebugger: avoid assert on context update
b53018e: java-xdebugger: fixed breakpoint tests
cd4b029: java-xdebugger: no need to recreate java breakpoints
dc17bb0: java-xdebugger: do not store empty expressions
36e4354: java-xdebugger: show breakpoint icon when hit for non line breakpoints
9ade5d7: java-xdebugger: add stepping filter action
acda84e: java-xdebugger: show extra array elements on demand
563511a: java-xdebugger: no need for extra constant
d7a2306: java-xdebugger: correct context for variables calculation
98cb4bd: java-xdebugger: display object id and correct string coloring
f882b59: java-xdebugger: show info messages in variables
1d06412: java-xdebugger: show return value on top of variables
b1e62e0: java-xdebugger: fixed incorrect frames refresh with enabled filters
e86c7d9: java-xdebugger: simplify external breakpoints handlers
764f536: java-xdebugger: fixed exceptions
d21c49b: java-xdebugger: avoid asserts
4f439fe: java-xdebugger: static group and this variable
cf699a8: java-xdebugger: variables icons
26a0ffb: java-xdebugger: get nashorn frames this way until we integrate fully
3bf4519: java-xdebugger: correctly handle frame change
f0b82ee: java-xdebugger: correctly handle session change
feb4620: java-xdebugger: lazy value label calculation
0f99ccd: java-xdebugger: avoid gathering variables with resumed context
6073323: java-xdebugger: do not calculate top stack frames for all threads
4b2ff87: java-xdebugger: avoid NPE
0ac53b8: java-xdebugger: fixed dump threads action
d9d1c74: java-xdebugger: fixed memory leaks
c83bb60: java-xdebugger: tooltip evaluation support
3b0b8f9: java-xdebugger: expressions language selection UI
b025aaa: java-xdebugger: removed duplicated debugger editors provider
d6299a6: java-xdebugger: correct context for expression
2293837: java-xdebugger: do not deprecate old setters
b0f9849: java-xdebugger: fixes after review
af29d44: java-xdebugger: store condition with language and custom info
071b912: java-xdebugger: custom java breakpoint handlers
2635efb: java-xdebugger: add to watch support
a3b7433: java-xdebugger: View text action support
570305e: java-xdebugger: filter library frames support
dbe29fe: java-xdebugger: jump to object source support
aef95ac: java-xdebugger: use debugger thread to store descriptors tree
04571c0: java-xdebugger: mark object
439666c: java-xdebugger: set value prototype - minor extra fixes
3c7f48c: java-xdebugger: set value prototype
9265407: java-xdebugger: support of "view as" action
74a63863: java-xdebugger: drop frame action on the debugger toolbar
b09633c: java-xdebugger: restore variables tree state when stepping
bf02fe4: java-xdebugger: fixed actions order
1c7a3d4: java-xdebugger: fixed thread name presentation
24fb430: java-xdebugger: correctly calculate frames
d5d617a: java-xdebugger: avoid ArrayIndexOutOfBoundsException
c160771: java-xdebugger: special icon for current thread
61d146a: java-xdebugger: drop frame action
f631088: java-xdebugger: do not show empty type
dc9789f: java-xdebugger: rebuild on auto variables setting change
223d0ae: java-xdebugger: return correct active execution stack
d225fdb: java-xdebugger: jump to source
59a75a5: java-xdebugger: do not create old DebugSessionTab
af21fe9: java-xdebugger: pause support
3aade1c: java-xdebugger: "view as" action prototype
ad3f1fc: java-xdebugger: init breakpoints on process attach
8835958: java-xdebugger: dump/export threads actions on the toolbar
8ce7361: java-xdebugger: customize data views action in watches context menu
7252d5d: java-xdebugger: customize thread views action in frames context menu
00ab63e: java-xdebugger: customize data views action in variables context menu
f2e28dd: java-xdebugger: use xdebugger's mute breakpoints state
1cdd4f7: java-xdebugger: settings popup support
ca891ba: java-xdebugger: imitate old frames renderer
ef0745a: java-xdebugger: threads tab support - update on select
b857507: java-xdebugger: assert not needed
27d67ad: java-xdebugger: threads tab support
60986c7: toString for line breakpoint
2479d8f: java-xdebugger: threads combo support prototype
d66fc12: java-xdebugger: watches support prototype
d2fe211: java-xdebugger: variables support prototype
ef6369f: java-xdebugger: initial commit: - debugger start - java breakpoints handlers - set/unset breakpoints - simple frames view support
f4fb687: EA-56555 (diagnostic)
8b27b16: Cleanup (weird exception constructor)
75f96f6: better corners for Darcula
a0fae704: Merge remote-tracking branch 'origin/master'
665e0e1: hang on start up
66df65d: rename test methods to run on java 8 (ClassFormatError fixed)
5898e80: Filter out remote sources dir from extra sys path (PY-12958).
0a0b678: hide service information
2b8c333: fixed type annotations restoring (IDEA-124889)
ef7c8b9: new inference: inference in terms of inference variable as type params (I)
0bea7ea: new inference: encapsulation
922715c: Fixed buggy calculateExtraSysPath
6133865: CR-IC-5378 add comment on java.io.File.exists usage instead of vfs refresh
b2d382e: IDEA-99541 New run configuration type to run Ant
14a4668: EA-56795 (diagnostic)
ee0b565: Cleanup (nullability; parameter type)
a95b6d6: Slim. Template languages must be over HTML_DOCUMENT element
3c41d20: Embedding Ruby code into Slim.
ba802ea: Merge branch 'master' of git.labs.intellij.net:idea/community
3a51776: Merge remote-tracking branch 'origin/master'
37c2e28: Gradle: extra tooling model build error builder added
4874a78: switch off couple inspection
9ad3184: EA-55850 (a constant used is only available since Win7)
56cea62: resources -> src
78e49f7: Cleanup (typo)
7a4ac7b: EA-56033 (early diagnostic)
e52b674: cleanup
b55f2ac: WEB-6779 JS Debugger / Variables: Automatically show used variables: mutability
4077052: Merge remote-tracking branch 'origin/master'
8b18eb8: IDEA-123484: independent GroovyLineMarkerProvider. Don't suggest implicit trait methods in implementation list
2f60fb3: IDEA-123484: concrete trait methods are implicitly generated in inheritors
a7eb2a0: IDEA-123484: abstract trait methods should have explicit 'abstract' keyword
2fbab8a: lazy Object type in GrLightParameter
44eaa05: remove dependency from Groovy PSI to Groovy completion
18fa4ff: split groovy completion contributor into separate completion providers
ce1beaf: IDEA-124731 semicolon or curly brace after constants
3a2dfbd: IDEA-124731 enum constants can be placed on several lines with comma placed on the next line
4bdc91c: lazy light PsiType for fast creation from string. Use it where possible in groovy psi
8c44253: cleanup stub generator
46a6910: IDEA-125082 (Weaken intention for LinkedHashSet suggests AbstractCollection)
38ba59f: fixed PY-12964 add path to the interpreter doesn't work
f07444f: uniformly handling class loading from paths containing special symbols like spaces
88eb679: Arrangement: store opened section rules instead of start comments
a4e42e3: WI-23285 Arranger: sections is keep added (already existed section is not found)
224ff5c: save some memory by not caching constructors in picocontainer; they're not reused anyway
3a25aee: DevKit: add InspectionDescriptionNotFoundInspectionTest.testHighlightingForDescriptionCustomShortName
ed33e55: revert changes that made tests hang
fedbb1d9: IDEA-124403, EA-55541, EA-55517, EA-49088, EA-49127 soft wraps recalculation issues
6112926: EA-55847 - AIOOBE: ImmutableText.charAt
d2d6f45: cleanup (following review #212)
e4f6aa4: EA-56779 - IVFAE: PersistentFSImpl.getFileId
2004f6a: improve description
16bd2ee: add "Suppress for 'Tests' scope" quickfix to "Prohibited exception caught" inspection
f96b846: new "'BigDecimal' legacy method called" inspection
2be4b97: better layout for combobox
eb6acaf: svn: Refactored RepositoryBrowserDialog.doGraphicalDiff - moved diff logic for "url + url" to SvnKitDiffClient
ed5d225: svn: Refactored RepositoryBrowserDialog.doGraphicalDiff - do not wrap obtained changes in custom UrlContentRevision (see c38efc0eaffe39c4ac4250cdd4c2615a321db906) - changes collected by SvnDiffEditor correctly work with "Show Diff" action
fb0c65a: InternetAttachSourceProvider: don't sync refresh from outside edt (EA-56768)
9770742: EA-56761 - PIEAE: ClsRepositoryPsiElement.getManager
8317a56: EA-56519 - CCE: PsiDocumentManagerBase.handleCommitWithoutPsi
2ecda67: vfs: create handler as soon as archive root is loaded
b9b3a28: Cleanup (unneeded second refresh dropped)
00140af: IDEA-124361 Suggested variable names for Optional variable needs improving
b92c65c: svn: Show "changes viewer" dialog even if changes collection is empty (dialog will display necessary information message by itself)
90a5b72: Merge remote-tracking branch 'origin/master'
6eb4127: fixed PY-11909 problem with extract variable
a9d6cb1: svn: Moved several utility methods to SvnUtil class
71177eb: fixed PY-11922 pytest problem with marker expression containing spaces
f895885: [log] IDEA-124546 enable "Edit Source" from commit list
3a483ab: [vcs] Delegate to the fuller constructor
8bc8177: fixed PY-11929 Insert type assertion should be disabled for references introduced in dict comprehensions
de5a5e6: Add new icons for new arrangement section rule
19d7bf9: Merge remote-tracking branch 'origin/master'
b61e49e: [log] Trim the hash in go-to-hash action
a351cff: fixed PY-12037 long path cause wide menu
5972053: touch file to recompile
3e8ef45: continue WEB-6779 JS Debugger / Variables: Automatically show used variables
0c8bcaa: init WEB-6779 JS Debugger / Variables: Automatically show used variables
ce66c08: compute memberFilter only once
1701927: IDEA-124688 Console scroll to the end - jumps on the end of the line sometimes
287390b: be prepared to event log changes when expiring notifications (EA-53620 - IOOBE: MarkupModelImpl.addLineHighlighter)
296a5dc: Platform: deleting shortcut from bound and inherited action fixed
d0e540a: IDEA-106517 Exception on welcome screen when using speedsearch
5978682: svn: Refactored DirectoryWithBranchComparer - SVNKit diff logic moved to SvnKitDiffClient
fc13a83: IDEA-116057 second-keystroke popup
1fe493a: fixed PY-12120 Invalid caret position after Enter press in unicode strings
69b6e2e: check for UserDataHolder directly
5cd9a8f: svn: Refactored SvnDiffEditor - use File instead of VirtualFile
8511042: diff: add icon for synchronized scrolling
8e9af01: js postfix templates: if, else, return, throw
a309ba7: Remove Open Directory action.
23db467: use Couple
fc77edb: fixed PY-12195 Changing signature of decorator removes '@'
6a1b621: cleanup
5653e4a: IDEA-124974 NPE from Switcher if 2 tool windows have the same first-strike char
f74c773: do not log refresh stacktrace
6da5c0c: OC-9871
fd6d2fa: Gradle: tooling version updated in libLicenses.gant
0290479: use Couple
e44b143: use Couple
baecc09: use Couple
3a60ce3: use Couple
47f6b1b: IDEA-125121 MacMessages: NPE when parent is null
637a597: do not expand stubs when evaluating getContextName
c39f3b1: Gradle: update Tooling API version: 1.12-rc-1 => 1.12
bbfc26b: IDEA-125119 NPE if message for MacMessage is null
5991df3: IDEA-124839 Gradle sync reports "Error:You can't change a configuration which is not in unresolved state!" and "Could not resolve all dependencies"
19c251b: fixed PY-12313 Incorrect __file__ value when running tests in a folder on Windows
f9d754d: IDEA-124839 Gradle sync reports "Error:You can't change a configuration which is not in unresolved state!"
b186522: @NotNull
4e90ea9: dependency fixed
3e98719: removed lang-api dependencies
e5ba40d: removed dependency on platform-api
19444a1: notnull
4a8f5f8: @NotNull
9accb24: moved to core
38f3c11: moved out of psi
c4390c4: Fixed IDEA-122488 IDEA 13 1 not using default font [CR-IC-5349]
8667c40: do not update when root name not changed
4569c83: fixed PY-12369 Code Inspection fails for negative default parameter
20c2b28: update plugins: check plugins according to the new version to be patched (IDEA-78385;IDEA-124308)
5f74804: IDEA-124543 sout template formatting
f0918e0: fixed PY-12401 inline refactoring looses comments
851fd3b: IDEA-124305 JSP: Import statements suddenly disappearing every now and then
850ea16: typo
acac62d: Cleanup (locale conversion; properties grouped)
21152ea: Cleanup (test simplified)
a979900: vfs: core JAR FS migrated
4bf38d4: vfs: archive file system
96aad22: IDEA-64312 Maven: frequent .iml changes after exclude/source folder updates; project leak fix
607ba58: Platform: keymap tests cosmetics
c6354dd: accept as cleanup tool
85f72e1: IDEA-123379 (Unable to generate toString() with template "+ super.toString()" if sub class doesn't have any variables)
2e1a1ab: fold some IJ boilerplate in console
fd7f7a6: remove PSI event nesting because of eager PushedFilePropertiesUpdater (EA-56525)
aafb2dc: use ensureValid (EA-56712)
5494673: TodoIndex supports snapshot mapping
e676714: myContents shared snapshot index can be reset via clear (due to todoindex dependency on settings)
55f9327: use TW stripeTitle if needed. skip extra popup-step in case of one item.
8b36033: separate NavBar action place into popup and NavBarToolbar places (IDEA-124199)
3e3b44b: fixes for review comments
136d116: move normalizeMemberName to debugger view support
b3675f7: IDEA-56033 Mercurial: add support for reverting an uncommitted merge
2b804da: IDEA-124393 Mercurial: Log: user filter applying causes empty log
27272f3: EA-55721 - assert: DbElementImpl.getDataSource
8c49254: EA-56008 - assert: DatabaseVirtualFileSystem.getProjectSafe
42cd37cc: related to EA-56292 - NPE: ComboBoxFieldPanel.createComponent
c4a273f: EA-56671 - E: Runtime.syscall
bffc362: adjust offset while looking for selector
6ef97d6: EA-56674 - IAE: DbFindUsagesHandlerFactory.canFindUsages
c96459f: postfix templates convert configurable postfix list to tree & change store strategy
8481ffc: IDEA-118554 (Inspection suggestion: BigDecimal divide() without specifying a scale and/or RoundingMode)
69d1364: batching tasks to reduce the number of events posted to EDT
c4e2bbe: remove useless HideFunctionValuesAction (now we correctly grouping functions)
a862f08: Merge remote-tracking branch 'origin/master'
2386bed: testdata for IDEA-24479
760d73e: use manager.getProject (CR-IC-5302)
5c11efb: Merge branch 'master' of git.labs.intellij.net:idea/community
5ab59ac: EA-55808 - ISE: ComponentManagerImpl.getPicoContainer - a more robust fix
543d9f6: Merge remote-tracking branch 'origin/master'
5b53af1: Merge remote-tracking branch 'origin/master'
8de4646: IDEA-123896 Navigate -> symbol action doesn't find overloaded methods
7dc78a8: IDEA-124772 Code completion in import statement: suggestion shows package from "java.lang", but when it's selected "java.lang" prefix isn't
7e77d3d: Merge remote-tracking branch 'origin/master'
4b6debd: Merge remote-tracking branch 'origin/master'
ad58ba7: results of code cleanup
1c55611: do not inspect binary files
0feba83: warn about raw arrays passed to varargs method (IDEA-16977)
b32b333: assignment fix for super wildcards (IDEA-125031)
6e2f355: EA-56686 - NPE: UnusedDeclarationInspection$$.runInspection
afe28ba: provide editor with project - no harm (IDEA-124656)
a02113f: Remove class.
304f5dd: IDEA-124097 Structure tool window steals a focus
174a488: Merge branch 'master' of git.labs.intellij.net:idea/community
6a0d559: Remote libraries stored in a directory separated from skeletons
0f964da: aware of not sourcemap file
adfec67: fix NPE Field is not nullable: className
a0a681e: IDEA-124956 PsiShortNameCacheImpl.getClassesByName() is O(N^2)
5b4eafc: Rearranger: allow nested sections processing
b638b32: Merge remote-tracking branch 'origin/master'
c7bcc11: make "Unpredictable BigDecimal constructor call" inspection and quickfix more accurate
6ee9d29a: read saved index data by default + save empty indexed results
ebece42: WEB-11680 chrome debugger doesn't start due to internal exceptions
e2ceeec: WI-4722 Debugger: Ability to skip certain functions with step into.
b63b703: continue WEB-11775 'Do Not Step Into' groupings
ec71671: cleanup
c309c6a: cleanup
0abe2e1: fix tests
51e2856: Merge remote-tracking branch 'origin/master'
1edc667: invalid psi range diagnostics (EA-49842 - assert: ExtendWordSelectionHandlerBase.select)
dd4697d: IDEA-124379 Avoid completion lookup glitching
7732843: IDEA-85517 Option to collapse by-default manual defined (editor-fold) regions [CR-IC-5228]
095153a: set explicit locale environment: GeneralCommandLine.getCharset() and LC_CTYPE should be the same, remove dependency on project and application default encodings
f4cd58b: DevKit: add InspectionDescriptionNotFoundInspectionTest
75ef7eb: fixed EA-53387 - CCE: PyElementGeneratorImpl.createParameter
440a2e5: Merge remote-tracking branch 'origin/master'
f1842c2: fixed EA-56204 - IOOBE: SegmentArray.findSegmentIndex
d5747a8: fixed EA-56667 - CCE: ReplaceListComprehensionWithForIntention.createForLoop
f72f65c: DevKit: simplify/remove dups in DescriptionNotFoundInspectionBase, add tests
520268f: fixed EA-56673 - IAE: ChangeUtil.copyElement
6edaf52: fixed EA-56683 - NPE: PyStatementMover.moveTheSameLevel
e78a1f8: Postfix cleanup
af4c18c: fixed EA-56684 - NPE: PyStructuredDocstringFormatter.formatDocstring
51255dd: Merge remote-tracking branch 'origin/master'
a6f328b: code cleanup available as intentions (IDEA-38653)
0e05ee3: Fixed a typo.
d350749: IDEA-64312 Maven: frequent .iml changes after exclude/source folder updates
a34f27b: Fix output handling.
6034330: DB: improve find usages grouping
0756490: scopes combo: filter duplicates, suppress internal-modules
2a680d2: DevKit: extract PluginDescriptorChooser, improve plugin.xml candidates list presentation, fix dumb mode error
598528c: Merge remote-tracking branch 'origin/master'
c64f813: hide empty vcs scope when vcs is not configured
2e0f935: update "Select word at caret" to "Extend Selection" in tips (IDEA-124727)
cc92f9a: use StringUtil.trimStart() CR-IC-5322#c26136
dfe9c11: DevKit: do not search in non-XML files for ExtensionPoint DOM usages CR-IC-5321
2fa4de5: ActionTestCase kicked
94c3177: fix puppet tests: clear extension cache on point registration
1d3458a: copy reference for directory: copy the relative path from some root (IDEA-92885)
3f1567e: enable CopyReferenceTest.testMethodOverloadCopy
dffcdb3: rename "Select word at caret" to "Extend Selection" (IDEA-124727)
2dc0e67: IDEA-124935 Completion font issue on Spring
b210fef: private EditorHyperlinkSupport.HyperlinkInfoTextAttributes
c394f80: cleanup
b3840bc: Cosmetics
b05ddda: DevKitBundle: use AbstractBundle
847d67f: continue JS Debugger test framework — stepping spec
578a54b: cleanup
cad6541: IDEA-125004 DevKit: support plugin.xml <depends> "config-file"
873fa26: Github: remove test
a7ae56f: Github: do not catch OperationCanceledException explicitly
63ef800: EA-52410 Github: do not throw runtime exception on parse error
fad9ec0: Github: fix dialog type
3c53f98: Github: remove final
0c7b058: Github: catch runtime exceptions in modal progress
f08ff4c: Github: GithubFullPath case-insensitive
0204ba2: access to super members fixed (IDEA-124985)
7ab104a: IDEA-124985
74f3980: IDEA-64312 Maven: frequent .iml changes after exclude/source folder updates
11d2cc4: restore EditorHyperlinkSupport.getHyperlinks for EA
5f8a926: let EditorHyperlinkSupport.clearHyperlinks clear the hyperlinks
9219a05: Attach Debugger in Python console.
e85f16d: IDEA-64312 Maven: frequent .iml changes after exclude/source folder updates
c5c6471: EA-45164 - more TypeConversionUtil.getSuperClassSubstitutor diagnostics
28d0e2e: javadoc for clearHyperlinks deprecation
1fe25c5: EA-54479 - CCE: CustomFileTypeCompletionContributor$.addCompletions
afba911: Emmet: use couple instead pair
3d63853: Custom templates: unify retrieving offset approach
0badce5: png optimization
df16471: move generated icon classes to new generated roots
420255e: patched AbstractClassGenerator from CGLIB moved to util module because it's used from InstanceofCheckerGenerator class
92d4c4c: "Node.js Express App" doesn't relate to "Static Web" group
f78f659: IDEA-124877 Run Configurations: Allow sort configurations under type or folder node (Exception fix)
7272ef6: remove copyright from intellij splash and about dialogs
be38201: manually render copyright
9c0ccb4: +copyrightForeground
f4cb6a8: Merge remote-tracking branch 'origin/master'
35f0811: fixed PY-12915 No project interpreter found after project creation
aeb677d: Start plugins wizard #28 (be Ubuntu-friendly)
ad086fe: Merge remote-tracking branch 'origin/master'
c78ae5f: cleanup tool -> inspectionEP
b52cf11: Fixed wrong usage.
421efd0: we must avoid use UUID.randomUUID — can cause long network calls
0ea9f4b: https://groups.google.com/forum/#!searchin/netty/netty$204.1/netty/8jf6SPFiN6g/nLg7opwUsxwJ
503f8cd: IDEA-124871 Can't build project if its name contains colon: convert project only when it is used as directory name
5bf5dd0: static methods in interfaces can't be hidden in subclasses (IDEA-124921)
ec8f194: Extract execution timeout registry key
f599645: fixed PY-12920 **kwargs in constructor parameters disappear after "Add super class call" quick fix
6a9db3b: Extract execution timeout registry key
26baf8f: extract superclass: pass target class in after data
838771a: code cleanup: allow to choose another profile; filter view to show cleanup tools only
2a25c8c: code cleanup (initial)
246bf70: Merge remote-tracking branch 'origin/master'
6e62c61: reverted back dc0ad20 due to PY-12904 IAE: Equal objects must have equal hashcodes
1eb6f78: - do not set databuffering if it was already set - make notifyAll in leave of StorageGuard if somebody is waiting
25ccd86: less lockStorage for put / get operations + store value file offsets in btree enumerator directly
1c8aaa4: lazier reparse range calculation
b488d43: incremental reparse should happen only on nodes fully covering the changed fragment (EA-54262)
66da2af: more diagnostics and possible fixes for EA-46770: don't let code fragment documents be gc'ed
79ff607: EA-51141 - IAE: CreateFromTemplateAction$.getActionName
277ffe0: EA-54226 - simplify JavaFileManager, remove "access only after startup activity" assertion
4b22fa6: EA-54691 insert completion char in write action
f4f67d2: EA-56504 - read action
b515187: less lockStorage for put / get operations + store value file offsets in btree enumerator directly
f0bdde8: move serialization of value container inside valuecontainer + added code for saving to use existing bitset used for large index values
f0dcc74: magic number moved to registry
e211432: check validity of RangeHighlighter
21afe13: [vcs] IDEA-120737 Change Merge button caption to Merge...
c5bcb11: [git] IDEA-118125 change url for Detached Head information
60036d0: PY-10016 Namespace support in PyCharm 2.7.3
c02eade: DevKit: highlight <extensions> "xmlns" as deprecated
2b0d162: Devkit: remove non-stub test data
34d6a8e: include PIEAE invalidation trace as an attachment
571bdea: EA-56524 - check for virtual file validity when searching in its document
d0a9cac: EA-56551 - don't expand structure view for invalid dom elements
7a6616d: EA-56593 - FindInProjectTask read action
73d32e6: move TraceableDisposable's own trace to the bottom as the least important
3fb8ff0: continue JS Debugger test framework
e426c3e: Platform: redefined shortcuts for bound actions work in inherited keymaps + such shortcuts is shown in the UI tree, in spite of being 'bound' (OC-9826)
7859a0d: IDEA-124960 DevKit: "register extension" fix for EP defined in custom plugin
bce419f: Devkit: rename/move RegisterExtensionFixProviderTest missed file
9359908: relaxed diagnostics
37894fc: Devkit: rename/move RegisterExtensionFixProviderTest
544ab30: CR-IU-696 - use activationComponent
0194c34: Devkit: add RegistrationProblemsInspectionCodeTest
92e0c60: extract PluginModuleTestCase
d5a13c3: rename
28ea4b5: Don't evaluate debugger vars in EDT.
5529340: Fixed args.
5d4bea7: Merge remote-tracking branch 'origin/master'
7846df8: Backported more Pydev stuff including Stackless debugging support and AppEngine debugging fix.
5214a06: DevKit: add XmlRegistrationProblemsInspectionTest
3aa92a1: Merge remote-tracking branch 'origin/master'
3ea1686: Backport some minor Pydev fixes.
f80095e: Rename modules.
8e6e512: [log] Don't send performAction request to the graph if below the log
fad27ec: Commit document before creating callback
fd61469: Allow emmet preview for xmlGenerator only
abe4c7e: Fix Emmet preview for big abbreviations
c8bb209: Fix emmet preview in injected fragments
ddcf07c: Live templates: add diagnostic
f5da90c: fix tests on linux (filename case)
5743b43: Merge remote-tracking branch 'origin/master'
48d9080e: Ability to perform force update when there are uncommitted merge added
001903a: clearer test data file name
11fdf63: Merge remote-tracking branch 'origin/master'
e72fd1a: Fixed conversion.
af0ae88: for CR-IC-5142
bff450e: make test light
e3d5657: IDEA-124876 ("Mismatched query and update of StringBuilder" false positive with lambda method reference.)
3f9ce9c: IDEA-106749 DevKit/ComponentNotRegisteredInspection inspection does not handle optional dependencies properly
39f062f: store complete map results for snapshot supporting indices
743ae27: Remove 'Support' from settings name.
d6c5e1f: Create thread on attempt to suspend on non existent thread.
ad86a82: Merge remote-tracking branch 'origin/master'
d42e997: We treat not started threads as alive, to support debugging of Python 3.4 threads created by start_new_thread.
cfd0595: IDEA-122909 Mercurial update: clean option
6aae6da: Save all documents added before update and merge actions from branchPopUp
206d45f: SOE
51aa5b4: NPE fix
bee86ca: new Pair<TypeA, TypeB>(a, b) -> Pair.create(a, b)
d659703: DevKit: add ComponentNotRegisteredInspectionTest
28dce08: IDEA-124032 IDEA plugin: Can't find com.sun.xml.internal.messaging.saaj.soap.LocalStrings bundle command line property set to true by default
262d5a9: IDEA-124871 Can't build project if its name contains colon
b3557e9: refactoring postfix templates
20fbada: IDEA-124032 IDEA plugin: Can't find com.sun.xml.internal.messaging.saaj.soap.LocalStrings bundle added command line property (false by default)
2d0c2f9: don't count 'runtime' dependencies when showing a warning about circular dependencies
e5e5915: IDEA-124859 Runtime module dependencies are not compiled when run configuration with dependent module is launched
c1980f4: check Pair types with actual parameters types
16906b2: IDEA-118714 Problem in automatically changing branch on tasks switching
c177312: IDEA-123898 IntelliJ Configuration server : create/manage JetBrains account error
7370dd9: new Pair<TypeA, TypeB>(a, b) -> Pair.create(a, b) inspection for lang level < 1.7
136ade1: fixed PY-4479 Add field to class: select created field for editing, not passed to constructor parameter
e7f0c33: add both add field and remove param QuickFixes for the param in __init__ method
00b00b6: Merge remote-tracking branch 'origin/master'
2d20acb: Merge remote-tracking branch 'origin/master'
3b0a609: track PsiFile invalidation trace
68ffeb6: use ensureValid (EA-55092)
a51e36f: add PsiSubstitutor.ensureValid (EA-55730, EA-55738)
9f4593b: use Couple in util
a249d4e: fixed PY-12825 Remove unused parameter: do not allow to remove last argument after star when refactoring function with keyword-only arguments
4ffed7a: IDEA-121171: fix failed tests
c053844: convert testng -> junit
e5b9d92: use Couple
5543b2f: use Couple
d156a40: convert testng -> junit
43ba882: inspection and quick fix for Couple class
7411352: testdata fixed
4d5b798: restore test
74ff7fd: testAssistance -> DevKit
03640d1: EA-56450 - CCE: JavaSuppressionUtil.getInspectionIdsSuppressedInAnnotation
5455834: IPython should be enabled by default.
25a4149: Fix option label text and size.
514ddf8: Merge remote-tracking branch 'origin/master'
b69e716: Return None in case of unknown source.
328fb7d: don't report ignored ioexception
f0a2a53: No need to inform about failure.
541a3d3: fixed literal conversion.
11e19f2: IDEA-124877 Run Configurations: Allow sort configurations under type or folder node
6e84490: Do not delete inserted semicolon for statement-based postfix templates
7b768e0: Simplify return postfix template
9e18381: Reformat postfix templates tests
f7abe52: Delete dummy semicolon after postfix template expansion
75264a6: Cleanup postfix templates
337ddff: TemplateManager -> service
f6cc0f9: use ensureValid (EA-56464, EA-56467)
2b3b0f3: EA-55808 - ISE: ComponentManagerImpl.getPicoContainer
c685b88: fix "already disposed" from EditorNotifications
de62ac0: Devkit tests: add @TestDataPath
ed97d68: PluginXmlDomStubsTest: move test data
7f98202: PostfixTemplateDescriptionNotFoundInspectionTest: rename/move test data
6d49cc4: extract ExtensionPointDocumentationProviderTest
aebd2ef: PluginXmlFunctionalTest: cleanup inline test data
2bdefc7: No sets language without project (fix for ui-designer creating over default constructor)
4d0fde11: svn: Refactored SvnVcs - moved ourBusyExceptionProcessor to RepeatSvnActionThroughBusy
81f13d8: PluginXmlFunctionalTest: fix enabled inspections
8656e4d: rename/cleanup InspectionMappingConsistencyInspectionTest
2e6a042: extract DevKitImplicitUsageProviderTest
29ac0ab: PostfixTemplateInspectionTest -> LightCodeInsightFixtureTestCase
494bc38: OS X: fixed NSString and NSOpenPanelDelegate_ leaking
add7462: Fix indentation to conform PEP8.
382ab4b: DOM stubs: exclude some dom extenders from stub building
72d5787: EA-56189 - ISE: LocalCanBeFinal.checkCodeBlock
788f777: EA-56308 - NPE: UnusedParametersInspection.checkElement
6dfed9d: EA-56333 - assert: PsiMethodReferenceCompatibilityConstraint.specialCase
606d8ea: EA-56286 - AIOOBE: BringVariableIntoScopeFix.invoke
659e657: svn: Refactored SvnVcs - removed old code that deletes previously used notification groups
fec2014: svn: Refactored SvnVcs - removed unnecessary logging
33da31b: make dumb aware
2c7c9e1: IDEA-124663 (Refused Bequest inspection should honor @OverridingMethodsMustInvokeSuper annotation)
c37b1a4: Merge remote-tracking branch 'origin/master'
08cf27c: select exact match in install package dialog
64f7299: read access
d83af54: accept raw substitutors during diamonds inference (IDEA-124836)
16b6498: [git] fix some editable comboboxes
27a2e0f: [log] Fix transitivity in compareTo
7e86caa: DOM stubs: exclude some dom extenders from stub building
bf339b6: Repository refresh added after pull action
8e41d7d: use Couple
a285781: +getEmpty
b4b6cc6: fixed PY-12660 PyCharm adds quotes in runconfiguration and fails
5f75f69: svn: Refactored SvnVcs, SvnUtil - removed unused code, warnings fixed, code simplified
85c621d: svn: Refactored SvnVcs - updated deprecated VcsListener registration
94af0a5: Merge remote-tracking branch 'origin/master'
a26b016: easier logic for indentation in console and fix PY-12542
209eb14: fix for PY-12542 and improve indentation in console logic
9b97079: fill PyConsoleIndentTest with new tests and improve old
2aa7076: fix console crashing on IPython completion (PY-11645)
0dbd022: editor now save file on transition to Python Console (PY-12487)
265a763: move console startup commands to Starting script field in Python Console settings (PY-11728)
74aa6cc: add checkbox for disable IPython in Console settings (PY-7425)
5f5a2bb: restore EditorHyperlinkSupper.addHyperlink for EA binary compatibility
af43068: OC-9742
e685f38: less disk operations on integrate
9db3107: fix expected wildcard types
f29a167: fixed PY-12679 Remove redundant parenthesis: false negative for duplicated parenthesis in complicated and or statements
cc743c3: testdata for JDK-8042508
1ab69ce: stream migration: missed qualifiers restored (IDEA-124820)
8862613: variable type by expression type suggestion (IDEA-124816)
909915f: Merge remote-tracking branch 'origin/master'
dc0ad20: fixed PY-12698 Noticeable hang when selecting not yet added virtualenv as project interpreter
f91fcdb: svn: Refactored SvnVcs - SVNKit related initialization logic moved to SvnKitManager
b67eac0: svn: Refactored SvnVcs - encapsulate access to configured ssl protocols
fa6941f: svn: Refactored SvnVcs - inner classes moved to separate files (and renamed)
0da2c6c: svn: Refactored logging logic in SvnVcs - code simplified, duplication removed
250b00c: IDEA-124250 Mercurial branches sorted in push dialog
17bf1db: ignore @org.mockito.Captor (IDEA-124802)
17e28ba: cleanup invalid fold regions (fix console leaks, IDEA-124626)
d2ead52: move postfix templates to lang-impl
d13a4e1: Emmet: add tests + fix existing ones
f9e13cb: Emmet: fix preview for abbreviations with 's' filter
6799aa2: Emmet: move calculating template text into hint alarm
b6f41b4: Emmet: parse incomplete more/climb abbreviations
3bc5114: Dispose hint on inner editor released
d28aae1: Emmet: performance improvement
0ef5647: Cleanup emmet generator
88dcca7: Emmet: fix logging message
83deceb: Emmet: add new action for preview
14b4449: Fix typo
b333197: Emmet: performance improvements, add segments limit.
3d5fcbc: Emmet: show new preview only if it's enabled in settings
b53ff16: HTML: enable autopopup after < and & only
be5c39d: Brand new emmet preview
c8ec330: Extract logger
6ca1101: fixed PY-12786 Python Interpreters: extra space in configuration popup from project creation dialog
1a15783: fixed PY-12824 Remove unused parameter: usages in function calls are not updated
03a2ae2: IDEA-116630 Run/Debug button disabled sometimes after breaking make process (and hanging thread)
07613ed: svn: Refactored SvnKitManager - @NotNull, removed duplication
2c91e9a: restore deprecated EditorHyperlinkSupport.clearHyperlinks for broken plugins
7c1c4b1: fixed PY-12825 Remove unused parameter: do not allow to remove last argument after star when refactoring function with keyword-only arguments
520b4be: fixed PY-12826 Remove unused parameter: remove references in the docstrings
2311a48: some additional checks for consistency
b79e433: svn: Refactored SvnVcs - methods related to SVNKit clients/repositories creation moved to separate SvnKitManager class
73c5925: better "cannot create branch from repository with no commits" problem handling it was unclear why "Create branch" option is not available for just created repo.
b28779b: IDEA-124570 (diagnostic)
65e5214: merge EditorNotifications requests, update automatically on dumb mode changes
4a210b0: IdeaPluginConverter#getAllPlugins: get rid of isIdeaProject() call (DOM stubs interfering)
87b041d: move postfix templates descr to resource-en
7ef1c6d: svn: Removed unnecessary SvnUtil.formatRepresentation
4c7c29f: Run/Debug buttons stay disabled, thread hanging if external tool configured as 'before launch task' fails to start.
8b762a5: Merge branch 'svn_18_3'
7ee2226: Merge remote-tracking branch 'origin/master'
b44a1bc: Removed unnecessary EP, introduced Rename Handler for Python Magic Literals: fixing some tests
6c5b423: revert IDEA-123956 Anonymous class formatting moves all args to the next line (32ae79905cb025f6bc213a925f1f4e58e9d6b8f7)
404cf1a: Merge remote-tracking branch 'origin/master'
f0ac2bd: unnecessarily qualified statically imported elements: do not ignore chained calls (IDEA-124806)
6f548f2: skip tests under resources roots
51e7e1c: compiler configuration: distinguish compilable resources (IDEA-124599)
6a76a7e: ignore deleted custom repositories (IDEA-124796)
a32723b: postfix template description inspection + tests
85f2823: remove alarm. Make SE faster
32ae799: IDEA-123956 Anonymous class formatting moves all args to the next line [CR-IC-5212]
6094264: Cleanup (unneeded interface)
899e7e3: [git] IDEA-124052 Support remote refs w/o a correspondent remote + test
172a3f9: [git] Don't log error in case of empty config file - it is allowed
95468ea: [log] Don't report the error too often
09f5029: [log] Fail safer if no refs were found at some head commit
ea5506c: WEB-10560 Debugger: second Mute Breakpoints invocation doesn't work
b967e4a: removed extra unneeded delegate
815a632: for given fragment: calc hash code once and do not use wrapper CharSequenceSubSequence for interning store CharSequence into Map of int -> CharSequence+
06778a8: cleanup
e01a5af3: initial
d197ca8: reformat only changed text checkboxes are enabled (IDEA-121171)
1c6ba24: Start plugins wizard #27 cleanup
78e7840: WEB-12106 node-webkit crashes when opening developer tools
61b298f: ^461 don't create invalid range marker
5790679: getter invocation in case of object-backed scope
2a38f58: IDEA-115737 Mercurial problem on Windows when repository replaced right inside disk directory.
7d53080: Merge remote-tracking branch 'origin/master'
a7bb442: Merge remote-tracking branch 'origin/master'
459d870: ignore @org.mockito.Spy (IDEA-124802)
efa4d64: junit 4 library setup (IDEA-124780)
85f6ea1: IDEA-121171 Reformat only VCS changed text - does not work when editor is not opened
765dacb: cosmetics
753dd85: do not remove contents added by other subsystems, which were run independently from any compile session (e.g. ant build)
6e20824: capitalization
f09a084: ensure cancel definitions search on popup closing
95e2ed2: Merge remote-tracking branch 'origin/master'
420f392: WinPty libs for Win XP.
161dd99: Removed unnecessary EP, introduced Rename Handler for Python Magic Literals: fixing some tests
e593086: remove unnecessary file
f585fcd: make test light
0e4f67f: IDEA-95363 ("for loop replaceable" inspection error)
1e1bb69: fix compilation
3078cb5: restore com.intellij.openapi.roots.LanguageLevelProjectExtension.reloadProjectOnLanguageLevelChange as deprecated
ae82d08: IDEA-124688 Console scroll to the end - jumps on the end of the line sometimes
5348fc6: IDEA-124644 Comparison of negative zero with positive zero incorrectly marked as always false
0897305: AIOOBE (IDEA-120790)
86141d8: assertion
e461a9bc: IDEA-124755 "Unnecessary {@inheritDoc} Javadoc Comment": do not warn when additional tags are present
4e13faa: Fixed IDEA-118781 Eclipse code style import: White Space: some categories are not applied
b306260: Fixed IDEA-124647 Quick switch scheme > switch code style scheme does not work
db2e799: don't clear cache for disposed MessageBus, fix tests
4c5d8e6: a higher output threshold for "Too much output" in console to avoid this message on long command lines
5b27a1f: navigate to function source: respect our navigation policy
1a04727: Merge remote-tracking branch 'origin/master'
288ccfa: most specific check: accept lambdas when target type is type parameter (IDEA-124725)
78e9bb7: type parameters should not cause deprecated constructor warning to appear (IDEA-124689)
91f6308: redundant suppressions: take into account alternative ids (IDEA-124690)
a2a076c0: lambda -> method ref: reject anonymous class replacement (IDEA-124748)
dc73135: accept static methods with body in interfaces (IDEA-124745)
e4a77b6: check suspicious ConcurrentHashMap.contains (IDEA-124698)
35206e4: uncomment testdata
2ae7053: check inferred type test
4cefdc5: testdata for IDEA-124547
51f1e0e: add option to ignore specified AutoCloseable subtypes
f8d2553: find field in superclasses also
747928f: fix broken readSettings() logic
83f842a: make magic checkbox work on private fields
dac9905: remove faulty annotation
24bdcbf: DOM: optimize JavaMethod annotation handling
dcdbc00: move MessageBusTest to platform-tests to avoid cyclic dependencies involving util
1da0bf9: save a bit memory on non-root message buses, relax test expectations a bit
0b888e5: IDEA-122914 (unclosed zip files)
cf649f8: [git tests] Dump to stdout if the test failed
9214eb2: [tests] Move enable debug logging for certain categories to a common place
32597b1: better java doc
2ec1705: fixed PY-12698 Noticeable hang when selecting not yet added virtualenv as project interpreter
d87049a: Merge remote-tracking branch 'origin/master'
bc78e99: RunConfigurationExtensionsManager API improved
8d20600: Cleanup (obsolete logging)
4a61e1a: Show debug command line action renamed (PY-12835).
4725ae6: Merge remote-tracking branch 'origin/master'
21de2af: fixed PY-12848 Adding local Python SDK does not work
c1c98cb: Merge remote-tracking branch 'origin/master'
7bd7aae: don't show hoisted variables
6de7d54: move BasicDebuggerViewSupport up
11656b8: init: don't show hoisted variables
f1b95b5: ColorChooserIntentionAction.isInsideDecodeOrGetColorMethod: use constants
00ee9ed: Merge remote-tracking branch 'origin/master'
ce4bafa: Until build moved forward.
2806dd8: OC-9570 OC-9663
0f15b6d: restore the ability to clear console from any thread
bb44094: IDEA-124646 Option to turn off "compilation successful" popup
adac2da: style: optimize if-statement and assignment
c32cafb: style
fcc2f18: Remove code duplication.
58be81d: Removed unnecessary EP, introduced Rename Handler for Python Magic Literals.
b2c7dea: fix console highlighting of lines added one by one, dispose range marker
56beb0e: take into account buffer overflows when calculating console fragment to highlight & fold
9065b24: be less public in new EditorHyperlinkSupport API
5f39988: clarify a bit when hyperlinks & foldings are disabled in console
18fdcfd: disabling of foldings removed
84162a7: continue processing when the output stops
97474ce: revert of condition
358ff50: continue processing when the output slowed down under the threshold
b19c269: more eager disabling
b788f32: removed todos, no issue here.
88ead50: removed manual action
e26bb7f: continue processing from last location - commit fix
15d968a: continue processing from last location
03969ac: removed myHighlighterToMessageInfoMap
f60543f: disable console when there is too much added text removed EditorHyperlinkSupport#myHighlighters list which seems redundant and would need some smart clearing.
9d3bfdf: little experiment
c572e4a: ^461 revert: we must create lazy range marker delegate on document changed, otherwise invalid delegate will be created
b828042: background notifications: cancel on file invalidation; don't leak
e429328: cache editor char width
a8e14ae: relax testStaticImportInTheSameClass expected timing
676f08a: IDEA-124317 super class method is not shown in basic and smart completion, while accessible
a655925: ability to serialize internal classes
51fabf9: cleanup
fe4a711: rename to BasicDebuggerViewSupport
0fa7a11: rename to RemoteVmConnection
a82fe46: init WEB-11968 Support debugging with embedded Chromium using projects such as CEF3
d3d775b: update editor notifications in background
2a77c0f: maven tests: tearDown more
f0a6a44: IDEA-124476 (slf4j parameterized logging inspection: add option to only apply to certain log levels.)
54ae382: we must create lazy range marker delegate on document changed, otherwise invalid delegate will be created
0c7dbd9: cleanup
991f719: we must check getLine() at first, because getOffset() can cause dramatic side effects
5cdeb11: ^192 fix selection
d652d59: done WEB-9842 Node debugging: Hitting a breakpoint with a local variable in scope that is a large buffer will cause the debugger to hang for a _long_ time WEB-9834 Node.js debugging: variable value calculation takes a while WEB-1892 NodeJS, Debugger: deadlock after stack frames has emptied WEB-7945 clustered view of Buffer class
73a6343: init: move getIndexedProperties to ObjectValue
3b4ad30: IDEA-124593 (Inspection "AutoCloseable used without try-with-resources" flags System.out.printf)
4290300: IDEA-124654 (replace with chained append() calls gives wrong result when the object is unknown)
4b68768: drop unused property
28c75d2: remove check covered by "Variable is assigned to itself" inspection (IDEA-124559)
5201ba0: prevent maven test initialization failing to spoil the fixture for other tests
1b55349: faster invalid line number detection in console exception filter (IDEA-124626)
db58c9b: IDEA-124556 MessageBus event processing should be faster with many child buses and (almost) no listeners
77c7af8: optimization for getChars handling
48a56e0: PY-12846 ("show hidden files" configurable through FileChooserDescriptor)
b0679d6: Cleanup (builder-style API; arrangement; duplicate deprecation)
4f5db0a: [git tests] fix paths comparison
fa24163: [git tests] simpler method names
6d469a1: [git tests] Fix testdata for cherry-pick
600259a: [git tests] Remove the test root directory after test execution, reorder teardowns
e422a99: [vcs] Fix already disposed exception
7cb3043: [git tests] turn cuke tests back on.
f9d83e6: [git tests] fix GitAddSteps
49fecac: [git tests] Make sure other tear downs finish even if one fails
e0847db: [tests] extract dumping log to stdout to the common test framework
07923cc: [git] Don't spam to stdout in tests unless test failed
84a8fbe: [git] Fix already disposed
90d2f45: new inference: early abort calculation by lambda return (IDEA-124276)
f2256ca: calculate target type in the same way for statement/expressions lambdas
896c488: testdata to expression lambda
8096d63: inference when incomplete
6f6367d: fix layout under darcula
3d3550e: escape ampersands
3768908: IDEA-123831 (Missing Method Count Limit in Inspections under Class Metrics)
04d3283: cleaning javac nametable: synchronization added
368bb54: [git tests] Better test name not to make some buildserver Gits "insane"
ea6683d: [git tests] Better test dir name not to make some buildserver Gits "insane"
41d0942: remove unused properties [CR-IC-5247]
cbf1c1e: ensure pushers are run before initial indexing, don't rely on chance
4c1dc50: let PushedFilePropertiesUpdater schedule reindex itself if necessary
79a75f2: update language level without project reloading
64c452e: Merge remote-tracking branch 'origin/master'
b86dec4: simplify
0c0fb61: change method name
ad18e4b: make method name discoverable
5529054: IDEA-124653 Keyboard shortcut for New does not work
3b7d2e9: svn: Refactored IdeaSvnkitBasedAuthenticationCallback - inner classes moved to separate files
dafd2d3: revert memory page size back
d5a0f9e: svn: Moved several authentication-related classes to "org.jetbrains.idea.svn.auth" package
ac99e82: svn: Refactored IdeaSvnkitBasedAuthenticationCallback - made inner classes to be static
e663caa: svn: Refactored IdeaSvnkitBasedAuthenticationCallback - code simplified, duplication removed
3ac7f7d: some dumb mode logging (IDEA-124604)
10bf86c: dom vfs listeners: don't load new vfs, support cyclic symlinks, simplify
2f76fdc: svn: Refactored IdeaSvnkitBasedAuthenticationCallback.AbstractAuthenticator and inheritors - code simplified
ec05ae8: svn: Refactored IdeaSvnkitBasedAuthenticationCallback.AbstractAuthenticator and inheritors - common parameters moved to base class, @NotNull
faf7595: ^451 add test
326278b: extract JsDebuggerViewSupport
bc771e7: show data-source name for table editors
0faa413: making sure all posted tasks are processed even after process terminated and dispose called by using sequential task executor instead of MergingUpdateQueue (IDEA-120167 Phantom eternal Ant task on make)
aff053d: Increase connection timeout for JIRA integration tests
ab7c4be: WEB-6584 Files opened by URL should also be able to open scripts used in the file
1461ed0: cache indexOf result
8b73b26: cleanup
62b7621: svn: Refactored IdeaSvnkitBasedAuthenticationCallback.AbstractAuthenticator - made it not generic
6d7d17b: IDEA-124580 Links in mac sheets dialogs do not work
534c2aa: Temporary solution for IDEA-124615
cebe7d9: IDEA-124535 com.sun.*.internal packages not found
21110f2: moved to core
d8cf8f5: @NotNull
979de11: moved to core-impl
9dd6e0d: moved to java-psi
bf77b14: OriginInfoAwareElement moved to psi to core
26ef0e3: completion-related classes moved out of psi to separate package
4fe1761: removed dependency on java-impl
46408be: method references: super methods treatment in exact check
a3f190a: svn: Refactored IdeaSvnkitBasedAuthenticationCallback.AbstractAuthenticator - removed unnecessary parameters from acknowledge() method
985bef3: Start plugins wizard #26 better color for selection (Darcula), hide dialog buttons when in "Customize plugin group" mode
4f97e92: Merge remote-tracking branch 'origin/master'
0081a7f: fixed PY-12819 Unable to run GAE nosetests
782ae0b: check directories consistency only for local files
95bce93: Cleanup (common URL check code extracted)
901e60d: Cleanup (arrangement; aggressive logging)
d3d0f73: CaseInsensitiveUrlHashingStrategy nullability
f144943: applicability constraints by method reference (IDEA-122018)
9e9c7e3: Cancel future if alarm was disposed in TaskItemProvider to prevent deadlocks
1e352af: Update CA certificate for SSL tests. Add link to test server used for client authentication
9ebf7ca: reparse files on language level change
29a7c80: will explicit System.gc lead to less ResolveClassTest blinking?
ea54399: less garbage in tests (VirtualDirectoryImpl.assertConsistency)
fba7ee7: postfix templates memory leak
eb639ae: Merge remote-tracking branch 'origin/master'
c5d0d5e: handle hidden groups correctly
0d2fc45: Merge remote-tracking branch 'origin/master'
577cef9: cleanup
d14900f: svn: Refactored IdeaSvnkitBasedAuthenticationCallback - removed unused methods related to saving proxy settings to svn config files (currently proxy settings are passed through command line arguments)
dedc16a: svn: Refactored IdeaSvnkitBasedAuthenticationCallback - code simplifications, @NotNull
ef226f6: restore extra space trimmed by fitInsideEditor()
779ebf1: save some invokeLater's in progress text2 updating
9c46a51: Cleanup (arrangement; warns in default logger)
b02b4c1: more accurate work with IOUtil.allocReadWriteBuffer: use IOUtil.write/readUTF that have thread local buffer upon softreference: - to avoid extra allocations (1G of garbage produced for our codebase indexing) - possible several threads accessing same buffer problem
bb2af75: Cleanup (locale use)
949b567: IDEA-120035 (diagnostic; log level lowered)
4f1e8e6: svn: Refactored SvnAuthenticationManager - removed unused/commented code, code simplifications, warnings fixes
2fde908: WEB-11690 TypeScript breakpoint is not hit We must respect fs case sensitivity sourcemap visualizer: ugly compiler can produce mappings for empty lines, and IDEA can then strip whitespace from line ends, but sourcemap still references to empty ranges
81f903f: remove LineSeparatorPainter
902e51a: substitute bounds (IDEA-123509)
547c62c: qualify conflicting fields name (IDEA-123969)
f58394c: IDEA-124019
291c431: notification group registration fixed
f9f9b39: Merge remote-tracking branch 'origin/master'
527f8db: fireModelUpdate for lazy structure building (Scala, etc)
f1c3652: IDEA-93452 Implement "section" support in rearrange menu: update parent offset on new section rule insert
47d8b3d: svn: Refactored MergeFromTheirsResolver - use common client factory model (instead of direct SVNWCClient usage)
fe22b91: Fixed: IDEA-87312 Custom code folding: editor-fold; Folding/unfolding behavior not available when an enum inside the block IDEA-122715 Region folding does work if contains interface definition
4f9003f: Update jediterm.jar with antialiasing restored.
118d6d9: do not process xs:include for stub building
ff22ffc: Fix tests
80dfa90: IDEA-93452 Implement "section" support in rearrange menu: section rules validation
fe96f39: IDEA-124461 New Module (Maven, Gradle) created in Empty project don't get the specified jdk
1b8f51f: IDEA-124400 New Module wizard: Gradle module misses options
952a2c5: IDEA-71508 Scroll with inertia (Mac os) should only work in the initial component
632d4c8: don't draw separator line if we cannot compute relationship properly for all lines (important in case of process input — nodejs for example)
2ebc871: WEB-11957 Valid JavaScript regexp marked as red
ca2c75e: svn: Refactored RepositoryBrowserDialog - removed unused and commented code
a15d397: cleanup after adding postfix templates preview
7a06f6d: EA-56182
59454cd: improve "Show Members" opption suppression
c115690: IDEA-124435 (Fix "Collapse catch blocks" produces non-compiling code)
c048414: 136 -> 138
8c562a2: bug fix postfix templates preview
2b4b915: avoid wildcard with null bound (IDEA-124377)
a6db438: testdata fixed
a4ac655: Gradle: IDEA-124477 sourceFolder order in iml file generated by gradle is unstable
c0f3611: IDEA-115374 "Print file" truncates the code on Mac
12a8f3b: Start plugins wizard #25 get rid of scrollpanes' borders
ffe6776: A typo.
9fa19ab: svn: Refactored RepositoryBrowserDialog - make "move file/folder" logic use common client factory model (instead of direct SVNCopyClient usage)
7a95352: svn: Implemented ability to move files/folders directly in repository (for CopyMoveClient)
240c396: more diagnostics on EA-56168 - assert: JavaFoldingBuilderBase.addToFold
ae954a6: remove unused ExtensionsArea.unregister*
3ad5dd7: Merge remote-tracking branch 'origin/master'
10fe5a5: skip symlinks
ff8a795: removing -ea from defaults because this may dramatically slow down compilation (e.g. eclipse compiler with annotations): https://jetbrains.zendesk.com/agent/#/tickets/27833
a465476: HardReference for PsiAnchors used for duplicates indexing, 2
885405f: features.xml fix
016de10: Cleanup (inner class abuse)
58734a2: svn: Refactored RepositoryBrowserDialog - make file/folder deletion logic use common client factory model (instead of direct SVNCommitClient usage)
65fd69c: svn: Implemented ability to delete files/folders directly from repository (for DeleteClient)
d9fe570: HardReference for PsiAnchors used for duplicates indexing
73a17e0: follow up for commit 288dd00: make util-rt available for all agents
55614bb: svn: Refactored RepositoryBrowserDialog - make folder creation logic use common client factory model (instead of direct SVNCommitClient usage)
7265d01: cleanup
10eb4a3: StartupManager: linear-time startup activities running
4708541: IDEA-124442 Update options for newer version of YourKit (get rid of deprecated warnings)
e225a23: IDEA-124503 Filter out IntellijIdeaRulezzz completion items
f41cb93: svn: Refactored CreateBranchOrTagAction - removed unnecessary parameters, removed todo items
54da0af: IDEA-122732 Javadoc invalid html closing tags
b8197a7: OC-9621
d19a902: svn: Removed unused classes
924f108: svn: Refactored ShareProjectAction - use common client factory model (instead of direct SVNCommitClient usage)
673351f: svn: Implemented ability to create folders directly in repository (for BrowseClient)
8bd8a4d: initialize file watcher in background
9d3c23d: avoid entering dumb mode to push properties to one file
0ab0ec6: Maven: cleanup test
e98a99f: Merge remote-tracking branch 'origin/master'
bdfec0f: Mantis integration: Reset repository configuration on Axis errors
67cf17d: Maven: stable order of source folders IDEA-64312 Maven: frequent .iml changes after exclude/source folder updates
5d0ce7c: enumerate items on demand
c520519: Merge remote-tracking branch 'origin/master'
c2957e7: anonymous -> lambda: conflicting ref in var declaration (IDEA-124525)
5dbbebe: extract superclass: process same name type parameters inside class members
84deec9: method refs: is exact should check super methods for this/super qualifiers only (IDEA-124507)
528a91b: Merge remote-tracking branch 'origin/master'
7a8f56c: fixed PY-12717 Improper replacement of the `print x, y` statement for Py3+
33aeb0b: fixed PY-12804 PyStatementEffect inspection doesn't wrap exec argument in parenthesis
9929113: IDEA-124394 Exception while creating New File w/o extension
933194d: IDEA-124073 (avoid requesting focus manager until app is loaded)
4d43dab9: add before-after preview for postfix templates
c4084ff: Merge remote-tracking branch 'origin/master'
e2d7505: fixed PY-12726 PyCharm does not recognize compatibility issues when importing from __future__
533e26d: do not reparse re-detected files in background in tests since it may cause unpleasant interference with e.g. highlighting
414c4ad: merged test classes
ba23594: IDEA-124155 Performance problem on live search in long lines
6ce3483: Revert "Templates performance: run segments changes in bulk mode"
c9284e0: Start plugins wizard #24 phrases
75472a5: calc hash from content once we have bytes available, don't delay its processing until we have only chars
5aac831: notnull
02a9c6e: notnull
81d2d98: cleanup
403d4f1: fix test
a98abce: Templates performance: run segments changes in bulk mode
587e216: Merge remote-tracking branch 'origin/master'
66821eb: init WEB-11393 Live edit doesn't work for linked css
1429f44: IDEA-124527 Shift-drag after Shift-click starts new selection in editor
d774199: IDEA-51883 IDEA prints out huge number of pages
8a80c54: Merge remote-tracking branch 'origin/master'
336be36: IDEA-98912 An Enter license button should be added to the welcome screen
72e2b3a: ensure each post-startup activity runs in smart mode (IDEA-123943)
e64ae73: Merge remote-tracking branch 'origin/master'
beeee74: make inspector work with modal dialogs
324ddc4: move getTokenType() up so white-spaces are skipped before rawTokenIndex() call
c808c3e: integrate Grammar-Kit/pull/31 from ligasgr
984922a: tests fix
afe050c: move xml lexers to gen root
2640f20: move groovy lexers to gen root
eb2047f: move spi lexers to gen root
cef748e: move java lexers to gen root
600887a: move reg exp lexer to gen root
fc01dd0: move templates* lexers to gen root
cbd64fa: move rest lexer to gen root
fd2765c: move python-community lexers to gen root
90d1e84: more compact format when we serialize
8f3acde: use isjavaidentifierpart + enable trigramindex for tests because test appeared
2adc37a: IDEA-65879 "idea" protocol handler to open files directly from a URL (for Mac)
8615aa2: vfs: handling of invalid .jar files
efb2fca: test framework: ability to intercept error/warn logs
0fff999: Groovy PSI doesn't depends on LookupElements anymore
92da487: some checkCanceled
876426a: magic constant
b01adfe: know recursion manager deleted
e9eaa2d: IDEA-124368 (diagnostic)
d321dd2: vfs: JAR FS cleanup and minor optimization
4155907: revert IDEA-123049 Rearrange Code is breaking code (Code Style > Java > Arrangement) (1162eb1847279461e17d200416be100351d0c668)
f62183c: IDEA-93452 Implement "section" support in rearrange menu: cleanup
e833f18: Merge remote-tracking branch 'origin/master'
a4f2f0e: create method from method ref: fis for type element qualifier (IDEA-124485)
baccb31: diamonds: resolve conflicts based on type params from constructor and containing class (IDEA-123518)
f3e1d96: IDEA-124385
1d32716: [by cdr, jeka] eliminating dependencies on utility classes in the code loaded by javac classloader to avoid NCDFE
d08964e: IDEA-93452 Implement "section" support in rearrange menu.
e16690a: better doctype detection
0645af6: move properties lexer to gen root
e851587: svn: Refactored CreateBranchOrTagAction - do not create non-existent folders manually (as "svn copy" supports corresponding "--parents" option)
1162eb1: IDEA-123049 Rearrange Code is breaking code (Code Style > Java > Arrangement)
0c4028d: IDEA-123074 Code formatting. Closing brace.
7456ac5: svn: Use RA_ILLEGAL_URL error code for SVNException in command line info client when passed target does not exist (so CreateBranchOrTagAction.dirExists() works correctly for command line)
1dab5da: Maven: do not unexclude excludeFolders on removal of the respective physical directory related issue - IDEA-120944 Maven integration inconsistently, frequently, and spuriously changes .iml files
8be02cd: Allow to specify ranges which can't be indented by PostprocessReformattingAspect (fixes WI-22725 surround heredoc block with try catch causes parse error) [CR-IC-5135]
18bbc9d: IDEA-115374 "Print file" truncates the code on Mac
0d4ccd0: svn: Added ability to track warnings to SvnBindException
7ec9633: svn: Refactored SvnBindException - use MultiMap to track errors
820d7b2: Switcher: use TW stripe title instead of id
936b1f2: Merge remote-tracking branch 'origin/master'
0c1ec73: IDEA-124163
6fd9884: extract notification groups (IDEA-124454)
71f8a4a: IDEA-124352
b3926cf: EA-55442 - CCE: DomStubBuilder.buildStubTree - a better fix
370e06e: Merge remote-tracking branch 'origin/master'
affac73: fixed PY-12731 Creating test profile from context menu unexpectedly creates django tests
eed0df2: do not process xs:include for stub building
9bc7805: Revert "Do not wrap border with TitledBorder if there is not title"
c255139: IDEA-119926 Completing Statements doesn't go to next line for non conditional statements
6988973: Ensure that indexes of substring are correct in YouTrackCompletionContributor
ae4b79f: IDEA-112189 Change YouTrack integration default query
30c5366: MantisFilter violates comparable contact
2255513: svn: Moved execute() methods from CommandUtil to BaseSvnClient
46b8888: in django project show unittest runner and the django one in context menu
8bc1d46: Start plugins wizard #22 fix Windows-specific exceptions (non-initialized Alloy license & NPE during Darcula initialization)
42bc957: svn: Several classes moved to corresponding packages (commit related and exception classes)
be188d5: Merge remote-tracking branch 'origin/master'
5e4c305: svn: "svn info" related classes moved to "org.jetbrains.idea.svn.info" package
19f9684: Start plugins wizard #20 icons for categories
93f8c1d: svn: Renamed info clients for both SVNKit and command line (interface and implementations)
063575b: svn: Make info clients (both for SVNKit and command line) satisfy common client factory model
c0dd002: svn: Refactored SvnCommandLineInfoClient - do not inherit SvnkitSvnWcClient (just implement corresponding SvnWcClientI interface)
3dac920: svn: "svn status" related classes moved to "org.jetbrains.idea.svn.status" package
0d2131d: svn: Refactored CmdStatusClient - do not use CommandExecutor.myCommandLine directly (use corresponding CommandExecutor methods instead)
7b6f441: svn: Renamed status clients for both SVNKit and command line (interface and implementations)
7f65a87: fix for Kubuntu/Firefox/Gmail (IDEA-67767)
526a02f: fixed PY-12779 Configure Template directories quick-fix: open project structure settings page right away
9ee64e4: fix "go to source/type" — XSourcePosition doesn't provide column number, so, we must override createNavigatable
3f8277c: ability to invoke/reinvoke getter value
a9663fe: Merge remote-tracking branch 'origin/master'
4ab4259: IDEA-123691 Minor project wizard edits: icons 16x16
b03f63d: fixed PY-12786 Python Interpreters: extra space in configuration popup from project creation dialog
546f9a5: Revert: IDEA-123160 Find Usages doing nothing in 13.1 (7371df17bd49da0804c600571671fd3a1fe90ec2)
3fbbef8: Merge remote-tracking branch 'origin/master'
2b99821: suppress project loading cancellation if in NonCancellableSection
25e6148: move PushedFilePropertiesUpdater to lang-impl
2782c66: semicolon->space in language-agnostic completion advertisement
a9888bb: Merge remote-tracking branch 'origin/master'
779df06: fix messages
d8f478c: changed to correct @NotNull
4f2dad1: EA-54648 - assert: PsiWalkingState.elementStarted
066ab8a: EA-55374 - NPE: DataFlowInspectionBase$.applyFix
16b53a5: EA-55442 - CCE: DomStubBuilder.buildStubTree
69fd8e8: EA-55457 - IE: CacheUpdateRunner.waitForAll
6b7c6a0: EA-55708 - NPE: FileEditorManager.getInstance
d8e6415: [by cdr] optimizations
d38a277: enabled by default
c49c422: don't calc content hash id twice
f82d4be: Merge remote-tracking branch 'origin/master'
4657702: Merge remote-tracking branch 'origin/master'
b275b4e: Netty channel id init is not reliable yet
13aec5a: cleanup
5a75f21: continue WEB-9103 nodejs: show get/set functions in addition
72276e4: init WEB-9103 nodejs: Debugger not showing value of getter
029f89a: testdata fixed
03cba2c: add undo to JBTextField
a344d0e: do not dbl substitution (type annotations could be lost after that)
b898f91: static method reference completion (IDEA-124043)
78dafa0: dfa: just don't assume initialized final fields are notnull, it doesn't help, but hurts and slows things down (IDEA-124323)
b6be84f: simplify HTML markup - updated test data (IDEA-67767)
622b745: simplify HTML markup (IDEA-67767)
bcc4a00: junit category support (IDEA-88389)
99b16f3: check for project.isDisposed added to DumbService, unnecessary checks at call sites removed
3f304a3: Animator: test
36d9ddc: Animator: corereview CR-IC-5059
9709e9f: Animator: fix stuck at 0th frame during repeatable animaion
1957b85: SemVer.getParsedVersion added
8e48a46: SemVer.toString added
ae2667d: Merge remote-tracking branch 'origin/master'
1be6001: fixed PY-12451 Interpreter added from project creation dialog is not set as project interpreter
5bb9964: fixed possible NPE
9359151: Platform: scrollbars survive background editors initialization
3ca40fc: fixed tests
f2b136a: fixed possible NPE
8fde24a: drain file type queue
835ce62: cleanup
f2fb07f: removed lang-impl dependency
edb88b8: notnull
6dd81f8: NPE when application is already disposed
c2545c2: unmute on session finish - spelling
223dffa: IDEA-122962 - Clouds: perform connection test in background
7cc9133: Merge remote-tracking branch 'origin/master'
c5bffbd: remove duplicated code (IDEA-67767)
a5a9d6a: Merge remote-tracking branch 'origin/master'
1167e96: fixed PY-12543 Project Interpreters: too big or too small details popup
cbc40e9: lazy array data loading — API/UI/v8 new backend done WEB-11784 slow nodejs debug: huge array cause 100 cpu debug
a892f89: add missing test data
2215544: fix another RTF background colouring issue (IDEA-67767)
fd2b253: Merge remote-tracking branch 'origin/master'
bc7a7ad: Merge remote-tracking branch 'origin/master'
fda4b8a: Merge remote-tracking branch 'origin/master'
c8474eb: fix testdata
ee2a5d0: introduce: process chained method calls during same occurrences processing (IDEA-124349)
9397fb0: CR-IC-5167 (cleanup)
8dbf638: Cleanup (post-review #330)
91e3ed4: vfs: .jar refresh test extended
115aaf1: vfs: unified attributes loading between JarHandler / JarFS
10efd8b: Cleanup (duplication; readability)
8231d80: IDEA-124032 IDEA plugin: Can't find com.sun.xml.internal.messaging.saaj.soap.LocalStrings bundle: allow bootstrap resources for root IDEA classloader
cb808a6: fixed PY-12723 Unable to run tests with Django 1.7b1: "RuntimeError: App registry isn't ready yet."
8c5c0ec: added project.isDisposed check to DumbService
1f2a568: code style
46881e4: Merge remote-tracking branch 'origin/master'
8401fa0: fixed windows path separator
6ae658f: fixed 'project is disposed'
6431234: Github: release editor in tests
c726c53: Github: small test cleanup
6d6d583: Platform: ShowFilePathAction correctly handles non-normalized paths (e.g. with ../)
8e1f7a7: Merge remote-tracking branch 'origin/master'
916d90b: Platform: always allow writing module files (IDEA-123899) + typo in test fixed
6e063ee: IDEA-121318 ClosureParamsEnhancer now uses call.getCallVariants() instead of call.resolve(). Erasing instance parameter's types when comparing them. It helps to select the correct one when we are comparing T and Iterable<T>.
637da92: IDEA-123712 Groovy: @language annotation on method parameters with default values reflected methods now have light parameters with original modifier lists
8ffcef2: EA-52671 - assert: AbstractMappingStrategy.processFoldRegion
30d14a3: fix RTF background colouring and make it compatible with Mac (IDEA-67767)
39d7edd: svn: Make status clients (both for SVNKit and command line) satisfy common client factory model
85e3825: Merge remote-tracking branch 'origin/master'
1d31c68: svn: Refactored SvnRecursiveStatusWalker.MyItem - use common client factory model (instead of direct SVNStatusClient usage)
3a14f0a: Use tree set to store remote roots to avoid duplicates.
343ea1c: svn: Refactored SvnRecursiveStatusWalker - methods extracted, warnings fixes
3ca64b6: svn: Merged StatusWalkerPartner interface and StatusWalkerPartnerImpl implementation to single class
93ddbc2: svn: Refactored SvnChangeProvider - use common client factory model (instead of direct SVNStatusClient usage)
416463d: svn: Refactored SvnRecursiveStatusWalker.MyItem.getClient() - removed unnecessary parameters
82af9cb: svn: SvnCommitRunner moved to "checkin" package and renamed
b0b7121: svn: Refactored SvnCheckinEnvironment - logic extracted to separate SVNKit and command line clients (common client factory model is used)
af5ad7c: svn: Refactored SvnCommitRunner - inlined several parameters (that have same value in all code paths)
beb8081: svn: Refactored SvnCheckinEnvironment - moved SVNKit related logic to corresponding SVNKit-scoped method
626d944: svn: Refactored SvnCheckinEnvironment - code simplified, unified code flows for SVNKit and command line
c636313: IDEA-124057 Manually load key store set using VM options. Don't modify default context, if Certificate Manager was disabled
4729cf2: svn: Refactored OneShotMergeInfoHelper and OneRecursiveShotMergeInfoWorker - removed unnecessary fields and utilize MergeContext instead
9fe9ac9: svn: Refactored LoadRecentBranchRevisions - removed unnecessary fields and utilize MergeContext instead
562e89d: svn: Refactored MergeCalculatorTask - removed unnecessary fields and utilize data from MergeContext instead
c85e79d: svn: Refactored QuickMerge - inner classes moved to separate files (and renamed)
3f10457: IDEA-122845 Add test to check that wrong credentials are recognized when testConnection() is used
cf3252c: svn: Refactored QuickMerge - make task classes do not directly depend on QuickMerge instance * MergeContext and QuickMergeInteraction parameters added to all task constructors * several QuickMerge methods moved to corresponding task classes
e62ad72: svn: Refactored QuickMerge - merge parameters extracted to separate MergeContext class
482733a: svn: Refactored LatestExistentSearcher - use common client factory model (instead of direct SVNLogClient usage)
a260ac4: svn: Refactored DefaultConfigLoader - code simplifications, warnings fixes
d899191: svn: Make sure start() method of the task queue (myBranchesLoader) in SvnBranchConfigurationManager is invoked - otherwise runnables passed to queue are not executed
a95c8b4: svn: Refactored DefaultConfigLoader - use common client factory model (instead of direct SVNLogClient usage)
1d33a57: svn: Refactored FirstInBranch - use common client factory model (instead of direct SVNLogClient usage)
372a664: svn: Refactored SvnMergeInfoCache - removed inner MyState class (fields added directly to SvnMergeInfoCache)
281cc56: svn: Refactored FirstInBranch - not null, code simplifications (exception handling)
ef8bad7: svn: Refactored FirstInBranch - code simplifications, warnings fixes
2104741: svn: Removed unnecessary SvnBranchPointsCalculator.Invertor interface
0b9815c: svn: Refactored SvnBranchPointsCalculator.WrapperInvertor - explicitly use SvnBranchPointsCalculator.BranchCopyData type (instead of being generic)
aa5e3fd: svn: Refactored MergerFactory - removed unused methods
9d618f1: svn: Refactored SvnMergeInfoTest - methods extracted, duplication removed, warning fixes
4947243: svn: Refactored SvnMergeInfoTest - methods extracted, lots of duplication removed
Change-Id: Id231a4e5444690193a99f454d027ea17f7c2845c
Diffstat (limited to 'plugins/devkit/src')
56 files changed, 4186 insertions, 662 deletions
diff --git a/plugins/devkit/src/DevKitBundle.java b/plugins/devkit/src/DevKitBundle.java index 8dfec4be8ee9..013fd2f43e6b 100644 --- a/plugins/devkit/src/DevKitBundle.java +++ b/plugins/devkit/src/DevKitBundle.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2013 JetBrains s.r.o. + * Copyright 2000-2014 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,37 +15,21 @@ */ package org.jetbrains.idea.devkit; -import com.intellij.CommonBundle; +import com.intellij.AbstractBundle; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.PropertyKey; -import java.lang.ref.Reference; -import java.lang.ref.SoftReference; -import java.util.ResourceBundle; - -/** - * User: anna - * Date: Aug 11, 2005 - */ -public class DevKitBundle { +public class DevKitBundle extends AbstractBundle { public static String message(@NotNull @PropertyKey(resourceBundle = BUNDLE) String key, @NotNull Object... params) { - return CommonBundle.message(getBundle(), key, params); + return ourInstance.getMessage(key, params); } - private static Reference<ResourceBundle> ourBundle; - @NonNls private static final String BUNDLE = "org.jetbrains.idea.devkit.DevKitBundle"; + @NonNls private static final String BUNDLE = "DevKitBundle"; + private static final DevKitBundle ourInstance = new DevKitBundle(); private DevKitBundle() { - } - - private static ResourceBundle getBundle() { - ResourceBundle bundle = com.intellij.reference.SoftReference.dereference(ourBundle); - if (bundle == null) { - bundle = ResourceBundle.getBundle(BUNDLE); - ourBundle = new SoftReference<ResourceBundle>(bundle); - } - return bundle; + super(BUNDLE); } } diff --git a/plugins/devkit/src/DevKitBundle.properties b/plugins/devkit/src/DevKitBundle.properties deleted file mode 100644 index 07d3b2914baf..000000000000 --- a/plugins/devkit/src/DevKitBundle.properties +++ /dev/null @@ -1,160 +0,0 @@ -#module type -module.title=IntelliJ Platform Plugin -module.description=Plugin modules are used for developing plugins for <b>IntelliJ Platform</b>-based IDEs.<br>\ - They provide <b>IntelliJ Platform Plugin SDK</b> and <b>Run Configuration</b> for running and debugging plugins inside an IDE. -plugin.descriptor=IntelliJ Platform Plugin Descriptor - -#Module Editor - Deployment -deployment.title=Plugin Deployment -deployment.cleanup=Clean up {0} directory -deployment.directory.location=Select {0} Directory Location -deployment.view.select=Select {0} -manifest.settings=Manifest Settings -manifest.selection=Selected {0} will be included in resulting distribution -manifest.use.user.defined=Use user manifest: -deployment.view.delete=Delete {0}? -deployment.view.meta-inf.label=Path to {0} -suggest.to.delete=Do you want to delete ''{0}''? -vm.parameters=&VM Options -program.parameters=&Program Arguments - -#Prepare for deployment action -select.plugin.modules.title=Select modules -select.plugin.modules.description=Select modules to be prepared for deployment -prepare.for.deployment.common=Preparing For Deployment -prepare.for.deployment=Prepare Plugin Module ''{0}'' For Deployment -prepare.for.deployment.all=Prepare All Plugin Modules For Deployment -saved.message.common={0} will be saved in selected directory -saved.message= {0, choice, 1#Zip|2#Jar} for module ''{1}'' was saved to {2} -success.deployment.message=Plugin Module ''{0}'' Successfully Prepared For Deployment -success.deployment.message.all=All Plugins Successfully Prepared For Deployment - -##Idea Sdk -sdk.title=IntelliJ Platform Plugin SDK -sandbox.specification=Please configure the sandbox -sandbox.home=Sandbox Home -sandbox.home.label=Sandbox Home: -sandbox.no.configured=No sandbox specified for IntelliJ Platform Plugin SDK -sandbox.purpose=Browse folder to put config, system and plugins for target IDE - -#errors -error.occurred=Error Occurred -sdk.no.specified=No SDK specified for plugin module ''{0}'' -sdk.type.incorrect=Wrong SDK type for plugin module ''{0}''. -sdk.type.incorrect.common=Wrong SDK type for plugin module -incorrect.dependency.non-plugin-module=The non-plugin module ''{0}'' cannot depend on plugin module ''{1}''. -incorrect.dependency.not-declared=The plugin module ''{0}'' doesn''t declare the dependency on ''{1}'' in its plugin.xml. -error.file.not.found=File not Found -error.file.not.found.message=File ''{0}'' not found -error.no.plugin.xml=No plugin.xml file found -error.plugin.xml.readonly=The plugin.xml file is read-only - -#run configurations -run.configuration.classpath.from.module.choose=Use Classpath and JDK from Module: -run.configuration.no.module.specified=No plugin module specified for configuration -run.configuration.title=Plugin -run.configuration.type.description=Plugin Sandbox Environment -idea.log.tab.title=IDEA LOG - -#Misc -info.message=Info -create.smth=Create {0} -show.smth=&Show {0} -presentable.plugin.module.name=Plugin Module ''{0}'' - -action.MakeJarAction.text=Prepare To Deploy -action.MakeAllJarsAction.text=Prepare All Plugins To Deploy - -dont.add.idea.libs.to.classpath=IDE-related libraries ({0}) must not be added to the module classpath. Please add them to the IntelliJ Platform Plugin SDK instead. -new.action.id=&Action ID: -new.action.description=&Description: -new.action.class.name=&Class Name: -new.action.text=&Name: -new.action.add.to.group=Add to Group -new.action.group.actions=Act&ions: -new.action.group.anchor=Anchor: -new.action.group.anchor.first=&First -new.action.group.anchor.=&Last -new.action.group.anchor.before=&Before -new.action.group.anchor.after=Af&ter -new.action.group.groups=&Groups: -new.action.keyboard.shortcuts=Keyboard Shortcuts -new.action.keyboard.first=First: -new.action.keyboard.second=Second: -new.action.keyboard.clear=X -new.action.keyboard.clear.tooltip=Clear shortcut -command.implement.externalizable=Implement Externalizable -new.menu.action.text=Action -new.menu.action.description=Create New Action -new.action.error=Cannot create action -new.action.command=Create Action -new.action.action.name=Creating new action: {0}.{1} -new.action.dialog.title=New Action -new.menu.application.component.text=Application Component -new.menu.application.component.description=Create New Application Component -new.application.component.error=Cannot create application component -new.application.component.command=Create Application Component -new.application.component.prompt.title=New Application Component -new.application.component.prompt=Enter new application component name: -new.application.component.action.name=Creating new application component: {0}.{1} -new.menu.module.component.text=Module Component -new.menu.module.component.description=Create New Module Component -new.module.component.error=Cannot create module component -new.module.component.command=Create Module Component -new.module.component.prompt.title=New Module Component -new.module.component.prompt=Enter new module component name: -new.module.component.action.name=Creating new module component: {0}.{1} -new.menu.project.component.text=Project Component -new.menu.project.component.description=Create New Project Component -new.project.component.error=Cannot create project component -new.project.component.command=Create Project Component -new.project.component.prompt.title=New Project Component -new.project.component.prompt=Enter new project component name: -new.project.component.action.name=Creating new project component: {0}.{1} - -select.plugin.modules.to.patch=Select Plugin Modules to Patch - -keyword.extend=extend -keyword.implement=implement -class.action=action -class.interface=interface -class.implementation=implementation - -inspections.group.name=Plugin DevKit -inspections.registration.problems.name=Component type mismatch -inspections.registration.problems.option.check.plugin.xml=Check Plugin Descriptor (plugin.xml) -inspections.registration.problems.option.check.java.actions=Check Java Actions -inspections.registration.problems.option.check.java.code=Check Java Code -inspections.registration.problems.quickfix.read-only=Class ''{0}'' is read-only -inspections.registration.problems.quickfix.make.public=Make {0} public -inspections.registration.problems.quickfix.create.constructor=Create no-argument constructor - -inspections.registration.problems.incompatible.message=According to its registration in plugin.xml, the class should {0} ''{1}'' -inspections.registration.problems.abstract=Plugin component class must not be abstract -inspections.registration.problems.missing.noarg.ctor=Action class must have a no-argument constructor - -inspections.registration.problems.missing.implementation.class=Missing implementation-class -inspections.registration.problems.cannot.resolve.class=Cannot resolve {0} class -inspections.registration.problems.component.should.implement=Component class must implement ''{0}'' -inspections.registration.problems.component.incompatible.interface=Component class is not assignable to its interface-class ''{0}'' -inspections.registration.problems.component.duplicate.interface=Multiple components with the same interface-class are not allowed -inspections.registration.problems.action.incompatible.class=Action class must extend ''{0}'' - -inspections.component.not.registered.name=Component/Action not registered -inspections.component.not.registered.message={0} is not registered in plugin.xml -inspections.component.not.registered.option.check.actions=Check Actions -inspections.component.not.registered.option.ignore.non.public=Ignore non-public classes -inspections.component.not.registered.quickfix.family=Register Component -inspections.component.not.registered.quickfix.name=Register {0} -inspections.component.not.registered.quickfix.error=Cannot Register {0} -InspectionUseGrayColor=Use Gray -ant.build.jar.comment=Build archive for plugin ''{0}'' -ant.build.jar.description=Build plugin archive for module ''{0}'' -project.title=Plugin Project -no.java.sdk.for.idea.sdk.found=No Java SDK of appropriate version found. In addition to the IntelliJ Platform Plugin SDK, you need to define a JDK with the same Java version ({0}). -no.idea.sdk.version.found=Failed to detect JDK version required for IntelliJ Platform Plugin SDK. -group.PluginDeployActions.text=Plugin Deployment Actions - -error.cannot.resolve.plugin=Cannot resolve plugin {0} -create.description.file=Create Description File -select.target.location.of.description=Select target location of {0} diff --git a/plugins/devkit/src/DevKitUseScopeEnlarger.java b/plugins/devkit/src/DevKitUseScopeEnlarger.java index f34c40e57e89..6b55133eb251 100644 --- a/plugins/devkit/src/DevKitUseScopeEnlarger.java +++ b/plugins/devkit/src/DevKitUseScopeEnlarger.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2011 JetBrains s.r.o. + * Copyright 2000-2014 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,6 +15,7 @@ */ package org.jetbrains.idea.devkit; +import com.intellij.ide.highlighter.XmlFileType; import com.intellij.pom.PomTarget; import com.intellij.pom.PomTargetPsiElement; import com.intellij.psi.PsiElement; @@ -37,7 +38,8 @@ public class DevKitUseScopeEnlarger extends UseScopeEnlarger { if (target instanceof DomTarget) { DomElement domElement = ((DomTarget)target).getDomElement(); if (domElement instanceof ExtensionPoint) { - return GlobalSearchScope.allScope(element.getProject()); + return GlobalSearchScope.getScopeRestrictedByFileTypes(GlobalSearchScope.allScope(element.getProject()), + XmlFileType.INSTANCE); } } } diff --git a/plugins/devkit/src/build/PluginBuildConfiguration.java b/plugins/devkit/src/build/PluginBuildConfiguration.java index 5b5dc7bf8394..3604b0f0d13a 100644 --- a/plugins/devkit/src/build/PluginBuildConfiguration.java +++ b/plugins/devkit/src/build/PluginBuildConfiguration.java @@ -39,6 +39,7 @@ import org.jdom.Element; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.TestOnly; import org.jetbrains.idea.devkit.DevKitBundle; import org.jetbrains.idea.devkit.module.PluginDescriptorConstants; @@ -110,6 +111,11 @@ public class PluginBuildConfiguration implements ModuleComponent, JDOMExternaliz return myPluginXmlContainer.getConfigFile(PluginDescriptorConstants.META_DATA); } + @TestOnly + public void setPluginXmlFromVirtualFile(VirtualFile virtualFile) { + myPluginXmlContainer.getConfiguration().replaceConfigFile(PluginDescriptorConstants.META_DATA, virtualFile.getUrl()); + } + private void createDescriptor(final String url) { final ConfigFileInfo descriptor = new ConfigFileInfo(PluginDescriptorConstants.META_DATA, url); myPluginXmlContainer.getConfiguration().addConfigFile(descriptor); diff --git a/plugins/devkit/src/build/PluginBuildParticipant.java b/plugins/devkit/src/build/PluginBuildParticipant.java index a5998966dba6..75f6c7cf3046 100644 --- a/plugins/devkit/src/build/PluginBuildParticipant.java +++ b/plugins/devkit/src/build/PluginBuildParticipant.java @@ -106,7 +106,7 @@ public class PluginBuildParticipant extends BuildParticipant { final DomElement domElement = DomManager.getDomManager(xmlFile.getProject()).getDomElement(document.getRootTag()); if (domElement instanceof IdeaPlugin) { for(Dependency dependency: ((IdeaPlugin)domElement).getDependencies()) { - final String file = dependency.getConfigFile().getValue(); + final String file = dependency.getConfigFile().getStringValue(); if (file != null) { final VirtualFile virtualFile = configFile.getVirtualFile(); assert virtualFile != null; diff --git a/plugins/devkit/src/dom/Dependency.java b/plugins/devkit/src/dom/Dependency.java index 5b3a90617a08..b48384d0cfcb 100644 --- a/plugins/devkit/src/dom/Dependency.java +++ b/plugins/devkit/src/dom/Dependency.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2013 JetBrains s.r.o. + * Copyright 2000-2014 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,6 +19,7 @@ package org.jetbrains.idea.devkit.dom; +import com.intellij.openapi.paths.PathReference; import com.intellij.util.xml.Convert; import com.intellij.util.xml.GenericAttributeValue; import com.intellij.util.xml.GenericDomValue; @@ -29,9 +30,11 @@ import org.jetbrains.idea.devkit.dom.impl.IdeaPluginConverter; @Stubbed @Convert(IdeaPluginConverter.class) public interface Dependency extends GenericDomValue<IdeaPlugin> { + @NotNull GenericAttributeValue<Boolean> getOptional(); @NotNull - GenericAttributeValue<String> getConfigFile(); + @Convert(DependencyConfigFileConverter.class) + GenericAttributeValue<PathReference> getConfigFile(); } diff --git a/plugins/devkit/src/dom/DependencyConfigFileConverter.java b/plugins/devkit/src/dom/DependencyConfigFileConverter.java new file mode 100644 index 000000000000..214d21e6e819 --- /dev/null +++ b/plugins/devkit/src/dom/DependencyConfigFileConverter.java @@ -0,0 +1,146 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.idea.devkit.dom; + +import com.intellij.ide.highlighter.XmlFileType; +import com.intellij.openapi.fileTypes.FileType; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.module.ModuleUtilCore; +import com.intellij.openapi.paths.PathReference; +import com.intellij.openapi.paths.PathReferenceManager; +import com.intellij.openapi.paths.PathReferenceProvider; +import com.intellij.openapi.paths.StaticPathReferenceProvider; +import com.intellij.openapi.roots.ModuleRootManager; +import com.intellij.openapi.util.Condition; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; +import com.intellij.psi.PsiFileSystemItem; +import com.intellij.psi.PsiReference; +import com.intellij.psi.impl.source.resolve.reference.impl.providers.FileReferenceSet; +import com.intellij.psi.xml.XmlElement; +import com.intellij.psi.xml.XmlFile; +import com.intellij.util.Processor; +import com.intellij.util.containers.ContainerUtil; +import com.intellij.util.xml.ConvertContext; +import com.intellij.util.xml.DomFileElement; +import com.intellij.util.xml.DomUtil; +import com.intellij.util.xml.GenericAttributeValue; +import com.intellij.util.xml.converters.PathReferenceConverter; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.idea.devkit.util.DescriptorUtil; +import org.jetbrains.jps.model.java.JavaModuleSourceRootTypes; + +import java.util.*; + +public class DependencyConfigFileConverter extends PathReferenceConverter { + + private static final PathReferenceProvider ourProvider = new StaticPathReferenceProvider(null) { + + @Override + public boolean createReferences(@NotNull final PsiElement psiElement, + int offset, + String text, + @NotNull List<PsiReference> references, + boolean soft) { + FileReferenceSet set = new FileReferenceSet(text, psiElement, offset, null, + true, true, + new FileType[]{XmlFileType.INSTANCE}) { + + private final Condition<PsiFileSystemItem> PLUGIN_XML_CONDITION = new Condition<PsiFileSystemItem>() { + @Override + public boolean value(PsiFileSystemItem item) { + return !item.isDirectory() && + !item.equals(getContainingFile()) && + (item instanceof XmlFile && DescriptorUtil.isPluginXml((PsiFile)item)) && + !isAlreadyUsed((XmlFile)item); + } + }; + + private boolean isAlreadyUsed(final XmlFile xmlFile) { + final PsiFile file = getContainingFile(); + if (!(file instanceof XmlFile)) return false; + final DomFileElement<IdeaPlugin> ideaPlugin = DescriptorUtil.getIdeaPlugin((XmlFile)file); + if (ideaPlugin == null) return false; + return !ContainerUtil.process(ideaPlugin.getRootElement().getDependencies(), new Processor<Dependency>() { + @Override + public boolean process(Dependency dependency) { + final GenericAttributeValue<PathReference> configFileAttribute = dependency.getConfigFile(); + if (!DomUtil.hasXml(configFileAttribute)) return true; + final PathReference pathReference = configFileAttribute.getValue(); + if (pathReference == null) return true; + return !xmlFile.equals(pathReference.resolve()); + } + }); + } + + @NotNull + @Override + public Collection<PsiFileSystemItem> computeDefaultContexts() { + final PsiFile containingFile = getContainingFile(); + if (containingFile == null) { + return super.getDefaultContexts(); + } + + final Module module = ModuleUtilCore.findModuleForPsiElement(getElement()); + if (module == null) { + return super.getDefaultContexts(); + } + + final Set<VirtualFile> roots = new HashSet<VirtualFile>(); + final VirtualFile parent = containingFile.getVirtualFile().getParent(); + roots.add(parent); + + for (VirtualFile sourceRoot : ModuleRootManager.getInstance(module).getSourceRoots(JavaModuleSourceRootTypes.PRODUCTION)) { + roots.add(sourceRoot.findChild("META-INF")); + } + return toFileSystemItems(roots); + } + + @Override + protected boolean isSoft() { + return true; + } + + @Override + protected Condition<PsiFileSystemItem> getReferenceCompletionFilter() { + return PLUGIN_XML_CONDITION; + } + }; + Collections.addAll(references, set.getAllReferences()); + return true; + } + }; + + @Override + public PathReference fromString(@Nullable String s, ConvertContext context) { + final XmlElement element = context.getXmlElement(); + final Module module = context.getModule(); + if (s == null || element == null || module == null) { + return null; + } + return PathReferenceManager.getInstance().getCustomPathReference(s, module, element, ourProvider); + } + + @NotNull + @Override + public PsiReference[] createReferences(@NotNull PsiElement psiElement, boolean soft) { + return PathReferenceManager.getInstance().createCustomReferences(psiElement, + soft, + ourProvider); + } +} diff --git a/plugins/devkit/src/dom/ExtensionPoint.java b/plugins/devkit/src/dom/ExtensionPoint.java index 6a5074f7ce02..ddd2573497a7 100644 --- a/plugins/devkit/src/dom/ExtensionPoint.java +++ b/plugins/devkit/src/dom/ExtensionPoint.java @@ -65,6 +65,20 @@ public interface ExtensionPoint extends DomElement { With addWith(); + /** + * Returns the fully qualified EP name + * + * @return {@code PluginID.name} or {@code qualifiedName}. + * @since 14 + */ + @NotNull + String getEffectiveQualifiedName(); + + /** + * Returns the actually defined name. + * + * @return {@link #getName()} if defined, {@link #getQualifiedName()} otherwise. + */ @NotNull String getEffectiveName(); diff --git a/plugins/devkit/src/dom/Extensions.java b/plugins/devkit/src/dom/Extensions.java index f07357e56fe4..738a3a792079 100644 --- a/plugins/devkit/src/dom/Extensions.java +++ b/plugins/devkit/src/dom/Extensions.java @@ -42,7 +42,7 @@ public interface Extensions extends DomElement { Extension addExtension(); - Extension addExtension(String name); + Extension addExtension(String qualifiedEPName); @NotNull String getEpPrefix(); diff --git a/plugins/devkit/src/dom/IdeaPlugin.java b/plugins/devkit/src/dom/IdeaPlugin.java index f1dae2fab0d1..d1d3c355f93f 100644 --- a/plugins/devkit/src/dom/IdeaPlugin.java +++ b/plugins/devkit/src/dom/IdeaPlugin.java @@ -105,7 +105,6 @@ public interface IdeaPlugin extends DomElement { @NotNull @SubTagList("extensions") - @Stubbed List<Extensions> getExtensions(); Extensions addExtensions(); diff --git a/plugins/devkit/src/dom/impl/ExtensionDomExtender.java b/plugins/devkit/src/dom/impl/ExtensionDomExtender.java index 7fdfdeecc632..f5c83ccb66bc 100644 --- a/plugins/devkit/src/dom/impl/ExtensionDomExtender.java +++ b/plugins/devkit/src/dom/impl/ExtensionDomExtender.java @@ -147,6 +147,11 @@ public class ExtensionDomExtender extends DomExtender<Extensions> { } } + @Override + public boolean supportsStubs() { + return false; + } + private static Set<IdeaPlugin> getVisiblePlugins(IdeaPlugin ideaPlugin) { Set<IdeaPlugin> result = ContainerUtil.newHashSet(); MultiMap<String, IdeaPlugin> byId = getPluginMap(ideaPlugin.getManager().getProject()); diff --git a/plugins/devkit/src/dom/impl/ExtensionPointImpl.java b/plugins/devkit/src/dom/impl/ExtensionPointImpl.java index f4eb409e6164..1287948c2752 100644 --- a/plugins/devkit/src/dom/impl/ExtensionPointImpl.java +++ b/plugins/devkit/src/dom/impl/ExtensionPointImpl.java @@ -45,4 +45,14 @@ public abstract class ExtensionPointImpl implements ExtensionPoint { return StringUtil.notNullize(plugin.getPluginId(), "com.intellij"); } + + @NotNull + @Override + public String getEffectiveQualifiedName() { + if (DomUtil.hasXml(getQualifiedName())) { + return getQualifiedName().getRawText(); + } + + return getNamePrefix() + "." + getName().getRawText(); + } } diff --git a/plugins/devkit/src/dom/impl/ExtensionsImpl.java b/plugins/devkit/src/dom/impl/ExtensionsImpl.java index e52cdca34c2f..bf915b31edbd 100644 --- a/plugins/devkit/src/dom/impl/ExtensionsImpl.java +++ b/plugins/devkit/src/dom/impl/ExtensionsImpl.java @@ -15,10 +15,12 @@ */ package org.jetbrains.idea.devkit.dom.impl; +import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.xml.XmlTag; import org.jetbrains.annotations.NotNull; import org.jetbrains.idea.devkit.dom.Extension; import org.jetbrains.idea.devkit.dom.Extensions; +import org.jetbrains.idea.devkit.dom.IdeaPlugin; /** * @author Dmitry Avdeev @@ -27,16 +29,20 @@ import org.jetbrains.idea.devkit.dom.Extensions; public abstract class ExtensionsImpl implements Extensions { @Override - public Extension addExtension(String name) { + public Extension addExtension(String qualifiedEPName) { Extension extension = addExtension(); XmlTag tag = extension.getXmlTag(); - tag.setName(name.substring(getDefaultExtensionNs().getStringValue().length() + 1)); + tag.setName(StringUtil.trimStart(qualifiedEPName, getEpPrefix())); return extension; } @NotNull public String getEpPrefix() { String prefix = getDefaultExtensionNs().getStringValue(); + if (prefix == null) { + final IdeaPlugin ideaPlugin = getParentOfType(IdeaPlugin.class, true); + prefix = ideaPlugin == null ? null : StringUtil.notNullize(ideaPlugin.getPluginId(), "com.intellij"); + } if (prefix == null) prefix = getXmlns().getStringValue(); return prefix != null ? prefix + "." : ""; } diff --git a/plugins/devkit/src/dom/impl/IdeaPluginConverter.java b/plugins/devkit/src/dom/impl/IdeaPluginConverter.java index 82b6b0dbecd1..1020f250d1c2 100644 --- a/plugins/devkit/src/dom/impl/IdeaPluginConverter.java +++ b/plugins/devkit/src/dom/impl/IdeaPluginConverter.java @@ -21,6 +21,7 @@ import com.intellij.openapi.util.Comparing; import com.intellij.openapi.util.Condition; import com.intellij.psi.search.GlobalSearchScope; import com.intellij.psi.search.GlobalSearchScopesCore; +import com.intellij.psi.search.ProjectScope; import com.intellij.util.Function; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.xml.ConvertContext; @@ -34,7 +35,6 @@ import org.jetbrains.annotations.Nullable; import org.jetbrains.idea.devkit.DevKitBundle; import org.jetbrains.idea.devkit.dom.IdeaPlugin; import org.jetbrains.idea.devkit.dom.PluginModule; -import org.jetbrains.idea.devkit.util.PsiUtil; import java.util.Collection; import java.util.Collections; @@ -108,8 +108,9 @@ public class IdeaPluginConverter extends ResolvingConverter<IdeaPlugin> { public static Collection<IdeaPlugin> getAllPlugins(final Project project) { if (DumbService.isDumb(project)) return Collections.emptyList(); - GlobalSearchScope scope = PsiUtil.isIdeaProject(project) ? - GlobalSearchScopesCore.projectProductionScope(project) : GlobalSearchScope.allScope(project); + + GlobalSearchScope scope = GlobalSearchScopesCore.projectProductionScope(project). + union(ProjectScope.getLibrariesScope(project)); List<DomFileElement<IdeaPlugin>> files = DomService.getInstance().getFileElements(IdeaPlugin.class, project, scope); return ContainerUtil.map(files, new Function<DomFileElement<IdeaPlugin>, IdeaPlugin>() { public IdeaPlugin fun(DomFileElement<IdeaPlugin> ideaPluginDomFileElement) { diff --git a/plugins/devkit/src/dom/impl/PluginXmlDomFileDescription.java b/plugins/devkit/src/dom/impl/PluginXmlDomFileDescription.java index f0dd66b5f8c4..80a3aabce8f3 100644 --- a/plugins/devkit/src/dom/impl/PluginXmlDomFileDescription.java +++ b/plugins/devkit/src/dom/impl/PluginXmlDomFileDescription.java @@ -49,6 +49,19 @@ public class PluginXmlDomFileDescription extends DomFileDescription<IdeaPlugin> else if (element instanceof IdeaVersion) { annotateIdeaVersion((IdeaVersion)element, holder); } + else if (element instanceof Extensions) { + annotateExtensions((Extensions)element, holder); + } + } + + private void annotateExtensions(Extensions extensions, DomElementAnnotationHolder holder) { + final GenericAttributeValue<IdeaPlugin> xmlnsAttribute = extensions.getXmlns(); + if (!DomUtil.hasXml(xmlnsAttribute)) return; + + final Annotation annotation = holder.createAnnotation(xmlnsAttribute, + HighlightSeverity.WARNING, + "Use defaultExtensionNs instead"); + annotation.setHighlightType(ProblemHighlightType.LIKE_DEPRECATED); } private void annotateIdeaVersion(IdeaVersion ideaVersion, DomElementAnnotationHolder holder) { @@ -106,6 +119,6 @@ public class PluginXmlDomFileDescription extends DomFileDescription<IdeaPlugin> @Override public int getStubVersion() { - return 2; + return 3; } } diff --git a/plugins/devkit/src/inspections/DescriptionCheckerUtil.java b/plugins/devkit/src/inspections/DescriptionCheckerUtil.java new file mode 100644 index 000000000000..d23e95cf148d --- /dev/null +++ b/plugins/devkit/src/inspections/DescriptionCheckerUtil.java @@ -0,0 +1,53 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.idea.devkit.inspections; + +import com.intellij.openapi.module.Module; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.psi.JavaPsiFacade; +import com.intellij.psi.PsiClass; +import com.intellij.psi.PsiDirectory; +import com.intellij.psi.PsiPackage; +import com.intellij.psi.search.GlobalSearchScope; +import org.jetbrains.annotations.Nullable; + +public class DescriptionCheckerUtil { + + public static PsiDirectory[] getDescriptionsDirs(Module module, + DescriptionType descriptionType) { + final JavaPsiFacade javaPsiFacade = JavaPsiFacade.getInstance(module.getProject()); + final PsiPackage psiPackage = javaPsiFacade.findPackage(descriptionType.getDescriptionFolder()); + if (psiPackage != null) { + return psiPackage.getDirectories(GlobalSearchScope.moduleWithDependenciesScope(module)); + } + return PsiDirectory.EMPTY_ARRAY; + } + + @Nullable + public static String getDescriptionDirName(PsiClass aClass) { + String descriptionDir = ""; + PsiClass each = aClass; + while (each != null) { + String name = each.getName(); + if (StringUtil.isEmptyOrSpaces(name)) { + return null; + } + descriptionDir = name + descriptionDir; + each = each.getContainingClass(); + } + return descriptionDir; + } +} diff --git a/plugins/devkit/src/inspections/DescriptionNotFoundInspectionBase.java b/plugins/devkit/src/inspections/DescriptionNotFoundInspectionBase.java new file mode 100644 index 000000000000..a5074f0ef6e0 --- /dev/null +++ b/plugins/devkit/src/inspections/DescriptionNotFoundInspectionBase.java @@ -0,0 +1,128 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.idea.devkit.inspections; + + +import com.intellij.codeInspection.InspectionManager; +import com.intellij.codeInspection.LocalQuickFix; +import com.intellij.codeInspection.ProblemDescriptor; +import com.intellij.codeInspection.ProblemHighlightType; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.module.ModuleUtilCore; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.*; +import com.intellij.psi.search.GlobalSearchScope; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.idea.devkit.inspections.quickfix.CreateHtmlDescriptionFix; +import org.jetbrains.idea.devkit.util.PsiUtil; + +abstract class DescriptionNotFoundInspectionBase extends DevKitInspectionBase { + + private final DescriptionType myDescriptionType; + + protected DescriptionNotFoundInspectionBase(DescriptionType descriptionType) { + myDescriptionType = descriptionType; + } + + @Override + public ProblemDescriptor[] checkClass(@NotNull PsiClass aClass, @NotNull InspectionManager manager, boolean isOnTheFly) { + final Project project = aClass.getProject(); + final PsiIdentifier nameIdentifier = aClass.getNameIdentifier(); + final Module module = ModuleUtilCore.findModuleForPsiElement(aClass); + + if (nameIdentifier == null || module == null || !PsiUtil.isInstantiable(aClass)) return null; + + final PsiClass base = JavaPsiFacade.getInstance(project).findClass(getClassName(), GlobalSearchScope.allScope(project)); + if (base == null || !aClass.isInheritor(base, true)) return null; + + String descriptionDir = DescriptionCheckerUtil.getDescriptionDirName(aClass); + if (StringUtil.isEmptyOrSpaces(descriptionDir)) { + return null; + } + + for (PsiDirectory description : getDescriptionsDirs(module)) { + PsiDirectory dir = description.findSubdirectory(descriptionDir); + if (dir == null) continue; + final PsiFile descr = dir.findFile("description.html"); + if (descr != null) { + if (!hasBeforeAndAfterTemplate(dir.getVirtualFile())) { + PsiElement problem = aClass.getNameIdentifier(); + ProblemDescriptor problemDescriptor = manager.createProblemDescriptor(problem == null ? nameIdentifier : problem, + getHasNotBeforeAfterError(), + isOnTheFly, + ProblemHighlightType.GENERIC_ERROR_OR_WARNING, false); + return new ProblemDescriptor[]{problemDescriptor}; + } + + return null; + } + } + + + final PsiElement problem = aClass.getNameIdentifier(); + final ProblemDescriptor problemDescriptor = manager + .createProblemDescriptor(problem == null ? nameIdentifier : problem, + getHasNotDescriptionError(), isOnTheFly, new LocalQuickFix[]{getFix(module, descriptionDir)}, + ProblemHighlightType.GENERIC_ERROR_OR_WARNING); + return new ProblemDescriptor[]{problemDescriptor}; + } + + protected CreateHtmlDescriptionFix getFix(Module module, String descriptionDir) { + return new CreateHtmlDescriptionFix(descriptionDir, module, myDescriptionType); + } + + private static boolean hasBeforeAndAfterTemplate(@NotNull VirtualFile dir) { + boolean hasBefore = false; + boolean hasAfter = false; + + for (VirtualFile file : dir.getChildren()) { + String name = file.getName(); + if (name.endsWith(".template")) { + if (name.startsWith("before.")) { + hasBefore = true; + } + else if (name.startsWith("after.")) { + hasAfter = true; + } + } + } + + return hasBefore && hasAfter; + } + + @Override + public boolean isEnabledByDefault() { + return true; + } + + @NotNull + protected String getClassName() { + return myDescriptionType.getClassName(); + } + + @NotNull + protected PsiDirectory[] getDescriptionsDirs(@NotNull Module module) { + return DescriptionCheckerUtil.getDescriptionsDirs(module, myDescriptionType); + } + + @NotNull + protected abstract String getHasNotDescriptionError(); + + @NotNull + protected abstract String getHasNotBeforeAfterError(); +} diff --git a/plugins/devkit/src/inspections/DescriptionType.java b/plugins/devkit/src/inspections/DescriptionType.java new file mode 100644 index 000000000000..ea5a88977b1f --- /dev/null +++ b/plugins/devkit/src/inspections/DescriptionType.java @@ -0,0 +1,51 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.idea.devkit.inspections; + +import com.intellij.codeInsight.intention.IntentionAction; +import com.intellij.codeInsight.template.postfix.templates.PostfixTemplate; +import com.intellij.codeInspection.InspectionProfileEntry; + +public enum DescriptionType { + + INTENTION(IntentionAction.class.getName(), "intentionDescriptions", true), + INSPECTION(InspectionProfileEntry.class.getName(), "inspectionDescriptions", false), + POSTFIX_TEMPLATES(PostfixTemplate.class.getName(), "postfixTemplates", true); + + private final String myClassName; + private final String myDescriptionFolder; + private final boolean myFixedDescriptionFilename; + + DescriptionType(String className, + String descriptionFolder, + boolean fixedDescriptionFilename) { + myFixedDescriptionFilename = fixedDescriptionFilename; + myClassName = className; + myDescriptionFolder = descriptionFolder; + } + + public String getClassName() { + return myClassName; + } + + public String getDescriptionFolder() { + return myDescriptionFolder; + } + + public boolean isFixedDescriptionFilename() { + return myFixedDescriptionFilename; + } +} diff --git a/plugins/devkit/src/inspections/DevKitInspectionBase.java b/plugins/devkit/src/inspections/DevKitInspectionBase.java index 9ded8e216fc0..c27c77800c10 100644 --- a/plugins/devkit/src/inspections/DevKitInspectionBase.java +++ b/plugins/devkit/src/inspections/DevKitInspectionBase.java @@ -20,15 +20,21 @@ import com.intellij.openapi.actionSystem.ActionGroup; import com.intellij.openapi.actionSystem.AnAction; import com.intellij.openapi.module.Module; import com.intellij.openapi.module.ModuleUtil; +import com.intellij.openapi.paths.PathReference; import com.intellij.openapi.project.Project; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.*; import com.intellij.psi.search.GlobalSearchScope; import com.intellij.psi.xml.*; import com.intellij.util.containers.ContainerUtil; +import com.intellij.util.xml.DomFileElement; +import com.intellij.util.xml.DomUtil; +import com.intellij.util.xml.GenericAttributeValue; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.idea.devkit.DevKitBundle; +import org.jetbrains.idea.devkit.dom.Dependency; +import org.jetbrains.idea.devkit.dom.IdeaPlugin; import org.jetbrains.idea.devkit.module.PluginModuleType; import org.jetbrains.idea.devkit.util.ActionType; import org.jetbrains.idea.devkit.util.ComponentType; @@ -62,7 +68,8 @@ public abstract class DevKitInspectionBase extends BaseJavaLocalInspectionTool { if (PluginModuleType.isOfType(module)) { return checkModule(module, psiClass, null, includeActions); - } else { + } + else { Set<PsiClass> types = null; final List<Module> modules = PluginModuleType.getCandidateModules(module); for (Module m : modules) { @@ -78,20 +85,28 @@ public abstract class DevKitInspectionBase extends BaseJavaLocalInspectionTool { if (!DescriptorUtil.isPluginXml(pluginXml)) return types; assert pluginXml != null; - final XmlDocument document = pluginXml.getDocument(); - assert document != null; - - final XmlTag rootTag = document.getRootTag(); - assert rootTag != null; - final String qualifiedName = psiClass.getQualifiedName(); if (qualifiedName != null) { final RegistrationTypeFinder finder = new RegistrationTypeFinder(psiClass, types); - DescriptorUtil.processComponents(rootTag, finder); - - if (includeActions) { - DescriptorUtil.processActions(rootTag, finder); + // "main" plugin.xml + processPluginXml(pluginXml, finder, includeActions); + + // <depends> plugin.xml files + final DomFileElement<IdeaPlugin> fileElement = DescriptorUtil.getIdeaPlugin(pluginXml); + for (Dependency dependency : fileElement.getRootElement().getDependencies()) { + final GenericAttributeValue<PathReference> configFileAttribute = dependency.getConfigFile(); + if (!DomUtil.hasXml(configFileAttribute)) continue; + + final PathReference configFile = configFileAttribute.getValue(); + if (configFile != null) { + final PsiElement resolve = configFile.resolve(); + if (!(resolve instanceof XmlFile)) continue; + final XmlFile depPluginXml = (XmlFile)resolve; + if (DescriptorUtil.isPluginXml(depPluginXml)) { + processPluginXml(depPluginXml, finder, includeActions); + } + } } types = finder.getTypes(); @@ -100,6 +115,18 @@ public abstract class DevKitInspectionBase extends BaseJavaLocalInspectionTool { return types; } + private static void processPluginXml(XmlFile xmlFile, RegistrationTypeFinder finder, boolean includeActions) { + final XmlDocument document = xmlFile.getDocument(); + if (document == null) return; + final XmlTag rootTag = document.getRootTag(); + if (rootTag == null) return; + + DescriptorUtil.processComponents(rootTag, finder); + if (includeActions) { + DescriptorUtil.processActions(rootTag, finder); + } + } + @Nullable protected static PsiElement getAttValueToken(@NotNull XmlAttribute attribute) { final XmlAttributeValue valueElement = attribute.getValueElement(); @@ -132,13 +159,14 @@ public abstract class DevKitInspectionBase extends BaseJavaLocalInspectionTool { return false; } - static class RegistrationTypeFinder implements ComponentType.Processor, ActionType.Processor { + + private static class RegistrationTypeFinder implements ComponentType.Processor, ActionType.Processor { private Set<PsiClass> myTypes; private final String myQualifiedName; private final PsiManager myManager; private final GlobalSearchScope myScope; - public RegistrationTypeFinder(PsiClass psiClass, Set<PsiClass> types) { + private RegistrationTypeFinder(PsiClass psiClass, Set<PsiClass> types) { myTypes = types; myQualifiedName = psiClass.getQualifiedName(); myManager = psiClass.getManager(); diff --git a/plugins/devkit/src/inspections/InspectionDescriptionInfo.java b/plugins/devkit/src/inspections/InspectionDescriptionInfo.java new file mode 100644 index 000000000000..9720322fed1b --- /dev/null +++ b/plugins/devkit/src/inspections/InspectionDescriptionInfo.java @@ -0,0 +1,89 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.idea.devkit.inspections; + +import com.intellij.codeInspection.InspectionProfileEntry; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.*; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.idea.devkit.util.PsiUtil; + +public class InspectionDescriptionInfo { + + private final String myFilename; + private final PsiMethod myMethod; + private final PsiFile myDescriptionFile; + + private InspectionDescriptionInfo(String filename, @Nullable PsiMethod method, @Nullable PsiFile descriptionFile) { + myFilename = filename; + myMethod = method; + myDescriptionFile = descriptionFile; + } + + public static InspectionDescriptionInfo create(Module module, PsiClass psiClass) { + PsiMethod method = PsiUtil.findNearestMethod("getShortName", psiClass); + if (method != null && + DescriptionType.INSPECTION.getClassName().equals(method.getContainingClass().getQualifiedName())) { + method = null; + } + final String filename = method == null ? + InspectionProfileEntry.getShortName(psiClass.getName()) : + PsiUtil.getReturnedLiteral(method, psiClass); + + PsiFile descriptionFile = resolveInspectionDescriptionFile(module, filename); + return new InspectionDescriptionInfo(filename, method, descriptionFile); + } + + @Nullable + private static PsiFile resolveInspectionDescriptionFile(Module module, @Nullable String filename) { + if (filename == null) return null; + + for (PsiDirectory description : DescriptionCheckerUtil.getDescriptionsDirs(module, DescriptionType.INSPECTION)) { + final PsiFile file = description.findFile(filename + ".html"); + if (file == null) continue; + final VirtualFile vf = file.getVirtualFile(); + if (vf == null) continue; + if (vf.getNameWithoutExtension().equals(filename)) { + return PsiManager.getInstance(module.getProject()).findFile(vf); + } + } + return null; + } + + public boolean isValid() { + return myFilename != null; + } + + public String getFilename() { + assert isValid(); + return myFilename; + } + + @Nullable + public PsiMethod getShortNameMethod() { + return myMethod; + } + + @Nullable + public PsiFile getDescriptionFile() { + return myDescriptionFile; + } + + public boolean hasDescriptionFile() { + return getDescriptionFile() != null; + } +} diff --git a/plugins/devkit/src/inspections/InspectionDescriptionNotFoundInspection.java b/plugins/devkit/src/inspections/InspectionDescriptionNotFoundInspection.java index fb8b520dd989..1271f0a9bdcc 100644 --- a/plugins/devkit/src/inspections/InspectionDescriptionNotFoundInspection.java +++ b/plugins/devkit/src/inspections/InspectionDescriptionNotFoundInspection.java @@ -16,11 +16,13 @@ package org.jetbrains.idea.devkit.inspections; -import com.intellij.codeInspection.*; +import com.intellij.codeInspection.InspectionManager; +import com.intellij.codeInspection.LocalQuickFix; +import com.intellij.codeInspection.ProblemDescriptor; +import com.intellij.codeInspection.ProblemHighlightType; import com.intellij.openapi.module.Module; -import com.intellij.openapi.module.ModuleUtil; +import com.intellij.openapi.module.ModuleUtilCore; import com.intellij.openapi.project.Project; -import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.*; import com.intellij.psi.search.GlobalSearchScope; import org.jetbrains.annotations.Nls; @@ -33,95 +35,59 @@ import org.jetbrains.idea.devkit.util.PsiUtil; /** * @author Konstantin Bulenkov */ -public class InspectionDescriptionNotFoundInspection extends DevKitInspectionBase{ - @NonNls static final String INSPECTION_PROFILE_ENTRY = InspectionProfileEntry.class.getName(); - @NonNls private static final String INSPECTION_DESCRIPTIONS = "inspectionDescriptions"; +public class InspectionDescriptionNotFoundInspection extends DevKitInspectionBase { + @NonNls private static final String INSPECTION_PROFILE_ENTRY = DescriptionType.INSPECTION.getClassName(); @Override - public ProblemDescriptor[] checkClass(@NotNull PsiClass aClass, @NotNull InspectionManager manager, boolean isOnTheFly) { - final Project project = aClass.getProject(); - final PsiIdentifier nameIdentifier = aClass.getNameIdentifier(); - final Module module = ModuleUtil.findModuleForPsiElement(aClass); + public ProblemDescriptor[] checkClass(@NotNull PsiClass psiClass, @NotNull InspectionManager manager, boolean isOnTheFly) { + final Project project = psiClass.getProject(); + final PsiIdentifier nameIdentifier = psiClass.getNameIdentifier(); + final Module module = ModuleUtilCore.findModuleForPsiElement(psiClass); - if (nameIdentifier == null || module == null || !PsiUtil.isInstantiable(aClass)) return null; + if (nameIdentifier == null || module == null || !PsiUtil.isInstantiable(psiClass)) return null; final PsiClass base = JavaPsiFacade.getInstance(project).findClass(INSPECTION_PROFILE_ENTRY, GlobalSearchScope.allScope(project)); + if (base == null || !psiClass.isInheritor(base, true) || isPathMethodsAreOverridden(psiClass)) return null; - if (base == null || ! aClass.isInheritor(base, true) || isPathMethodsAreOverridden(aClass)) return null; + final InspectionDescriptionInfo info = InspectionDescriptionInfo.create(module, psiClass); + if (!info.isValid() || info.hasDescriptionFile()) return null; - PsiMethod method = findNearestMethod("getShortName", aClass); - if (method != null && method.getContainingClass().getQualifiedName().equals(INSPECTION_PROFILE_ENTRY)) { - method = null; - } - final String filename = method == null ? InspectionProfileEntry.getShortName(aClass.getName()) : PsiUtil.getReturnedLiteral(method, aClass); - if (filename == null) return null; - - - for (PsiDirectory description : getInspectionDescriptionsDirs(module)) { - final PsiFile file = description.findFile(filename + ".html"); - if (file == null) continue; - final VirtualFile vf = file.getVirtualFile(); - if (vf == null) continue; - if (vf.getNameWithoutExtension().equals(filename)) { - return null; - } - } - - - final PsiElement problem = getProblemElement(aClass, method); + final PsiElement problemElement = getProblemElement(psiClass, info.getShortNameMethod()); final ProblemDescriptor problemDescriptor = manager - .createProblemDescriptor(problem == null ? nameIdentifier : problem, - "Inspection does not have a description", isOnTheFly, new LocalQuickFix[]{new CreateHtmlDescriptionFix(filename, module, false)}, + .createProblemDescriptor(problemElement == null ? nameIdentifier : problemElement, + "Inspection does not have a description", isOnTheFly, + new LocalQuickFix[]{new CreateHtmlDescriptionFix(info.getFilename(), module, DescriptionType.INSPECTION)}, ProblemHighlightType.GENERIC_ERROR_OR_WARNING); return new ProblemDescriptor[]{problemDescriptor}; } @Nullable - private static PsiElement getProblemElement(PsiClass aClass, @Nullable PsiMethod method) { - if (method != null && method.getContainingClass() == aClass) { + private static PsiElement getProblemElement(PsiClass psiClass, @Nullable PsiMethod method) { + if (method != null && method.getContainingClass() == psiClass) { return PsiUtil.getReturnedExpression(method); - } else { - return aClass.getNameIdentifier(); } + return psiClass.getNameIdentifier(); } - private static boolean isPathMethodsAreOverridden(PsiClass aClass) { - return! ( isLastMethodDefinitionIn("getStaticDescription", INSPECTION_PROFILE_ENTRY, aClass) - && isLastMethodDefinitionIn("getDescriptionUrl", INSPECTION_PROFILE_ENTRY, aClass) - && isLastMethodDefinitionIn("getDescriptionContextClass", INSPECTION_PROFILE_ENTRY, aClass) - && isLastMethodDefinitionIn("getDescriptionFileName", INSPECTION_PROFILE_ENTRY, aClass)); + private static boolean isPathMethodsAreOverridden(PsiClass psiClass) { + return !(isLastMethodDefinitionIn("getStaticDescription", INSPECTION_PROFILE_ENTRY, psiClass) + && isLastMethodDefinitionIn("getDescriptionUrl", INSPECTION_PROFILE_ENTRY, psiClass) + && isLastMethodDefinitionIn("getDescriptionContextClass", INSPECTION_PROFILE_ENTRY, psiClass) + && isLastMethodDefinitionIn("getDescriptionFileName", INSPECTION_PROFILE_ENTRY, psiClass)); } - private static boolean isLastMethodDefinitionIn(@NotNull String methodName, @NotNull String classFQN, PsiClass cls) { - if (cls == null) return false; - for (PsiMethod method : cls.getMethods()) { + private static boolean isLastMethodDefinitionIn(@NotNull String methodName, + @NotNull String classFQN, + @Nullable PsiClass psiClass) { + if (psiClass == null) return false; + for (PsiMethod method : psiClass.getMethods()) { if (method.getName().equals(methodName)) { final PsiClass containingClass = method.getContainingClass(); if (containingClass == null) return false; return classFQN.equals(containingClass.getQualifiedName()); } } - return isLastMethodDefinitionIn(methodName, classFQN, cls.getSuperClass()); - } - - public static PsiDirectory[] getInspectionDescriptionsDirs(Module module) { - final PsiPackage aPackage = JavaPsiFacade.getInstance(module.getProject()).findPackage(INSPECTION_DESCRIPTIONS); - if (aPackage != null) { - return aPackage.getDirectories(GlobalSearchScope.moduleWithDependenciesScope(module)); - } else { - return PsiDirectory.EMPTY_ARRAY; - } - } - - @Nullable - private static PsiMethod findNearestMethod(String name, @Nullable PsiClass cls) { - if (cls == null) return null; - for (PsiMethod method : cls.getMethods()) { - if (method.getParameterList().getParametersCount() == 0 && method.getName().equals(name)) { - return method.getModifierList().hasModifierProperty(PsiModifier.ABSTRACT) ? null : method; - } - } - return findNearestMethod(name, cls.getSuperClass()); + return isLastMethodDefinitionIn(methodName, classFQN, psiClass.getSuperClass()); } @Nls diff --git a/plugins/devkit/src/inspections/IntentionDescriptionNotFoundInspection.java b/plugins/devkit/src/inspections/IntentionDescriptionNotFoundInspection.java index e424c70f1106..209c5720ce48 100644 --- a/plugins/devkit/src/inspections/IntentionDescriptionNotFoundInspection.java +++ b/plugins/devkit/src/inspections/IntentionDescriptionNotFoundInspection.java @@ -15,116 +15,32 @@ */ package org.jetbrains.idea.devkit.inspections; -import com.intellij.codeInspection.InspectionManager; -import com.intellij.codeInspection.LocalQuickFix; -import com.intellij.codeInspection.ProblemDescriptor; -import com.intellij.codeInspection.ProblemHighlightType; import com.intellij.openapi.module.Module; -import com.intellij.openapi.module.ModuleUtil; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.text.StringUtil; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.psi.*; -import com.intellij.psi.search.GlobalSearchScope; import org.jetbrains.annotations.Nls; -import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; import org.jetbrains.idea.devkit.inspections.quickfix.CreateHtmlDescriptionFix; -import org.jetbrains.idea.devkit.util.PsiUtil; /** * @author Konstantin Bulenkov */ -public class IntentionDescriptionNotFoundInspection extends DevKitInspectionBase{ - @NonNls private static final String INTENTION = "com.intellij.codeInsight.intention.IntentionAction"; - @NonNls private static final String INSPECTION_DESCRIPTIONS = "intentionDescriptions"; +public class IntentionDescriptionNotFoundInspection extends DescriptionNotFoundInspectionBase { - @Override - public ProblemDescriptor[] checkClass(@NotNull PsiClass aClass, @NotNull InspectionManager manager, boolean isOnTheFly) { - final Project project = aClass.getProject(); - final PsiIdentifier nameIdentifier = aClass.getNameIdentifier(); - final Module module = ModuleUtil.findModuleForPsiElement(aClass); - - if (nameIdentifier == null || module == null || !PsiUtil.isInstantiable(aClass)) return null; - - final PsiClass base = JavaPsiFacade.getInstance(project).findClass(INTENTION, GlobalSearchScope.allScope(project)); - - if (base == null || ! aClass.isInheritor(base, true)) return null; - - String descriptionDir = getDescriptionDirName(aClass); - if (StringUtil.isEmptyOrSpaces(descriptionDir)) { - return null; - } - - for (PsiDirectory description : getIntentionDescriptionsDirs(module)) { - PsiDirectory dir = description.findSubdirectory(descriptionDir); - if (dir == null) continue; - final PsiFile descr = dir.findFile("description.html"); - if (descr != null) { - if (!hasBeforeAndAfterTemplate(dir.getVirtualFile())) { - PsiElement problem = aClass.getNameIdentifier(); - ProblemDescriptor problemDescriptor = manager.createProblemDescriptor(problem == null ? nameIdentifier : problem, - "Intention must have 'before.*.template' and 'after.*.template' beside 'description.html'", - isOnTheFly, - ProblemHighlightType.GENERIC_ERROR_OR_WARNING, false); - return new ProblemDescriptor[]{problemDescriptor}; - } - - return null; - } - } - - - final PsiElement problem = aClass.getNameIdentifier(); - final ProblemDescriptor problemDescriptor = manager - .createProblemDescriptor(problem == null ? nameIdentifier : problem, - "Intention does not have a description", isOnTheFly, new LocalQuickFix[]{new CreateHtmlDescriptionFix(descriptionDir, module, true)}, - ProblemHighlightType.GENERIC_ERROR_OR_WARNING); - return new ProblemDescriptor[]{problemDescriptor}; + public IntentionDescriptionNotFoundInspection() { + super(DescriptionType.INTENTION); } - @Nullable - private static String getDescriptionDirName(PsiClass aClass) { - String descriptionDir = ""; - PsiClass each = aClass; - while (each != null) { - String name = each.getName(); - if (StringUtil.isEmptyOrSpaces(name)) { - return null; - } - descriptionDir = name + descriptionDir; - each = each.getContainingClass(); - } - return descriptionDir; + protected CreateHtmlDescriptionFix getFix(Module module, String descriptionDir) { + return new CreateHtmlDescriptionFix(descriptionDir, module, DescriptionType.INTENTION); } - private static boolean hasBeforeAndAfterTemplate(@NotNull VirtualFile dir) { - boolean hasBefore = false; - boolean hasAfter = false; - - for (VirtualFile file : dir.getChildren()) { - String name = file.getName(); - if (name.endsWith(".template")) { - if (name.startsWith("before.")) { - hasBefore = true; - } - else if (name.startsWith("after.")) { - hasAfter = true; - } - } - } - - return hasBefore && hasAfter; + @NotNull + protected String getHasNotDescriptionError() { + return "Intention does not have a description"; } - public static PsiDirectory[] getIntentionDescriptionsDirs(Module module) { - final PsiPackage aPackage = JavaPsiFacade.getInstance(module.getProject()).findPackage(INSPECTION_DESCRIPTIONS); - if (aPackage != null) { - return aPackage.getDirectories(GlobalSearchScope.moduleWithDependenciesScope(module)); - } else { - return PsiDirectory.EMPTY_ARRAY; - } + @NotNull + protected String getHasNotBeforeAfterError() { + return "Intention must have 'before.*.template' and 'after.*.template' beside 'description.html'"; } @Nls @@ -137,9 +53,4 @@ public class IntentionDescriptionNotFoundInspection extends DevKitInspectionBase public String getShortName() { return "IntentionDescriptionNotFoundInspection"; } - - @Override - public boolean isEnabledByDefault() { - return true; - } } diff --git a/plugins/devkit/src/inspections/PostfixTemplateDescriptionNotFoundInspection.java b/plugins/devkit/src/inspections/PostfixTemplateDescriptionNotFoundInspection.java new file mode 100644 index 000000000000..fed345f22b25 --- /dev/null +++ b/plugins/devkit/src/inspections/PostfixTemplateDescriptionNotFoundInspection.java @@ -0,0 +1,53 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.idea.devkit.inspections; + +import org.jetbrains.annotations.Nls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.idea.devkit.DevKitBundle; + + +public class PostfixTemplateDescriptionNotFoundInspection extends DescriptionNotFoundInspectionBase { + + public PostfixTemplateDescriptionNotFoundInspection() { + super(DescriptionType.POSTFIX_TEMPLATES); + } + + @NotNull + @Override + protected String getHasNotDescriptionError() { + return "Postfix template does not have a description"; + } + + @NotNull + @Override + protected String getHasNotBeforeAfterError() { + return "Postfix template must have 'before.*.template' and 'after.*.template' beside 'description.html'"; + } + + @Nls + @NotNull + @Override + public String getDisplayName() { + return DevKitBundle.message("inspections.component.postfix.template.not.found.description.name"); + } + + @NotNull + @Override + public String getShortName() { + return "PostfixTemplateDescriptionNotFound"; + } +} diff --git a/plugins/devkit/src/inspections/internal/DontUseNewPairInspection.java b/plugins/devkit/src/inspections/internal/DontUseNewPairInspection.java new file mode 100644 index 000000000000..9e6fea3f6071 --- /dev/null +++ b/plugins/devkit/src/inspections/internal/DontUseNewPairInspection.java @@ -0,0 +1,70 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.idea.devkit.inspections.internal; + +import com.intellij.codeInspection.ProblemHighlightType; +import com.intellij.codeInspection.ProblemsHolder; +import com.intellij.psi.*; +import org.jetbrains.annotations.Nls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.idea.devkit.inspections.quickfix.ChangeToPairCreateQuickFix; + +import java.util.Arrays; + +/** + * @author Konstantin Bulenkov + */ +public class DontUseNewPairInspection extends InternalInspection { + private static final String PAIR_FQN = "com.intellij.openapi.util.Pair"; + + @Override + public PsiElementVisitor buildInternalVisitor(@NotNull final ProblemsHolder holder, boolean isOnTheFly) { + return new JavaElementVisitor() { + @Override + public void visitNewExpression(PsiNewExpression expression) { + final PsiType type = expression.getType(); + final PsiExpressionList params = expression.getArgumentList(); + if (type instanceof PsiClassType + && ((PsiClassType)type).rawType().equalsToText(PAIR_FQN) + && params != null + && expression.getArgumentList() != null + //&& !PsiUtil.getLanguageLevel(expression).isAtLeast(LanguageLevel.JDK_1_7) //diamonds + ) { + final PsiType[] types = ((PsiClassType)type).getParameters(); + if (Arrays.equals(types, params.getExpressionTypes())) { + holder.registerProblem(expression, "Replace to Pair.create()", ProblemHighlightType.GENERIC_ERROR_OR_WARNING, + new ChangeToPairCreateQuickFix()); + } + } + super.visitNewExpression(expression); + } + }; + } + + @Nls + @NotNull + @Override + public String getDisplayName() { + return "Don't use Pair constructor"; + } + + @NotNull + @Override + public String getShortName() { + return "DontUsePairConstructor"; + } +} + diff --git a/plugins/devkit/src/inspections/internal/UseCoupleInspection.java b/plugins/devkit/src/inspections/internal/UseCoupleInspection.java new file mode 100644 index 000000000000..4b1a97f59aaf --- /dev/null +++ b/plugins/devkit/src/inspections/internal/UseCoupleInspection.java @@ -0,0 +1,92 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.idea.devkit.inspections.internal; + +import com.intellij.codeInspection.ProblemHighlightType; +import com.intellij.codeInspection.ProblemsHolder; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.psi.*; +import org.jetbrains.annotations.Nls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.idea.devkit.inspections.quickfix.UseCoupleQuickFix; + +import java.util.List; + +/** + * @author Konstantin Bulenkov + */ +public class UseCoupleInspection extends InternalInspection { + private static final String PAIR_FQN = "com.intellij.openapi.util.Pair"; + + @Override + public PsiElementVisitor buildInternalVisitor(@NotNull final ProblemsHolder holder, boolean isOnTheFly) { + return new JavaElementVisitor() { + + @Override + public void visitTypeElement(PsiTypeElement type) { + final String canonicalText = type.getType().getCanonicalText(); + if (canonicalText.startsWith(PAIR_FQN)) { + if (canonicalText.contains("<") && canonicalText.endsWith(">")) { + String genericTypes = canonicalText.substring(canonicalText.indexOf('<') + 1, canonicalText.length() - 1); + final List<String> types = StringUtil.split(genericTypes, ","); + if (types.size() == 2 && StringUtil.equals(types.get(0), types.get(1))) { + final List<String> parts = StringUtil.split(types.get(0), "."); + String typeName = parts.get(parts.size() - 1); + final String name = "Change to Couple<" + typeName + ">"; + holder.registerProblem(type, name, ProblemHighlightType.GENERIC_ERROR_OR_WARNING, new UseCoupleQuickFix(name)); + } + } + + } + super.visitTypeElement(type); + } + + @Override + public void visitMethodCallExpression(PsiMethodCallExpression expression) { + if (expression.getText().startsWith("Pair.create")) { + final PsiReference reference = expression.getMethodExpression().getReference(); + if (reference != null) { + final PsiElement method = reference.resolve(); + if (method instanceof PsiMethod) { + final PsiClass psiClass = ((PsiMethod)method).getContainingClass(); + if (psiClass != null && PAIR_FQN.equals(psiClass.getQualifiedName())) { + final PsiType[] types = expression.getArgumentList().getExpressionTypes(); + if (types.length == 2 && types[0].equals(types[1])) { + final String name = "Change to Couple.newOne"; + holder.registerProblem(expression, name, ProblemHighlightType.GENERIC_ERROR_OR_WARNING, new UseCoupleQuickFix(name)); + } + } + } + } + } + super.visitMethodCallExpression(expression); + } + }; + } + + @Nls + @NotNull + @Override + public String getDisplayName() { + return "Use Couple<T> instead of Pair<T, T>"; + } + + @NotNull + @Override + public String getShortName() { + return "UseCouple"; + } +} diff --git a/plugins/devkit/src/inspections/internal/UseVirtualFileEqualsInspection.java b/plugins/devkit/src/inspections/internal/UseVirtualFileEqualsInspection.java new file mode 100644 index 000000000000..ddbedb4e559f --- /dev/null +++ b/plugins/devkit/src/inspections/internal/UseVirtualFileEqualsInspection.java @@ -0,0 +1,54 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.idea.devkit.inspections.internal; + +import com.intellij.codeInspection.ProblemHighlightType; +import com.intellij.codeInspection.ProblemsHolder; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.*; +import com.intellij.psi.util.InheritanceUtil; +import com.siyeh.ig.psiutils.ComparisonUtils; +import org.jetbrains.annotations.NotNull; + +/** + * @author peter + */ +public class UseVirtualFileEqualsInspection extends InternalInspection { + @Override + public PsiElementVisitor buildInternalVisitor(@NotNull final ProblemsHolder holder, boolean isOnTheFly) { + return new JavaElementVisitor() { + @Override + public void visitBinaryExpression(@NotNull PsiBinaryExpression expression) { + super.visitBinaryExpression(expression); + if (!ComparisonUtils.isEqualityComparison(expression)) { + return; + } + final PsiExpression lhs = expression.getLOperand(); + final PsiExpression rhs = expression.getROperand(); + if (rhs == null || + lhs.textMatches(PsiKeyword.NULL) || rhs.textMatches(PsiKeyword.NULL) || + lhs.textMatches(PsiKeyword.THIS) || rhs.textMatches(PsiKeyword.THIS)) { + return; + } + + if (InheritanceUtil.isInheritor(lhs.getType(), VirtualFile.class.getName()) || InheritanceUtil.isInheritor(rhs.getType(), VirtualFile.class.getName())) { + holder.registerProblem(expression, "VirtualFile objects should be compared by equals(), not ==", ProblemHighlightType.GENERIC_ERROR_OR_WARNING); + + } + } + }; + } +} diff --git a/plugins/devkit/src/inspections/quickfix/ChangeToPairCreateQuickFix.java b/plugins/devkit/src/inspections/quickfix/ChangeToPairCreateQuickFix.java new file mode 100644 index 000000000000..a8462a002486 --- /dev/null +++ b/plugins/devkit/src/inspections/quickfix/ChangeToPairCreateQuickFix.java @@ -0,0 +1,49 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.idea.devkit.inspections.quickfix; + +import com.intellij.codeInspection.LocalQuickFixBase; +import com.intellij.codeInspection.ProblemDescriptor; +import com.intellij.openapi.project.Project; +import com.intellij.psi.JavaPsiFacade; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiElementFactory; +import com.intellij.psi.PsiExpression; +import com.intellij.psi.codeStyle.JavaCodeStyleManager; +import org.jetbrains.annotations.NotNull; + +/** + * @author Konstantin Bulenkov + */ +public class ChangeToPairCreateQuickFix extends LocalQuickFixBase { + public ChangeToPairCreateQuickFix() { + super("Change to Pair.create(..., ...)"); + } + + @Override + public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) { + PsiElement element = descriptor.getPsiElement(); + if (element == null) { + return; + } + String text = element.getText(); + String newText = "com.intellij.openapi.util.Pair.create(" + text.substring(text.indexOf('(') + 1); + PsiElementFactory factory = JavaPsiFacade.getInstance(project).getElementFactory(); + PsiExpression expression = factory.createExpressionFromText(newText, element.getContext()); + PsiElement newElement = element.replace(expression); + JavaCodeStyleManager.getInstance(project).shortenClassReferences(newElement); + } +} diff --git a/plugins/devkit/src/inspections/quickfix/CreateHtmlDescriptionFix.java b/plugins/devkit/src/inspections/quickfix/CreateHtmlDescriptionFix.java index e8f498dd28ea..67db267ea30a 100644 --- a/plugins/devkit/src/inspections/quickfix/CreateHtmlDescriptionFix.java +++ b/plugins/devkit/src/inspections/quickfix/CreateHtmlDescriptionFix.java @@ -31,7 +31,7 @@ import com.intellij.openapi.roots.ModuleRootManager; import com.intellij.openapi.ui.popup.JBPopup; import com.intellij.openapi.ui.popup.JBPopupFactory; import com.intellij.openapi.util.Iconable; -import com.intellij.openapi.vfs.VfsUtil; +import com.intellij.openapi.vfs.VfsUtilCore; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiDirectory; import com.intellij.psi.PsiElement; @@ -43,8 +43,8 @@ import com.intellij.util.ArrayUtil; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.idea.devkit.DevKitBundle; -import org.jetbrains.idea.devkit.inspections.InspectionDescriptionNotFoundInspection; -import org.jetbrains.idea.devkit.inspections.IntentionDescriptionNotFoundInspection; +import org.jetbrains.idea.devkit.inspections.DescriptionCheckerUtil; +import org.jetbrains.idea.devkit.inspections.DescriptionType; import org.jetbrains.jps.model.java.JavaModuleSourceRootTypes; import org.jetbrains.jps.model.java.JavaResourceRootType; @@ -57,15 +57,45 @@ import java.util.List; * @author Konstantin Bulenkov */ public class CreateHtmlDescriptionFix implements LocalQuickFix, Iconable { + + @NonNls + private static final String TEMPLATE_NAME = "InspectionDescription.html"; + private final String myFilename; - private final Module myModule; - @NonNls private static final String TEMPLATE_NAME = "InspectionDescription.html"; - private final boolean isIntention; + private final Module myModule; + private final DescriptionType myDescriptionType; - public CreateHtmlDescriptionFix(String filename, Module module, boolean isIntention) { + public CreateHtmlDescriptionFix(String filename, Module module, DescriptionType descriptionType) { myModule = module; - this.isIntention = isIntention; - myFilename = isIntention ? filename : filename + ".html"; + myDescriptionType = descriptionType; + myFilename = getNormalizedFileName(filename); + } + + private boolean isFixedDescriptionFilename() { + return myDescriptionType.isFixedDescriptionFilename(); + } + + private static List<VirtualFile> getPotentialRoots(Module module, PsiDirectory[] dirs) { + if (dirs.length != 0) { + final List<VirtualFile> result = new ArrayList<VirtualFile>(); + for (PsiDirectory dir : dirs) { + final PsiDirectory parent = dir.getParentDirectory(); + if (parent != null) result.add(parent.getVirtualFile()); + } + return result; + } + else { + ModuleRootManager rootManager = ModuleRootManager.getInstance(module); + List<VirtualFile> resourceRoots = rootManager.getSourceRoots(JavaResourceRootType.RESOURCE); + if (!resourceRoots.isEmpty()) { + return resourceRoots; + } + return rootManager.getSourceRoots(JavaModuleSourceRootTypes.SOURCES); + } + } + + private String getNormalizedFileName(String filename) { + return myDescriptionType.isFixedDescriptionFilename() ? filename : filename + ".html"; } @NotNull @@ -79,30 +109,20 @@ public class CreateHtmlDescriptionFix implements LocalQuickFix, Iconable { } public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) { - final PsiDirectory[] dirs; - if (isIntention) { - dirs = IntentionDescriptionNotFoundInspection.getIntentionDescriptionsDirs(myModule); - } - else { - dirs = InspectionDescriptionNotFoundInspection.getInspectionDescriptionsDirs(myModule); - } + final PsiDirectory[] dirs = getDirectories(); final List<VirtualFile> virtualFiles = getPotentialRoots(myModule, dirs); - final VirtualFile[] roots = prepare(VfsUtil.toVirtualFileArray(virtualFiles)); + final VirtualFile[] roots = prepare(VfsUtilCore.toVirtualFileArray(virtualFiles)); if (roots.length == 1) { ApplicationManager.getApplication().runWriteAction(new Runnable() { public void run() { createDescription(roots[0]); } }); - } else { List<String> options = new ArrayList<String>(); for (VirtualFile file : roots) { - String path = file.getPresentableUrl() + File.separator + getDescriptionFolderName() + File.separator + myFilename; - if (isIntention) { - path += File.separator + "description.html"; - } + String path = getPath(file); options.add(path); } final JBList files = new JBList(ArrayUtil.toStringArray(options)); @@ -111,40 +131,33 @@ public class CreateHtmlDescriptionFix implements LocalQuickFix, Iconable { .createListPopupBuilder(files) .setTitle(DevKitBundle.message("select.target.location.of.description", myFilename)) .setItemChoosenCallback(new Runnable() { - public void run() { - final int index = files.getSelectedIndex(); - if (0 <= index && index < roots.length) { - ApplicationManager.getApplication().runWriteAction(new Runnable() { - public void run() { - createDescription(roots[index]); - } - }); + public void run() { + final int index = files.getSelectedIndex(); + if (0 <= index && index < roots.length) { + ApplicationManager.getApplication().runWriteAction(new Runnable() { + public void run() { + createDescription(roots[index]); + } + }); + } } - } - }).createPopup(); + }).createPopup(); final Editor editor = FileEditorManager.getInstance(myModule.getProject()).getSelectedTextEditor(); if (editor == null) return; popup.showInBestPositionFor(editor); } } - private static List<VirtualFile> getPotentialRoots(Module module, PsiDirectory[] dirs) { - if (dirs.length != 0) { - final List<VirtualFile> result = new ArrayList<VirtualFile>(); - for (PsiDirectory dir : dirs) { - final PsiDirectory parent = dir.getParentDirectory(); - if (parent != null) result.add(parent.getVirtualFile()); - } - return result; - } - else { - ModuleRootManager rootManager = ModuleRootManager.getInstance(module); - List<VirtualFile> resourceRoots = rootManager.getSourceRoots(JavaResourceRootType.RESOURCE); - if (!resourceRoots.isEmpty()) { - return resourceRoots; - } - return rootManager.getSourceRoots(JavaModuleSourceRootTypes.SOURCES); + private String getPath(VirtualFile file) { + String path = file.getPresentableUrl() + File.separator + getDescriptionFolderName() + File.separator + myFilename; + if (isFixedDescriptionFilename()) { + path += File.separator + "description.html"; } + return path; + } + + private PsiDirectory[] getDirectories() { + return DescriptionCheckerUtil.getDescriptionsDirs(myModule, myDescriptionType); } private void createDescription(VirtualFile root) { @@ -162,14 +175,15 @@ public class CreateHtmlDescriptionFix implements LocalQuickFix, Iconable { try { descrRoot = descrRoot == null ? psiRoot.createSubdirectory(getDescriptionFolderName()) : descrRoot; - if (isIntention) { + if (isFixedDescriptionFilename()) { PsiDirectory dir = descrRoot.findSubdirectory(myFilename); if (dir == null) { descrRoot = descrRoot.createSubdirectory(myFilename); } } final FileTemplate descrTemplate = FileTemplateManager.getInstance().getJ2eeTemplate(TEMPLATE_NAME); - final PsiElement template = FileTemplateUtil.createFromTemplate(descrTemplate, isIntention? "description.html" : myFilename, null, descrRoot); + final PsiElement template = + FileTemplateUtil.createFromTemplate(descrTemplate, getNewFileName(), null, descrRoot); if (template instanceof PsiFile) { final VirtualFile file = ((PsiFile)template).getVirtualFile(); if (file != null) { @@ -181,6 +195,10 @@ public class CreateHtmlDescriptionFix implements LocalQuickFix, Iconable { } } + private String getNewFileName() { + return isFixedDescriptionFilename() ? "description.html" : myFilename; + } + public Icon getIcon(int flags) { return new LayeredIcon(AllIcons.FileTypes.Html, AllIcons.Actions.New); } @@ -192,7 +210,7 @@ public class CreateHtmlDescriptionFix implements LocalQuickFix, Iconable { found.add(root); } } - return found.size() > 0 ? VfsUtil.toVirtualFileArray(found) : roots; + return found.size() > 0 ? VfsUtilCore.toVirtualFileArray(found) : roots; } private boolean containsDescriptionDir(VirtualFile root) { @@ -206,6 +224,6 @@ public class CreateHtmlDescriptionFix implements LocalQuickFix, Iconable { } private String getDescriptionFolderName() { - return isIntention ? "intentionDescriptions" : "inspectionDescriptions"; + return myDescriptionType.getDescriptionFolder(); } } diff --git a/plugins/devkit/src/inspections/quickfix/PluginDescriptorChooser.java b/plugins/devkit/src/inspections/quickfix/PluginDescriptorChooser.java new file mode 100644 index 000000000000..f46429fcf44f --- /dev/null +++ b/plugins/devkit/src/inspections/quickfix/PluginDescriptorChooser.java @@ -0,0 +1,237 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.idea.devkit.inspections.quickfix; + +import com.google.common.collect.ImmutableMap; +import com.intellij.codeInsight.hint.HintManager; +import com.intellij.icons.AllIcons; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.module.ModuleUtilCore; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.roots.ProjectRootManager; +import com.intellij.openapi.roots.ui.configuration.ModulesAlphaComparator; +import com.intellij.openapi.ui.popup.JBPopupFactory; +import com.intellij.openapi.ui.popup.ListSeparator; +import com.intellij.openapi.ui.popup.PopupStep; +import com.intellij.openapi.ui.popup.util.BaseListPopupStep; +import com.intellij.openapi.util.Comparing; +import com.intellij.openapi.util.Condition; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.PsiFile; +import com.intellij.util.Consumer; +import com.intellij.util.Function; +import com.intellij.util.containers.ContainerUtil; +import com.intellij.util.ui.EmptyIcon; +import com.intellij.util.xml.DomFileElement; +import com.intellij.util.xml.DomService; +import com.intellij.xml.util.IncludedXmlTag; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.idea.devkit.dom.Extensions; +import org.jetbrains.idea.devkit.dom.IdeaPlugin; + +import javax.swing.*; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +public class PluginDescriptorChooser { + + private static final ImmutableMap<String, String> INTELLIJ_MODULES = ImmutableMap.<String, String>builder() + .put("platform-api", "PlatformExtensions.xml") + .put("platform-impl", "PlatformExtensions.xml") + .put("lang-api", "LangExtensions.xml") + .put("lang-impl", "LangExtensions.xml") + .put("vcs-api", "VcsExtensions.xml") + .put("vcs-impl", "VcsExtensions.xml") + .put("openapi", "IdeaPlugin.xml") + .put("java-impl", "IdeaPlugin.xml") + .build(); + + public static void show(final Project project, + final Editor editor, + final PsiFile file, + final Consumer<DomFileElement<IdeaPlugin>> consumer) { + final Module module = ModuleUtilCore.findModuleForPsiElement(file); + assert module != null; + List<DomFileElement<IdeaPlugin>> elements = + DomService.getInstance().getFileElements(IdeaPlugin.class, + project, + module.getModuleContentWithDependenciesScope()); + + elements = ContainerUtil.filter(elements, new Condition<DomFileElement<IdeaPlugin>>() { + @Override + public boolean value(DomFileElement<IdeaPlugin> element) { + VirtualFile virtualFile = element.getFile().getVirtualFile(); + return virtualFile != null && ProjectRootManager.getInstance(project).getFileIndex().isInContent(virtualFile); + } + }); + + elements = findAppropriateIntelliJModule(module.getName(), elements); + + if (elements.isEmpty()) { + HintManager.getInstance().showErrorHint(editor, "Cannot find plugin descriptor"); + return; + } + + if (elements.size() == 1) { + consumer.consume(elements.get(0)); + return; + } + + final BaseListPopupStep<PluginDescriptorCandidate> popupStep = + new BaseListPopupStep<PluginDescriptorCandidate>("Choose Plugin Descriptor", + createCandidates(module, elements)) { + + @Override + public boolean isSpeedSearchEnabled() { + return true; + } + + @Override + public Icon getIconFor(PluginDescriptorCandidate candidate) { + return candidate.getIcon(); + } + + @NotNull + @Override + public String getTextFor(PluginDescriptorCandidate candidate) { + return candidate.getText(); + } + + @Nullable + @Override + public ListSeparator getSeparatorAbove(PluginDescriptorCandidate candidate) { + final String separatorText = candidate.getSeparatorText(); + if (separatorText != null) { + return new ListSeparator(separatorText); + } + return null; + } + + @Override + public PopupStep onChosen(PluginDescriptorCandidate selectedValue, boolean finalChoice) { + consumer.consume(selectedValue.myDomFileElement); + return FINAL_CHOICE; + } + }; + JBPopupFactory.getInstance().createListPopup(popupStep).showInBestPositionFor(editor); + } + + @NotNull + public static Extensions findOrCreateExtensionsForEP(DomFileElement<IdeaPlugin> domFileElement, String epName) { + final IdeaPlugin ideaPlugin = domFileElement.getRootElement(); + final List<Extensions> extensionsList = ideaPlugin.getExtensions(); + for (Extensions extensions : extensionsList) { + if (extensions.getXmlTag() instanceof IncludedXmlTag) { + continue; + } + String s = extensions.getDefaultExtensionNs().getStringValue(); + if (s != null && epName.startsWith(s)) { + return extensions; + } + } + + Extensions extensions = ideaPlugin.addExtensions(); + final String epPrefix = StringUtil.getPackageName(epName); + extensions.getDefaultExtensionNs().setStringValue(epPrefix); + return extensions; + } + + private static List<PluginDescriptorCandidate> createCandidates(final Module currentModule, + List<DomFileElement<IdeaPlugin>> elements) { + Collections.sort(elements, new Comparator<DomFileElement<IdeaPlugin>>() { + @Override + public int compare(DomFileElement<IdeaPlugin> o1, DomFileElement<IdeaPlugin> o2) { + // current module = first group + final Module module1 = o1.getModule(); + if (currentModule.equals(module1)) return -1; + final Module module2 = o2.getModule(); + if (currentModule.equals(module2)) return 1; + + return ModulesAlphaComparator.INSTANCE.compare(module1, module2); + } + }); + Collections.sort(elements, new Comparator<DomFileElement<IdeaPlugin>>() { + @Override + public int compare(DomFileElement<IdeaPlugin> o1, DomFileElement<IdeaPlugin> o2) { + if (!Comparing.equal(o1.getModule(), o2.getModule())) return 0; + return o1.getFile().getName().compareTo(o2.getFile().getName()); + } + }); + + return ContainerUtil.map(elements, new Function<DomFileElement<IdeaPlugin>, PluginDescriptorCandidate>() { + + private Module myLastModule = currentModule; + + @Override + public PluginDescriptorCandidate fun(DomFileElement<IdeaPlugin> element) { + final Module module = element.getModule(); + boolean startsNewGroup = !myLastModule.equals(module); + myLastModule = module; + return new PluginDescriptorCandidate(element, startsNewGroup); + } + }); + } + + private static List<DomFileElement<IdeaPlugin>> findAppropriateIntelliJModule(String moduleName, + List<DomFileElement<IdeaPlugin>> elements) { + String extensionsFile = INTELLIJ_MODULES.get(moduleName); + if (extensionsFile != null) { + for (DomFileElement<IdeaPlugin> element : elements) { + if (element.getFile().getName().equals(extensionsFile)) { + return Collections.singletonList(element); + } + } + } + return elements; + } + + + private static class PluginDescriptorCandidate { + private final DomFileElement<IdeaPlugin> myDomFileElement; + private final boolean myStartsNewGroup; + + private PluginDescriptorCandidate(DomFileElement<IdeaPlugin> domFileElement, + boolean startsNewGroup) { + myDomFileElement = domFileElement; + myStartsNewGroup = startsNewGroup; + } + + public String getText() { + final String name = myDomFileElement.getFile().getName(); + final String pluginId = getPluginId(); + return pluginId != null ? name + " [" + pluginId + "]" : name; + } + + public Icon getIcon() { + return getPluginId() != null ? AllIcons.Nodes.Plugin : EmptyIcon.create(AllIcons.Nodes.Plugin); + } + + public String getSeparatorText() { + if (!myStartsNewGroup) return null; + + final Module module = myDomFileElement.getModule(); + return module == null ? null : module.getName(); + } + + private String getPluginId() { + return myDomFileElement.getRootElement().getPluginId(); + } + } +} diff --git a/plugins/devkit/src/inspections/quickfix/RegisterActionFix.java b/plugins/devkit/src/inspections/quickfix/RegisterActionFix.java index 061deef3d7b3..3eb7ef22ca46 100644 --- a/plugins/devkit/src/inspections/quickfix/RegisterActionFix.java +++ b/plugins/devkit/src/inspections/quickfix/RegisterActionFix.java @@ -16,14 +16,17 @@ package org.jetbrains.idea.devkit.inspections.quickfix; import com.intellij.codeInspection.ProblemDescriptor; +import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.project.Project; import com.intellij.psi.PsiClass; import com.intellij.psi.xml.XmlFile; import com.intellij.util.IncorrectOperationException; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.TestOnly; import org.jetbrains.idea.devkit.DevKitBundle; import org.jetbrains.idea.devkit.actions.NewActionDialog; +import org.jetbrains.idea.devkit.util.ActionData; import org.jetbrains.idea.devkit.util.ActionType; -import org.jetbrains.annotations.NotNull; public class RegisterActionFix extends AbstractRegisterFix { private NewActionDialog myDialog; @@ -37,6 +40,11 @@ public class RegisterActionFix extends AbstractRegisterFix { } public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) { + if (ApplicationManager.getApplication().isUnitTestMode()) { + super.applyFix(project, descriptor); + return; + } + try { myDialog = new NewActionDialog(myClass); myDialog.show(); @@ -52,9 +60,20 @@ public class RegisterActionFix extends AbstractRegisterFix { public void patchPluginXml(XmlFile pluginXml, PsiClass aClass) throws IncorrectOperationException { if (ActionType.GROUP.isOfType(aClass)) { - ActionType.GROUP.patchPluginXml(pluginXml, aClass, myDialog); - } else { - ActionType.ACTION.patchPluginXml(pluginXml, aClass, myDialog); + ActionType.GROUP.patchPluginXml(pluginXml, aClass, getActionData()); + } + else { + ActionType.ACTION.patchPluginXml(pluginXml, aClass, getActionData()); + } + } + + @TestOnly + public static ActionData ourTestActionData = null; + + private ActionData getActionData() { + if (ApplicationManager.getApplication().isUnitTestMode()) { + return ourTestActionData; } + return myDialog; } } diff --git a/plugins/devkit/src/inspections/quickfix/RegisterExtensionFix.java b/plugins/devkit/src/inspections/quickfix/RegisterExtensionFix.java index 65392a815e93..e51616977ec6 100644 --- a/plugins/devkit/src/inspections/quickfix/RegisterExtensionFix.java +++ b/plugins/devkit/src/inspections/quickfix/RegisterExtensionFix.java @@ -23,6 +23,7 @@ import com.intellij.openapi.command.WriteCommandAction; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.extensions.KeyedFactoryEPBean; import com.intellij.openapi.fileTypes.FileTypeExtensionPoint; +import com.intellij.openapi.project.DumbService; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.popup.JBPopupFactory; import com.intellij.openapi.ui.popup.PopupStep; @@ -42,6 +43,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.idea.devkit.dom.Extension; import org.jetbrains.idea.devkit.dom.Extensions; import org.jetbrains.idea.devkit.dom.IdeaPlugin; +import org.jetbrains.idea.devkit.util.ExtensionPointCandidate; import java.util.List; @@ -71,12 +73,12 @@ public class RegisterExtensionFix implements IntentionAction { @Override public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file) { - return true; + return !DumbService.isDumb(project); } @Override public void invoke(@NotNull Project project, final Editor editor, PsiFile file) throws IncorrectOperationException { - RegisterInspectionFix.choosePluginDescriptor(project, editor, file, new Consumer<DomFileElement<IdeaPlugin>>() { + PluginDescriptorChooser.show(project, editor, file, new Consumer<DomFileElement<IdeaPlugin>>() { @Override public void consume(DomFileElement<IdeaPlugin> element) { doFix(editor, element); @@ -101,11 +103,11 @@ public class RegisterExtensionFix implements IntentionAction { } } - private void registerExtension(final DomFileElement<IdeaPlugin> element, final ExtensionPointCandidate candidate) { - PsiElement navTarget = new WriteCommandAction<PsiElement>(element.getFile().getProject(), element.getFile()) { + private void registerExtension(final DomFileElement<IdeaPlugin> selectedValue, final ExtensionPointCandidate candidate) { + PsiElement navTarget = new WriteCommandAction<PsiElement>(selectedValue.getFile().getProject(), selectedValue.getFile()) { @Override - protected void run(Result<PsiElement> result) throws Throwable { - Extensions extensions = RegisterInspectionFix.getExtension(element.getRootElement(), candidate.epName); + protected void run(@NotNull Result<PsiElement> result) throws Throwable { + Extensions extensions = PluginDescriptorChooser.findOrCreateExtensionsForEP(selectedValue, candidate.epName); Extension extension = extensions.addExtension(candidate.epName); XmlTag tag = extension.getXmlTag(); PsiElement navTarget = null; diff --git a/plugins/devkit/src/inspections/quickfix/RegisterExtensionFixProvider.java b/plugins/devkit/src/inspections/quickfix/RegisterExtensionFixProvider.java index ba02d703c93c..815d597442c5 100644 --- a/plugins/devkit/src/inspections/quickfix/RegisterExtensionFixProvider.java +++ b/plugins/devkit/src/inspections/quickfix/RegisterExtensionFixProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2012 JetBrains s.r.o. + * Copyright 2000-2014 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,19 +21,14 @@ import com.intellij.codeInspection.InspectionEP; import com.intellij.codeInspection.LocalInspectionEP; import com.intellij.codeInspection.LocalInspectionTool; import com.intellij.codeInspection.reference.UnusedDeclarationFixProvider; -import com.intellij.ide.highlighter.XmlFileType; -import com.intellij.psi.*; -import com.intellij.psi.search.GlobalSearchScope; -import com.intellij.psi.search.ProjectScope; -import com.intellij.psi.search.PsiNonJavaFileReferenceProcessor; -import com.intellij.psi.search.PsiSearchHelper; +import com.intellij.psi.PsiClass; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiIdentifier; import com.intellij.psi.util.InheritanceUtil; -import com.intellij.psi.util.PsiTreeUtil; -import com.intellij.psi.xml.XmlTag; import org.jetbrains.annotations.NotNull; +import org.jetbrains.idea.devkit.util.ExtensionPointCandidate; +import org.jetbrains.idea.devkit.util.ExtensionPointLocator; -import java.util.ArrayList; -import java.util.HashSet; import java.util.List; /** @@ -48,79 +43,20 @@ public class RegisterExtensionFixProvider implements UnusedDeclarationFixProvide if (!(element instanceof PsiIdentifier)) return IntentionAction.EMPTY_ARRAY; PsiElement parent = element.getParent(); if (!(parent instanceof PsiClass)) return IntentionAction.EMPTY_ARRAY; - PsiClass parentClass = (PsiClass)parent; - if (InheritanceUtil.isInheritor(parentClass, LocalInspectionTool.class.getName())) { - return new IntentionAction[] { new RegisterInspectionFix(parentClass, LocalInspectionEP.LOCAL_INSPECTION) }; - } - if (InheritanceUtil.isInheritor(parentClass, GlobalInspectionTool.class.getName())) { - return new IntentionAction[] { new RegisterInspectionFix(parentClass, InspectionEP.GLOBAL_INSPECTION) }; - } - List<ExtensionPointCandidate> candidateList = new ArrayList<ExtensionPointCandidate>(); - findExtensionPointCandidatesInHierarchy(parentClass, candidateList, new HashSet<PsiClass>()); - if (!candidateList.isEmpty()) { - return new IntentionAction[] { new RegisterExtensionFix(parentClass, candidateList) }; - } - return IntentionAction.EMPTY_ARRAY; - } - - private static void findExtensionPointCandidatesInHierarchy(PsiClass aClass, - List<ExtensionPointCandidate> list, - HashSet<PsiClass> processed) { - for (PsiClass superClass : aClass.getSupers()) { - if (!processed.add(superClass) || CommonClassNames.JAVA_LANG_OBJECT.equals(superClass.getQualifiedName())) { - continue; - } - findExtensionPointCandidates(superClass, list); - findExtensionPointCandidatesInHierarchy(superClass, list, processed); - } - } - - private static void findExtensionPointCandidates(PsiClass aClass, final List<ExtensionPointCandidate> list) { - String name = aClass.getQualifiedName(); - if (name == null) { - return; - } - GlobalSearchScope scope = GlobalSearchScope.getScopeRestrictedByFileTypes(ProjectScope.getAllScope(aClass.getProject()), XmlFileType.INSTANCE); - PsiSearchHelper.SERVICE.getInstance(aClass.getProject()).processUsagesInNonJavaFiles(name, new PsiNonJavaFileReferenceProcessor() { - @Override - public boolean process(PsiFile file, int startOffset, int endOffset) { - PsiElement element = file.findElementAt(startOffset); - processExtensionPointCandidate(element, list); - return true; - } - }, scope); - } - private static void processExtensionPointCandidate(PsiElement element, List<ExtensionPointCandidate> list) { - XmlTag tag = PsiTreeUtil.getParentOfType(element, XmlTag.class); - if (tag == null) return; - if ("extensionPoint".equals(tag.getName())) { - String epName = getEPName(tag); - if (epName != null) { - list.add(new ExtensionPointCandidate(epName)); - } + PsiClass psiClass = (PsiClass)parent; + if (InheritanceUtil.isInheritor(psiClass, LocalInspectionTool.class.getName())) { + return new IntentionAction[]{new RegisterInspectionFix(psiClass, LocalInspectionEP.LOCAL_INSPECTION)}; } - else if ("with".equals(tag.getName())) { - XmlTag extensionPointTag = tag.getParentTag(); - if (!"extensionPoint".equals(extensionPointTag.getName())) return; - String attrName = tag.getAttributeValue("attribute"); - String tagName = tag.getAttributeValue("tag"); - String epName = getEPName(extensionPointTag); - String beanClassName = extensionPointTag.getAttributeValue("beanClass"); - if ((attrName == null && tagName == null) || epName == null) return; - list.add(new ExtensionPointCandidate(epName, attrName, tagName, beanClassName)); + if (InheritanceUtil.isInheritor(psiClass, GlobalInspectionTool.class.getName())) { + return new IntentionAction[]{new RegisterInspectionFix(psiClass, InspectionEP.GLOBAL_INSPECTION)}; } - } - private static String getEPName(XmlTag tag) { - String qName = tag.getAttributeValue("qualifiedName"); - if (qName != null) { - return qName; - } - String name = tag.getAttributeValue("name"); - if (name != null) { - return "com.intellij." + name; + ExtensionPointLocator extensionPointLocator = new ExtensionPointLocator(psiClass); + List<ExtensionPointCandidate> candidateList = extensionPointLocator.findSuperCandidates(); + if (!candidateList.isEmpty()) { + return new IntentionAction[]{new RegisterExtensionFix(psiClass, candidateList)}; } - return null; + return IntentionAction.EMPTY_ARRAY; } } diff --git a/plugins/devkit/src/inspections/quickfix/RegisterInspectionFix.java b/plugins/devkit/src/inspections/quickfix/RegisterInspectionFix.java index 2c1aec6f72c7..fb4fa6893f8d 100644 --- a/plugins/devkit/src/inspections/quickfix/RegisterInspectionFix.java +++ b/plugins/devkit/src/inspections/quickfix/RegisterInspectionFix.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2013 JetBrains s.r.o. + * Copyright 2000-2014 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,48 +15,30 @@ */ package org.jetbrains.idea.devkit.inspections.quickfix; -import com.google.common.collect.ImmutableMap; -import com.intellij.codeInsight.hint.HintManager; import com.intellij.codeInsight.intention.IntentionAction; import com.intellij.codeInspection.InspectionEP; -import com.intellij.ide.TypePresentationService; import com.intellij.openapi.application.Result; import com.intellij.openapi.command.WriteCommandAction; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.extensions.ExtensionPointName; -import com.intellij.openapi.module.Module; -import com.intellij.openapi.module.ModuleUtil; +import com.intellij.openapi.project.DumbService; import com.intellij.openapi.project.Project; -import com.intellij.openapi.roots.ProjectRootManager; -import com.intellij.openapi.ui.popup.JBPopupFactory; -import com.intellij.openapi.ui.popup.PopupStep; -import com.intellij.openapi.ui.popup.util.BaseListPopupStep; -import com.intellij.openapi.util.Condition; -import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiClass; import com.intellij.psi.PsiFile; import com.intellij.psi.xml.XmlTag; import com.intellij.util.Consumer; import com.intellij.util.IncorrectOperationException; import com.intellij.util.PsiNavigateUtil; -import com.intellij.util.containers.ContainerUtil; import com.intellij.util.xml.DomFileElement; -import com.intellij.util.xml.DomService; -import com.intellij.xml.util.IncludedXmlTag; import org.jetbrains.annotations.NotNull; import org.jetbrains.idea.devkit.DevKitBundle; import org.jetbrains.idea.devkit.dom.Extension; import org.jetbrains.idea.devkit.dom.Extensions; import org.jetbrains.idea.devkit.dom.IdeaPlugin; -import javax.swing.*; -import java.util.Collections; -import java.util.List; - /** -* @author Dmitry Avdeev -* Date: 1/20/12 -*/ + * @author Dmitry Avdeev + */ class RegisterInspectionFix implements IntentionAction { private final PsiClass myPsiClass; @@ -81,12 +63,12 @@ class RegisterInspectionFix implements IntentionAction { @Override public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file) { - return true; + return !DumbService.isDumb(project); } @Override public void invoke(@NotNull final Project project, final Editor editor, final PsiFile file) throws IncorrectOperationException { - choosePluginDescriptor(project, editor, file, new Consumer<DomFileElement<IdeaPlugin>>() { + PluginDescriptorChooser.show(project, editor, file, new Consumer<DomFileElement<IdeaPlugin>>() { @Override public void consume(DomFileElement<IdeaPlugin> element) { doFix(element, project, file); @@ -94,94 +76,12 @@ class RegisterInspectionFix implements IntentionAction { }); } - public static void choosePluginDescriptor(final Project project, Editor editor, final PsiFile file, - final Consumer<DomFileElement<IdeaPlugin>> consumer) { - Module module = ModuleUtil.findModuleForPsiElement(file); - assert module != null; - List<DomFileElement<IdeaPlugin>> elements = - DomService.getInstance().getFileElements(IdeaPlugin.class, project, module.getModuleContentWithDependenciesScope()); - - elements = ContainerUtil.filter(elements, new Condition<DomFileElement<IdeaPlugin>>() { - @Override - public boolean value(DomFileElement<IdeaPlugin> element) { - VirtualFile virtualFile = element.getFile().getVirtualFile(); - return virtualFile != null && ProjectRootManager.getInstance(project).getFileIndex().isInContent(virtualFile); - } - }); - - elements = findAppropriateIntelliJModule(module.getName(), elements); - - if (elements.isEmpty()) { - HintManager.getInstance().showErrorHint(editor, "Cannot find plugin descriptor"); - return; - } - - if (elements.size() == 1) { - consumer.consume(elements.get(0)); - return; - } - - final BaseListPopupStep<DomFileElement<IdeaPlugin>> popupStep = - new BaseListPopupStep<DomFileElement<IdeaPlugin>>("Choose Plugin Descriptor", elements) { - - @Override - public boolean isSpeedSearchEnabled() { - return true; - } - - @Override - public Icon getIconFor(DomFileElement<IdeaPlugin> aValue) { - return TypePresentationService.getService().getIcon(aValue); - } - - @NotNull - @Override - public String getTextFor(DomFileElement<IdeaPlugin> value) { - final String name = value.getFile().getName(); - final Module module = value.getModule(); - return module != null ? name + " [" + module.getName() + "]" : name; - } - - @Override - public PopupStep onChosen(DomFileElement<IdeaPlugin> selectedValue, boolean finalChoice) { - consumer.consume(selectedValue); - return FINAL_CHOICE; - } - }; - JBPopupFactory.getInstance().createListPopup(popupStep) - .showInBestPositionFor(editor); - } - - private static final ImmutableMap<String, String> INTELLIJ_MODULES = ImmutableMap.<String, String>builder() - .put("platform-api", "PlatformExtensions.xml") - .put("platform-impl", "PlatformExtensions.xml") - .put("lang-api", "LangExtensions.xml") - .put("lang-impl", "LangExtensions.xml") - .put("vcs-api", "VcsExtensions.xml") - .put("vcs-impl", "VcsExtensions.xml") - .put("openapi", "IdeaPlugin.xml") - .put("java-impl", "IdeaPlugin.xml") - .build(); - - private static List<DomFileElement<IdeaPlugin>> findAppropriateIntelliJModule(String name, List<DomFileElement<IdeaPlugin>> elements) { - String extensionsFile = INTELLIJ_MODULES.get(name); - if (extensionsFile != null) { - for (DomFileElement<IdeaPlugin> element : elements) { - if (element.getFile().getName().equals(extensionsFile)) { - return Collections.singletonList(element); - } - } - } - return elements; - } - - private void doFix(DomFileElement<IdeaPlugin> selectedValue, final Project project, final PsiFile file) { - final IdeaPlugin plugin = selectedValue.getRootElement(); + private void doFix(final DomFileElement<IdeaPlugin> selectedValue, final Project project, final PsiFile file) { Extension extension = new WriteCommandAction<Extension>(project, file) { @Override - protected void run(Result<Extension> result) throws Throwable { - final Extensions extensions = getExtension(plugin, myEp.getName()); + protected void run(@NotNull Result<Extension> result) throws Throwable { + final Extensions extensions = PluginDescriptorChooser.findOrCreateExtensionsForEP(selectedValue, myEp.getName()); Extension extension = extensions.addExtension(myEp.getName()); XmlTag tag = extension.getXmlTag(); tag.setAttribute("implementationClass", myPsiClass.getQualifiedName()); @@ -191,26 +91,6 @@ class RegisterInspectionFix implements IntentionAction { PsiNavigateUtil.navigate(extension.getXmlTag()); } - public static Extensions getExtension(IdeaPlugin plugin, String epName) { - final List<Extensions> extensionsList = plugin.getExtensions(); - Extensions extensions = null; - for (Extensions e : extensionsList) { - if (e.getXmlTag() instanceof IncludedXmlTag) { - continue; - } - String s = e.getDefaultExtensionNs().getStringValue(); - if (s != null && epName.startsWith(s)) { - extensions = e; - break; - } - } - if (extensions == null) { - extensions = plugin.addExtensions(); - extensions.getDefaultExtensionNs().setStringValue("com.intellij"); - } - return extensions; - } - @Override public boolean startInWriteAction() { return false; diff --git a/plugins/devkit/src/inspections/quickfix/UseCoupleQuickFix.java b/plugins/devkit/src/inspections/quickfix/UseCoupleQuickFix.java new file mode 100644 index 000000000000..74850b937a5c --- /dev/null +++ b/plugins/devkit/src/inspections/quickfix/UseCoupleQuickFix.java @@ -0,0 +1,52 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.idea.devkit.inspections.quickfix; + +import com.intellij.codeInspection.LocalQuickFixBase; +import com.intellij.codeInspection.ProblemDescriptor; +import com.intellij.openapi.project.Project; +import com.intellij.psi.*; +import com.intellij.psi.codeStyle.JavaCodeStyleManager; +import org.jetbrains.annotations.NotNull; + +/** +* @author Konstantin Bulenkov +*/ +public class UseCoupleQuickFix extends LocalQuickFixBase { + private static final String COUPLE_FQN = "com.intellij.openapi.util.Couple"; + + public UseCoupleQuickFix(String text) { + super(text); + } + + @Override + public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) { + final PsiElement element = descriptor.getPsiElement(); + final PsiElementFactory factory = JavaPsiFacade.getInstance(project).getElementFactory(); + final PsiElement newElement; + if (element instanceof PsiTypeElement) { + final String canonicalText = ((PsiTypeElement)element).getType().getCanonicalText(); + final String type = canonicalText.substring(canonicalText.indexOf('<') + 1, canonicalText.indexOf(',')); + final PsiTypeElement newType = factory.createTypeElementFromText(COUPLE_FQN + "<" + type + ">", element.getContext()); + newElement = element.replace(newType); + } else { + final String text = COUPLE_FQN + ".newOne" + element.getText().substring("Pair.create".length()); + final PsiExpression expression = factory.createExpressionFromText(text, element.getContext()); + newElement = element.replace(expression); + } + JavaCodeStyleManager.getInstance(project).shortenClassReferences(newElement); + } +} diff --git a/plugins/devkit/src/internal/DumpCleanHighlightingTestdataAction.java b/plugins/devkit/src/internal/DumpCleanHighlightingTestdataAction.java new file mode 100644 index 000000000000..aeee529d9935 --- /dev/null +++ b/plugins/devkit/src/internal/DumpCleanHighlightingTestdataAction.java @@ -0,0 +1,99 @@ +/* + * Copyright 2000-2013 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.idea.devkit.internal; + +import com.intellij.openapi.actionSystem.*; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.editor.Document; +import com.intellij.openapi.fileChooser.FileChooser; +import com.intellij.openapi.fileChooser.FileChooserDescriptor; +import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory; +import com.intellij.openapi.fileEditor.FileDocumentManager; +import com.intellij.openapi.project.DumbAware; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.Messages; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.vfs.VfsUtilCore; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.PsiFile; +import com.intellij.testFramework.ExpectedHighlightingData; + +import java.io.File; +import java.io.IOException; + +public class DumpCleanHighlightingTestdataAction extends AnAction implements DumbAware { + private static final Logger LOG = Logger.getInstance("#" + DumpCleanHighlightingTestdataAction.class); + + public DumpCleanHighlightingTestdataAction() { + super("Dump highlighting-markup-free data"); + } + + @Override + public void actionPerformed(final AnActionEvent event) { + final DataContext dataContext = event.getDataContext(); + final Project project = CommonDataKeys.PROJECT.getData(dataContext); + final PsiFile psiFile = CommonDataKeys.PSI_FILE.getData(dataContext); + if (psiFile != null) { + final VirtualFile virtualFile = psiFile.getVirtualFile(); + if (virtualFile != null) { + final Document document = FileDocumentManager.getInstance().getDocument(virtualFile); + if (document != null) { + final ExpectedHighlightingData data = new ExpectedHighlightingData(document, true, true); + data.init(); + } + return; + } + } + final FileChooserDescriptor descriptor = FileChooserDescriptorFactory.createSingleFolderDescriptor(); + descriptor.setTitle("Choose Directory"); + descriptor.setDescription("Directory containing highlighting test data"); + final VirtualFile dirToProcess = FileChooser.chooseFile(descriptor, project, null); + if (dirToProcess != null) { + LOG.assertTrue(project != null); + final FileChooserDescriptor targetDescriptor = FileChooserDescriptorFactory.createSingleFolderDescriptor(); + targetDescriptor.setTitle("Choose Directory"); + targetDescriptor.setDescription("Directory where highlighting-markup-free copies would be placed"); + final VirtualFile destinationFolder = FileChooser.chooseFile(targetDescriptor, project, null); + if (dirToProcess.equals(destinationFolder)) { + Messages.showErrorDialog(project, "Source and destination roots should differ", "Reject to Proceed"); + return; + } + if (destinationFolder != null) { + final File destination = VfsUtilCore.virtualToIoFile(destinationFolder); + final VirtualFile[] files = dirToProcess.getChildren(); + for (VirtualFile virtualFile : files) { + final Document document = FileDocumentManager.getInstance().getDocument(virtualFile); + if (document != null) { + final ExpectedHighlightingData data = new ExpectedHighlightingData(document, true, true); + data.init(); + final File file = new File(destination, virtualFile.getName()); + try { + FileUtil.writeToFile(file, document.getText()); + } + catch (IOException e) { + LOG.error(e); + } + } + } + } + } + } + + @Override + public void update(AnActionEvent e) { + e.getPresentation().setEnabled(CommonDataKeys.PROJECT.getData(e.getDataContext()) != null); + } +}
\ No newline at end of file diff --git a/plugins/devkit/src/navigation/DescriptionTypeRelatedItemLineMarkerProvider.java b/plugins/devkit/src/navigation/DescriptionTypeRelatedItemLineMarkerProvider.java new file mode 100644 index 000000000000..1d759b6a8669 --- /dev/null +++ b/plugins/devkit/src/navigation/DescriptionTypeRelatedItemLineMarkerProvider.java @@ -0,0 +1,159 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.idea.devkit.navigation; + +import com.intellij.codeInsight.daemon.RelatedItemLineMarkerInfo; +import com.intellij.codeInsight.daemon.RelatedItemLineMarkerProvider; +import com.intellij.codeInsight.navigation.NavigationGutterIconBuilder; +import com.intellij.icons.AllIcons; +import com.intellij.navigation.GotoRelatedItem; +import com.intellij.openapi.editor.markup.GutterIconRenderer; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.module.ModuleUtilCore; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.psi.*; +import com.intellij.psi.search.GlobalSearchScope; +import com.intellij.psi.util.InheritanceUtil; +import com.intellij.util.NotNullFunction; +import com.intellij.util.containers.ContainerUtil; +import com.intellij.util.containers.SortedList; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.idea.devkit.inspections.DescriptionCheckerUtil; +import org.jetbrains.idea.devkit.inspections.DescriptionType; +import org.jetbrains.idea.devkit.inspections.InspectionDescriptionInfo; +import org.jetbrains.idea.devkit.util.PsiUtil; + +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +public class DescriptionTypeRelatedItemLineMarkerProvider extends RelatedItemLineMarkerProvider { + + private static final NotNullFunction<PsiFile, Collection<? extends PsiElement>> CONVERTER = + new NotNullFunction<PsiFile, Collection<? extends PsiElement>>() { + @NotNull + @Override + public Collection<? extends PsiElement> fun(PsiFile psiFile) { + return ContainerUtil.createMaybeSingletonList(psiFile); + } + }; + + private static final NotNullFunction<PsiFile, Collection<? extends GotoRelatedItem>> RELATED_ITEM_PROVIDER = + new NotNullFunction<PsiFile, Collection<? extends GotoRelatedItem>>() { + @NotNull + @Override + public Collection<? extends GotoRelatedItem> fun(PsiFile psiFile) { + return GotoRelatedItem.createItems(Collections.singleton(psiFile), "DevKit"); + } + }; + + @Override + protected void collectNavigationMarkers(@NotNull PsiElement element, Collection<? super RelatedItemLineMarkerInfo> result) { + if (element instanceof PsiClass) { + process((PsiClass)element, result); + } + } + + private static void process(PsiClass psiClass, Collection<? super RelatedItemLineMarkerInfo> result) { + if (!PsiUtil.isInstantiable(psiClass)) return; + + Module module = ModuleUtilCore.findModuleForPsiElement(psiClass); + if (module == null) return; + + final GlobalSearchScope scope = GlobalSearchScope.moduleRuntimeScope(module, false); + final PsiClass actionClass = JavaPsiFacade.getInstance(psiClass.getProject()) + .findClass(DescriptionType.INSPECTION.getClassName(), scope); + if (actionClass == null) return; + + PsiElement highlightingElement = psiClass.getNameIdentifier(); + if (highlightingElement == null) return; + + for (DescriptionType type : DescriptionType.values()) { + if (!InheritanceUtil.isInheritor(psiClass, type.getClassName())) { + continue; + } + + String descriptionDirName = DescriptionCheckerUtil.getDescriptionDirName(psiClass); + if (StringUtil.isEmptyOrSpaces(descriptionDirName)) { + return; + } + + if (type == DescriptionType.INSPECTION) { + final InspectionDescriptionInfo info = InspectionDescriptionInfo.create(module, psiClass); + if (info.hasDescriptionFile()) { + addDescriptionFileGutterIcon(highlightingElement, info.getDescriptionFile(), result); + } + return; + } + + for (PsiDirectory descriptionDir : DescriptionCheckerUtil.getDescriptionsDirs(module, type)) { + PsiDirectory dir = descriptionDir.findSubdirectory(descriptionDirName); + if (dir == null) continue; + final PsiFile descriptionFile = dir.findFile("description.html"); + if (descriptionFile != null) { + addDescriptionFileGutterIcon(highlightingElement, descriptionFile, result); + + addBeforeAfterTemplateFilesGutterIcon(highlightingElement, dir, result); + return; + } + } + return; + } + } + + private static void addDescriptionFileGutterIcon(PsiElement highlightingElement, + PsiFile descriptionFile, + Collection<? super RelatedItemLineMarkerInfo> result) { + final RelatedItemLineMarkerInfo<PsiElement> info = NavigationGutterIconBuilder + .create(AllIcons.FileTypes.Html, CONVERTER, RELATED_ITEM_PROVIDER) + .setTarget(descriptionFile) + .setTooltipText("Description") + .setAlignment(GutterIconRenderer.Alignment.RIGHT) + .createLineMarkerInfo(highlightingElement); + result.add(info); + } + + private static void addBeforeAfterTemplateFilesGutterIcon(PsiElement highlightingElement, + PsiDirectory descriptionDirectory, + Collection<? super RelatedItemLineMarkerInfo> result) { + final List<PsiFile> templateFiles = new SortedList<PsiFile>(new Comparator<PsiFile>() { + @Override + public int compare(PsiFile o1, PsiFile o2) { + return o1.getName().compareTo(o2.getName()); + } + }); + for (PsiFile file : descriptionDirectory.getFiles()) { + final String fileName = file.getName(); + if (fileName.endsWith(".template")) { + if (fileName.startsWith("after.") || + fileName.startsWith("before.")) { + templateFiles.add(file); + } + } + } + if (templateFiles.isEmpty()) return; + + final RelatedItemLineMarkerInfo<PsiElement> info = NavigationGutterIconBuilder + .create(AllIcons.Actions.Diff, CONVERTER, RELATED_ITEM_PROVIDER) + .setTargets(templateFiles) + .setPopupTitle("Select Template") + .setTooltipText("Before/After Templates") + .setAlignment(GutterIconRenderer.Alignment.RIGHT) + .createLineMarkerInfo(highlightingElement); + result.add(info); + } +} diff --git a/plugins/devkit/src/navigation/ExtensionPointDeclarationRelatedItemLineMarkerProvider.java b/plugins/devkit/src/navigation/ExtensionPointDeclarationRelatedItemLineMarkerProvider.java new file mode 100644 index 000000000000..deb75fda4354 --- /dev/null +++ b/plugins/devkit/src/navigation/ExtensionPointDeclarationRelatedItemLineMarkerProvider.java @@ -0,0 +1,155 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.idea.devkit.navigation; + +import com.intellij.codeInsight.daemon.RelatedItemLineMarkerInfo; +import com.intellij.codeInsight.daemon.RelatedItemLineMarkerProvider; +import com.intellij.codeInsight.navigation.NavigationGutterIconBuilder; +import com.intellij.icons.AllIcons; +import com.intellij.navigation.GotoRelatedItem; +import com.intellij.openapi.editor.markup.GutterIconRenderer; +import com.intellij.openapi.extensions.ExtensionPointName; +import com.intellij.openapi.util.Condition; +import com.intellij.psi.*; +import com.intellij.psi.util.PsiTypesUtil; +import com.intellij.util.NotNullFunction; +import com.intellij.util.containers.ContainerUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.idea.devkit.util.ExtensionPointCandidate; +import org.jetbrains.idea.devkit.util.ExtensionPointLocator; + +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +public class ExtensionPointDeclarationRelatedItemLineMarkerProvider extends RelatedItemLineMarkerProvider { + + private static final NotNullFunction<ExtensionPointCandidate, Collection<? extends PsiElement>> CONVERTER = + new NotNullFunction<ExtensionPointCandidate, Collection<? extends PsiElement>>() { + @NotNull + @Override + public Collection<? extends PsiElement> fun(ExtensionPointCandidate candidate) { + return Collections.singleton(candidate.pointer.getElement()); + } + }; + + private static final NotNullFunction<ExtensionPointCandidate, Collection<? extends GotoRelatedItem>> RELATED_ITEM_PROVIDER = + new NotNullFunction<ExtensionPointCandidate, Collection<? extends GotoRelatedItem>>() { + @NotNull + @Override + public Collection<? extends GotoRelatedItem> fun(ExtensionPointCandidate candidate) { + return GotoRelatedItem.createItems(Collections.singleton(candidate.pointer.getElement()), "DevKit"); + } + }; + + @Override + protected void collectNavigationMarkers(@NotNull PsiElement element, Collection<? super RelatedItemLineMarkerInfo> result) { + if (element instanceof PsiField) { + process((PsiField)element, result); + } + } + + private static void process(PsiField psiField, Collection<? super RelatedItemLineMarkerInfo> result) { + if (!isExtensionPointNameDeclarationField(psiField)) return; + + final PsiClass epClass = resolveExtensionPointClass(psiField); + if (epClass == null) return; + + final String epName = resolveEpName(psiField); + if (epName == null) return; + + + ExtensionPointLocator locator = new ExtensionPointLocator(epClass); + List<ExtensionPointCandidate> targets = + ContainerUtil.filter(locator.findDirectCandidates(), new Condition<ExtensionPointCandidate>() { + @Override + public boolean value(ExtensionPointCandidate candidate) { + return epName.equals(candidate.epName); + } + }); + + final RelatedItemLineMarkerInfo<PsiElement> info = NavigationGutterIconBuilder + .create(AllIcons.Nodes.Plugin, CONVERTER, RELATED_ITEM_PROVIDER) + .setTargets(targets) + .setPopupTitle("Choose Extension Point") + .setTooltipText("Extension Point Declaration") + .setAlignment(GutterIconRenderer.Alignment.RIGHT) + .createLineMarkerInfo(psiField.getNameIdentifier()); + result.add(info); + } + + @Nullable + private static PsiClass resolveExtensionPointClass(PsiField psiField) { + final PsiClassType type = (PsiClassType)psiField.getType(); + final PsiClassType.ClassResolveResult resolveResult = type.resolveGenerics(); + final PsiClass psiClass = resolveResult.getElement(); + if (psiClass == null) return null; + final PsiTypeParameter[] parameters = psiClass.getTypeParameters(); + if (parameters.length != 1) return null; + final PsiTypeParameter parameter = parameters[0]; + final PsiSubstitutor substitutor = resolveResult.getSubstitutor(); + final PsiType substituteType = substitutor.substitute(parameter); + return PsiTypesUtil.getPsiClass(substituteType); + } + + private static String resolveEpName(PsiField psiField) { + final PsiExpression initializer = psiField.getInitializer(); + + PsiExpressionList expressionList = null; + if (initializer instanceof PsiMethodCallExpression) { + expressionList = ((PsiMethodCallExpression)initializer).getArgumentList(); + } + else if (initializer instanceof PsiNewExpression) { + expressionList = ((PsiNewExpression)initializer).getArgumentList(); + } + if (expressionList == null) return null; + + final PsiExpression[] expressions = expressionList.getExpressions(); + if (expressions.length != 1) return null; + + final PsiExpression epNameExpression = expressions[0]; + final PsiConstantEvaluationHelper helper = JavaPsiFacade.getInstance(psiField.getProject()).getConstantEvaluationHelper(); + final Object o = helper.computeConstantExpression(epNameExpression); + return o instanceof String ? (String)o : null; + } + + private static boolean isExtensionPointNameDeclarationField(PsiField psiField) { + // *do* allow non-public + if (!psiField.hasModifierProperty(PsiModifier.FINAL) || + !psiField.hasModifierProperty(PsiModifier.STATIC) || + psiField.hasModifierProperty(PsiModifier.ABSTRACT)) { + return false; + } + + if (!psiField.hasInitializer()) { + return false; + } + + final PsiExpression initializer = psiField.getInitializer(); + if (!(initializer instanceof PsiMethodCallExpression) && + !(initializer instanceof PsiNewExpression)) { + return false; + } + + final PsiClass fieldClass = PsiTypesUtil.getPsiClass(psiField.getType()); + if (fieldClass == null) { + return false; + } + + return ExtensionPointName.class.getName().equals(fieldClass.getQualifiedName()); + } +} diff --git a/plugins/devkit/src/testAssistant/NavigateToTestDataAction.java b/plugins/devkit/src/testAssistant/NavigateToTestDataAction.java new file mode 100644 index 000000000000..89e10e019632 --- /dev/null +++ b/plugins/devkit/src/testAssistant/NavigateToTestDataAction.java @@ -0,0 +1,132 @@ +/* + * Copyright 2000-2013 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.idea.devkit.testAssistant; + +import com.intellij.codeInsight.AnnotationUtil; +import com.intellij.execution.Location; +import com.intellij.execution.junit.JUnitUtil; +import com.intellij.execution.junit2.PsiMemberParameterizedLocation; +import com.intellij.notification.Notification; +import com.intellij.notification.NotificationType; +import com.intellij.notification.Notifications; +import com.intellij.openapi.actionSystem.AnAction; +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.actionSystem.CommonDataKeys; +import com.intellij.openapi.actionSystem.DataContext; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.roots.ProjectFileIndex; +import com.intellij.openapi.roots.ProjectRootManager; +import com.intellij.openapi.ui.popup.JBPopupFactory; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.psi.*; +import com.intellij.psi.util.PsiTreeUtil; +import com.intellij.testFramework.Parameterized; +import com.intellij.ui.awt.RelativePoint; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Collections; +import java.util.List; + +/** + * @author yole + */ +public class NavigateToTestDataAction extends AnAction { + @Override + public void actionPerformed(AnActionEvent e) { + final DataContext dataContext = e.getDataContext(); + final Project project = CommonDataKeys.PROJECT.getData(dataContext); + List<String> fileNames = findTestDataFiles(dataContext); + if (fileNames == null || fileNames.isEmpty()) { + String message = "Cannot find testdata files for class"; + final Notification notification = new Notification("testdata", "Found no testdata files", message, NotificationType.INFORMATION); + Notifications.Bus.notify(notification, project); + } + else { + final Editor editor = e.getData(CommonDataKeys.EDITOR); + final JBPopupFactory popupFactory = JBPopupFactory.getInstance(); + final RelativePoint point = editor != null ? popupFactory.guessBestPopupLocation(editor) : popupFactory.guessBestPopupLocation(dataContext); + + TestDataNavigationHandler.navigate(point, fileNames, project); + } + } + + @Nullable + public static List<String> findTestDataFiles(@NotNull DataContext context) { + final PsiMethod method = findTargetMethod(context); + if (method == null) { + return null; + } + final String name = method.getName(); + + if (name.startsWith("test")) { + String testDataPath = TestDataLineMarkerProvider.getTestDataBasePath(method.getContainingClass()); + final TestDataReferenceCollector collector = new TestDataReferenceCollector(testDataPath, name.substring(4)); + return collector.collectTestDataReferences(method); + } + + final Location<?> location = Location.DATA_KEY.getData(context); + if (location instanceof PsiMemberParameterizedLocation) { + PsiClass containingClass = ((PsiMemberParameterizedLocation)location).getContainingClass(); + if (containingClass == null) { + containingClass = PsiTreeUtil.getParentOfType(location.getPsiElement(), PsiClass.class, false); + } + if (containingClass != null) { + final PsiAnnotation annotation = AnnotationUtil.findAnnotationInHierarchy(containingClass, Collections.singleton(JUnitUtil.RUN_WITH)); + if (annotation != null) { + final PsiAnnotationMemberValue memberValue = annotation.findAttributeValue("value"); + if (memberValue instanceof PsiClassObjectAccessExpression) { + final PsiTypeElement operand = ((PsiClassObjectAccessExpression)memberValue).getOperand(); + if (operand.getType().equalsToText(Parameterized.class.getName())) { + final String testDataPath = TestDataLineMarkerProvider.getTestDataBasePath(containingClass); + final String paramSetName = ((PsiMemberParameterizedLocation)location).getParamSetName(); + final String baseFileName = StringUtil.trimEnd(StringUtil.trimStart(paramSetName, "["), "]"); + final ProjectFileIndex fileIndex = ProjectRootManager.getInstance(containingClass.getProject()).getFileIndex(); + return TestDataGuessByExistingFilesUtil.suggestTestDataFiles(fileIndex, baseFileName, testDataPath, containingClass); + } + } + } + } + } + + return null; + } + + @Override + public void update(AnActionEvent e) { + e.getPresentation().setEnabled(findTargetMethod(e.getDataContext()) != null); + } + + @Nullable + private static PsiMethod findTargetMethod(@NotNull DataContext context) { + final Editor editor = CommonDataKeys.EDITOR.getData(context); + final PsiFile file = CommonDataKeys.PSI_FILE.getData(context); + if (file != null && editor != null) { + PsiElement element = file.findElementAt(editor.getCaretModel().getOffset()); + return PsiTreeUtil.getParentOfType(element, PsiMethod.class); + } + + final Location<?> location = Location.DATA_KEY.getData(context); + if (location != null) { + final PsiElement element = location.getPsiElement(); + if (element instanceof PsiMethod) { + return (PsiMethod)element; + } + } + return null; + } +} diff --git a/plugins/devkit/src/testAssistant/TestCaseAsRelatedFileProvider.java b/plugins/devkit/src/testAssistant/TestCaseAsRelatedFileProvider.java new file mode 100644 index 000000000000..bbe3e54899f7 --- /dev/null +++ b/plugins/devkit/src/testAssistant/TestCaseAsRelatedFileProvider.java @@ -0,0 +1,56 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.idea.devkit.testAssistant; + +import com.intellij.execution.Location; +import com.intellij.navigation.GotoRelatedItem; +import com.intellij.navigation.GotoRelatedProvider; +import com.intellij.openapi.actionSystem.CommonDataKeys; +import com.intellij.openapi.actionSystem.DataContext; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.util.Function; +import com.intellij.util.containers.ContainerUtil; +import org.jetbrains.annotations.NotNull; + +import java.util.Collections; +import java.util.List; + +public class TestCaseAsRelatedFileProvider extends GotoRelatedProvider { + @NotNull + @Override + public List<? extends GotoRelatedItem> getItems(@NotNull DataContext context) { + final Editor editor = CommonDataKeys.EDITOR.getData(context); + final Project project = CommonDataKeys.PROJECT.getData(context); + final VirtualFile file = CommonDataKeys.VIRTUAL_FILE.getData(context); + if (editor == null || file == null || project == null) { + return Collections.emptyList(); + } + + final List<Location> locations = TestLocationDataRule.collectRelativeLocations(project, file); + if (locations.isEmpty()) { + return Collections.emptyList(); + } + + return ContainerUtil.map(locations, new Function<Location, GotoRelatedItem>() { + @Override + public GotoRelatedItem fun(Location location) { + return new GotoRelatedItem(location.getPsiElement()); + } + }); + } +} diff --git a/plugins/devkit/src/testAssistant/TestDataAsRelatedFileProvider.java b/plugins/devkit/src/testAssistant/TestDataAsRelatedFileProvider.java new file mode 100644 index 000000000000..f4cb2cf2f608 --- /dev/null +++ b/plugins/devkit/src/testAssistant/TestDataAsRelatedFileProvider.java @@ -0,0 +1,64 @@ +/* + * Copyright 2000-2013 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.idea.devkit.testAssistant; + +import com.intellij.navigation.GotoRelatedItem; +import com.intellij.navigation.GotoRelatedProvider; +import com.intellij.openapi.actionSystem.CommonDataKeys; +import com.intellij.openapi.actionSystem.DataContext; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.project.Project; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiMethod; +import org.jetbrains.annotations.NotNull; + +import java.util.Collections; +import java.util.List; + +/** + * @author Denis Zhdanov + * @since 5/24/11 4:52 PM + */ +public class TestDataAsRelatedFileProvider extends GotoRelatedProvider { + + @NotNull + @Override + public List<? extends GotoRelatedItem> getItems(@NotNull DataContext context) { + final Editor editor = CommonDataKeys.EDITOR.getData(context); + final Project project = CommonDataKeys.PROJECT.getData(context); + final PsiElement element = CommonDataKeys.PSI_ELEMENT.getData(context); + if (editor == null || element == null || project == null) { + return Collections.emptyList(); + } + + PsiMethod method = null; + for (PsiElement e = element; e != null; e = e.getParent()) { + if (e instanceof PsiMethod) { + method = (PsiMethod)e; + break; + } + } + if (method == null) { + return Collections.emptyList(); + } + + final List<String> testDataFiles = NavigateToTestDataAction.findTestDataFiles(context); + if (testDataFiles == null || testDataFiles.isEmpty()) { + return Collections.emptyList(); + } + return Collections.singletonList(new TestDataRelatedItem(method, editor, testDataFiles)); + } +} diff --git a/plugins/devkit/src/testAssistant/TestDataGroupEditorProvider.java b/plugins/devkit/src/testAssistant/TestDataGroupEditorProvider.java new file mode 100644 index 000000000000..75b17e33dcfc --- /dev/null +++ b/plugins/devkit/src/testAssistant/TestDataGroupEditorProvider.java @@ -0,0 +1,62 @@ +/* + * Copyright 2000-2010 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.idea.devkit.testAssistant; + +import com.intellij.openapi.fileEditor.FileEditor; +import com.intellij.openapi.fileEditor.FileEditorPolicy; +import com.intellij.openapi.fileEditor.FileEditorProvider; +import com.intellij.openapi.fileEditor.FileEditorState; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Disposer; +import com.intellij.openapi.vfs.VirtualFile; +import org.jdom.Element; +import org.jetbrains.annotations.NotNull; + +/** + * @author yole + */ +public class TestDataGroupEditorProvider implements FileEditorProvider { + public boolean accept(@NotNull Project project, @NotNull VirtualFile file) { + return file instanceof TestDataGroupVirtualFile; + } + + @NotNull + public FileEditor createEditor(@NotNull Project project, @NotNull VirtualFile file) { + return new TestDataGroupFileEditor(project, (TestDataGroupVirtualFile) file); + } + + public void disposeEditor(@NotNull FileEditor editor) { + Disposer.dispose(editor); + } + + @NotNull + public FileEditorState readState(@NotNull Element sourceElement, @NotNull Project project, @NotNull VirtualFile file) { + return FileEditorState.INSTANCE; + } + + public void writeState(@NotNull FileEditorState state, @NotNull Project project, @NotNull Element targetElement) { + } + + @NotNull + public String getEditorTypeId() { + return "TestDataGroup"; + } + + @NotNull + public FileEditorPolicy getPolicy() { + return FileEditorPolicy.HIDE_DEFAULT_EDITOR; + } +} diff --git a/plugins/devkit/src/testAssistant/TestDataGroupFileEditor.java b/plugins/devkit/src/testAssistant/TestDataGroupFileEditor.java new file mode 100644 index 000000000000..70d3cdec18bb --- /dev/null +++ b/plugins/devkit/src/testAssistant/TestDataGroupFileEditor.java @@ -0,0 +1,130 @@ +/* + * Copyright 2000-2010 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.idea.devkit.testAssistant; + +import com.intellij.codeHighlighting.BackgroundEditorHighlighter; +import com.intellij.ide.structureView.StructureViewBuilder; +import com.intellij.openapi.fileEditor.FileEditor; +import com.intellij.openapi.fileEditor.FileEditorLocation; +import com.intellij.openapi.fileEditor.FileEditorState; +import com.intellij.openapi.fileEditor.FileEditorStateLevel; +import com.intellij.openapi.fileEditor.impl.text.TextEditorProvider; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.Splitter; +import com.intellij.openapi.util.UserDataHolderBase; +import com.intellij.reference.SoftReference; +import org.jetbrains.annotations.NotNull; + +import javax.swing.*; +import java.awt.*; +import java.beans.PropertyChangeListener; +import java.lang.ref.WeakReference; + +/** + * @author yole + */ +public class TestDataGroupFileEditor extends UserDataHolderBase implements FileEditor { + private WeakReference<JComponent> myComponent; + private final TestDataGroupVirtualFile myFile; + private final FileEditor myBeforeEditor; + private final FileEditor myAfterEditor; + + public TestDataGroupFileEditor(Project project, TestDataGroupVirtualFile file) { + myFile = file; + myBeforeEditor = TextEditorProvider.getInstance().createEditor(project, file.getBeforeFile()); + myAfterEditor = TextEditorProvider.getInstance().createEditor(project, file.getAfterFile()); + } + + @NotNull + public JComponent getComponent() { + JComponent result = SoftReference.dereference(myComponent); + if (result == null) { + myComponent = new WeakReference<JComponent>(result = createComponent()); + } + return result; + } + + private JComponent createComponent() { + Splitter splitter = new Splitter(false); + splitter.setFirstComponent(wrapWithTitle(myFile.getBeforeFile().getName(), myBeforeEditor)); + splitter.setSecondComponent(wrapWithTitle(myFile.getAfterFile().getName(), myAfterEditor)); + return splitter; + } + + private static JComponent wrapWithTitle(String name, final FileEditor beforeEditor) { + JPanel panel = new JPanel(new BorderLayout()); + final JLabel label = new JLabel(name); + label.setBorder(BorderFactory.createEmptyBorder(1, 4, 2, 0)); + label.setFont(label.getFont().deriveFont(Font.BOLD)); + panel.add(BorderLayout.NORTH, label); + panel.add(BorderLayout.CENTER, beforeEditor.getComponent()); + return panel; + } + + public JComponent getPreferredFocusedComponent() { + return null; + } + + @NotNull + public String getName() { + return myFile.getName(); + } + + @NotNull + public FileEditorState getState(@NotNull FileEditorStateLevel level) { + return FileEditorState.INSTANCE; + } + + public void setState(@NotNull FileEditorState state) { + } + + public boolean isModified() { + return myBeforeEditor.isModified() || myAfterEditor.isModified(); + } + + public boolean isValid() { + return myBeforeEditor.isValid() && myAfterEditor.isValid(); + } + + public void selectNotify() { + } + + public void deselectNotify() { + } + + public void addPropertyChangeListener(@NotNull PropertyChangeListener listener) { + } + + public void removePropertyChangeListener(@NotNull PropertyChangeListener listener) { + } + + public BackgroundEditorHighlighter getBackgroundHighlighter() { + return null; + } + + public FileEditorLocation getCurrentLocation() { + return null; + } + + public StructureViewBuilder getStructureViewBuilder() { + return null; + } + + public void dispose() { + TextEditorProvider.getInstance().disposeEditor(myBeforeEditor); + TextEditorProvider.getInstance().disposeEditor(myAfterEditor); + } +} diff --git a/plugins/devkit/src/testAssistant/TestDataGroupVirtualFile.java b/plugins/devkit/src/testAssistant/TestDataGroupVirtualFile.java new file mode 100644 index 000000000000..14b7ab3f0a53 --- /dev/null +++ b/plugins/devkit/src/testAssistant/TestDataGroupVirtualFile.java @@ -0,0 +1,132 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.idea.devkit.testAssistant; + +import com.intellij.ide.presentation.Presentation; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.openapi.vfs.LocalFileSystem; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.openapi.vfs.VirtualFileSystem; +import org.jetbrains.annotations.NotNull; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +/** + * @author yole + */ +@Presentation(icon = "AllIcons.Nodes.TestSourceFolder") +public class TestDataGroupVirtualFile extends VirtualFile { + private final VirtualFile myBeforeFile; + private final VirtualFile myAfterFile; + + public TestDataGroupVirtualFile(VirtualFile beforeFile, VirtualFile afterFile) { + myBeforeFile = beforeFile; + myAfterFile = afterFile; + } + + @NotNull + @Override + public String getName() { + final String prefix = StringUtil.commonPrefix(myBeforeFile.getName(), myAfterFile.getName()); + if (prefix.isEmpty()) { + return StringUtil.commonSuffix(myBeforeFile.getName(), myAfterFile.getName()); + } + return prefix + "." + myBeforeFile.getExtension(); + } + + public VirtualFile getBeforeFile() { + return myBeforeFile; + } + + public VirtualFile getAfterFile() { + return myAfterFile; + } + + @NotNull + @Override + public VirtualFileSystem getFileSystem() { + return LocalFileSystem.getInstance(); + } + + @NotNull + @Override + public String getPath() { + return myBeforeFile.getPath(); + } + + @Override + public boolean isWritable() { + return true; + } + + @Override + public boolean isDirectory() { + return false; + } + + @Override + public boolean isValid() { + return myBeforeFile.isValid() && myAfterFile.isValid(); + } + + @Override + public VirtualFile getParent() { + return null; + } + + @Override + public VirtualFile[] getChildren() { + return EMPTY_ARRAY; + } + + @NotNull + @Override + public OutputStream getOutputStream(Object requestor, long newModificationStamp, long newTimeStamp) throws IOException { + throw new UnsupportedOperationException(); + } + + @NotNull + @Override + public byte[] contentsToByteArray() throws IOException { + throw new UnsupportedOperationException(); + } + + @Override + public long getTimeStamp() { + return 0; + } + + @Override + public long getLength() { + return 0; + } + + @Override + public long getModificationStamp() { + return 0; + } + + @Override + public void refresh(boolean asynchronous, boolean recursive, Runnable postRunnable) { + } + + @Override + public InputStream getInputStream() throws IOException { + throw new UnsupportedOperationException(); + } +} diff --git a/plugins/devkit/src/testAssistant/TestDataGuessByExistingFilesUtil.java b/plugins/devkit/src/testAssistant/TestDataGuessByExistingFilesUtil.java new file mode 100644 index 000000000000..770e8e89c83f --- /dev/null +++ b/plugins/devkit/src/testAssistant/TestDataGuessByExistingFilesUtil.java @@ -0,0 +1,564 @@ +package org.jetbrains.idea.devkit.testAssistant; + +import com.intellij.codeInsight.AnnotationUtil; +import com.intellij.ide.util.gotoByName.GotoFileModel; +import com.intellij.openapi.extensions.Extensions; +import com.intellij.openapi.progress.ProgressManager; +import com.intellij.openapi.roots.ProjectFileIndex; +import com.intellij.openapi.roots.ProjectRootManager; +import com.intellij.openapi.util.Pair; +import com.intellij.openapi.util.Trinity; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.PsiClass; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; +import com.intellij.psi.PsiMethod; +import com.intellij.psi.codeStyle.NameUtil; +import com.intellij.psi.util.PsiTreeUtil; +import com.intellij.testIntegration.TestFramework; +import com.intellij.util.PathUtil; +import com.intellij.util.containers.ConcurrentHashMap; +import com.intellij.util.containers.HashSet; +import com.intellij.util.containers.LinkedMultiMap; +import com.intellij.util.containers.MultiMap; +import com.intellij.util.text.Matcher; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.File; +import java.util.*; +import java.util.concurrent.TimeUnit; + +/** + * There is a possible case that particular test class is not properly configured with test annotations but uses test data files. + * This class contains utility methods for guessing test data files location and name patterns from existing one. + * + * @author Denis Zhdanov + * @since 5/24/11 2:28 PM + */ +public class TestDataGuessByExistingFilesUtil { + + private static final long CACHE_ENTRY_TTL_MS = TimeUnit.MILLISECONDS.convert(5, TimeUnit.MINUTES); + + private static final Map<String, Pair<TestDataDescriptor, Long>> CACHE = new ConcurrentHashMap<String, Pair<TestDataDescriptor, Long>>(); + private static final Set<String> CLASSES_WITHOUT_TEST_DATA = new java.util.HashSet<String>(); + + private TestDataGuessByExistingFilesUtil() { + } + + /** + * Tries to guess what test data files match to the given method if it's test method and there are existing test data + * files for the target test class. + * + * @param method test method candidate + * @return collection of paths to the test data files for the given test if it's possible to guess them; + * <code>null</code> otherwise + */ + @Nullable + static List<String> collectTestDataByExistingFiles(@NotNull PsiMethod method) { + if (getTestName(method) == null) { + return null; + } + PsiFile psiFile = getParent(method, PsiFile.class); + if (psiFile == null) { + return null; + } + return collectTestDataByExistingFiles(psiFile, getTestName(method.getName())); + } + + @Nullable + private static <T extends PsiElement> T getParent(@NotNull PsiElement element, Class<T> clazz) { + for (PsiElement e = element; e != null; e = e.getParent()) { + if (clazz.isAssignableFrom(e.getClass())) { + return clazz.cast(e); + } + } + return null; + } + + @Nullable + static List<String> collectTestDataByExistingFiles(@NotNull PsiFile psiFile, @NotNull String testName) { + TestDataDescriptor descriptor = buildDescriptorFromExistingTestData(psiFile); + if (descriptor == null || !descriptor.isComplete()) { + return null; + } + + return descriptor.generate(testName); + } + + @Nullable + private static String getTestName(@NotNull PsiMethod method) { + final PsiClass psiClass = getParent(method, PsiClass.class); + if (psiClass == null) { + return null; + } + + TestFramework[] frameworks = Extensions.getExtensions(TestFramework.EXTENSION_NAME); + TestFramework framework = null; + for (TestFramework each : frameworks) { + if (each.isTestClass(psiClass)) { + framework = each; + break; + } + } + + if (framework == null || isUtilityMethod(method, psiClass, framework)) { + return null; + } + + return getTestName(method.getName()); + } + + private static boolean isUtilityMethod(@NotNull PsiMethod method, @NotNull PsiClass psiClass, @NotNull TestFramework framework) { + if (method == framework.findSetUpMethod(psiClass) || method == framework.findTearDownMethod(psiClass)) { + return true; + } + + // JUnit3 + if (framework.getClass().getName().contains("JUnit3")) { + return !method.getName().startsWith("test"); + } + + // JUnit4 + else if (framework.getClass().getName().contains("JUnit4")) { + return !AnnotationUtil.isAnnotated(method, "org.junit.Test", false); + } + return false; + } + + @NotNull + public static String getTestName(@NotNull String methodName) { + return methodName.startsWith("test") ? methodName.substring("test".length()) : methodName; + } + + @Nullable + private static TestDataDescriptor buildDescriptorFromExistingTestData(@NotNull PsiFile file) { + final PsiClass psiClass = PsiTreeUtil.getChildOfType(file, PsiClass.class); + if (psiClass == null) { + return null; + } + + final String qualifiedName = psiClass.getQualifiedName(); + if (CLASSES_WITHOUT_TEST_DATA.contains(qualifiedName)) { + return null; + } + final Pair<TestDataDescriptor, Long> cached = CACHE.get(qualifiedName); + if (cached != null) { + if (cached.first.isComplete()) { + return cached.first; + } + if (cached.second > System.currentTimeMillis()) { + return null; + } + } + + TestFramework[] frameworks = Extensions.getExtensions(TestFramework.EXTENSION_NAME); + TestFramework framework = null; + for (TestFramework each : frameworks) { + if (each.isTestClass(psiClass)) { + framework = each; + break; + } + } + if (framework == null) { + return null; + } + + final PsiElement setUpMethod = framework.findSetUpMethod(psiClass); + final PsiElement tearDownMethod = framework.findTearDownMethod(psiClass); + List<String> testNames = new ArrayList<String>(); + for (PsiMethod method : psiClass.getMethods()) { + final String name = getTestName(method.getName()); + if (StringUtil.isEmpty(name) || method == setUpMethod || method == tearDownMethod || name.equals(psiClass.getName()) + || isUtilityMethod(method, psiClass, framework)) + { + continue; + } + testNames.add(name); + } + + ProjectFileIndex fileIndex = ProjectRootManager.getInstance(psiClass.getProject()).getFileIndex(); + final TestDataDescriptor descriptor = buildDescriptor(fileIndex, testNames, psiClass); + if (isClassWithoutTestData(descriptor, testNames, psiClass)) { + CLASSES_WITHOUT_TEST_DATA.add(qualifiedName); + return null; + } + CACHE.put(qualifiedName, new Pair<TestDataDescriptor, Long>(descriptor, System.currentTimeMillis() + CACHE_ENTRY_TTL_MS)); + return descriptor; + } + + private static boolean isClassWithoutTestData(@NotNull TestDataDescriptor descriptor, @NotNull List<String> testNames, + @NotNull PsiClass psiClass) { + if (testNames.size() <= 1) { + // There is a possible case that the test class is just created. + return false; + } + + if (!descriptor.isComplete()) { + return true; + } + + boolean tooGenericNames = true; + genericNamesLoop: + for (String testName : testNames) { + for (int i = 0; i < testName.length(); i++) { + if (!Character.isDigit(testName.charAt(i))) { + tooGenericNames = false; + break genericNamesLoop; + } + } + } + + final String simpleClassName = getSimpleClassName(psiClass); + if (tooGenericNames + && (simpleClassName == null || !descriptor.myDescriptors.get(0).dir.toLowerCase().contains(simpleClassName.toLowerCase()))) + { + return true; + } + + // We assume that test has test data if max(2; half of tests) tests already have test data. + int toMatch = Math.max(2, testNames.size() / 2); + for (String testName : testNames) { + if (toMatch <= 0) { + return false; + } + final List<String> testDataFiles = descriptor.generate(testName); + for (String path : testDataFiles) { + if (new File(path).isFile()) { + // There is a possible case that particular test has only one test data file though the others have + // two (e.g. during testing caret position at virtual space). + toMatch--; + break; + } + } + } + + return toMatch > 0; + } + + //@NotNull + //private static Collection<VirtualFile> getMatchedFiles(@NotNull final Project project, @NotNull final String testName) { + // final List<VirtualFile> result = new ArrayList<VirtualFile>(); + // final char c = testName.charAt(0); + // final String testNameWithDifferentRegister = + // (Character.isLowerCase(c) ? Character.toUpperCase(c) : Character.toLowerCase(c)) + testName.substring(1); + // final GlobalSearchScope scope = ProjectScope.getProjectScope(project); + // FileBasedIndex.getInstance().processAllKeys(FilenameIndex.NAME, new Processor<String>() { + // @Override + // public boolean process(String s) { + // if (!s.contains(testName) && !s.contains(testNameWithDifferentRegister)) { + // return true; + // } + // + // final NavigationItem[] items = FilenameIndex.getFilesByName(project, s, scope); + // if (items != null) { + // for (NavigationItem item : items) { + // if (item instanceof PsiFile) { + // result.add(((PsiFile)item).getVirtualFile()); + // } + // } + // } + // return true; + // } + // }, project); + // return result; + //} + + public static List<String> suggestTestDataFiles(@NotNull ProjectFileIndex fileIndex, + @NotNull String testName, + String testDataPath, + @NotNull PsiClass psiClass){ + return buildDescriptor(fileIndex, Collections.singletonList(testName), psiClass).generate(testName, testDataPath); + } + + + @NotNull + private static TestDataDescriptor buildDescriptor(@NotNull ProjectFileIndex fileIndex, + @NotNull Collection<String> testNames, + @NotNull PsiClass psiClass) + { + GotoFileModel gotoModel = new GotoFileModel(psiClass.getProject()); + List<Trinity<Matcher, String, String>> input = new ArrayList<Trinity<Matcher, String, String>>(); + Set<String> testNamesLowerCase = new HashSet<String>(); + for (String testName : testNames) { + String pattern = String.format("*%s*", testName); + input.add(new Trinity<Matcher, String, String>( + NameUtil.buildMatcher(pattern, 0, true, true, pattern.toLowerCase().equals(pattern)), testName, pattern + )); + testNamesLowerCase.add(testName.toLowerCase()); + } + Set<TestLocationDescriptor> descriptors = new HashSet<TestLocationDescriptor>(); + MultiMap<String, Trinity<Matcher, String, String>> map = getAllFileNames(input, gotoModel); + for (String name : map.keySet()) { + ProgressManager.checkCanceled(); + boolean currentNameProcessed = false; + for (Trinity<Matcher, String, String> trinity : map.get(name)) { + final Object[] elements = gotoModel.getElementsByName(name, false, trinity.third); + if (elements == null) { + continue; + } + for (Object element : elements) { + if (!(element instanceof PsiFile)) { + continue; + } + final VirtualFile file = ((PsiFile)element).getVirtualFile(); + if (file == null || fileIndex.isInSource(file)) { + continue; + } + + + final String filePath = PathUtil.getFileName(file.getPath()).toLowerCase(); + int i = filePath.indexOf(trinity.second.toLowerCase()); + // Skip files that doesn't contain target test name and files that contain digit after target test name fragment. + // Example: there are tests with names 'testEnter()' and 'testEnter2()' and we don't want test data file 'testEnter2' + // to be matched to the test 'testEnter()'. + if (i < 0 || (i + trinity.second.length() < filePath.length()) + && Character.isDigit(filePath.charAt(i + trinity.second.length()))) + { + continue; + } + + TestLocationDescriptor current = new TestLocationDescriptor(); + current.populate(trinity.second, file); + if (!current.isComplete()) { + continue; + } + + // Handle situations like the one below: + // *) test class has tests with names 'testAlignedParameters' and 'testNonAlignedParameters'; + // *) test data files with the following names present: 'AlignedParameters.java' and 'NonAlignedParameters.java'; + // *) we're processing the following (test; test data file) pair - ('testAlignedParameters'; 'NonAlignedParameters.java'); + // We don't want to store descriptor with file prefix 'Non' here. + // The same is true for suffixes, e.g. tests like 'testLeaveValidCodeBlock()' and 'testLeaveValidCodeBlockWithEmptyLineAfterIt()' + String prefixPattern = current.filePrefix.toLowerCase(); + boolean checkPrefix = !StringUtil.isEmpty(prefixPattern); + String suffixPattern = current.fileSuffix; + for (TestLocationDescriptor descriptor : descriptors) { + if (suffixPattern.endsWith(descriptor.fileSuffix)) { + suffixPattern = suffixPattern.substring(0, suffixPattern.length() - descriptor.fileSuffix.length()); + } + } + suffixPattern = suffixPattern.toLowerCase(); + boolean checkSuffix = !StringUtil.isEmpty(suffixPattern); + boolean skip = false; + for (String testName : testNamesLowerCase) { + if (testName.equals(trinity.second)) { + continue; + } + if ((checkPrefix && testName.startsWith(prefixPattern)) || (checkSuffix && testName.endsWith(suffixPattern))) { + skip = true; + break; + } + } + if (skip) { + continue; + } + + currentNameProcessed = true; + if (descriptors.isEmpty() || (descriptors.iterator().next().dir.equals(current.dir) && !descriptors.contains(current))) { + descriptors.add(current); + continue; + } + if (moreRelevantPath(current, descriptors, psiClass)) { + descriptors.clear(); + descriptors.add(current); + } + } + if (currentNameProcessed) { + break; + } + } + } + return new TestDataDescriptor(descriptors); + } + + private static synchronized MultiMap<String, Trinity<Matcher, String, String>> getAllFileNames(List<Trinity<Matcher, String, String>> input, + final GotoFileModel model) { + LinkedMultiMap<String, Trinity<Matcher, String, String>> map = new LinkedMultiMap<String, Trinity<Matcher, String, String>>(); + for (String name : model.getNames(false)) { + ProgressManager.checkCanceled(); + for (Trinity<Matcher, String, String> trinity : input) { + if (trinity.first.matches(name)) { + map.putValue(name, trinity); + } + } + } + return map; + } + + @Nullable + private static String getSimpleClassName(@NotNull PsiClass psiClass) { + String result = psiClass.getQualifiedName(); + if (result == null) { + return null; + } + if (result.endsWith("Test")) { + result = result.substring(0, result.length() - "Test".length()); + } + int i = result.lastIndexOf('.'); + if (i >= 0) { + result = result.substring(i + 1); + } + return result; + } + + private static boolean moreRelevantPath(@NotNull TestLocationDescriptor candidate, + @NotNull Set<TestLocationDescriptor> currentDescriptors, + @NotNull PsiClass psiClass) + { + final String className = psiClass.getQualifiedName(); + if (className == null) { + return false; + } + + final TestLocationDescriptor current = currentDescriptors.iterator().next(); + boolean candidateMatched; + boolean currentMatched; + + // By package. + int i = className.lastIndexOf("."); + if (i >= 0) { + String packageAsPath = className.substring(0, i).replace('.', '/').toLowerCase(); + candidateMatched = candidate.dir.toLowerCase().contains(packageAsPath); + currentMatched = current.dir.toLowerCase().contains(packageAsPath); + if (candidateMatched ^ currentMatched) { + return candidateMatched; + } + } + + // By class name. + String simpleName = getSimpleClassName(psiClass); + if (simpleName != null) { + String pattern = simpleName.toLowerCase(); + candidateMatched = candidate.dir.toLowerCase().contains(pattern); + currentMatched = current.dir.toLowerCase().contains(pattern); + if (candidateMatched ^ currentMatched) { + return candidateMatched; + } + } + + return false; + } + + private static class TestLocationDescriptor { + + public String dir; + public String filePrefix; + public String fileSuffix; + public String ext; + public boolean startWithLowerCase; + + public boolean isComplete() { + return dir != null && filePrefix != null && fileSuffix != null && ext != null; + } + + public void populate(@NotNull String testName, @NotNull VirtualFile matched) { + final String withoutExtension = FileUtil.getNameWithoutExtension(testName); + boolean excludeExtension = !withoutExtension.equals(testName); + testName = withoutExtension; + final String fileName = matched.getNameWithoutExtension(); + int i = fileName.indexOf(testName); + final char firstChar = testName.charAt(0); + boolean testNameStartsWithLowerCase = Character.isLowerCase(firstChar); + if (i < 0) { + i = fileName.indexOf( + (testNameStartsWithLowerCase ? Character.toUpperCase(firstChar) : Character.toLowerCase(firstChar)) + testName.substring(1) + ); + startWithLowerCase = !testNameStartsWithLowerCase; + } + else { + startWithLowerCase = testNameStartsWithLowerCase; + } + if (i < 0) { + return; + } + + filePrefix = fileName.substring(0, i); + fileSuffix = fileName.substring(i + testName.length()); + ext = excludeExtension ? "" : matched.getExtension(); + dir = matched.getParent().getPath(); + } + + @Override + public int hashCode() { + int result = dir != null ? dir.hashCode() : 0; + result = 31 * result + (filePrefix != null ? filePrefix.hashCode() : 0); + result = 31 * result + (fileSuffix != null ? fileSuffix.hashCode() : 0); + result = 31 * result + (ext != null ? ext.hashCode() : 0); + result = 31 * result + (startWithLowerCase ? 1 : 0); + return result; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + TestLocationDescriptor that = (TestLocationDescriptor)o; + if (startWithLowerCase != that.startWithLowerCase) return false; + if (dir != null ? !dir.equals(that.dir) : that.dir != null) return false; + if (ext != null ? !ext.equals(that.ext) : that.ext != null) return false; + if (filePrefix != null ? !filePrefix.equals(that.filePrefix) : that.filePrefix != null) return false; + if (fileSuffix != null ? !fileSuffix.equals(that.fileSuffix) : that.fileSuffix != null) return false; + + return true; + } + + @Override + public String toString() { + return String.format("%s/%s[...]%s.%s", dir, filePrefix, fileSuffix, ext); + } + } + + private static class TestDataDescriptor { + + private final List<TestLocationDescriptor> myDescriptors = new ArrayList<TestLocationDescriptor>(); + + TestDataDescriptor(Collection<TestLocationDescriptor> descriptors) { + myDescriptors.addAll(descriptors); + } + + public boolean isComplete() { + if (myDescriptors.isEmpty()) { + return false; + } + + for (TestLocationDescriptor descriptor : myDescriptors) { + if (!descriptor.isComplete()) { + return false; + } + } + return true; + } + + @NotNull + public List<String> generate(@NotNull final String testName) { + return generate(testName, null); + } + + @NotNull + public List<String> generate(@NotNull final String testName, String root) { + List<String> result = new ArrayList<String>(); + if (StringUtil.isEmpty(testName)) { + return result; + } + for (TestLocationDescriptor descriptor : myDescriptors) { + if (root != null && !root.equals(descriptor.dir)) continue; + result.add(String.format( + "%s/%s%c%s%s.%s", + descriptor.dir, descriptor.filePrefix, + descriptor.startWithLowerCase ? Character.toLowerCase(testName.charAt(0)) : Character.toUpperCase(testName.charAt(0)), + testName.substring(1), descriptor.fileSuffix, descriptor.ext + )); + } + return result; + } + + @Override + public String toString() { + return myDescriptors.toString(); + } + } +} diff --git a/plugins/devkit/src/testAssistant/TestDataHighlightingPass.java b/plugins/devkit/src/testAssistant/TestDataHighlightingPass.java new file mode 100644 index 000000000000..fcdd14ebf032 --- /dev/null +++ b/plugins/devkit/src/testAssistant/TestDataHighlightingPass.java @@ -0,0 +1,105 @@ +/* + * Copyright 2000-2013 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.idea.devkit.testAssistant; + +import com.intellij.codeHighlighting.TextEditorHighlightingPass; +import com.intellij.icons.AllIcons; +import com.intellij.openapi.editor.Document; +import com.intellij.openapi.editor.impl.DocumentMarkupModel; +import com.intellij.openapi.editor.markup.*; +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Key; +import com.intellij.util.ui.PlatformColors; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import java.awt.*; + +/** + * @author Konstantin Bulenkov + */ +public class TestDataHighlightingPass extends TextEditorHighlightingPass { + private static final Key<Object> KEY = Key.create("TestDataHighlighterKey"); + private static final Object VALUE = new Object(); + + private static final GutterIconRenderer ICON_RENDERER = new MyGutterIconRenderer(); + + private static final TextAttributes CARET_ATTRIBUTES = new TextAttributes(PlatformColors.BLUE, null, null, null, Font.BOLD); + private static final String CARET = "<caret>"; + + protected TestDataHighlightingPass(@NotNull final Project project, @Nullable final Document document) { + super(project, document); + } + + @Override + public void doCollectInformation(@NotNull ProgressIndicator progress) { + } + + @Override + public void doApplyInformationToEditor() { + removeHighlighters(); + + if (myDocument == null) { + return; + } + final MarkupModel model = DocumentMarkupModel.forDocument(myDocument, myProject, true); + final String text = myDocument.getText(); + + if (text != null) { + int ind = -1; + while ((ind = text.indexOf(CARET, ind + 1)) >= 0) { + final RangeHighlighter highlighter = model.addRangeHighlighter(ind, + ind + CARET.length(), + HighlighterLayer.ADDITIONAL_SYNTAX, + CARET_ATTRIBUTES, + HighlighterTargetArea.EXACT_RANGE); + highlighter.setGutterIconRenderer(ICON_RENDERER); + highlighter.putUserData(KEY, VALUE); + } + } + } + + private void removeHighlighters() { + if (myDocument == null) { + return; + } + final MarkupModel model = DocumentMarkupModel.forDocument(myDocument, myProject, true); + for (RangeHighlighter highlighter : model.getAllHighlighters()) { + if (highlighter.getUserData(KEY) == VALUE) { + highlighter.dispose(); + } + } + } + + private static class MyGutterIconRenderer extends GutterIconRenderer { + @NotNull + @Override + public Icon getIcon() { + return AllIcons.RunConfigurations.Junit; + } + + @Override + public boolean equals(Object obj) { + return obj instanceof MyGutterIconRenderer; + } + @Override + public int hashCode() { + return getIcon().hashCode(); + } + } +} diff --git a/plugins/devkit/src/testAssistant/TestDataHighlightingPassFactory.java b/plugins/devkit/src/testAssistant/TestDataHighlightingPassFactory.java new file mode 100644 index 000000000000..a3284ffbdf93 --- /dev/null +++ b/plugins/devkit/src/testAssistant/TestDataHighlightingPassFactory.java @@ -0,0 +1,90 @@ +/* + * Copyright 2000-2010 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.idea.devkit.testAssistant; + +import com.intellij.codeHighlighting.TextEditorHighlightingPass; +import com.intellij.codeHighlighting.TextEditorHighlightingPassFactory; +import com.intellij.codeHighlighting.TextEditorHighlightingPassRegistrar; +import com.intellij.openapi.components.AbstractProjectComponent; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.fileTypes.StdFileTypes; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.roots.ProjectRootManager; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.PsiDocumentManager; +import com.intellij.psi.PsiFile; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Arrays; +import java.util.List; + +/** + * @author Konstantin Bulenkov + */ +public class TestDataHighlightingPassFactory extends AbstractProjectComponent implements TextEditorHighlightingPassFactory { + public static final List<String> SUPPORTED_FILE_TYPES = Arrays.asList( + StdFileTypes.JAVA.getDefaultExtension() + ); + public static final List<String> SUPPORTED_IN_TEST_DATA_FILE_TYPES = Arrays.asList( + "js", "php", "css", "html", "xhtml", "jsp", "test", "py", "aj" + ); + private static final int MAX_HOPES = 3; + private static final String TEST_DATA = "testdata"; + + + public TestDataHighlightingPassFactory(Project project, TextEditorHighlightingPassRegistrar highlightingPassRegistrar) { + super(project); + highlightingPassRegistrar.registerTextEditorHighlightingPass(this, null, null, true, -1); + } + + @NonNls + @NotNull + public String getComponentName() { + return getClass().getName(); + } + + @Nullable + public TextEditorHighlightingPass createHighlightingPass(@NotNull PsiFile file, @NotNull final Editor editor) { + final VirtualFile virtualFile = file.getVirtualFile(); + if (virtualFile != null && isSupported(virtualFile)) { + return new TestDataHighlightingPass(myProject, PsiDocumentManager.getInstance(myProject).getDocument(file)); + } + return null; + } + + public boolean isSupported(@NotNull VirtualFile file) { + final String ext = file.getExtension(); + if (SUPPORTED_FILE_TYPES.contains(ext)) { + return ProjectRootManager.getInstance(myProject).getFileIndex().getSourceRootForFile(file) == null; + } + + if (SUPPORTED_IN_TEST_DATA_FILE_TYPES.contains(ext)) { + int i = 0; + VirtualFile parent = file.getParent(); + while (parent != null && i < MAX_HOPES) { + if (parent.getName().toLowerCase().contains(TEST_DATA)) { + return true; + } + i++; + parent = parent.getParent(); + } + } + return false; + } +} + diff --git a/plugins/devkit/src/testAssistant/TestDataLineMarkerProvider.java b/plugins/devkit/src/testAssistant/TestDataLineMarkerProvider.java new file mode 100644 index 000000000000..916e38c9f876 --- /dev/null +++ b/plugins/devkit/src/testAssistant/TestDataLineMarkerProvider.java @@ -0,0 +1,147 @@ +/* + * Copyright 2000-2011 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.idea.devkit.testAssistant; + +import com.intellij.codeHighlighting.Pass; +import com.intellij.codeInsight.AnnotationUtil; +import com.intellij.codeInsight.daemon.GutterIconNavigationHandler; +import com.intellij.codeInsight.daemon.LineMarkerInfo; +import com.intellij.codeInsight.daemon.LineMarkerProvider; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.editor.markup.GutterIconRenderer; +import com.intellij.openapi.fileEditor.OpenFileDescriptor; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.roots.ProjectFileIndex; +import com.intellij.openapi.roots.ProjectRootManager; +import com.intellij.openapi.vfs.VfsUtil; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.*; +import com.intellij.psi.util.PsiUtilCore; +import com.intellij.util.PlatformIcons; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.awt.event.MouseEvent; +import java.io.File; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +/** + * @author yole + */ +public class TestDataLineMarkerProvider implements LineMarkerProvider { + public static final String TEST_DATA_PATH_ANNOTATION_QUALIFIED_NAME = "com.intellij.testFramework.TestDataPath"; + public static final String CONTENT_ROOT_VARIABLE = "$CONTENT_ROOT"; + public static final String PROJECT_ROOT_VARIABLE = "$PROJECT_ROOT"; + + public LineMarkerInfo getLineMarkerInfo(@NotNull PsiElement element) { + if (ApplicationManager.getApplication().isUnitTestMode()) { + return null; + } + final VirtualFile file = PsiUtilCore.getVirtualFile(element); + if (file == null || !ProjectFileIndex.SERVICE.getInstance(element.getProject()).isInTestSourceContent(file)) { + return null; + } + if (element instanceof PsiMethod) { + final PsiMethod method = (PsiMethod)element; + if (isTestMethod(method)) { + return new LineMarkerInfo<PsiMethod>( + method, method.getModifierList().getTextRange(), PlatformIcons.TEST_SOURCE_FOLDER, Pass.UPDATE_ALL, null, new TestDataNavigationHandler(), + GutterIconRenderer.Alignment.LEFT); + } + } else if (element instanceof PsiClass) { + final PsiClass psiClass = (PsiClass)element; + final String basePath = getTestDataBasePath(psiClass); + if (basePath != null) { + return new LineMarkerInfo<PsiClass>( + psiClass, psiClass.getModifierList().getTextRange(), PlatformIcons.TEST_SOURCE_FOLDER, Pass.UPDATE_ALL, null, new GutterIconNavigationHandler<PsiClass>() { + @Override + public void navigate(MouseEvent e, PsiClass elt) { + final VirtualFile baseDir = VfsUtil.findFileByIoFile(new File(basePath), true); + if (baseDir != null) { + new OpenFileDescriptor(psiClass.getProject(), baseDir).navigate(true); + } + } + }, + GutterIconRenderer.Alignment.LEFT); + } + } + return null; + } + + private static boolean isTestMethod(@NotNull PsiMethod method) { + if (isTestMethodWithAnnotation(method)) { + return true; + } + + final List<String> files = TestDataGuessByExistingFilesUtil.collectTestDataByExistingFiles(method); + return files != null && !files.isEmpty(); + } + + private static boolean isTestMethodWithAnnotation(@NotNull PsiMethod method) { + String name = method.getName(); + if (!name.startsWith("test")) { + return false; + } + String testDataPath = getTestDataBasePath(method.getContainingClass()); + if (testDataPath == null) { + return false; + } + List<String> fileNames = new TestDataReferenceCollector(testDataPath, name.substring(4)).collectTestDataReferences(method); + return fileNames != null && !fileNames.isEmpty(); + } + + public void collectSlowLineMarkers(@NotNull List<PsiElement> elements, @NotNull Collection<LineMarkerInfo> result) { + } + + @Nullable + public static String getTestDataBasePath(PsiClass psiClass) { + final PsiAnnotation annotation = AnnotationUtil.findAnnotationInHierarchy(psiClass, + Collections.singleton(TEST_DATA_PATH_ANNOTATION_QUALIFIED_NAME)); + if (annotation != null) { + final PsiAnnotationMemberValue value = annotation.findAttributeValue(PsiAnnotation.DEFAULT_REFERENCED_METHOD_NAME); + if (value instanceof PsiExpression) { + final Project project = value.getProject(); + final PsiConstantEvaluationHelper evaluationHelper = JavaPsiFacade.getInstance(project).getConstantEvaluationHelper(); + final Object constantValue = evaluationHelper.computeConstantExpression(value, false); + if (constantValue instanceof String) { + String path = (String) constantValue; + if (path.contains(CONTENT_ROOT_VARIABLE)) { + final ProjectFileIndex fileIndex = ProjectRootManager.getInstance(project).getFileIndex(); + final VirtualFile file = psiClass.getContainingFile().getVirtualFile(); + if (file == null) { + return null; + } + final VirtualFile contentRoot = fileIndex.getContentRootForFile(file); + if (contentRoot == null) return null; + path = path.replace(CONTENT_ROOT_VARIABLE, contentRoot.getPath()); + } + if (path.contains(PROJECT_ROOT_VARIABLE)) { + final VirtualFile baseDir = project.getBaseDir(); + if (baseDir == null) { + return null; + } + path = path.replace(PROJECT_ROOT_VARIABLE, baseDir.getPath()); + } + return path; + } + } + } + return null; + } + +} diff --git a/plugins/devkit/src/testAssistant/TestDataNavigationHandler.java b/plugins/devkit/src/testAssistant/TestDataNavigationHandler.java new file mode 100644 index 000000000000..143ce0311d16 --- /dev/null +++ b/plugins/devkit/src/testAssistant/TestDataNavigationHandler.java @@ -0,0 +1,191 @@ +/* + * Copyright 2000-2013 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.idea.devkit.testAssistant; + +import com.intellij.codeInsight.daemon.GutterIconNavigationHandler; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.fileEditor.OpenFileDescriptor; +import com.intellij.openapi.fileTypes.FileType; +import com.intellij.openapi.fileTypes.FileTypeManager; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.Messages; +import com.intellij.openapi.ui.popup.PopupChooserBuilder; +import com.intellij.openapi.util.Computable; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.openapi.vfs.LocalFileSystem; +import com.intellij.openapi.vfs.VfsUtil; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.PsiMethod; +import com.intellij.ui.ColoredListCellRenderer; +import com.intellij.ui.awt.RelativePoint; +import com.intellij.ui.components.JBList; +import com.intellij.util.ArrayUtil; +import com.intellij.util.PathUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import java.awt.event.MouseEvent; +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +/** +* @author yole +*/ +public class TestDataNavigationHandler implements GutterIconNavigationHandler<PsiMethod> { + public void navigate(MouseEvent e, final PsiMethod elt) { + navigate(elt, new RelativePoint(e)); + } + + public static void navigate(PsiMethod method, final RelativePoint point) { + List<String> fileNames = null; + String testDataPath = TestDataLineMarkerProvider.getTestDataBasePath(method.getContainingClass()); + if (testDataPath != null) { + fileNames = new TestDataReferenceCollector(testDataPath, method.getName().substring(4)).collectTestDataReferences(method); + } + + if (fileNames == null || fileNames.isEmpty()) { + fileNames = TestDataGuessByExistingFilesUtil.collectTestDataByExistingFiles(method); + } + + if (fileNames == null || fileNames.isEmpty()) { + return; + } + navigate(point, fileNames, method.getProject()); + } + + public static void navigate(@NotNull final RelativePoint point, + @NotNull List<String> testDataFiles, + final Project project) { + if (testDataFiles.size() == 1) { + openFileByIndex(project, testDataFiles, 0); + } + else if (testDataFiles.size() > 1) { + TestDataGroupVirtualFile groupFile = getTestDataGroup(testDataFiles); + if (groupFile != null) { + new OpenFileDescriptor(project, groupFile).navigate(true); + } + else { + showNavigationPopup(project, testDataFiles, point); + } + } + } + + @Nullable + private static TestDataGroupVirtualFile getTestDataGroup(List<String> fileNames) { + if (fileNames.size() != 2) { + return null; + } + VirtualFile file1 = LocalFileSystem.getInstance().refreshAndFindFileByPath(fileNames.get(0)); + VirtualFile file2 = LocalFileSystem.getInstance().refreshAndFindFileByPath(fileNames.get(1)); + if (file1 == null || file2 == null) { + return null; + } + final int commonPrefixLength = StringUtil.commonPrefixLength(file1.getName(), file2.getName()); + if (file1.getName().substring(commonPrefixLength).toLowerCase().contains("after")) { + return new TestDataGroupVirtualFile(file2, file1); + } + if (file2.getName().substring(commonPrefixLength).toLowerCase().contains("after")) { + return new TestDataGroupVirtualFile(file1, file2); + } + return null; + } + + private static void showNavigationPopup(final Project project, final List<String> fileNames, final RelativePoint point) { + List<String> listPaths = new ArrayList<String>(fileNames); + final String CREATE_MISSING_OPTION = "Create Missing Files"; + if (fileNames.size() == 2) { + VirtualFile file1 = LocalFileSystem.getInstance().refreshAndFindFileByPath(fileNames.get(0)); + VirtualFile file2 = LocalFileSystem.getInstance().refreshAndFindFileByPath(fileNames.get(1)); + if (file1 == null || file2 == null) { + listPaths.add(CREATE_MISSING_OPTION); + } + } + final JList list = new JBList(ArrayUtil.toStringArray(listPaths)); + list.setCellRenderer(new ColoredListCellRenderer() { + @Override + protected void customizeCellRenderer(JList list, Object value, int index, boolean selected, boolean hasFocus) { + String path = (String)value; + String fileName = PathUtil.getFileName(path); + if (!fileName.equals(CREATE_MISSING_OPTION)) { + final FileType fileType = FileTypeManager.getInstance().getFileTypeByFileName(fileName); + setIcon(fileType.getIcon()); + } + append(String.format("%s (%s)", fileName, PathUtil.getParentPath(path))); + } + }); + PopupChooserBuilder builder = new PopupChooserBuilder(list); + builder.setItemChoosenCallback(new Runnable() { + public void run() { + final int[] indices = list.getSelectedIndices(); + if (ArrayUtil.indexOf(indices, fileNames.size()) >= 0) { + createMissingFiles(project, fileNames); + } + else { + for (int index : indices) { + openFileByIndex(project, fileNames, index); + } + } + } + }).createPopup().show(point); + } + + private static void createMissingFiles(Project project, List<String> fileNames) { + for (String name : fileNames) { + if (LocalFileSystem.getInstance().refreshAndFindFileByPath(name) == null) { + createFileByName(project, name); + } + } + final TestDataGroupVirtualFile testDataGroup = getTestDataGroup(fileNames); + if (testDataGroup != null) { + new OpenFileDescriptor(project, testDataGroup).navigate(true); + } + } + + private static void openFileByIndex(final Project project, final List<String> fileNames, final int index) { + final String path = fileNames.get(index); + final VirtualFile file = LocalFileSystem.getInstance().refreshAndFindFileByPath(path); + if (file != null) { + new OpenFileDescriptor(project, file).navigate(true); + } + else { + int rc = Messages.showYesNoDialog(project, "The referenced testdata file " + path + " does not exist. Would you like to create it?", + "Create Testdata File", Messages.getQuestionIcon()); + if (rc == Messages.YES) { + VirtualFile vFile = createFileByName(project, path); + new OpenFileDescriptor(project, vFile).navigate(true); + } + } + } + + private static VirtualFile createFileByName(final Project project, final String path) { + return ApplicationManager.getApplication().runWriteAction(new Computable<VirtualFile>() { + public VirtualFile compute() { + try { + final File file = new File(path); + final VirtualFile parent = VfsUtil.createDirectories(file.getParent()); + return parent.createChildData(this, file.getName()); + } + catch (IOException e) { + Messages.showErrorDialog(project, e.getMessage(), "Create Testdata File"); + return null; + } + } + }); + } +} diff --git a/plugins/devkit/src/testAssistant/TestDataReferenceCollector.java b/plugins/devkit/src/testAssistant/TestDataReferenceCollector.java new file mode 100644 index 000000000000..85ce75c4cfb1 --- /dev/null +++ b/plugins/devkit/src/testAssistant/TestDataReferenceCollector.java @@ -0,0 +1,184 @@ +/* + * Copyright 2000-2013 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.idea.devkit.testAssistant; + +import com.intellij.openapi.util.Computable; +import com.intellij.openapi.util.NullableComputable; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.psi.*; +import com.intellij.testFramework.UsefulTestCase; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.File; +import java.util.*; + +/** + * @author yole + */ +public class TestDataReferenceCollector { + private static final String TEST_DATA_FILE_ANNOTATION_QUALIFIED_NAME = "com.intellij.testFramework.TestDataFile"; + private final String myTestDataPath; + private final String myTestName; + private final List<String> myLogMessages = new ArrayList<String>(); + private PsiClass myContainingClass; + private boolean myFoundTestDataParameters = false; + + public TestDataReferenceCollector(@Nullable String testDataPath, String testName) { + if (StringUtil.isNotEmpty(testDataPath) && !StringUtil.endsWithChar(testDataPath, File.separatorChar)) { + testDataPath += File.separatorChar; + } + myTestDataPath = testDataPath; + myTestName = testName; + } + + @Nullable + List<String> collectTestDataReferences(@NotNull final PsiMethod method) { + myContainingClass = method.getContainingClass(); + List<String> result = collectTestDataReferences(method, new HashMap<String, Computable<String>>()); + if (!myFoundTestDataParameters) { + myLogMessages.add("Found no parameters annotated with @TestDataFile"); + } + + if (result == null || result.isEmpty()) { + result = TestDataGuessByExistingFilesUtil.collectTestDataByExistingFiles(method); + } + return result; + } + + private List<String> collectTestDataReferences(final PsiMethod method, final Map<String, Computable<String>> argumentMap) { + final List<String> result = new ArrayList<String>(); + if (myTestDataPath == null) { + return result; + } + method.accept(new JavaRecursiveElementVisitor() { + @Override + public void visitMethodCallExpression(PsiMethodCallExpression expression) { + String callText = expression.getMethodExpression().getReferenceName(); + if (callText == null) return; + PsiMethod callee = expression.resolveMethod(); + if (callee != null && callee.hasModifierProperty(PsiModifier.ABSTRACT)) { + final PsiClass calleeContainingClass = callee.getContainingClass(); + if (calleeContainingClass != null && myContainingClass.isInheritor(calleeContainingClass, true)) { + final PsiMethod implementation = myContainingClass.findMethodBySignature(callee, true); + if (implementation != null) { + callee = implementation; + } + } + } + if (callee != null) { + boolean haveAnnotatedParameters = false; + final PsiParameter[] psiParameters = callee.getParameterList().getParameters(); + for (int i = 0, psiParametersLength = psiParameters.length; i < psiParametersLength; i++) { + PsiParameter psiParameter = psiParameters[i]; + final PsiModifierList modifierList = psiParameter.getModifierList(); + if (modifierList != null && modifierList.findAnnotation(TEST_DATA_FILE_ANNOTATION_QUALIFIED_NAME) != null) { + myFoundTestDataParameters = true; + processCallArgument(expression, argumentMap, result, i); + haveAnnotatedParameters = true; + } + } + if (expression.getMethodExpression().getQualifierExpression() == null && !haveAnnotatedParameters) { + result.addAll(collectTestDataReferences(callee, buildArgumentMap(expression, callee))); + } + } + } + }); + return result; + } + + private void processCallArgument(PsiMethodCallExpression expression, Map<String, Computable<String>> argumentMap, List<String> result, final int index) { + final PsiExpression[] arguments = expression.getArgumentList().getExpressions(); + if (arguments.length > index) { + String testDataFile = evaluate(arguments [index], argumentMap); + if (testDataFile != null) { + result.add(myTestDataPath + testDataFile); + } + } + } + + private Map<String, Computable<String>> buildArgumentMap(PsiMethodCallExpression expression, PsiMethod method) { + Map<String, Computable<String>> result = new HashMap<String, Computable<String>>(); + final PsiParameter[] parameters = method.getParameterList().getParameters(); + final PsiExpression[] arguments = expression.getArgumentList().getExpressions(); + for (int i = 0; i < arguments.length && i < parameters.length; i++) { + final int finalI = i; + result.put(parameters [i].getName(), new NullableComputable<String>() { + public String compute() { + return evaluate(arguments [finalI], Collections.<String, Computable<String>>emptyMap()); + } + }); + } + return result; + } + + @Nullable + private String evaluate(PsiExpression expression, Map<String, Computable<String>> arguments) { + if (expression instanceof PsiPolyadicExpression) { + PsiPolyadicExpression binaryExpression = (PsiPolyadicExpression)expression; + if (binaryExpression.getOperationTokenType() == JavaTokenType.PLUS) { + String r = ""; + for (PsiExpression op : binaryExpression.getOperands()) { + String lhs = evaluate(op, arguments); + if (lhs == null) return null; + r += lhs; + } + return r; + } + } + else if (expression instanceof PsiLiteralExpression) { + final Object value = ((PsiLiteralExpression)expression).getValue(); + if (value instanceof String) { + return (String) value; + } + } + else if (expression instanceof PsiReferenceExpression) { + final PsiElement result = ((PsiReferenceExpression)expression).resolve(); + if (result instanceof PsiParameter) { + final String name = ((PsiParameter)result).getName(); + final Computable<String> arg = arguments.get(name); + return arg == null ? null : arg.compute(); + } + if (result instanceof PsiVariable) { + final PsiExpression initializer = ((PsiVariable)result).getInitializer(); + if (initializer != null) { + return evaluate(initializer, arguments); + } + } + } + else if (expression instanceof PsiMethodCallExpression) { + final PsiMethodCallExpression methodCall = (PsiMethodCallExpression)expression; + final String callText = methodCall.getMethodExpression().getText(); + if (callText.equals("getTestName")) { + final PsiExpression[] psiExpressions = methodCall.getArgumentList().getExpressions(); + if (psiExpressions.length == 1) { + if ("true".equals(psiExpressions[0].getText()) && !StringUtil.isEmpty(myTestName)) { + return UsefulTestCase.lowercaseFirstLetter(myTestName, true); + } + return myTestName; + } + } + } + if (expression != null) { + myLogMessages.add("Failed to evaluate " + expression.getText()); + } + return null; + } + + public String getLog() { + return StringUtil.join(myLogMessages, "\n"); + } +} diff --git a/plugins/devkit/src/testAssistant/TestDataReferenceContributor.java b/plugins/devkit/src/testAssistant/TestDataReferenceContributor.java new file mode 100644 index 000000000000..de3d4a446bb1 --- /dev/null +++ b/plugins/devkit/src/testAssistant/TestDataReferenceContributor.java @@ -0,0 +1,167 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.idea.devkit.testAssistant; + +import com.intellij.openapi.project.Project; +import com.intellij.openapi.roots.ProjectFileIndex; +import com.intellij.openapi.roots.ProjectRootManager; +import com.intellij.openapi.util.Condition; +import com.intellij.openapi.util.TextRange; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.openapi.vfs.newvfs.ManagingFS; +import com.intellij.psi.*; +import com.intellij.psi.impl.source.resolve.reference.impl.providers.FileInfoManager; +import com.intellij.psi.impl.source.resolve.reference.impl.providers.FileReference; +import com.intellij.psi.impl.source.resolve.reference.impl.providers.FileReferenceSet; +import com.intellij.util.ArrayUtil; +import com.intellij.util.ProcessingContext; +import com.intellij.util.containers.ContainerUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Collection; + +import static com.intellij.patterns.PsiJavaPatterns.literalExpression; +import static org.jetbrains.idea.devkit.testAssistant.TestDataLineMarkerProvider.*; + +/** + * @author zolotov + * @since 9/20/13 + */ +public class TestDataReferenceContributor extends PsiReferenceContributor { + @Override + public void registerReferenceProviders(PsiReferenceRegistrar registrar) { + registrar.registerReferenceProvider(literalExpression().annotationParam(TEST_DATA_PATH_ANNOTATION_QUALIFIED_NAME), + new TestDataReferenceProvider()); + } + + private static class TestDataReferenceProvider extends PsiReferenceProvider { + @NotNull + @Override + public PsiReference[] getReferencesByElement(@NotNull final PsiElement element, @NotNull final ProcessingContext context) { + final TestDataReferenceSet referenceSet = new TestDataReferenceSet(element); + referenceSet.addCustomization(FileReferenceSet.DEFAULT_PATH_EVALUATOR_OPTION, FileReferenceSet.ABSOLUTE_TOP_LEVEL); + return referenceSet.getAllReferences(); + } + } + + private static class TestDataReferenceSet extends FileReferenceSet { + public TestDataReferenceSet(@NotNull PsiElement element) { + super(element); + } + + @Override + public boolean isEmptyPathAllowed() { + return false; + } + + @Override + public boolean isAbsolutePathReference() { + return super.isAbsolutePathReference() || StringUtil.startsWithChar(getPathString(), '$'); + } + + @Override + public FileReference createFileReference(TextRange range, int index, String text) { + return new TestDataReference(this, range, index, text); + } + + @NotNull + @Override + public Collection<PsiFileSystemItem> computeDefaultContexts() { + return toFileSystemItems(ManagingFS.getInstance().getLocalRoots()); + } + + @Override + protected Condition<PsiFileSystemItem> getReferenceCompletionFilter() { + return DIRECTORY_FILTER; + } + } + + public static class TestDataReference extends FileReference { + public TestDataReference(@NotNull FileReferenceSet fileReferenceSet, TextRange range, int index, String text) { + super(fileReferenceSet, range, index, text); + } + + @NotNull + @Override + public Object[] getVariants() { + if (getIndex() == 0 && !StringUtil.startsWithChar(getFileReferenceSet().getPathString(), '/')) { + Collection<Object> variants = ContainerUtil.newHashSet(super.getVariants()); + final PsiDirectory projectPsiRoot = getProjectPsiRoot(); + if (projectPsiRoot != null) { + variants.add(FileInfoManager.getFileLookupItem(projectPsiRoot, PROJECT_ROOT_VARIABLE, projectPsiRoot.getIcon(0)) + .withTypeText(projectPsiRoot.getVirtualFile().getPath(), true)); + } + + final PsiDirectory contentPsiRoot = getContentPsiRoot(); + if (contentPsiRoot != null) { + variants.add(FileInfoManager.getFileLookupItem(contentPsiRoot, CONTENT_ROOT_VARIABLE, contentPsiRoot.getIcon(0)) + .withTypeText(contentPsiRoot.getVirtualFile().getPath(), true)); + } + return ArrayUtil.toObjectArray(variants); + } + + return super.getVariants(); + } + + @NotNull + @Override + protected ResolveResult[] innerResolve(boolean caseSensitive, @NotNull PsiFile containingFile) { + if (getIndex() == 0 && StringUtil.startsWithChar(getText(), '$')) { + if (PROJECT_ROOT_VARIABLE.equals(getText())) { + final PsiDirectory projectPsiRoot = getProjectPsiRoot(); + if (projectPsiRoot != null) { + return new ResolveResult[]{new PsiElementResolveResult(projectPsiRoot)}; + } + } + else if (CONTENT_ROOT_VARIABLE.equals(getText())) { + final PsiDirectory contentPsiRoot = getContentPsiRoot(); + if (contentPsiRoot != null) { + return new ResolveResult[]{new PsiElementResolveResult(contentPsiRoot)}; + } + } + } + return super.innerResolve(caseSensitive, containingFile); + } + + @Nullable + private PsiDirectory getProjectPsiRoot() { + final Project project = getElement().getProject(); + final VirtualFile projectDir = project.getBaseDir(); + if (projectDir != null) { + final PsiManager psiManager = PsiManager.getInstance(project); + return psiManager.findDirectory(projectDir); + } + return null; + } + + @Nullable + private PsiDirectory getContentPsiRoot() { + final Project project = getElement().getProject(); + final ProjectFileIndex fileIndex = ProjectRootManager.getInstance(project).getFileIndex(); + final VirtualFile file = getElement().getContainingFile().getOriginalFile().getVirtualFile(); + if (file != null) { + final VirtualFile contentRoot = fileIndex.getContentRootForFile(file); + if (contentRoot != null) { + final PsiManager psiManager = PsiManager.getInstance(project); + return psiManager.findDirectory(contentRoot); + } + } + return null; + } + } +} diff --git a/plugins/devkit/src/testAssistant/TestDataRelatedItem.java b/plugins/devkit/src/testAssistant/TestDataRelatedItem.java new file mode 100644 index 000000000000..5278c4fa6036 --- /dev/null +++ b/plugins/devkit/src/testAssistant/TestDataRelatedItem.java @@ -0,0 +1,59 @@ +/* + * Copyright 2000-2012 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.idea.devkit.testAssistant; + +import com.intellij.navigation.GotoRelatedItem; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.ui.popup.JBPopupFactory; +import com.intellij.psi.PsiMethod; +import com.intellij.util.PathUtil; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +/** + * @author Denis Zhdanov + * @since 5/24/11 4:59 PM + */ +public class TestDataRelatedItem extends GotoRelatedItem{ + + private final List<String> myTestDataFiles = new ArrayList<String>(); + private final Editor myEditor; + private final PsiMethod myMethod; + + public TestDataRelatedItem(@NotNull PsiMethod method, @NotNull Editor editor, @NotNull Collection<String> testDataFiles) { + super(method, "Test Data"); + myMethod = method; + myEditor = editor; + myTestDataFiles.addAll(testDataFiles); + } + + @Override + public String getCustomName() { + if (myTestDataFiles.size() != 1) { + return "test data"; + } + return PathUtil.getFileName(myTestDataFiles.get(0)); + } + + @Override + public void navigate() { + TestDataNavigationHandler.navigate(JBPopupFactory.getInstance().guessBestPopupLocation(myEditor), myTestDataFiles, + myMethod.getProject()); + } +} diff --git a/plugins/devkit/src/testAssistant/TestLocationDataRule.java b/plugins/devkit/src/testAssistant/TestLocationDataRule.java new file mode 100644 index 000000000000..6dccd0f808da --- /dev/null +++ b/plugins/devkit/src/testAssistant/TestLocationDataRule.java @@ -0,0 +1,149 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.idea.devkit.testAssistant; + +import com.intellij.codeInsight.AnnotationUtil; +import com.intellij.execution.Location; +import com.intellij.execution.PsiLocation; +import com.intellij.execution.junit2.PsiMemberParameterizedLocation; +import com.intellij.execution.junit2.info.MethodLocation; +import com.intellij.ide.impl.dataRules.GetDataRule; +import com.intellij.openapi.actionSystem.CommonDataKeys; +import com.intellij.openapi.actionSystem.DataProvider; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.roots.ProjectFileIndex; +import com.intellij.openapi.roots.ProjectRootManager; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.openapi.vfs.VfsUtilCore; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.*; +import com.intellij.psi.search.GlobalSearchScope; +import com.intellij.psi.search.GlobalSearchScopesCore; +import com.intellij.psi.search.PsiSearchHelper; +import com.intellij.testFramework.Parameterized; +import com.intellij.testFramework.TestDataPath; +import com.intellij.util.CommonProcessors; +import com.intellij.util.containers.ContainerUtil; +import gnu.trove.THashSet; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.*; + +public class TestLocationDataRule implements GetDataRule { + @Nullable + @Override + public Object getData(DataProvider dataProvider) { + final Project project = CommonDataKeys.PROJECT.getData(dataProvider); + final VirtualFile file = CommonDataKeys.VIRTUAL_FILE.getData(dataProvider); + if (project != null && file != null) { + final List<Location> locations = collectRelativeLocations(project, file); + return locations.size() == 1 ? locations.get(0) : null; + } + return null; + } + + @NotNull + protected static List<Location> collectRelativeLocations(Project project, VirtualFile file) { + final List<Location> locations = new ArrayList<Location>(); + final ProjectFileIndex fileIndex = ProjectRootManager.getInstance(project).getFileIndex(); + if (fileIndex.isInContent(file) && !fileIndex.isInSource(file) && !fileIndex.isInLibraryClasses(file)) { + final VirtualFile parent = file.getParent(); + final VirtualFile contentRoot = fileIndex.getContentRootForFile(file); + if (contentRoot != null) { + final String relativePath = VfsUtilCore.getRelativePath(parent, contentRoot, '/'); + if (relativePath != null) { + final PsiSearchHelper searchHelper = PsiSearchHelper.SERVICE.getInstance(project); + final List<String> words = StringUtil.getWordsIn(relativePath); + // put longer strings first + Collections.sort(words, new Comparator<String>() { + @Override + public int compare(final String o1, final String o2) { + return o2.length() - o1.length(); + } + }); + + final GlobalSearchScope testScope = GlobalSearchScopesCore.projectTestScope(project); + Set<PsiFile> resultFiles = null; + for (String word : words) { + if (word.length() < 5) { + continue; + } + final Set<PsiFile> files = new THashSet<PsiFile>(); + searchHelper.processAllFilesWithWordInLiterals(word, testScope, new CommonProcessors.CollectProcessor<PsiFile>(files)); + if (resultFiles == null) { + resultFiles = files; + } + else { + resultFiles.retainAll(files); + } + if (resultFiles.isEmpty()) break; + } + if (resultFiles != null) { + for (Iterator<PsiFile> iterator = resultFiles.iterator(); iterator.hasNext(); ) { + if (!VfsUtilCore.isAncestor(contentRoot, iterator.next().getVirtualFile(), true)) { + iterator.remove(); + } + } + + final String fileName = file.getName(); + final String nameWithoutExtension = file.getNameWithoutExtension(); + + + for (PsiFile resultFile : resultFiles) { + if (resultFile instanceof PsiClassOwner) { + final PsiClass[] classes = ((PsiClassOwner)resultFile).getClasses(); + if (classes.length > 0) { + ContainerUtil.addIfNotNull(locations, getLocation(project, fileName, nameWithoutExtension, classes[0])); + } + } + } + } + } + } + } + return locations; + } + + @Nullable + private static Location getLocation(Project project, + String fileName, + String nameWithoutExtension, + PsiClass aClass) { + final PsiAnnotation annotation = AnnotationUtil.findAnnotation(aClass, TestDataPath.class.getName()); + if (annotation != null) { + final Location parameterizedLocation = + PsiMemberParameterizedLocation.getParameterizedLocation(aClass, "[" + fileName + "]", Parameterized.class.getName()); + if (parameterizedLocation != null) { + return parameterizedLocation; + } + if (StringUtil.isJavaIdentifier(nameWithoutExtension)) { + final PsiElementFactory elementFactory = JavaPsiFacade.getElementFactory(project); + PsiMethod method = aClass.findMethodBySignature(elementFactory.createMethod("test" + nameWithoutExtension, PsiType.VOID), true); + if (method != null) { + return MethodLocation.elementInClass(method, aClass); + } + + method = aClass.findMethodBySignature(elementFactory.createMethod("test" + StringUtil.capitalize(nameWithoutExtension), PsiType.VOID), true); + if (method != null) { + return MethodLocation.elementInClass(method, aClass); + } + } + return new PsiLocation<PsiElement>(project, aClass); + } + return null; + } +} diff --git a/plugins/devkit/src/util/DescriptorUtil.java b/plugins/devkit/src/util/DescriptorUtil.java index 3a773e9e5fa9..3cf574f9ca48 100644 --- a/plugins/devkit/src/util/DescriptorUtil.java +++ b/plugins/devkit/src/util/DescriptorUtil.java @@ -87,7 +87,7 @@ public class DescriptorUtil { return getIdeaPlugin((XmlFile)file) != null; } - private static DomFileElement<IdeaPlugin> getIdeaPlugin(XmlFile file) { + public static DomFileElement<IdeaPlugin> getIdeaPlugin(XmlFile file) { return DomManager.getDomManager(file.getProject()).getFileElement(file, IdeaPlugin.class); } } diff --git a/plugins/devkit/src/inspections/quickfix/ExtensionPointCandidate.java b/plugins/devkit/src/util/ExtensionPointCandidate.java index 5bedaf3281da..3ae694a0e7d0 100644 --- a/plugins/devkit/src/inspections/quickfix/ExtensionPointCandidate.java +++ b/plugins/devkit/src/util/ExtensionPointCandidate.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2013 JetBrains s.r.o. + * Copyright 2000-2014 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,25 +13,35 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.jetbrains.idea.devkit.inspections.quickfix; +package org.jetbrains.idea.devkit.util; + +import com.intellij.psi.SmartPsiElementPointer; /** -* @author yole -*/ -class ExtensionPointCandidate { + * @author yole + */ +public class ExtensionPointCandidate { + public final SmartPsiElementPointer pointer; public final String epName; public final String attributeName; public final String tagName; public final String beanClassName; - ExtensionPointCandidate(String epName, String attributeName, String tagName, String beanClassName) { + public ExtensionPointCandidate(SmartPsiElementPointer pointer, + String epName, + String attributeName, + String tagName, + String beanClassName) { + this.pointer = pointer; this.epName = epName; this.attributeName = attributeName; this.tagName = tagName; this.beanClassName = beanClassName; } - ExtensionPointCandidate(String epName) { + public ExtensionPointCandidate(SmartPsiElementPointer pointer, + String epName) { + this.pointer = pointer; this.epName = epName; this.attributeName = "implementation"; this.tagName = null; diff --git a/plugins/devkit/src/util/ExtensionPointLocator.java b/plugins/devkit/src/util/ExtensionPointLocator.java new file mode 100644 index 000000000000..c4a5b19c156a --- /dev/null +++ b/plugins/devkit/src/util/ExtensionPointLocator.java @@ -0,0 +1,122 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.idea.devkit.util; + +import com.intellij.openapi.project.Project; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.*; +import com.intellij.psi.search.GlobalSearchScope; +import com.intellij.psi.search.PsiNonJavaFileReferenceProcessor; +import com.intellij.psi.search.PsiSearchHelper; +import com.intellij.psi.util.PsiTreeUtil; +import com.intellij.psi.xml.XmlTag; +import com.intellij.util.SmartList; +import com.intellij.util.xml.DomElement; +import com.intellij.util.xml.DomService; +import com.intellij.util.xml.DomUtil; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.idea.devkit.dom.ExtensionPoint; +import org.jetbrains.idea.devkit.dom.IdeaPlugin; + +import java.util.Collection; +import java.util.HashSet; +import java.util.List; + +public class ExtensionPointLocator { + + private final PsiClass myPsiClass; + + public ExtensionPointLocator(PsiClass psiClass) { + myPsiClass = psiClass; + } + + public List<ExtensionPointCandidate> findDirectCandidates() { + final List<ExtensionPointCandidate> candidates = new SmartList<ExtensionPointCandidate>(); + findExtensionPointCandidates(myPsiClass, candidates); + return candidates; + } + + public List<ExtensionPointCandidate> findSuperCandidates() { + final List<ExtensionPointCandidate> candidates = new SmartList<ExtensionPointCandidate>(); + findExtensionPointCandidatesInHierarchy(myPsiClass, candidates, new HashSet<PsiClass>()); + return candidates; + } + + private static void findExtensionPointCandidatesInHierarchy(PsiClass psiClass, + List<ExtensionPointCandidate> list, + HashSet<PsiClass> processed) { + for (PsiClass superClass : psiClass.getSupers()) { + if (!processed.add(superClass) || + CommonClassNames.JAVA_LANG_OBJECT.equals(superClass.getQualifiedName())) { + continue; + } + findExtensionPointCandidates(superClass, list); + findExtensionPointCandidatesInHierarchy(superClass, list, processed); + } + } + + private static void findExtensionPointCandidates(PsiClass psiClass, final List<ExtensionPointCandidate> list) { + String name = psiClass.getQualifiedName(); + if (name == null) { + return; + } + + final Project project = psiClass.getProject(); + final Collection<VirtualFile> candidates = DomService.getInstance().getDomFileCandidates(IdeaPlugin.class, project); + GlobalSearchScope scope = GlobalSearchScope.filesScope(project, candidates); + PsiSearchHelper.SERVICE.getInstance(project).processUsagesInNonJavaFiles(name, new PsiNonJavaFileReferenceProcessor() { + @Override + public boolean process(PsiFile file, int startOffset, int endOffset) { + PsiElement element = file.findElementAt(startOffset); + processExtensionPointCandidate(element, list); + return true; + } + }, scope); + } + + private static void processExtensionPointCandidate(PsiElement element, List<ExtensionPointCandidate> list) { + XmlTag tag = PsiTreeUtil.getParentOfType(element, XmlTag.class); + if (tag == null) return; + if ("extensionPoint".equals(tag.getName())) { + String epName = getEPName(tag); + if (epName != null) { + list.add(new ExtensionPointCandidate(createPointer(tag), epName)); + } + } + else if ("with".equals(tag.getName())) { + XmlTag extensionPointTag = tag.getParentTag(); + if (extensionPointTag == null) return; + if (!"extensionPoint".equals(extensionPointTag.getName())) return; + String attrName = tag.getAttributeValue("attribute"); + String tagName = tag.getAttributeValue("tag"); + String epName = getEPName(extensionPointTag); + String beanClassName = extensionPointTag.getAttributeValue("beanClass"); + if ((attrName == null && tagName == null) || epName == null) return; + list.add(new ExtensionPointCandidate(createPointer(extensionPointTag), epName, attrName, tagName, beanClassName)); + } + } + + private static SmartPsiElementPointer createPointer(XmlTag extensionPointTag) { + return SmartPointerManager.getInstance(extensionPointTag.getProject()).createSmartPsiElementPointer(extensionPointTag); + } + + @Nullable + private static String getEPName(XmlTag tag) { + final DomElement domElement = DomUtil.getDomElement(tag); + if (!(domElement instanceof ExtensionPoint)) return null; + return ((ExtensionPoint)domElement).getEffectiveQualifiedName(); + } +} diff --git a/plugins/devkit/src/util/PsiUtil.java b/plugins/devkit/src/util/PsiUtil.java index 58b6e8c065dc..8d093acb8017 100644 --- a/plugins/devkit/src/util/PsiUtil.java +++ b/plugins/devkit/src/util/PsiUtil.java @@ -31,9 +31,11 @@ import org.jetbrains.annotations.Nullable; public class PsiUtil { private static final Key<Boolean> IDEA_PROJECT = Key.create("idea.internal.inspections.enabled"); private static final String IDE_PROJECT_MARKER_CLASS = JBList.class.getName(); - public static final PsiElementVisitor EMPTY_VISITOR = new PsiElementVisitor() { }; + public static final PsiElementVisitor EMPTY_VISITOR = new PsiElementVisitor() { + }; - private PsiUtil() { } + private PsiUtil() { + } public static boolean isInstantiable(@NotNull PsiClass cls) { final PsiModifierList modList = cls.getModifierList(); @@ -80,9 +82,9 @@ public class PsiUtil { String text = expr.getText(); if (text == null) return false; text = text.replaceAll(" ", "") - .replaceAll("\n", "") - .replaceAll("\t", "") - .replaceAll("\r", ""); + .replaceAll("\n", "") + .replaceAll("\t", "") + .replaceAll("\r", ""); return "getClass().getSimpleName()".equals(text) || "this.getClass().getSimpleName()".equals(text); } @@ -99,6 +101,18 @@ public class PsiUtil { return null; } + @Nullable + public static PsiMethod findNearestMethod(String name, @Nullable PsiClass cls) { + if (cls == null) return null; + for (PsiMethod method : cls.getMethods()) { + if (method.getParameterList().getParametersCount() == 0 && method.getName().equals(name)) { + return method.getModifierList().hasModifierProperty(PsiModifier.ABSTRACT) ? null : method; + } + } + return findNearestMethod(name, cls.getSuperClass()); + } + + public static boolean isIdeaProject(@Nullable Project project) { if (project == null) return false; @@ -111,9 +125,25 @@ public class PsiUtil { return flag; } + private static boolean isIntelliJBasedDir(VirtualFile baseDir) { + if (baseDir == null) { + return false; + } + + for (VirtualFile dir : new VirtualFile[]{baseDir, baseDir.findChild("community")}) { + if (dir == null || !dir.isDirectory()) { + continue; + } + if (dir.findChild("idea.iml") != null || dir.findChild("community-main.iml") != null) { + return true; + } + } + + return false; + } + private static boolean checkIdeaProject(@NotNull Project project) { - VirtualFile baseDir = project.getBaseDir(); - if (baseDir == null || baseDir.findChild("idea.iml") == null && baseDir.findChild("community-main.iml") == null) { + if (!isIntelliJBasedDir(project.getBaseDir())) { return false; } |