From 85cf96f2a1494548d7d649b84e22a0891720d4f4 Mon Sep 17 00:00:00 2001 From: Konstantin Bulenkov Date: Sun, 30 Oct 2011 01:18:15 +0200 Subject: [PATCH] check regexp in balloon --- RegExpSupport/src/META-INF/RegExpPlugin.xml | 5 + .../after.java.template | 3 + .../before.java.template | 3 + .../description.html | 5 + .../inject/quickedit/CheckRegExpForm.form | 75 +++++++++ .../inject/quickedit/CheckRegExpForm.java | 143 ++++++++++++++++++ .../quickedit/CheckRegExpIntentionAction.java | 78 ++++++++++ .../inject/quickedit/QuickEditAction.java | 100 +++++++++--- 8 files changed, 387 insertions(+), 25 deletions(-) create mode 100644 plugins/IntelliLang/src/intentionDescriptions/CheckRegExpIntentionAction/after.java.template create mode 100644 plugins/IntelliLang/src/intentionDescriptions/CheckRegExpIntentionAction/before.java.template create mode 100644 plugins/IntelliLang/src/intentionDescriptions/CheckRegExpIntentionAction/description.html create mode 100644 plugins/IntelliLang/src/org/intellij/plugins/intelliLang/inject/quickedit/CheckRegExpForm.form create mode 100644 plugins/IntelliLang/src/org/intellij/plugins/intelliLang/inject/quickedit/CheckRegExpForm.java create mode 100644 plugins/IntelliLang/src/org/intellij/plugins/intelliLang/inject/quickedit/CheckRegExpIntentionAction.java diff --git a/RegExpSupport/src/META-INF/RegExpPlugin.xml b/RegExpSupport/src/META-INF/RegExpPlugin.xml index 54f6f622997a..10c622345617 100644 --- a/RegExpSupport/src/META-INF/RegExpPlugin.xml +++ b/RegExpSupport/src/META-INF/RegExpPlugin.xml @@ -9,5 +9,10 @@ + + + org.intellij.plugins.intelliLang.inject.quickedit.CheckRegExpIntentionAction + Declaration + diff --git a/plugins/IntelliLang/src/intentionDescriptions/CheckRegExpIntentionAction/after.java.template b/plugins/IntelliLang/src/intentionDescriptions/CheckRegExpIntentionAction/after.java.template new file mode 100644 index 000000000000..5fddae87c53a --- /dev/null +++ b/plugins/IntelliLang/src/intentionDescriptions/CheckRegExpIntentionAction/after.java.template @@ -0,0 +1,3 @@ +public class RegExp { + Pattern PATTERN = Pattern.compile("[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,4}"); +} diff --git a/plugins/IntelliLang/src/intentionDescriptions/CheckRegExpIntentionAction/before.java.template b/plugins/IntelliLang/src/intentionDescriptions/CheckRegExpIntentionAction/before.java.template new file mode 100644 index 000000000000..aefaacc64c48 --- /dev/null +++ b/plugins/IntelliLang/src/intentionDescriptions/CheckRegExpIntentionAction/before.java.template @@ -0,0 +1,3 @@ +public class RegExp { + Pattern PATTERN = Pattern.compile("[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{3,4}"); +} diff --git a/plugins/IntelliLang/src/intentionDescriptions/CheckRegExpIntentionAction/description.html b/plugins/IntelliLang/src/intentionDescriptions/CheckRegExpIntentionAction/description.html new file mode 100644 index 000000000000..729358641801 --- /dev/null +++ b/plugins/IntelliLang/src/intentionDescriptions/CheckRegExpIntentionAction/description.html @@ -0,0 +1,5 @@ + + +This intention allows you to play with regular expression and check it for bugs. + + diff --git a/plugins/IntelliLang/src/org/intellij/plugins/intelliLang/inject/quickedit/CheckRegExpForm.form b/plugins/IntelliLang/src/org/intellij/plugins/intelliLang/inject/quickedit/CheckRegExpForm.form new file mode 100644 index 000000000000..0272a0f10158 --- /dev/null +++ b/plugins/IntelliLang/src/org/intellij/plugins/intelliLang/inject/quickedit/CheckRegExpForm.form @@ -0,0 +1,75 @@ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/plugins/IntelliLang/src/org/intellij/plugins/intelliLang/inject/quickedit/CheckRegExpForm.java b/plugins/IntelliLang/src/org/intellij/plugins/intelliLang/inject/quickedit/CheckRegExpForm.java new file mode 100644 index 000000000000..3ceebc01d760 --- /dev/null +++ b/plugins/IntelliLang/src/org/intellij/plugins/intelliLang/inject/quickedit/CheckRegExpForm.java @@ -0,0 +1,143 @@ +/* + * 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.intellij.plugins.intelliLang.inject.quickedit; + +import com.intellij.ide.util.PropertiesComponent; +import com.intellij.openapi.editor.Document; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.editor.event.DocumentAdapter; +import com.intellij.openapi.editor.event.DocumentEvent; +import com.intellij.openapi.fileTypes.PlainTextFileType; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.popup.Balloon; +import com.intellij.openapi.ui.popup.JBPopupAdapter; +import com.intellij.openapi.ui.popup.LightweightWindowEvent; +import com.intellij.openapi.util.Pair; +import com.intellij.openapi.util.Ref; +import com.intellij.openapi.wm.IdeFocusManager; +import com.intellij.psi.PsiDocumentManager; +import com.intellij.psi.PsiFile; +import com.intellij.ui.BalloonImpl; +import com.intellij.ui.EditorTextField; +import com.intellij.util.Alarm; +import org.intellij.lang.regexp.RegExpLanguage; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.KeyAdapter; +import java.awt.event.KeyEvent; +import java.util.regex.Pattern; + +/** + * @author Konstantin Bulenkov + */ +public class CheckRegExpForm { + private static final String LAST_EDITED_REGEXP = "last.edited.regexp"; + private Pair> myParams; + + private EditorTextField mySampleText; //TODO[kb]: make it multiline + + private EditorTextField myRegExp; + private JPanel myRootPanel; + private Ref myRef; + private Project myProject; + + + public CheckRegExpForm(Pair> params) { + myParams = params; + } + + private void createUIComponents() { + PsiFile file = myParams.first; + myProject = file.getProject(); + myRef = myParams.second; + Document document = PsiDocumentManager.getInstance(myProject).getDocument(file); + + myRegExp = new EditorTextField(document, myProject, RegExpLanguage.INSTANCE.getAssociatedFileType()); + final String sampleText = PropertiesComponent.getInstance(myProject).getValue(LAST_EDITED_REGEXP, "Sample Text"); + mySampleText = new EditorTextField(sampleText, myProject, PlainTextFileType.INSTANCE); + myRootPanel = new JPanel(new BorderLayout()) { + @Override + public void addNotify() { + super.addNotify(); + IdeFocusManager.getGlobalInstance().requestFocus(mySampleText, true); + + final KeyAdapter escaper = new KeyAdapter() { + @Override + public void keyPressed(KeyEvent e) { + if (e.getKeyCode() == KeyEvent.VK_ESCAPE) { + if (!myRef.get().isDisposed()) { + myRef.get().hide(); + } + } + } + }; + + final Editor fieldEditor = myRegExp.getEditor(); + final Editor textEditor = mySampleText.getEditor(); + + assert fieldEditor != null && textEditor != null; + + fieldEditor.getContentComponent().addKeyListener(escaper); + textEditor.getContentComponent().addKeyListener(escaper); + + myRef.get().addListener(new JBPopupAdapter() { + @Override + public void onClosed(LightweightWindowEvent event) { + PropertiesComponent.getInstance(myProject).setValue(LAST_EDITED_REGEXP, mySampleText.getText()); + } + }); + + final Alarm updater = new Alarm(Alarm.ThreadToUse.SWING_THREAD, myRef.get()); + final DocumentAdapter documentListener = new DocumentAdapter() { + @Override + public void documentChanged(DocumentEvent e) { + updater.cancelAllRequests(); + updater.addRequest(new Runnable() { + @Override + public void run() { + updateBalloon(); + } + }, 200); + } + }; + myRegExp.addDocumentListener(documentListener); + mySampleText.addDocumentListener(documentListener); + + updateBalloon(); + mySampleText.selectAll(); + } + }; + } + + + public JPanel getRootPanel() { + return myRootPanel; + } + + private void updateBalloon() { + boolean correct = false; + try { + correct = Pattern.compile(myRegExp.getText()).matcher(mySampleText.getText()).matches(); + } catch (Exception ignore) {} + + mySampleText.setBackground(correct ? new Color(231, 250, 219) : new Color(255, 177, 160)); + BalloonImpl balloon = (BalloonImpl)myRef.get(); + if (balloon != null && balloon.isDisposed()) { + balloon.revalidate(); + } + } +} diff --git a/plugins/IntelliLang/src/org/intellij/plugins/intelliLang/inject/quickedit/CheckRegExpIntentionAction.java b/plugins/IntelliLang/src/org/intellij/plugins/intelliLang/inject/quickedit/CheckRegExpIntentionAction.java new file mode 100644 index 000000000000..64bdcbad25a7 --- /dev/null +++ b/plugins/IntelliLang/src/org/intellij/plugins/intelliLang/inject/quickedit/CheckRegExpIntentionAction.java @@ -0,0 +1,78 @@ +/* + * 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.intellij.plugins.intelliLang.inject.quickedit; + +import com.intellij.openapi.editor.Document; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.popup.Balloon; +import com.intellij.openapi.util.Iconable; +import com.intellij.openapi.util.Pair; +import com.intellij.openapi.util.Ref; +import com.intellij.openapi.util.TextRange; +import com.intellij.psi.PsiDocumentManager; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; +import org.intellij.lang.regexp.RegExpLanguage; +import org.jetbrains.annotations.NotNull; + +import javax.swing.*; + +/** + * @author Konstantin Bulenkov + */ +public class CheckRegExpIntentionAction extends QuickEditAction implements Iconable { + @Override + public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file) { + final Pair pair = getRangePair(file, editor); + /*super.isAvailable(project, editor, file) && */ + return pair != null && pair.first != null && pair.first.getLanguage() == RegExpLanguage.INSTANCE; + } + + @Override + protected boolean isShowInBalloon() { + return true; + } + + @Override + protected JComponent createBalloonComponent(PsiFile file, final Ref ref) { + final Project project = file.getProject(); + final Document document = PsiDocumentManager.getInstance(project).getDocument(file); + if (document != null) { + return new CheckRegExpForm(new Pair>(file, ref)).getRootPanel(); + } + return null; + } + + + @NotNull + @Override + public String getText() { + return "Check RegExp"; + } + + @NotNull + @Override + public String getFamilyName() { + return getText(); + } + + @Override + public Icon getIcon(int flags) { + //noinspection ConstantConditions + return RegExpLanguage.INSTANCE.getAssociatedFileType().getIcon(); + } +} diff --git a/plugins/IntelliLang/src/org/intellij/plugins/intelliLang/inject/quickedit/QuickEditAction.java b/plugins/IntelliLang/src/org/intellij/plugins/intelliLang/inject/quickedit/QuickEditAction.java index 0580a0487202..72a0c69ce0c1 100644 --- a/plugins/IntelliLang/src/org/intellij/plugins/intelliLang/inject/quickedit/QuickEditAction.java +++ b/plugins/IntelliLang/src/org/intellij/plugins/intelliLang/inject/quickedit/QuickEditAction.java @@ -41,6 +41,8 @@ import com.intellij.openapi.fileEditor.impl.EditorWindow; import com.intellij.openapi.fileEditor.impl.EditorWithProviderComposite; import com.intellij.openapi.fileTypes.FileType; import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.popup.Balloon; +import com.intellij.openapi.ui.popup.JBPopupFactory; import com.intellij.openapi.util.*; import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.*; @@ -50,13 +52,17 @@ import com.intellij.psi.impl.source.tree.injected.InjectedLanguageUtil; import com.intellij.psi.impl.source.tree.injected.Place; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.testFramework.LightVirtualFile; +import com.intellij.ui.awt.RelativePoint; import com.intellij.util.IncorrectOperationException; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.containers.Convertor; +import com.intellij.util.ui.PositionTracker; +import com.intellij.util.ui.UIUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.swing.*; +import java.awt.*; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -67,27 +73,20 @@ import java.util.Set; * fragment's complete prefix and suffix in non-editable areas and allows to edit the fragment * without having to consider any additional escaping rules (e.g. when editing regexes in String * literals). + * + * @author Gregory Shrago + * @author Konstantin Bulenkov */ public class QuickEditAction implements IntentionAction, LowPriorityAction { - private String myLastLanguageName; - @NotNull - public String getText() { - return "Edit "+ StringUtil.notNullize(myLastLanguageName, "Injected")+" Fragment"; - } - - @NotNull - public String getFamilyName() { - return "Edit Injected Fragment"; - } - public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file) { - return getRangePair(file, editor.getCaretModel().getOffset()) != null; + return getRangePair(file, editor) != null; } @Nullable - private Pair getRangePair(final PsiFile file, final int offset) { + protected Pair getRangePair(final PsiFile file, final Editor editor) { + final int offset = editor.getCaretModel().getOffset(); final PsiLanguageInjectionHost host = PsiTreeUtil.getParentOfType(file.findElementAt(offset), PsiLanguageInjectionHost.class, false); if (host == null) return null; @@ -107,7 +106,7 @@ public class QuickEditAction implements IntentionAction, LowPriorityAction { public void invoke(@NotNull final Project project, final Editor editor, PsiFile file) throws IncorrectOperationException { final int offset = editor.getCaretModel().getOffset(); - final Pair pair = getRangePair(file, offset); + final Pair pair = getRangePair(file, editor); assert pair != null; final PsiFile injectedFile = (PsiFile)pair.first; final int injectedOffset = ((DocumentWindow)PsiDocumentManager.getInstance(project).getDocument(injectedFile)).hostToInjected(offset); @@ -119,20 +118,41 @@ public class QuickEditAction implements IntentionAction, LowPriorityAction { } private static final Key QUICK_EDIT_HANDLER = Key.create("QUICK_EDIT_HANDLER"); + @NotNull - private static MyHandler getHandler(Project project, PsiFile injectedFile, Editor editor, PsiFile origFile) { + private MyHandler getHandler(Project project, PsiFile injectedFile, Editor editor, PsiFile origFile) { MyHandler handler = injectedFile.getUserData(QUICK_EDIT_HANDLER); if (handler != null && handler.isValid()) { return handler; } - injectedFile.putUserData(QUICK_EDIT_HANDLER, handler = new MyHandler(project, injectedFile, origFile, editor.getDocument())); + handler = new MyHandler(project, injectedFile, origFile, editor); + injectedFile.putUserData(QUICK_EDIT_HANDLER, handler); return handler; } + + protected boolean isShowInBalloon() { + return false; + } + + @Nullable + protected JComponent createBalloonComponent(PsiFile file, Ref ref) { + return null; + } - private static class MyHandler extends DocumentAdapter implements Disposable { + @NotNull + public String getText() { + return "Edit "+ StringUtil.notNullize(myLastLanguageName, "Injected")+" Fragment"; + } + @NotNull + public String getFamilyName() { + return "Edit Injected Fragment"; + } + + private class MyHandler extends DocumentAdapter implements Disposable { private final Project myProject; private final PsiFile myInjectedFile; + private final Editor myEditor; private final Document myOrigDocument; private final PsiFile myNewFile; private final LightVirtualFile myNewVirtualFile; @@ -142,10 +162,11 @@ public class QuickEditAction implements IntentionAction, LowPriorityAction { private EditorWindow mySplittedWindow; private boolean myReleased; - private MyHandler(Project project, PsiFile injectedFile, final PsiFile origFile, Document origDocument) { + private MyHandler(Project project, PsiFile injectedFile, final PsiFile origFile, Editor editor) { myProject = project; myInjectedFile = injectedFile; - myOrigDocument = origDocument; + myEditor = editor; + myOrigDocument = editor.getDocument(); final Place shreds = InjectedLanguageUtil.getShreds(myInjectedFile); final FileType fileType = injectedFile.getFileType(); final Language language = injectedFile.getLanguage(); @@ -210,13 +231,42 @@ public class QuickEditAction implements IntentionAction, LowPriorityAction { } public void navigate(int injectedOffset) { - final FileEditorManagerEx fileEditorManager = FileEditorManagerEx.getInstanceEx(myProject); - final FileEditor[] editors = fileEditorManager.getEditors(myNewVirtualFile); - if (editors.length == 0) { - final EditorWindow curWindow = fileEditorManager.getCurrentWindow(); - mySplittedWindow = curWindow.split(SwingConstants.HORIZONTAL, false, myNewVirtualFile, true); + if (isShowInBalloon()) { + Ref ref = Ref.create(null); + final JComponent component = createBalloonComponent(myNewFile, ref); + if (component != null) { + final Balloon balloon = JBPopupFactory.getInstance().createBalloonBuilder(component) + .setShadow(true) + .setAnimationCycle(0) + .setHideOnClickOutside(true) + .setHideOnKeyOutside(true) + .setHideOnAction(false) + .setFillColor(UIUtil.getControlColor()) + .createBalloon(); + ref.set(balloon); + Disposer.register(myNewFile.getProject(), balloon); + balloon.show(new PositionTracker(myEditor.getContentComponent()) { + @Override + public RelativePoint recalculateLocation(Balloon object) { + final RelativePoint target = JBPopupFactory.getInstance().guessBestPopupLocation(myEditor); + final Point screenPoint = target.getScreenPoint(); + int y = screenPoint.y; + if (target.getPoint().getY() > myEditor.getLineHeight() + balloon.getPreferredSize().getHeight()) { + y -= myEditor.getLineHeight(); + } + return new RelativePoint(new Point(screenPoint.x, y)); + } + }, Balloon.Position.above); + } + } else { + final FileEditorManagerEx fileEditorManager = FileEditorManagerEx.getInstanceEx(myProject); + final FileEditor[] editors = fileEditorManager.getEditors(myNewVirtualFile); + if (editors.length == 0) { + final EditorWindow curWindow = fileEditorManager.getCurrentWindow(); + mySplittedWindow = curWindow.split(SwingConstants.HORIZONTAL, false, myNewVirtualFile, true); + } + fileEditorManager.openTextEditor(new OpenFileDescriptor(myProject, myNewVirtualFile, injectedOffset), true); } - fileEditorManager.openTextEditor(new OpenFileDescriptor(myProject, myNewVirtualFile, injectedOffset), true); } @Override