mirror of
https://github.com/chylex/IntelliJ-IdeaVim.git
synced 2025-08-18 19:24:55 +02:00
doc
gradle
resources
src
com
maddyhome
idea
vim
action
command
common
ex
extension
group
handler
helper
key
listener
option
regexp
ui
ClipboardHandler.java
ExDocument.java
ExEditorKit.java
ExEntryPanel.java
ExKeyBindings.java
ExOutputPanel.java
ExPanelBorder.kt
ExShortcutKeyAction.kt
ExTextField.java
ModalEntry.java
ModalEntryDialog.java
VimEmulationConfigurable.java
EventFacade.java
KeyHandler.java
RegisterActions.java
VimLocalConfig.kt
VimPlugin.java
VimTypedActionHandler.java
package-info.java
test
.editorconfig
.gitignore
AUTHORS.md
CHANGES.md
CODE_OF_CONDUCT.md
CONTRIBUTING.md
LICENSE.txt
README.md
build.gradle
gradle.properties
gradlew
gradlew.bat
settings.gradle
310 lines
9.8 KiB
Java
310 lines
9.8 KiB
Java
/*
|
|
* IdeaVim - Vim emulator for IDEs based on the IntelliJ platform
|
|
* Copyright (C) 2003-2019 The IdeaVim authors
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
package com.maddyhome.idea.vim.ui;
|
|
|
|
import com.intellij.ide.ui.LafManager;
|
|
import com.intellij.ide.ui.LafManagerListener;
|
|
import com.intellij.openapi.actionSystem.DataContext;
|
|
import com.intellij.openapi.application.ApplicationManager;
|
|
import com.intellij.openapi.diagnostic.Logger;
|
|
import com.intellij.openapi.editor.Editor;
|
|
import com.intellij.openapi.editor.colors.EditorColors;
|
|
import com.intellij.openapi.editor.markup.RangeHighlighter;
|
|
import com.intellij.openapi.editor.markup.TextAttributes;
|
|
import com.intellij.ui.DocumentAdapter;
|
|
import com.intellij.util.IJSwingUtilities;
|
|
import com.maddyhome.idea.vim.common.TextRange;
|
|
import com.maddyhome.idea.vim.group.MotionGroup;
|
|
import com.maddyhome.idea.vim.group.SearchGroup;
|
|
import com.maddyhome.idea.vim.helper.UiHelper;
|
|
import com.maddyhome.idea.vim.option.Options;
|
|
import org.jetbrains.annotations.NotNull;
|
|
import org.jetbrains.annotations.Nullable;
|
|
|
|
import javax.swing.*;
|
|
import javax.swing.event.DocumentEvent;
|
|
import javax.swing.event.DocumentListener;
|
|
import java.awt.*;
|
|
import java.awt.event.ComponentAdapter;
|
|
import java.awt.event.ComponentEvent;
|
|
|
|
/**
|
|
* This is used to enter ex commands such as searches and "colon" commands
|
|
*/
|
|
public class ExEntryPanel extends JPanel implements LafManagerListener {
|
|
public static ExEntryPanel getInstance() {
|
|
if (instance == null) {
|
|
instance = new ExEntryPanel();
|
|
}
|
|
|
|
return instance;
|
|
}
|
|
|
|
private ExEntryPanel() {
|
|
label = new JLabel(" ");
|
|
entry = new ExTextField();
|
|
|
|
GridBagLayout layout = new GridBagLayout();
|
|
GridBagConstraints gbc = new GridBagConstraints();
|
|
|
|
setLayout(layout);
|
|
gbc.gridx = 0;
|
|
layout.setConstraints(this.label, gbc);
|
|
add(this.label);
|
|
gbc.gridx = 1;
|
|
gbc.weightx = 1.0;
|
|
gbc.fill = GridBagConstraints.HORIZONTAL;
|
|
layout.setConstraints(entry, gbc);
|
|
add(entry);
|
|
|
|
adapter = new ComponentAdapter() {
|
|
public void componentResized(ComponentEvent e) {
|
|
positionPanel();
|
|
}
|
|
};
|
|
|
|
new ExShortcutKeyAction(this).registerCustomShortcutSet();
|
|
|
|
LafManager.getInstance().addLafManagerListener(this);
|
|
|
|
updateUI();
|
|
}
|
|
|
|
@Override
|
|
public void lookAndFeelChanged(@NotNull LafManager source) {
|
|
// Calls updateUI on this and child components
|
|
IJSwingUtilities.updateComponentTreeUI(this);
|
|
}
|
|
|
|
private void setFontForElements() {
|
|
final Font font = UiHelper.getEditorFont();
|
|
label.setFont(font);
|
|
entry.setFont(font);
|
|
}
|
|
|
|
/**
|
|
* Turns on the ex entry field for the given editor
|
|
*
|
|
* @param editor The editor to use for display
|
|
* @param context The data context
|
|
* @param label The label for the ex entry (i.e. :, /, or ?)
|
|
* @param initText The initial text for the entry
|
|
* @param count A holder for the ex entry count
|
|
*/
|
|
public void activate(@NotNull Editor editor, DataContext context, @NotNull String label, String initText, int count) {
|
|
this.label.setText(label);
|
|
this.count = count;
|
|
setFontForElements();
|
|
entry.reset();
|
|
entry.setEditor(editor, context);
|
|
entry.setText(initText);
|
|
entry.setType(label);
|
|
parent = editor.getContentComponent();
|
|
if (!ApplicationManager.getApplication().isUnitTestMode()) {
|
|
JRootPane root = SwingUtilities.getRootPane(parent);
|
|
oldGlass = (JComponent)root.getGlassPane();
|
|
oldLayout = oldGlass.getLayout();
|
|
wasOpaque = oldGlass.isOpaque();
|
|
oldGlass.setLayout(null);
|
|
oldGlass.setOpaque(false);
|
|
oldGlass.add(this);
|
|
oldGlass.addComponentListener(adapter);
|
|
positionPanel();
|
|
oldGlass.setVisible(true);
|
|
if (isIncSearchEnabled(label)) {
|
|
entry.getDocument().addDocumentListener(documentListener);
|
|
verticalOffset = editor.getScrollingModel().getVerticalScrollOffset();
|
|
horizontalOffset = editor.getScrollingModel().getHorizontalScrollOffset();
|
|
}
|
|
entry.requestFocusInWindow();
|
|
}
|
|
active = true;
|
|
}
|
|
|
|
// Called automatically when the LAF is changed and the component is visible, and manually by the LAF listener handler
|
|
@Override
|
|
public void updateUI() {
|
|
super.updateUI();
|
|
|
|
setBorder(new ExPanelBorder());
|
|
|
|
// Can be null when called from base constructor
|
|
//noinspection ConstantConditions
|
|
if (entry != null && label != null) {
|
|
|
|
setFontForElements();
|
|
|
|
// Label background is automatically picked up
|
|
label.setForeground(entry.getForeground());
|
|
}
|
|
}
|
|
|
|
// Entry can be null if getForeground is called during base class initialisation
|
|
@SuppressWarnings("ConstantConditions")
|
|
@Override
|
|
public Color getForeground() {
|
|
return entry != null ? entry.getForeground() : super.getForeground();
|
|
}
|
|
|
|
@SuppressWarnings("ConstantConditions")
|
|
@Override
|
|
public Color getBackground() {
|
|
return entry != null ? entry.getBackground() : super.getBackground();
|
|
}
|
|
|
|
/**
|
|
* Gets the label for the ex entry. This should be one of ":", "/", or "?"
|
|
*
|
|
* @return The ex entry label
|
|
*/
|
|
public String getLabel() {
|
|
return label.getText();
|
|
}
|
|
|
|
/**
|
|
* Gets the count given during activation
|
|
*
|
|
* @return The count
|
|
*/
|
|
public int getCount() {
|
|
return count;
|
|
}
|
|
|
|
/**
|
|
* Pass the keystroke on to the text edit for handling
|
|
*
|
|
* @param stroke The keystroke
|
|
*/
|
|
public void handleKey(@NotNull KeyStroke stroke) {
|
|
entry.handleKey(stroke);
|
|
}
|
|
|
|
private void positionPanel() {
|
|
if (parent == null) return;
|
|
|
|
Container scroll = SwingUtilities.getAncestorOfClass(JScrollPane.class, parent);
|
|
int height = (int)getPreferredSize().getHeight();
|
|
if (scroll != null) {
|
|
Rectangle bounds = scroll.getBounds();
|
|
bounds.translate(0, scroll.getHeight() - height);
|
|
bounds.height = height;
|
|
Point pos = SwingUtilities.convertPoint(scroll.getParent(), bounds.getLocation(), oldGlass);
|
|
bounds.setLocation(pos);
|
|
setBounds(bounds);
|
|
repaint();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Gets the text entered by the user. This includes any initial text but does not include the label
|
|
*
|
|
* @return The user entered text
|
|
*/
|
|
public String getText() {
|
|
return entry.getText();
|
|
}
|
|
|
|
@NotNull
|
|
public ExTextField getEntry() {
|
|
return entry;
|
|
}
|
|
|
|
/**
|
|
* Turns off the ex entry field and optionally puts the focus back to the original component
|
|
*/
|
|
public void deactivate(boolean refocusOwningEditor) {
|
|
logger.info("deactivate");
|
|
if (!active) return;
|
|
active = false;
|
|
entry.deactivate();
|
|
|
|
if (!ApplicationManager.getApplication().isUnitTestMode()) {
|
|
if (refocusOwningEditor && parent != null) {
|
|
UiHelper.requestFocus(parent);
|
|
}
|
|
|
|
oldGlass.removeComponentListener(adapter);
|
|
oldGlass.setVisible(false);
|
|
oldGlass.remove(this);
|
|
oldGlass.setOpaque(wasOpaque);
|
|
oldGlass.setLayout(oldLayout);
|
|
if (isIncSearchEnabled(label.getText())) {
|
|
entry.getDocument().removeDocumentListener(documentListener);
|
|
final Editor editor = entry.getEditor();
|
|
editor.getScrollingModel().scrollVertically(verticalOffset);
|
|
editor.getScrollingModel().scrollHorizontally(horizontalOffset);
|
|
if (incHighlighter != null) {
|
|
editor.getMarkupModel().removeHighlighter(incHighlighter);
|
|
}
|
|
}
|
|
}
|
|
parent = null;
|
|
}
|
|
|
|
private boolean isIncSearchEnabled(@NotNull String labelText) {
|
|
return (labelText.equals("/") || labelText.equals("?")) && Options.getInstance().isSet(Options.INCREMENTAL_SEARCH);
|
|
}
|
|
|
|
/**
|
|
* Checks if the ex entry panel is currently active
|
|
*
|
|
* @return true if active, false if not
|
|
*/
|
|
public boolean isActive() {
|
|
return active;
|
|
}
|
|
|
|
@Nullable private JComponent parent;
|
|
@NotNull private final JLabel label;
|
|
@NotNull private final ExTextField entry;
|
|
private JComponent oldGlass;
|
|
private LayoutManager oldLayout;
|
|
private boolean wasOpaque;
|
|
@NotNull private final ComponentAdapter adapter;
|
|
private int count;
|
|
@Nullable private RangeHighlighter incHighlighter = null;
|
|
private int verticalOffset;
|
|
private int horizontalOffset;
|
|
|
|
@NotNull private final DocumentListener documentListener = new DocumentAdapter() {
|
|
@Override
|
|
protected void textChanged(@NotNull DocumentEvent e) {
|
|
final Editor editor = entry.getEditor();
|
|
final boolean forwards = !label.getText().equals("?");
|
|
if (incHighlighter != null) {
|
|
editor.getMarkupModel().removeHighlighter(incHighlighter);
|
|
}
|
|
final String pattern = entry.getText();
|
|
final TextRange range = SearchGroup.findNext(editor, pattern, editor.getCaretModel().getOffset(), true, forwards);
|
|
if (range != null) {
|
|
final TextAttributes color = editor.getColorsScheme().getAttributes(EditorColors.SEARCH_RESULT_ATTRIBUTES);
|
|
incHighlighter = SearchGroup.highlightMatch(editor, range.getStartOffset(), range.getEndOffset());
|
|
incHighlighter.setErrorStripeMarkColor(color.getBackgroundColor());
|
|
incHighlighter.setErrorStripeTooltip(pattern);
|
|
MotionGroup.scrollPositionIntoView(editor, editor.offsetToVisualPosition(range.getStartOffset()), true);
|
|
}
|
|
}
|
|
};
|
|
|
|
private boolean active;
|
|
|
|
private static ExEntryPanel instance;
|
|
private static final Logger logger = Logger.getInstance(ExEntryPanel.class.getName());
|
|
}
|