mirror of
https://github.com/chylex/IntelliJ-IdeaVim.git
synced 2025-07-24 02:59:02 +02:00
659 lines
26 KiB
Java
Executable File
659 lines
26 KiB
Java
Executable File
/*
|
|
* IdeaVim - Vim emulator for IDEs based on the IntelliJ platform
|
|
* Copyright (C) 2003-2022 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 <https://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
package com.maddyhome.idea.vim.group;
|
|
|
|
import com.intellij.ide.bookmark.BookmarkGroup;
|
|
import com.intellij.ide.bookmark.BookmarkType;
|
|
import com.intellij.ide.bookmark.BookmarksManager;
|
|
import com.intellij.ide.bookmark.LineBookmark;
|
|
import com.intellij.openapi.components.PersistentStateComponent;
|
|
import com.intellij.openapi.components.RoamingType;
|
|
import com.intellij.openapi.components.State;
|
|
import com.intellij.openapi.components.Storage;
|
|
import com.intellij.openapi.diagnostic.Logger;
|
|
import com.intellij.openapi.editor.Document;
|
|
import com.intellij.openapi.editor.Editor;
|
|
import com.intellij.openapi.editor.LogicalPosition;
|
|
import com.intellij.openapi.editor.event.DocumentEvent;
|
|
import com.intellij.openapi.editor.event.DocumentListener;
|
|
import com.intellij.openapi.editor.event.EditorFactoryEvent;
|
|
import com.intellij.openapi.fileEditor.FileDocumentManager;
|
|
import com.intellij.openapi.fileEditor.ex.IdeDocumentHistory;
|
|
import com.intellij.openapi.project.Project;
|
|
import com.intellij.openapi.util.text.StringUtil;
|
|
import com.intellij.openapi.vfs.VirtualFile;
|
|
import com.intellij.openapi.vfs.VirtualFileManager;
|
|
import com.maddyhome.idea.vim.VimPlugin;
|
|
import com.maddyhome.idea.vim.api.VimEditor;
|
|
import com.maddyhome.idea.vim.command.Command;
|
|
import com.maddyhome.idea.vim.command.CommandState;
|
|
import com.maddyhome.idea.vim.common.TextRange;
|
|
import com.maddyhome.idea.vim.helper.EditorHelper;
|
|
import com.maddyhome.idea.vim.helper.HelperKt;
|
|
import com.maddyhome.idea.vim.mark.*;
|
|
import com.maddyhome.idea.vim.newapi.IjVimEditor;
|
|
import com.maddyhome.idea.vim.options.OptionConstants;
|
|
import com.maddyhome.idea.vim.options.OptionScope;
|
|
import org.jdom.Element;
|
|
import org.jetbrains.annotations.NotNull;
|
|
import org.jetbrains.annotations.Nullable;
|
|
|
|
import java.util.*;
|
|
import java.util.stream.Collectors;
|
|
|
|
import static com.maddyhome.idea.vim.mark.VimMarkConstants.*;
|
|
|
|
/**
|
|
* This class contains all the mark related functionality
|
|
*/
|
|
@State(name = "VimMarksSettings", storages = {
|
|
@Storage(value = "$APP_CONFIG$/vim_settings_local.xml", roamingType = RoamingType.DISABLED)
|
|
})
|
|
public class MarkGroup extends VimMarkGroupBase implements PersistentStateComponent<Element> {
|
|
public void editorReleased(@NotNull EditorFactoryEvent event) {
|
|
// Save off the last caret position of the file before it is closed
|
|
Editor editor = event.getEditor();
|
|
setMark(editor, '"', editor.getCaretModel().getOffset());
|
|
}
|
|
|
|
/**
|
|
* Saves the caret location prior to doing a jump
|
|
*
|
|
* @param editor The editor the jump will occur in
|
|
*/
|
|
@Override
|
|
public void saveJumpLocation(@NotNull VimEditor editor) {
|
|
saveJumpLocation(((IjVimEditor)editor).getEditor());
|
|
}
|
|
|
|
public void saveJumpLocation(@NotNull Editor editor) {
|
|
addJump(new IjVimEditor(editor), true);
|
|
setMark(editor, '\'');
|
|
|
|
Project project = editor.getProject();
|
|
if (project != null) {
|
|
IdeDocumentHistory.getInstance(project).includeCurrentCommandAsNavigation();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get's a mark from the file
|
|
*
|
|
* @param editor The editor to get the mark from
|
|
* @param ch The mark to get
|
|
* @return The mark in the current file, if set, null if no such mark
|
|
*/
|
|
public @Nullable Mark getFileMark(@NotNull Editor editor, char ch) {
|
|
if (ch == '`') ch = '\'';
|
|
final HashMap<Character, Mark> fmarks = getFileMarks(editor.getDocument());
|
|
if (fmarks == null) {
|
|
return null;
|
|
}
|
|
Mark mark = fmarks.get(ch);
|
|
if (mark != null && mark.isClear()) {
|
|
fmarks.remove(ch);
|
|
mark = null;
|
|
}
|
|
|
|
return mark;
|
|
}
|
|
|
|
/**
|
|
* Sets the specified mark to the caret position of the editor
|
|
*
|
|
* @param editor The editor to get the current position from
|
|
* @param ch The mark set set
|
|
* @return True if a valid, writable mark, false if not
|
|
*/
|
|
public boolean setMark(@NotNull Editor editor, char ch) {
|
|
return VALID_SET_MARKS.indexOf(ch) >= 0 && setMark(editor, ch, editor.getCaretModel().getOffset());
|
|
}
|
|
|
|
/**
|
|
* Sets the specified mark to the specified location.
|
|
*
|
|
* @param editor The editor the mark is associated with
|
|
* @param ch The mark to set
|
|
* @param offset The offset to set the mark to
|
|
* @return true if able to set the mark, false if not
|
|
*/
|
|
public boolean setMark(@NotNull Editor editor, char ch, int offset) {
|
|
if (ch == '`') ch = '\'';
|
|
LogicalPosition lp = editor.offsetToLogicalPosition(offset);
|
|
|
|
final VirtualFile vf = EditorHelper.getVirtualFile(editor);
|
|
if (vf == null) {
|
|
return false;
|
|
}
|
|
|
|
// File specific marks get added to the file
|
|
if (FILE_MARKS.indexOf(ch) >= 0) {
|
|
HashMap<Character, Mark> fmarks = getFileMarks(editor.getDocument());
|
|
if (fmarks == null) return false;
|
|
|
|
Mark mark = new VimMark(ch, lp.line, lp.column, vf.getPath(), extractProtocol(vf));
|
|
fmarks.put(ch, mark);
|
|
}
|
|
// Global marks get set to both the file and the global list of marks
|
|
else if (GLOBAL_MARKS.indexOf(ch) >= 0) {
|
|
HashMap<Character, Mark> fmarks = getFileMarks(editor.getDocument());
|
|
if (fmarks == null) return false;
|
|
|
|
@Nullable LineBookmark systemMark = SystemMarks.createOrGetSystemMark(ch, lp.line, editor);
|
|
Mark mark;
|
|
if (systemMark != null) {
|
|
mark = new IntellijMark(systemMark, lp.column, editor.getProject());
|
|
} else {
|
|
mark = new VimMark(ch, lp.line, lp.column, vf.getPath(), extractProtocol(vf));
|
|
}
|
|
fmarks.put(ch, mark);
|
|
Mark oldMark = globalMarks.put(ch, mark);
|
|
if (oldMark instanceof VimMark) {
|
|
oldMark.clear();
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
public static String extractProtocol(@NotNull VirtualFile vf) {
|
|
return VirtualFileManager.extractProtocol(vf.getUrl());
|
|
}
|
|
|
|
public void setVisualSelectionMarks(@NotNull Editor editor, @NotNull TextRange range) {
|
|
setMark(editor, MARK_VISUAL_START, range.getStartOffset());
|
|
setMark(editor, MARK_VISUAL_END, range.getEndOffset());
|
|
}
|
|
|
|
@Override
|
|
public void setChangeMarks(@NotNull VimEditor vimEditor, @NotNull TextRange range) {
|
|
Editor editor = ((IjVimEditor)vimEditor).getEditor();
|
|
setMark(editor, MARK_CHANGE_START, range.getStartOffset());
|
|
setMark(editor, MARK_CHANGE_END, range.getEndOffset()-1);
|
|
}
|
|
|
|
public @Nullable TextRange getChangeMarks(@NotNull Editor editor) {
|
|
return getMarksRange(editor, MARK_CHANGE_START, MARK_CHANGE_END);
|
|
}
|
|
|
|
public @Nullable TextRange getVisualSelectionMarks(@NotNull Editor editor) {
|
|
return getMarksRange(editor, MARK_VISUAL_START, MARK_VISUAL_END);
|
|
}
|
|
|
|
private @Nullable TextRange getMarksRange(@NotNull Editor editor, char startMark, char endMark) {
|
|
final Mark start = getMark(new IjVimEditor(editor), startMark);
|
|
final Mark end = getMark(new IjVimEditor(editor), endMark);
|
|
if (start != null && end != null) {
|
|
final int startOffset = EditorHelper.getOffset(editor, start.getLogicalLine(), start.getCol());
|
|
final int endOffset = EditorHelper.getOffset(editor, end.getLogicalLine(), end.getCol());
|
|
return new TextRange(startOffset, endOffset+1);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
public void resetAllMarks() {
|
|
globalMarks.clear();
|
|
fileMarks.clear();
|
|
jumps.clear();
|
|
}
|
|
|
|
public void removeMark(char ch, @NotNull Mark mark) {
|
|
if (FILE_MARKS.indexOf(ch) >= 0) {
|
|
HashMap<Character, Mark> fmarks = getFileMarks(mark.getFilename());
|
|
fmarks.remove(ch);
|
|
}
|
|
else if (GLOBAL_MARKS.indexOf(ch) >= 0) {
|
|
// Global marks are added to global and file marks
|
|
HashMap<Character, Mark> fmarks = getFileMarks(mark.getFilename());
|
|
fmarks.remove(ch);
|
|
globalMarks.remove(ch);
|
|
}
|
|
|
|
mark.clear();
|
|
}
|
|
|
|
public @NotNull List<Mark> getMarks(@NotNull Editor editor) {
|
|
HashSet<Mark> res = new HashSet<>();
|
|
|
|
final FileMarks<Character, Mark> marks = getFileMarks(editor.getDocument());
|
|
if (marks != null) {
|
|
res.addAll(marks.values());
|
|
}
|
|
res.addAll(globalMarks.values());
|
|
|
|
ArrayList<Mark> list = new ArrayList<>(res);
|
|
|
|
list.sort(Mark.KeySorter.INSTANCE);
|
|
|
|
return list;
|
|
}
|
|
|
|
public @NotNull List<Jump> getJumps() {
|
|
return jumps;
|
|
}
|
|
|
|
public int getJumpSpot() {
|
|
return jumpSpot;
|
|
}
|
|
|
|
/**
|
|
* Gets the map of marks for the specified file
|
|
*
|
|
* @param doc The editor to get the marks for
|
|
* @return The map of marks. The keys are <code>Character</code>s of the mark names, the values are
|
|
* <code>Mark</code>s.
|
|
*/
|
|
private @Nullable FileMarks<Character, Mark> getFileMarks(final @NotNull Document doc) {
|
|
VirtualFile vf = FileDocumentManager.getInstance().getFile(doc);
|
|
if (vf == null) {
|
|
return null;
|
|
}
|
|
|
|
return getFileMarks(vf.getPath());
|
|
}
|
|
|
|
private @Nullable HashMap<Character, Mark> getAllFileMarks(final @NotNull Document doc) {
|
|
VirtualFile vf = FileDocumentManager.getInstance().getFile(doc);
|
|
if (vf == null) {
|
|
return null;
|
|
}
|
|
|
|
HashMap<Character, Mark> res = new HashMap<>();
|
|
FileMarks<Character, Mark> fileMarks = getFileMarks(doc);
|
|
if (fileMarks != null) {
|
|
res.putAll(fileMarks);
|
|
}
|
|
|
|
for (Character ch : globalMarks.keySet()) {
|
|
Mark mark = globalMarks.get(ch);
|
|
if (vf.getPath().equals(mark.getFilename())) {
|
|
res.put(ch, mark);
|
|
}
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
public void saveData(@NotNull Element element) {
|
|
Element marksElem = new Element("globalmarks");
|
|
if (!VimPlugin.getOptionService().isSet(OptionScope.GLOBAL.INSTANCE, OptionConstants.ideamarksName, OptionConstants.ideamarksName)) {
|
|
for (Mark mark : globalMarks.values()) {
|
|
if (!mark.isClear()) {
|
|
Element markElem = new Element("mark");
|
|
markElem.setAttribute("key", Character.toString(mark.getKey()));
|
|
markElem.setAttribute("line", Integer.toString(mark.getLogicalLine()));
|
|
markElem.setAttribute("column", Integer.toString(mark.getCol()));
|
|
markElem.setAttribute("filename", StringUtil.notNullize(mark.getFilename()));
|
|
markElem.setAttribute("protocol", StringUtil.notNullize(mark.getProtocol(), "file"));
|
|
marksElem.addContent(markElem);
|
|
if (logger.isDebugEnabled()) {
|
|
logger.debug("saved mark = " + mark);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
element.addContent(marksElem);
|
|
|
|
Element fileMarksElem = new Element("filemarks");
|
|
|
|
List<FileMarks<Character, Mark>> files = new ArrayList<>(fileMarks.values());
|
|
files.sort(Comparator.comparing(o -> o.getMyTimestamp()));
|
|
|
|
if (files.size() > SAVE_MARK_COUNT) {
|
|
files = files.subList(files.size() - SAVE_MARK_COUNT, files.size());
|
|
}
|
|
|
|
for (String file : fileMarks.keySet()) {
|
|
FileMarks<Character, Mark> marks = fileMarks.get(file);
|
|
if (!files.contains(marks)) {
|
|
continue;
|
|
}
|
|
|
|
if (marks.size() > 0) {
|
|
Element fileMarkElem = new Element("file");
|
|
fileMarkElem.setAttribute("name", file);
|
|
fileMarkElem.setAttribute("timestamp", Long.toString(marks.getMyTimestamp().getTime()));
|
|
for (Mark mark : marks.values()) {
|
|
if (!mark.isClear() && !Character.isUpperCase(mark.getKey()) &&
|
|
SAVE_FILE_MARKS.indexOf(mark.getKey()) >= 0) {
|
|
Element markElem = new Element("mark");
|
|
markElem.setAttribute("key", Character.toString(mark.getKey()));
|
|
markElem.setAttribute("line", Integer.toString(mark.getLogicalLine()));
|
|
markElem.setAttribute("column", Integer.toString(mark.getCol()));
|
|
fileMarkElem.addContent(markElem);
|
|
}
|
|
}
|
|
fileMarksElem.addContent(fileMarkElem);
|
|
}
|
|
}
|
|
element.addContent(fileMarksElem);
|
|
|
|
Element jumpsElem = new Element("jumps");
|
|
for (Jump jump : jumps) {
|
|
Element jumpElem = new Element("jump");
|
|
jumpElem.setAttribute("line", Integer.toString(jump.getLogicalLine()));
|
|
jumpElem.setAttribute("column", Integer.toString(jump.getCol()));
|
|
jumpElem.setAttribute("filename", StringUtil.notNullize(jump.getFilepath()));
|
|
jumpsElem.addContent(jumpElem);
|
|
if (logger.isDebugEnabled()) {
|
|
logger.debug("saved jump = " + jump);
|
|
}
|
|
}
|
|
element.addContent(jumpsElem);
|
|
}
|
|
|
|
public void readData(@NotNull Element element) {
|
|
// We need to keep the filename for now and create the virtual file later. Any attempt to call
|
|
// LocalFileSystem.getInstance().findFileByPath() results in the following error:
|
|
// Read access is allowed from event dispatch thread or inside read-action only
|
|
// (see com.intellij.openapi.application.Application.runReadAction())
|
|
|
|
Element marksElem = element.getChild("globalmarks");
|
|
if (marksElem != null && !VimPlugin.getOptionService().isSet(OptionScope.GLOBAL.INSTANCE, OptionConstants.ideamarksName, OptionConstants.ideamarksName)) {
|
|
List<Element> markList = marksElem.getChildren("mark");
|
|
for (Element aMarkList : markList) {
|
|
Mark mark = VimMark.create(aMarkList.getAttributeValue("key").charAt(0),
|
|
Integer.parseInt(aMarkList.getAttributeValue("line")),
|
|
Integer.parseInt(aMarkList.getAttributeValue("column")),
|
|
aMarkList.getAttributeValue("filename"),
|
|
aMarkList.getAttributeValue("protocol"));
|
|
|
|
if (mark != null) {
|
|
globalMarks.put(mark.getKey(), mark);
|
|
HashMap<Character, Mark> fmarks = getFileMarks(mark.getFilename());
|
|
fmarks.put(mark.getKey(), mark);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (logger.isDebugEnabled()) {
|
|
logger.debug("globalMarks=" + globalMarks);
|
|
}
|
|
|
|
Element fileMarksElem = element.getChild("filemarks");
|
|
if (fileMarksElem != null) {
|
|
List<Element> fileList = fileMarksElem.getChildren("file");
|
|
for (Element aFileList : fileList) {
|
|
String filename = aFileList.getAttributeValue("name");
|
|
Date timestamp = new Date();
|
|
try {
|
|
long date = Long.parseLong(aFileList.getAttributeValue("timestamp"));
|
|
timestamp.setTime(date);
|
|
}
|
|
catch (NumberFormatException e) {
|
|
// ignore
|
|
}
|
|
FileMarks<Character, Mark> fmarks = getFileMarks(filename);
|
|
List<Element> markList = aFileList.getChildren("mark");
|
|
for (Element aMarkList : markList) {
|
|
Mark mark = VimMark.create(aMarkList.getAttributeValue("key").charAt(0),
|
|
Integer.parseInt(aMarkList.getAttributeValue("line")),
|
|
Integer.parseInt(aMarkList.getAttributeValue("column")),
|
|
filename,
|
|
aMarkList.getAttributeValue("protocol"));
|
|
|
|
if (mark != null) fmarks.put(mark.getKey(), mark);
|
|
}
|
|
fmarks.setTimestamp(timestamp);
|
|
}
|
|
}
|
|
|
|
if (logger.isDebugEnabled()) {
|
|
logger.debug("fileMarks=" + fileMarks);
|
|
}
|
|
|
|
jumps.clear();
|
|
Element jumpsElem = element.getChild("jumps");
|
|
if (jumpsElem != null) {
|
|
List<Element> jumpList = jumpsElem.getChildren("jump");
|
|
for (Element aJumpList : jumpList) {
|
|
Jump jump = new Jump(Integer.parseInt(aJumpList.getAttributeValue("line")),
|
|
Integer.parseInt(aJumpList.getAttributeValue("column")),
|
|
aJumpList.getAttributeValue("filename"));
|
|
|
|
jumps.add(jump);
|
|
}
|
|
}
|
|
|
|
if (logger.isDebugEnabled()) {
|
|
logger.debug("jumps=" + jumps);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* This updates all the marks for a file whenever text is deleted from the file. If the line that contains a mark
|
|
* is completely deleted then the mark is deleted too. If the deleted text is before the marked line, the mark is
|
|
* moved up by the number of deleted lines.
|
|
*
|
|
* @param editor The modified editor
|
|
* @param marks The editor's marks to update
|
|
* @param delStartOff The offset within the editor where the deletion occurred
|
|
* @param delLength The length of the deleted text
|
|
*/
|
|
public static void updateMarkFromDelete(@Nullable Editor editor, @Nullable HashMap<Character, Mark> marks, int delStartOff, int delLength) {
|
|
// Skip all this work if there are no marks
|
|
if (marks != null && marks.size() > 0 && editor != null) {
|
|
// Calculate the logical position of the start and end of the deleted text
|
|
int delEndOff = delStartOff + delLength - 1;
|
|
LogicalPosition delStart = editor.offsetToLogicalPosition(delStartOff);
|
|
LogicalPosition delEnd = editor.offsetToLogicalPosition(delEndOff + 1);
|
|
if (logger.isDebugEnabled()) logger.debug("mark delete. delStart = " + delStart + ", delEnd = " + delEnd);
|
|
|
|
// Now analyze each mark to determine if it needs to be updated or removed
|
|
for (Character ch : marks.keySet()) {
|
|
Mark myMark = marks.get(ch);
|
|
if (!(myMark instanceof VimMark)) continue;
|
|
VimMark mark = (VimMark) myMark;
|
|
|
|
if (logger.isDebugEnabled()) logger.debug("mark = " + mark);
|
|
// If the end of the deleted text is prior to the marked line, simply shift the mark up by the
|
|
// proper number of lines.
|
|
if (delEnd.line < mark.getLogicalLine()) {
|
|
int lines = delEnd.line - delStart.line;
|
|
if (logger.isDebugEnabled()) logger.debug("Shifting mark by " + lines + " lines");
|
|
mark.setLogicalLine(mark.getLogicalLine() - lines);
|
|
}
|
|
// If the deleted text begins before the mark and ends after the mark then it may be shifted or deleted
|
|
else if (delStart.line <= mark.getLogicalLine() && delEnd.line >= mark.getLogicalLine()) {
|
|
int markLineStartOff = EditorHelper.getLineStartOffset(editor, mark.getLogicalLine());
|
|
int markLineEndOff = EditorHelper.getLineEndOffset(editor, mark.getLogicalLine(), true);
|
|
|
|
Command command = CommandState.getInstance(new IjVimEditor(editor)).getExecutingCommand();
|
|
// If text is being changed from the start of the mark line (a special case for mark deletion)
|
|
boolean changeFromMarkLineStart = command != null && command.getType() == Command.Type.CHANGE
|
|
&& delStartOff == markLineStartOff;
|
|
// If the marked line is completely within the deleted text, remove the mark (except the special case)
|
|
if (delStartOff <= markLineStartOff && delEndOff >= markLineEndOff && !changeFromMarkLineStart) {
|
|
VimPlugin.getMark().removeMark(ch, mark);
|
|
logger.debug("Removed mark");
|
|
}
|
|
// The deletion only covers part of the marked line so shift the mark only if the deletion begins
|
|
// on a line prior to the marked line (which means the deletion must end on the marked line).
|
|
else if (delStart.line < mark.getLogicalLine()) {
|
|
// shift mark
|
|
mark.setLogicalLine(delStart.line);
|
|
if (logger.isDebugEnabled()) logger.debug("Shifting mark to line " + delStart.line);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* This updates all the marks for a file whenever text is inserted into the file. If the line that contains a mark
|
|
* that is after the start of the insertion point, shift the mark by the number of new lines added.
|
|
*
|
|
* @param editor The editor that was updated
|
|
* @param marks The editor's marks
|
|
* @param insStartOff The insertion point
|
|
* @param insLength The length of the insertion
|
|
*/
|
|
public static void updateMarkFromInsert(@Nullable Editor editor, @Nullable HashMap<Character, Mark> marks, int insStartOff, int insLength) {
|
|
if (marks != null && marks.size() > 0 && editor != null) {
|
|
int insEndOff = insStartOff + insLength;
|
|
LogicalPosition insStart = editor.offsetToLogicalPosition(insStartOff);
|
|
LogicalPosition insEnd = editor.offsetToLogicalPosition(insEndOff);
|
|
if (logger.isDebugEnabled()) logger.debug("mark insert. insStart = " + insStart + ", insEnd = " + insEnd);
|
|
int lines = insEnd.line - insStart.line;
|
|
if (lines == 0) return;
|
|
|
|
for (VimMark mark : marks.values().stream().filter(VimMark.class::isInstance).map(VimMark.class::cast).collect(Collectors.toList())) {
|
|
if (logger.isDebugEnabled()) logger.debug("mark = " + mark);
|
|
// Shift the mark if the insertion began on a line prior to the marked line.
|
|
if (insStart.line < mark.getLogicalLine()) {
|
|
mark.setLogicalLine(mark.getLogicalLine() + lines);
|
|
if (logger.isDebugEnabled()) logger.debug("Shifting mark by " + lines + " lines");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
@Nullable
|
|
@Override
|
|
public Element getState() {
|
|
Element element = new Element("marks");
|
|
saveData(element);
|
|
return element;
|
|
}
|
|
|
|
@Override
|
|
public void loadState(@NotNull Element state) {
|
|
readData(state);
|
|
}
|
|
|
|
/**
|
|
* This class is used to listen to editor document changes
|
|
*/
|
|
public static class MarkUpdater implements DocumentListener {
|
|
|
|
public static MarkUpdater INSTANCE = new MarkUpdater();
|
|
/**
|
|
* Creates the listener for the supplied editor
|
|
*/
|
|
private MarkUpdater() {
|
|
}
|
|
|
|
/**
|
|
* This event indicates that a document is about to be changed. We use this event to update all the
|
|
* editor's marks if text is about to be deleted.
|
|
*
|
|
* @param event The change event
|
|
*/
|
|
@Override
|
|
public void beforeDocumentChange(@NotNull DocumentEvent event) {
|
|
if (!VimPlugin.isEnabled()) return;
|
|
|
|
if (logger.isDebugEnabled()) logger.debug("MarkUpdater before, event = " + event);
|
|
if (event.getOldLength() == 0) return;
|
|
|
|
Document doc = event.getDocument();
|
|
updateMarkFromDelete(getAnEditor(doc), VimPlugin.getMark().getAllFileMarks(doc), event.getOffset(),
|
|
event.getOldLength());
|
|
// TODO - update jumps
|
|
}
|
|
|
|
/**
|
|
* This event indicates that a document was just changed. We use this event to update all the editor's
|
|
* marks if text was just added.
|
|
*
|
|
* @param event The change event
|
|
*/
|
|
@Override
|
|
public void documentChanged(@NotNull DocumentEvent event) {
|
|
if (!VimPlugin.isEnabled()) return;
|
|
|
|
if (logger.isDebugEnabled()) logger.debug("MarkUpdater after, event = " + event);
|
|
if (event.getNewLength() == 0 || (event.getNewLength() == 1 && event.getNewFragment().charAt(0) != '\n')) return;
|
|
|
|
Document doc = event.getDocument();
|
|
updateMarkFromInsert(getAnEditor(doc), VimPlugin.getMark().getAllFileMarks(doc), event.getOffset(),
|
|
event.getNewLength());
|
|
// TODO - update jumps
|
|
}
|
|
|
|
private @Nullable Editor getAnEditor(@NotNull Document doc) {
|
|
List<Editor> editors = HelperKt.localEditors(doc);
|
|
|
|
if (editors.size() > 0) {
|
|
return editors.get(0);
|
|
}
|
|
else {
|
|
return null;
|
|
}
|
|
}
|
|
}
|
|
|
|
public static class VimBookmarksListener implements com.intellij.ide.bookmark.BookmarksListener {
|
|
private final Project myProject;
|
|
|
|
public VimBookmarksListener(Project project) {
|
|
myProject = project;
|
|
}
|
|
|
|
@Override
|
|
public void bookmarkAdded(@NotNull BookmarkGroup group, com.intellij.ide.bookmark.@NotNull Bookmark bookmark) {
|
|
if (!VimPlugin.isEnabled()) return;
|
|
if (!VimPlugin.getOptionService().isSet(OptionScope.GLOBAL.INSTANCE, OptionConstants.ideamarksName, OptionConstants.ideamarksName)) return;
|
|
|
|
if (!(bookmark instanceof LineBookmark)) return;
|
|
BookmarksManager bookmarksManager = BookmarksManager.getInstance(myProject);
|
|
if (bookmarksManager == null) return;
|
|
BookmarkType type = bookmarksManager.getType(bookmark);
|
|
if (type == null) return;
|
|
|
|
char mnemonic = type.getMnemonic();
|
|
if (GLOBAL_MARKS.indexOf(mnemonic) == -1) return;
|
|
|
|
createVimMark((LineBookmark)bookmark, mnemonic);
|
|
}
|
|
|
|
@Override
|
|
public void bookmarkRemoved(@NotNull BookmarkGroup group, com.intellij.ide.bookmark.@NotNull Bookmark bookmark) {
|
|
if (!VimPlugin.isEnabled()) return;
|
|
if (!VimPlugin.getOptionService().isSet(OptionScope.GLOBAL.INSTANCE, OptionConstants.ideamarksName, OptionConstants.ideamarksName)) return;
|
|
|
|
if (!(bookmark instanceof LineBookmark)) return;
|
|
BookmarksManager bookmarksManager = BookmarksManager.getInstance(myProject);
|
|
if (bookmarksManager == null) return;
|
|
BookmarkType type = bookmarksManager.getType(bookmark);
|
|
if (type == null) return;
|
|
char ch = type.getMnemonic();
|
|
if (GLOBAL_MARKS.indexOf(ch) != -1) {
|
|
FileMarks<Character, Mark> fmarks = VimPlugin.getMark().getFileMarks(((LineBookmark)bookmark).getFile().getPath());
|
|
fmarks.remove(ch);
|
|
VimPlugin.getMark().globalMarks.remove(ch);
|
|
}
|
|
}
|
|
|
|
private void createVimMark(@NotNull LineBookmark b, char mnemonic) {
|
|
int col = 0;
|
|
Editor editor = EditorHelper.getEditor(b.getFile());
|
|
if (editor != null) col = editor.getCaretModel().getCurrentCaret().getLogicalPosition().column;
|
|
IntellijMark mark = new IntellijMark(b, col, myProject);
|
|
FileMarks<Character, Mark> fmarks = VimPlugin.getMark().getFileMarks(b.getFile().getPath());
|
|
fmarks.put(mnemonic, mark);
|
|
VimPlugin.getMark().globalMarks.put(mnemonic, mark);
|
|
}
|
|
}
|
|
|
|
private static final int SAVE_MARK_COUNT = 20;
|
|
|
|
private static final Logger logger = Logger.getInstance(MarkGroup.class.getName());
|
|
}
|