| /* |
| * 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 com.intellij.codeInsight.template.emmet; |
| |
| import com.intellij.codeInsight.hint.HintManager; |
| import com.intellij.codeInsight.hint.HintManagerImpl; |
| import com.intellij.openapi.Disposable; |
| import com.intellij.openapi.application.ApplicationManager; |
| import com.intellij.openapi.editor.*; |
| import com.intellij.openapi.editor.colors.EditorColors; |
| import com.intellij.openapi.editor.colors.EditorColorsScheme; |
| import com.intellij.openapi.editor.event.DocumentAdapter; |
| import com.intellij.openapi.editor.event.DocumentEvent; |
| import com.intellij.openapi.editor.event.EditorFactoryAdapter; |
| import com.intellij.openapi.editor.event.EditorFactoryEvent; |
| import com.intellij.openapi.editor.ex.EditorEx; |
| import com.intellij.openapi.fileTypes.FileType; |
| import com.intellij.openapi.ui.popup.Balloon; |
| import com.intellij.openapi.util.Disposer; |
| import com.intellij.openapi.util.Key; |
| import com.intellij.openapi.util.Pair; |
| import com.intellij.openapi.util.text.StringUtil; |
| import com.intellij.psi.impl.source.tree.injected.InjectedLanguageUtil; |
| import com.intellij.ui.HintHint; |
| import com.intellij.ui.IdeBorderFactory; |
| import com.intellij.ui.LightweightHint; |
| import com.intellij.ui.components.JBPanel; |
| import com.intellij.util.Alarm; |
| import com.intellij.util.DocumentUtil; |
| import com.intellij.util.Producer; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| import org.jetbrains.annotations.TestOnly; |
| |
| import javax.swing.*; |
| import java.awt.*; |
| |
| public class EmmetPreviewHint extends LightweightHint implements Disposable { |
| private static final Key<EmmetPreviewHint> KEY = new Key<EmmetPreviewHint>("emmet.preview"); |
| @NotNull private final Editor myParentEditor; |
| @NotNull private final Editor myEditor; |
| @NotNull private final Alarm myAlarm = new Alarm(this); |
| private boolean isDisposed = false; |
| |
| private EmmetPreviewHint(@NotNull JBPanel panel, @NotNull Editor editor, @NotNull Editor parentEditor) { |
| super(panel); |
| myParentEditor = parentEditor; |
| myEditor = editor; |
| |
| final Editor topLevelEditor = InjectedLanguageUtil.getTopLevelEditor(myParentEditor); |
| EditorFactory.getInstance().addEditorFactoryListener(new EditorFactoryAdapter() { |
| @Override |
| public void editorReleased(@NotNull EditorFactoryEvent event) { |
| if (event.getEditor() == myParentEditor || event.getEditor() == myEditor || event.getEditor() == topLevelEditor) { |
| Disposer.dispose(EmmetPreviewHint.this); |
| } |
| } |
| }, this); |
| |
| myEditor.getDocument().addDocumentListener(new DocumentAdapter() { |
| @Override |
| public void documentChanged(DocumentEvent event) { |
| if (!isDisposed && event.isWholeTextReplaced()) { |
| Pair<Point, Short> position = guessPosition(); |
| HintManagerImpl.adjustEditorHintPosition(EmmetPreviewHint.this, myParentEditor, position.first, position.second); |
| myEditor.getScrollingModel().scrollVertically(0); |
| } |
| } |
| }, this); |
| } |
| |
| public void showHint() { |
| myParentEditor.putUserData(KEY, this); |
| |
| Pair<Point, Short> position = guessPosition(); |
| JRootPane pane = myParentEditor.getComponent().getRootPane(); |
| JComponent layeredPane = pane != null ? pane.getLayeredPane() : myParentEditor.getComponent(); |
| HintHint hintHint = new HintHint(layeredPane, position.first) |
| .setAwtTooltip(true) |
| .setContentActive(true) |
| .setExplicitClose(true) |
| .setShowImmediately(true) |
| .setPreferredPosition(position.second == HintManager.ABOVE ? Balloon.Position.above : Balloon.Position.below) |
| .setTextBg(myParentEditor.getColorsScheme().getDefaultBackground()) |
| .setBorderInsets(new Insets(1, 1, 1, 1)); |
| |
| int hintFlags = HintManager.HIDE_BY_OTHER_HINT | HintManager.HIDE_BY_ESCAPE | HintManager.UPDATE_BY_SCROLLING; |
| HintManagerImpl.getInstanceImpl().showEditorHint(this, myParentEditor, position.first, hintFlags, 0, false, hintHint); |
| } |
| |
| public void updateText(@NotNull final Producer<String> contentProducer) { |
| myAlarm.cancelAllRequests(); |
| myAlarm.addRequest(new Runnable() { |
| @Override |
| public void run() { |
| if (!isDisposed) { |
| final String newText = contentProducer.produce(); |
| if (StringUtil.isEmpty(newText)) { |
| hide(); |
| } |
| else if (!myEditor.getDocument().getText().equals(newText)) { |
| DocumentUtil.writeInRunUndoTransparentAction(new Runnable() { |
| @Override |
| public void run() { |
| myEditor.getDocument().setText(newText); |
| } |
| }); |
| } |
| } |
| } |
| }, 100); |
| } |
| |
| @TestOnly |
| @NotNull |
| public String getContent() { |
| return myEditor.getDocument().getText(); |
| } |
| |
| @Nullable |
| public static EmmetPreviewHint getExistingHint(@NotNull Editor parentEditor) { |
| EmmetPreviewHint emmetPreviewHint = KEY.get(parentEditor); |
| if (emmetPreviewHint != null) { |
| if (!emmetPreviewHint.isDisposed) { |
| return emmetPreviewHint; |
| } |
| emmetPreviewHint.hide(); |
| } |
| return null; |
| } |
| |
| @NotNull |
| public static EmmetPreviewHint createHint(@NotNull final EditorEx parentEditor, @NotNull String templateText, @NotNull FileType fileType) { |
| EditorFactory editorFactory = EditorFactory.getInstance(); |
| Document document = editorFactory.createDocument(templateText); |
| final EditorEx previewEditor = (EditorEx)editorFactory.createEditor(document, parentEditor.getProject(), fileType, true); |
| final EditorSettings settings = previewEditor.getSettings(); |
| settings.setLineNumbersShown(false); |
| settings.setAdditionalLinesCount(1); |
| settings.setAdditionalColumnsCount(1); |
| settings.setRightMarginShown(false); |
| settings.setFoldingOutlineShown(false); |
| settings.setLineMarkerAreaShown(false); |
| settings.setIndentGuidesShown(false); |
| settings.setVirtualSpace(false); |
| settings.setWheelFontChangeEnabled(false); |
| previewEditor.setCaretEnabled(false); |
| previewEditor.setBorder(IdeBorderFactory.createEmptyBorder()); |
| |
| EditorColorsScheme colorsScheme = previewEditor.getColorsScheme(); |
| colorsScheme.setColor(EditorColors.CARET_ROW_COLOR, null); |
| |
| JBPanel panel = new JBPanel(new BorderLayout()) { |
| @NotNull |
| @Override |
| public Dimension getPreferredSize() { |
| Dimension size = super.getPreferredSize(); |
| Dimension parentEditorSize = parentEditor.getScrollPane().getSize(); |
| int maxWidth = (int)parentEditorSize.getWidth() / 3; |
| int maxHeight = (int)parentEditorSize.getHeight() / 2; |
| Dimension contentSize = previewEditor.getContentSize(); |
| return new Dimension(maxWidth > contentSize.getWidth() && !settings.isUseSoftWraps() ? (int)size.getWidth() : maxWidth, |
| maxHeight > contentSize.getHeight() ? (int)size.getHeight() : maxHeight); |
| } |
| |
| @NotNull |
| @Override |
| public Insets getInsets() { |
| return new Insets(1, 2, 0, 0); |
| } |
| }; |
| panel.setBackground(previewEditor.getBackgroundColor()); |
| panel.add(previewEditor.getComponent(), BorderLayout.CENTER); |
| return new EmmetPreviewHint(panel, previewEditor, parentEditor); |
| } |
| |
| @Override |
| public boolean vetoesHiding() { |
| return true; |
| } |
| |
| @Override |
| public void hide(boolean ok) { |
| super.hide(ok); |
| ApplicationManager.getApplication().invokeLater(new Runnable() { |
| @Override |
| public void run() { |
| Disposer.dispose(EmmetPreviewHint.this); |
| } |
| }); |
| } |
| |
| @Override |
| public void dispose() { |
| isDisposed = true; |
| myAlarm.cancelAllRequests(); |
| EmmetPreviewHint existingBalloon = myParentEditor.getUserData(KEY); |
| if (existingBalloon == this) { |
| myParentEditor.putUserData(KEY, null); |
| } |
| if (!myEditor.isDisposed()) { |
| EditorFactory.getInstance().releaseEditor(myEditor); |
| } |
| } |
| |
| @NotNull |
| private Pair<Point, Short> guessPosition() { |
| JRootPane rootPane = myParentEditor.getContentComponent().getRootPane(); |
| JComponent layeredPane = rootPane != null ? rootPane.getLayeredPane() : myParentEditor.getComponent(); |
| LogicalPosition logicalPosition = myParentEditor.getCaretModel().getLogicalPosition(); |
| |
| LogicalPosition pos = new LogicalPosition(logicalPosition.line, logicalPosition.column); |
| Point p1 = HintManagerImpl.getHintPosition(this, myParentEditor, pos, HintManager.UNDER); |
| Point p2 = HintManagerImpl.getHintPosition(this, myParentEditor, pos, HintManager.ABOVE); |
| |
| boolean p1Ok = p1.y + getComponent().getPreferredSize().height < layeredPane.getHeight(); |
| boolean p2Ok = p2.y >= 0; |
| |
| if (p1Ok) return new Pair<Point, Short>(p1, HintManager.UNDER); |
| if (p2Ok) return new Pair<Point, Short>(p2, HintManager.ABOVE); |
| |
| int underSpace = layeredPane.getHeight() - p1.y; |
| int aboveSpace = p2.y; |
| return aboveSpace > underSpace |
| ? new Pair<Point, Short>(new Point(p2.x, 0), HintManager.UNDER) |
| : new Pair<Point, Short>(p1, HintManager.ABOVE); |
| } |
| } |