Perl5 plugin for Intellij IDEA
Hi everyone.
Recently i've decided to try to make perl5 plugin for InterlliJ IDEA. I've seen feew attempts to start, but they've failed after creating four base classes :)
The problem with perl is his too free syntax, which requires a very custom lexer and parser. I've tried to port perl's original lexer, but it's too big, got lot of legacy stuff, so i've droped the idea.
Currently, i'm making lexer with JFlex and it works, but need tunings for different perlish situations. Anyway, some syntax is already highighted:

The second problem is that language plugins development documentation looks outdated (of course, not so many readers), but examples helps in such cases. Sometimes...
But is there some gurus in language plugins who could help with tricky questions or just to save some time in useless tries?
Also, if you would like to participate in perl5 plugin development, you are welcome: https://github.com/hurricup/Perl5-IDEA
Please sign in to leave a comment.
this code is called whenever (in-place?) rename action is triggered:
https://github.com/JetBrains/intellij-community/blob/master/platform/lang-impl/src/com/intellij/refactoring/rename/RenameHandlerRegistry.java#L96-L101
and whenever RenameHandler EP is registered, availableHandlers.size() is always > 1
maybe change logic of getRenameHandler so that only the first EP RenameHandler that isRenaming(dataContext),
ends up in availableHandlers, so availableHandlers.size() == 1?
That's not correct. Multiple handlers were introduced intentionally to be able to suggest user to choose one renamer, e.g. in case of Project View with module content root selection - IDE can't predict what is the user's intent: to rename module or to rename the directory. Handlers must override isAvailable and if no availables found - default would be chosen.
> IDE can't predict what is the user's intent: to rename module or to rename the directory
well in my case (I met with the same problem) the HandlersChooser pops up when user tries to rename a symbol in code.
isn't user intention clear in this case?
in the EP RenameHandler implementation I just changed isAvailableOnDataContext (which calls isAvailable) to return true (temporarily, just to test).
the same behaviour: HandlersChooser pops up and presents puzzling options to user.
I suppose it's clear and it's clear is a bug in one of the handlers mentioned in the popup.
it is one bug in this plugin (could be in plugin code, of course) I could not get around so far.
'availableHandlers.size() is always > 1' is incorrect, otherwise you'll see the dialog every time you rename variable in java code which is not true. What another handler which is shown in the dialog? Without details I can't help you, hope you understand
> and whenever RenameHandler EP is registered, availableHandlers.size() is always > 1
Ania, let's try a simple test:
register below code as the only custom renameHandler EP.
then (assuming inline renaming is enabled), try to rename a variable. What do you see?
on my PC a "Select Refactoring" dialog pops up:
renameHandler EP code:
VariableInplaceRenameHandler should be disabled if you like to provide your own implementation for it, please read above my explanations to Alexander.
> VariableInplaceRenameHandler should be disabled if you like to provide your own implementation for it
this gets rid of the chooser popup. Thank you for clarifying this! :)
I however enabled isInplaceRenameAvailable to get in-file variable highlighting in case of local scope renaming.
If references are found only in one file, inplace renamer handles it. It highlights the symbols which is very handy.
If references are found in other files, performDialogRename is called with its own popup.
well I can see benefit to users from this ideal scenario for renaming code symbols:
1) place cursor on symbol
2) press F2 (or another shortcut) - no selector popups
3) if local scoped - in place renamer with symbol highlighting without popup
4) if external references - PsiElementRenameHandler with 1 popup
at the moment it works just as above - except @ 2) we get a puzzling popup where I would always select the same option. The users are likely to experiment and file bugs.
if there were a way
to suppress HandlerChooser
with coexisting VariableInplaceRenameHandler and custom RenameHandler,
this would be very nice ;\
It looks to me that you need your custom handler to override VariableInplaceHandler with ability to performRefactoring which would rename usages in other files. MemberInplaceRenameHandler is an example where it is done.
Third change :)
To make cleaner global entities resolution i need to build resolution scope for elements inside a file. I need to traverse all current file imports and imports of imports and on and on...
I've just tried to traverse stubs, but even then - it is too slow. What is the proper way here?
There's no "proper" way, it's up to you to devise an efficient algorithm taking into account the language rules. Normally some kind of caching for each intermediate file helps. See PsiJavaFileBaseImpl.MyCacheBuilder for an example.
From time to time starting to get warnings while working in my plugin. Always at the same PsiElements. But no idea why and what to do with this:
WARN - pi.editor.impl.RangeMarkerTree - Too many range markers (50) registered for interval (2750,2750)
Can you give me any directions?
As the message says, there are too many range markers registered for the same interval. You can try to debug who creates them and why there are so many of them. Then you can either reuse them, or dispose the ones you don't need anymore (RangeMarker#dispose)
Well it's a general way, yes. But, the problem that i don't know what are these markers, what are they for (well, i've read javadoc in the corresponding interface) and where are they coming from. I'm not generating them directly i believe.
Then it's harder. But you probably still need to debug them (a condition breakpoint in RangeMarkerImpl should do it) and understand which IDEA's subsystems create them in such quantities.
Ok. Single space typing generating 2-5 empty markers at the same position. Warnings does not appears everytime, so looks like an incorrect caching. Here is the trace (says nothing to me yet):
"AWT-EventQueue-0 14.1.5#IU-141.2735.5, eap:false@2529" prio=6 tid=0x1b nid=NA runnable
java.lang.Thread.State: RUNNABLE
at com.intellij.openapi.editor.impl.RangeMarkerImpl.<init>(RangeMarkerImpl.java:40)
at com.intellij.openapi.editor.impl.RangeMarkerImpl.<init>(RangeMarkerImpl.java:37)
at com.intellij.openapi.editor.impl.DocumentImpl.createRangeMarker(DocumentImpl.java:457)
at com.intellij.openapi.editor.impl.DocumentImpl.createRangeMarker(DocumentImpl.java:448)
at com.intellij.openapi.editor.impl.DocumentImpl.createRangeMarker(DocumentImpl.java:1015)
at com.intellij.codeInsight.daemon.impl.UpdateHighlightersUtil.a(UpdateHighlightersUtil.java:443)
at com.intellij.codeInsight.daemon.impl.UpdateHighlightersUtil.access$300(UpdateHighlightersUtil.java:49)
at com.intellij.codeInsight.daemon.impl.UpdateHighlightersUtil$9.consume(UpdateHighlightersUtil.java:400)
at com.intellij.codeInsight.daemon.impl.UpdateHighlightersUtil$9.consume(UpdateHighlightersUtil.java:368)
at com.intellij.openapi.editor.impl.RangeHighlighterImpl.changeAttributesNoEvents(RangeHighlighterImpl.java:311)
at com.intellij.openapi.editor.impl.MarkupModelImpl.a(MarkupModelImpl.java:137)
at com.intellij.openapi.editor.impl.MarkupModelImpl.addRangeHighlighterAndChangeAttributes(MarkupModelImpl.java:125)
at com.intellij.codeInsight.daemon.impl.UpdateHighlightersUtil.a(UpdateHighlightersUtil.java:406)
at com.intellij.codeInsight.daemon.impl.UpdateHighlightersUtil.addHighlighterToEditorIncrementally(UpdateHighlightersUtil.java:115)
at com.intellij.codeInsight.daemon.impl.HighlightingSessionImpl$1.process(HighlightingSessionImpl.java:110)
at com.intellij.codeInsight.daemon.impl.HighlightingSessionImpl$1.process(HighlightingSessionImpl.java:106)
at com.intellij.util.containers.TransferToEDTQueue.processNext(TransferToEDTQueue.java:98)
at com.intellij.util.containers.TransferToEDTQueue.access$300(TransferToEDTQueue.java:36)
at com.intellij.util.containers.TransferToEDTQueue$1.run(TransferToEDTQueue.java:57)
at java.awt.event.InvocationEvent.dispatch(InvocationEvent.java:312)
at java.awt.EventQueue.dispatchEventImpl(EventQueue.java:733)
at java.awt.EventQueue.access$200(EventQueue.java:103)
at java.awt.EventQueue$3.run(EventQueue.java:694)
at java.awt.EventQueue$3.run(EventQueue.java:692)
at java.security.AccessController.doPrivileged(AccessController.java:-1)
at java.security.ProtectionDomain$1.doIntersectionPrivilege(ProtectionDomain.java:76)
at java.awt.EventQueue.dispatchEvent(EventQueue.java:703)
at com.intellij.ide.IdeEventQueue.e(IdeEventQueue.java:734)
at com.intellij.ide.IdeEventQueue._dispatchEvent(IdeEventQueue.java:569)
at com.intellij.ide.IdeEventQueue.dispatchEvent(IdeEventQueue.java:382)
at java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:242)
at java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:161)
at java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:150)
at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:146)
at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:138)
at java.awt.EventDispatchThread.run(EventDispatchThread.java:91)
Abstract situation:
Have a class definition PsiElement in a PsiFile.
In some cases, class definition should be renamed together with containing psiFIle.
But, if there are other elements in very same PsiFile i'm starting to get problems.
I've implemented RenamePsiElementProcessor for class definition and put there logic, which decides if we should rename file as well. In case we do, I add a PsiFile to the allRenames map.
But, seems IDEA first renames file, than trying to rename other elements in the very same file, but they are not valid anymore, because file name has been changed. And i'm getting:
[ 70496] ERROR - pplication.impl.LaterInvocator - Element: class com.perl5.lang.perl.psi.impl.PerlNamespaceElementImpl because: different providers: SingleRootFileViewProvider{myVirtualFile=file://C:/Repository/Perl5-IDEA-Test/test/lib2/Foo/Bar12.pm, content=DocumentContent{size=130}}(cad7e2b); SingleRootFileViewProvider{myVirtualFile=file://C:/Repository/Perl5-IDEA-Test/test/lib2/Foo/Bar12.pm, content=VirtualFileContent{size=139}}(39c7eef6)
invalidated at: no info; com.perl5.lang.perl.psi.impl.PerlNamespaceElementImpl:PerlTokenType.PACKAGE / com.intellij.psi.impl.source.tree.CompositeElement:NAMESPACE / com.intellij.psi.impl.source.tree.FileElement:Perl5
com.intellij.psi.PsiInvalidElementAccessException: Element: class com.perl5.lang.perl.psi.impl.PerlNamespaceElementImpl because: different providers: SingleRootFileViewProvider{myVirtualFile=file://C:/Repository/Perl5-IDEA-Test/test/lib2/Foo/Bar12.pm, content=DocumentContent{size=130}}(cad7e2b); SingleRootFileViewProvider{myVirtualFile=file://C:/Repository/Perl5-IDEA-Test/test/lib2/Foo/Bar12.pm, content=VirtualFileContent{size=139}}(39c7eef6)
invalidated at: no info; com.perl5.lang.perl.psi.impl.PerlNamespaceElementImpl:PerlTokenType.PACKAGE / com.intellij.psi.impl.source.tree.CompositeElement:NAMESPACE / com.intellij.psi.impl.source.tree.FileElement:Perl5
More general question:
If we have a file and few elements in this file, that depends on containing file location/name. How properly handle such file rename/move. It's seems to me incorrect to adjust nested items before file rename/move, but if i collect items before rename/move and trying to modify them after - they are not valid anymore.
TargetElementUtilBase#getReferenceOrReferencedElement uses resolve() to get target element to highlight. So if there are several variants, only one will be highlighted, which has been returned by resolve.
How to make it collect all reference targets?
Please provide a full stack trace of the error. If your plugin is working with IDEA 15, please put a breakpoint in PsiFileImpl#markInvalidated and provide the stack trace of the invalidation during rename.
In general, keeping smart pointers (see SmartPointerManager) instead of direct references to PSI might help.
HighlightUsagesHandler#getUsageTargets already uses multiResolve. Do you mean some other kind of highlighting?
Unfotunatelly i've made a dirty workaround for this, so can't repeat. Has only partial (I guess) stacktrace:
[ 26832] ERROR - pplication.impl.LaterInvocator - Element: class com.perl5.lang.perl.psi.impl.PerlNamespaceElementImpl because: different providers: SingleRootFileViewProvider{myVirtualFile=file://C:/Repository/Perl5-IDEA-Test/test/lib2/Foo/Bar12.pm, content=DocumentContent{size=130}}(170b09e); SingleRootFileViewProvider{myVirtualFile=file://C:/Repository/Perl5-IDEA-Test/test/lib2/Foo/Bar12.pm, content=VirtualFileContent{size=139}}(2a5afbcf)
invalidated at: no info; com.perl5.lang.perl.psi.impl.PerlNamespaceElementImpl:PerlTokenType.PACKAGE / com.intellij.psi.impl.source.tree.CompositeElement:NAMESPACE / com.intellij.psi.impl.source.tree.FileElement:Perl5
com.intellij.psi.PsiInvalidElementAccessException: Element: class com.perl5.lang.perl.psi.impl.PerlNamespaceElementImpl because: different providers: SingleRootFileViewProvider{myVirtualFile=file://C:/Repository/Perl5-IDEA-Test/test/lib2/Foo/Bar12.pm, content=DocumentContent{size=130}}(170b09e); SingleRootFileViewProvider{myVirtualFile=file://C:/Repository/Perl5-IDEA-Test/test/lib2/Foo/Bar12.pm, content=VirtualFileContent{size=139}}(2a5afbcf)
invalidated at: no info; com.perl5.lang.perl.psi.impl.PerlNamespaceElementImpl:PerlTokenType.PACKAGE / com.intellij.psi.impl.source.tree.CompositeElement:NAMESPACE / com.intellij.psi.impl.source.tree.FileElement:Perl5
at com.intellij.psi.impl.source.tree.LeafPsiElement.invalid(LeafPsiElement.java:107)
at com.intellij.psi.impl.source.tree.LeafPsiElement.getContainingFile(LeafPsiElement.java:91)
at com.intellij.psi.impl.source.codeStyle.CodeEditUtil.saveWhitespacesInfo(CodeEditUtil.java:147)
at com.intellij.psi.impl.source.tree.ChangeUtil.saveIndentationToCopy(ChangeUtil.java:128)
at com.intellij.psi.impl.source.tree.ChangeUtil.copyLeafWithText(ChangeUtil.java:106)
at com.intellij.psi.impl.source.tree.LeafElement.replaceWithText(LeafElement.java:152)
at com.perl5.lang.perl.idea.manipulators.PerlNamespaceElementManipulator.handleContentChange(PerlNamespaceElementManipulator.java:48)
at com.perl5.lang.perl.idea.manipulators.PerlNamespaceElementManipulator.handleContentChange(PerlNamespaceElementManipulator.java:30)
at com.intellij.psi.AbstractElementManipulator.handleContentChange(AbstractElementManipulator.java:30)
at com.perl5.lang.perl.psi.utils.PerlPsiUtil.renameElement(PerlPsiUtil.java:185)
at com.perl5.lang.perl.psi.mixins.PerlNamespaceDefinitionImplMixin.setName(PerlNamespaceDefinitionImplMixin.java:87)
at com.intellij.refactoring.rename.RenameUtil.doRenameGenericNamedElement(RenameUtil.java:236)
at com.intellij.refactoring.rename.RenamePsiElementProcessor.renameElement(RenamePsiElementProcessor.java:60)
at com.intellij.refactoring.rename.RenameUtil.doRename(RenameUtil.java:187)
at com.intellij.refactoring.rename.RenameProcessor.performRefactoring(RenameProcessor.java:405)
at com.intellij.refactoring.BaseRefactoringProcessor$7.run(BaseRefactoringProcessor.java:475)
at com.intellij.openapi.application.impl.ApplicationImpl.runWriteAction(ApplicationImpl.java:931)
at com.intellij.refactoring.BaseRefactoringProcessor.doRefactoring(BaseRefactoringProcessor.java:448)
at com.intellij.refactoring.BaseRefactoringProcessor.access$100(BaseRefactoringProcessor.java:75)
at com.intellij.refactoring.BaseRefactoringProcessor$3.run(BaseRefactoringProcessor.java:293)
at com.intellij.openapi.command.impl.CoreCommandProcessor.executeCommand(CoreCommandProcessor.java:124)
at com.intellij.openapi.command.impl.CoreCommandProcessor.executeCommand(CoreCommandProcessor.java:99)
at com.intellij.refactoring.BaseRefactoringProcessor.execute(BaseRefactoringProcessor.java:289)
at com.intellij.refactoring.BaseRefactoringProcessor.doRun(BaseRefactoringProcessor.java:226)
at com.intellij.refactoring.rename.RenameProcessor.doRun(RenameProcessor.java:127)
at com.intellij.refactoring.rename.inplace.MemberInplaceRenamer$2.doRun(MemberInplaceRenamer.java:246)
at com.intellij.refactoring.BaseRefactoringProcessor$9.run(BaseRefactoringProcessor.java:554)
at com.intellij.openapi.project.DumbServiceImpl.runWhenSmart(DumbServiceImpl.java:146)
at com.intellij.openapi.project.DumbServiceImpl$5.run(DumbServiceImpl.java:344)
at com.intellij.openapi.application.impl.LaterInvocator$FlushQueue.run(LaterInvocator.java:332)
at java.awt.event.InvocationEvent.dispatch(InvocationEvent.java:209)
at java.awt.EventQueue.dispatchEventImpl(EventQueue.java:666)
at java.awt.EventQueue.access$400(EventQueue.java:81)
at java.awt.EventQueue$2.run(EventQueue.java:627)
at java.awt.EventQueue$2.run(EventQueue.java:625)
at java.security.AccessController.doPrivileged(Native Method)
at java.security.AccessControlContext$1.doIntersectionPrivilege(AccessControlContext.java:87)
at java.awt.EventQueue.dispatchEvent(EventQueue.java:636)
at com.intellij.ide.IdeEventQueue.defaultDispatchEvent(IdeEventQueue.java:734)
at com.intellij.ide.IdeEventQueue._dispatchEvent(IdeEventQueue.java:569)
at com.intellij.ide.IdeEventQueue.dispatchEvent(IdeEventQueue.java:382)
at java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:269)
at java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:184)
at java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:174)
at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:169)
at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:161)
at java.awt.EventDispatchThread.run(EventDispatchThread.java:122)
My bad again, got some legacy redundant named element from my early experiments. Everything works fine.
Noticed, that default find usages acts strange.
We have a declaration. And somewhere in other file we have a reference from literal (string).
If i'm trying to find usages of declarations - no results found.
But, if i put some code reference to the same declaration in the file with literal reference - Find Usages finds them both.
Why so and how to fix this?
By default, CachesBasedRefSearcher is used which requests (via p.getOptimizer().searchWord) search to be performed in UsageSearchContext.IN_CODE | UsageSearchContext.IN_FOREIGN_LANGUAGES | UsageSearchContext.IN_COMMENTS. Apparenty your strings are not indexed as IN_FOREIGN_LANGUAGES in WordsScanner (if they support language injection, they should be). If you know exactly that your references should be searched in strings in a particular language, you can add another ReferencesSearch extension in your plugin and request searching in UsageSearchContext.IN_STRINGS with correct scope (most likely GlobalSearchScope#getScopeRestrictedByFileTypes).
Seems DefaultWordsScanner#setMayHaveFileRefsInLiterals(true) solves problem
Nedd some guidance.
Perl has a lot of situatinally optional things, like quotes, commas, semicolonos. I'd like to add such things to the code-style settings and make formatter to add/remove them according to settings.
I've already made formatter, figured out spacing, aligning and wrapping.
The question: how to properly make formatter to add or remove psi elements?
Hi,
Take a look at PreFormatProcessor and its implementations
Works like a charm, thank you!
Got problem with partial in-place refactoring.
I've got an identifier, but real identifier is only part of it and it's not possible to split it into two tokens on the lexing phase.
In-place refactoring works fine if extra chars are in the end of the token (I just need to override getTextRange of the element and make it few chars shorter.)
But, if extra chars are in the beginning of the token - getting problem: when inplace re-factoring starts, it eats extra chars after identifier.
For example:
+someid is a single token. But real identifier is someid.
If i set textRange to someid, in-place re-factoring eats closing quote. Guess it's somehow related to token getTextLength() but it seems not allowed to change it, getting Underestimated text length exception.
Is it possible to do such thing? and how?