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:
46fdd3eb65.jpg
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

1
236 comments

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?

0

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.

0

> 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.

0

I suppose it's clear and it's clear is a bug in one of the handlers mentioned in the popup.

0

it is one bug in this plugin (could be in plugin code, of course) I could not get around so far.

0

'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

0

> 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:

What would you like to do?

TestRenameHandler@...

com.intellij.refactoring.rename.inplace.VariableInplaceRenameHandler@...




renameHandler EP code:

 
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.PsiFile;
import
com.intellij.refactoring.rename.RenameHandler;

public class
TestRenameHandler implements RenameHandler {
   @Override
   public boolean isAvailableOnDataContext
(DataContext dataContext) {
      return true;
   
}

   @Override
   public boolean isRenaming
(DataContext dataContext) {
      return true;
   
}

   @Override
   public void invoke
(Project project, Editor editor, PsiFile psiFile, DataContext dataContext) {
      boolean d = true;
   
}

   @Override
   public void invoke
(Project project, PsiElement[] psiElements, DataContext dataContext) {
      boolean d = true;
   
}
}
0

VariableInplaceRenameHandler should be disabled if you like to provide your own implementation for it, please read above my explanations to Alexander.

0

> 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 ;\

0

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.

0
Avatar
Alexandr Evstigneev

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?

0

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.

0
Avatar
Alexandr Evstigneev

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?

0

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)

0
Avatar
Alexandr Evstigneev

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.

0

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.

0
Avatar
Alexandr Evstigneev

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)

0
Avatar
Alexandr Evstigneev

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.

0
Avatar
Alexandr Evstigneev

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?

0

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.

0

HighlightUsagesHandler#getUsageTargets already uses multiResolve. Do you mean some other kind of highlighting?

0
Avatar
Alexandr Evstigneev

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)

0
Avatar
Alexandr Evstigneev

My bad again, got some legacy redundant named element from my early experiments. Everything works fine.

0
Avatar
Alexandr Evstigneev

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?

0

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).

0
Avatar
Alexandr Evstigneev

Seems DefaultWordsScanner#setMayHaveFileRefsInLiterals(true) solves problem     

0
Avatar
Alexandr Evstigneev

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?

0

Hi,

Take a look at PreFormatProcessor and its implementations

0
Avatar
Alexandr Evstigneev

Works like a charm, thank you!

0
Avatar
Alexandr Evstigneev

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:

 has '+someid' => (...); 


+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?
0

Please sign in to leave a comment.