blob: f9777958e6891d768096a674122c3d0e72bd93a0 [file] [log] [blame]
/*
* 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);
}
}