1
0
mirror of https://github.com/chylex/IntelliJ-IdeaVim.git synced 2025-08-17 16:31:45 +02:00

Compare commits

..

11 Commits
0.24 ... 0.25

Author SHA1 Message Date
Andrey Vlasovskikh
6a90f95712 Minor numbering scheme update 2012-12-19 21:46:50 +04:00
Andrey Vlasovskikh
910c51004d Temporarily include build number in version again 2012-12-19 21:36:56 +04:00
Andrey Vlasovskikh
bdb05d1dee Bumped version to 0.24.1 2012-12-19 21:14:08 +04:00
Andrey Vlasovskikh
abc68eb900 Don't include build number in version 2012-12-19 21:11:35 +04:00
Andrey Vlasovskikh
5a50c9e5ac Updated changelog 2012-12-19 21:10:30 +04:00
Andrey Vlasovskikh
1d15417a83 Switch to IntelliJ 12 plugin SDK by default 2012-12-19 21:10:11 +04:00
Andrey Vlasovskikh
233d318e48 Cleanup 2012-12-17 00:00:52 +04:00
Andrey Vlasovskikh
a0c52f017c Switch to Base64 codec from Apache Commons Codec library 2012-12-16 23:47:49 +04:00
Andrey Vlasovskikh
f89d824367 VIM-400 Fixed saving special characters in settings 2012-12-16 23:32:27 +04:00
Andrey Vlasovskikh
d5daf5de67 Instructions for setting up the development environment 2012-12-05 01:41:14 +04:00
Andrey Vlasovskikh
49459b2d81 Added link to the bug tracker 2012-12-05 01:39:05 +04:00
18 changed files with 361 additions and 417 deletions

View File

@@ -3,6 +3,17 @@ The Changelog
History of changes in IdeaVim for the IntelliJ platform.
0.25, 2012-12-19
------------------
A bugfix release.
* VIM-400 Fixed saving characters with key modifiers in plugin settings
* VIM-319 Fixed saving plugin settings when registers contain the null
character
0.24, 2012-12-03
----------------

View File

@@ -9,6 +9,7 @@ Resources:
* [Plugin homepage](http://plugins.intellij.net/plugin/?id=164)
* [Changelog](https://github.com/JetBrains/ideavim/blob/master/CHANGES.md)
* [Bug tracker](http://youtrack.jetbrains.com/issues/VIM)
* [Continuous integration builds](http://teamcity.jetbrains.com/project.html?projectId=project55)
@@ -127,6 +128,37 @@ keyboard shortcuts, and their new VIM keystrokes.
Check In Project Ctrl-K <None>
Development
-----------
### Development Environment
1. Fork IdeaVim on GitHub and clone the repository on your local machine.
2. Open the project in IntelliJ IDEA 11+ (Community or Ultimate) using "File |
Open... | /path/to/ideavim".
3. Set up a JDK if you haven't got it yet. Use "File | Project Structure | SDKs
| Add new JDK".
4. Set up an IntelliJ plugin SDK using "File | Project Structure | SDKs | Add
new IntelliJ IDEA Plugin SDK". The correct path to your current installation
of IntelliJ will be suggested automatically. You will be prompted to select a
JDK for your plugin SDK. Select the JDK from the previous step. You
**should** name your plugin SDK `IntelliJ Plugin SDK` in order to match the
name in the project settings stored in the Git repository.
5. Select a project SDK for your project using "File | Project Structure |
Project | Project SDK". Choose the plugin SDK you have created at the
previous step.
6. Build IdeaVim and run IntelliJ with IdeaVim enabled using the "IdeaVim" run
configuration (use "Run | Run... | IdeaVim").
7. In order to be able to run tests in your IntelliJ edition uncomment the
appropriate lines in the constructor of the `VimTestCase` class.
Authors
-------

View File

@@ -1,4 +1,4 @@
version-id:0.24
version-id:0.25
platform-version:110.0
idea.download.url=http://download.jetbrains.com/idea/ideaIU-11.zip
idea.download.url=http://download.jetbrains.com/idea/ideaIU-12.0.zip
build.number=x

View File

@@ -27,7 +27,7 @@
<property name="idea.home" value="${idea}/unzip"/>
<property environment="env"/>
<property name="tools.jar" value="${env.JAVA_HOME}/lib/tools.jar"/>
<property name="version" value="${version-id}-${build.number}"/>
<property name="version" value="${version-id}"/>
<property name="filename" value="ideavim-${version}"/>
<!--Output-->

View File

@@ -22,8 +22,10 @@ package com.maddyhome.idea.vim.common;
import com.maddyhome.idea.vim.command.SelectionType;
import com.maddyhome.idea.vim.helper.StringHelper;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.awt.event.KeyEvent;
import java.util.Comparator;
import java.util.List;
@@ -31,24 +33,19 @@ import java.util.List;
* Represents a register.
*/
public class Register {
/**
* Create a register of the specified type for the given text
*
* @param name The character
* @param type The register type
* @param text The text to store
*/
public Register(char name, @NotNull SelectionType type, String text) {
private char name;
@NotNull private SelectionType type;
@NotNull private List<KeyStroke> keys;
public Register(char name, @NotNull SelectionType type, @NotNull String text) {
this.name = name;
this.type = type;
this.text = text;
this.keys = null;
this.keys = StringHelper.stringToKeys(text);
}
public Register(char name, @NotNull SelectionType type, List<KeyStroke> keys) {
public Register(char name, @NotNull SelectionType type, @NotNull List<KeyStroke> keys) {
this.name = name;
this.type = type;
this.text = null;
this.keys = keys;
}
@@ -57,18 +54,14 @@ public class Register {
}
/**
* Gets the name the register is assigned to
*
* @return The register name
* Get the name the register is assigned to.
*/
public char getName() {
return name;
}
/**
* Get the register type
*
* @return The register type
* Get the register type.
*/
@NotNull
public SelectionType getType() {
@@ -76,65 +69,38 @@ public class Register {
}
/**
* Get the text in the register
*
* @return The register text
* Get the text in the register.
*/
@Nullable
public String getText() {
if (text == null && keys != null) {
return StringHelper.keysToString(keys);
final StringBuilder builder = new StringBuilder();
for (KeyStroke key : keys) {
final char c = key.getKeyChar();
if (c == KeyEvent.CHAR_UNDEFINED) {
return null;
}
builder.append(c);
}
return text;
return builder.toString();
}
/**
* Get the sequence of keys in the register
*
* @return The register keys
* Get the sequence of keys in the register.
*/
@NotNull
public List<KeyStroke> getKeys() {
if (keys == null && text != null) {
return StringHelper.stringToKeys(text);
}
return keys;
}
/**
* Appends the supplied text to any existing text
*
* @param text The text to add
* Append the supplied text to any existing text.
*/
public void addText(String text) {
if (this.text != null) {
this.text = this.text + text;
}
else if (this.keys != null) {
addKeys(StringHelper.stringToKeys(text));
}
else {
this.text = text;
}
public void addText(@NotNull String text) {
addKeys(StringHelper.stringToKeys(text));
}
public void addKeys(List<KeyStroke> keys) {
if (this.keys != null) {
this.keys.addAll(keys);
}
else if (this.text != null) {
this.text = this.text + StringHelper.keysToString(keys);
}
else {
this.keys = keys;
}
}
public boolean isText() {
return text != null;
}
public boolean isKeys() {
return keys != null;
public void addKeys(@NotNull List<KeyStroke> keys) {
this.keys.addAll(keys);
}
public static class KeySorter<V> implements Comparator<V> {
@@ -152,9 +118,4 @@ public class Register {
}
}
}
private char name;
@NotNull private SelectionType type;
private String text;
private List<KeyStroke> keys;
}

View File

@@ -28,6 +28,7 @@ import com.maddyhome.idea.vim.group.CommandGroups;
import com.maddyhome.idea.vim.group.HistoryGroup;
import com.maddyhome.idea.vim.helper.MessageHelper;
import com.maddyhome.idea.vim.helper.Msg;
import org.jetbrains.annotations.NotNull;
/**
* Maintains a tree of Ex commands based on the required and optional parts of the command names. Parses and
@@ -124,14 +125,15 @@ public class CommandParser {
* @throws ExException if any part of the command was invalid
*/
public boolean processLastCommand(Editor editor, DataContext context, int count) throws ExException {
Register reg = CommandGroups.getInstance().getRegister().getRegister(':');
if (reg == null) {
return false;
final Register reg = CommandGroups.getInstance().getRegister().getRegister(':');
if (reg != null) {
final String text = reg.getText();
if (text != null) {
processCommand(editor, context, text, count);
return true;
}
}
processCommand(editor, context, reg.getText(), count);
return true;
return false;
}
/**
@@ -144,7 +146,7 @@ public class CommandParser {
* @return A bitwise collection of flags, if any, from the result of running the command.
* @throws ExException if any part of the command is invalid or unknown
*/
public int processCommand(Editor editor, DataContext context, String cmd, int count) throws ExException {
public int processCommand(Editor editor, DataContext context, @NotNull String cmd, int count) throws ExException {
// Nothing entered
int result = 0;
if (cmd.length() == 0) {
@@ -193,7 +195,7 @@ public class CommandParser {
boolean ok = handler.process(editor, context, new ExCommand(res.getRanges(), command, res.getArgument()), count);
if (ok && (handler.getArgFlags() & CommandHandler.DONT_SAVE_LAST) == 0) {
CommandGroups.getInstance().getRegister().storeTextInternal(editor, new TextRange(-1, -1), cmd,
SelectionType.CHARACTER_WISE, ':', false, false);
SelectionType.CHARACTER_WISE, ':', false);
}
if (ok && (handler.getArgFlags() & CommandHandler.KEEP_FOCUS) != 0) {

View File

@@ -53,15 +53,13 @@ public class RegistersHandler extends CommandHandler {
text.append(reg.getName());
text.append(" ");
text.append(StringHelper.escape(reg.getText()));
text.append(StringHelper.escape(reg.getKeys()));
text.append("\n");
}
MorePanel panel = MorePanel.getInstance(editor);
panel.setText(text.toString());
//panel.setVisible(true);
return true;
}
}

View File

@@ -20,26 +20,25 @@ package com.maddyhome.idea.vim.group;
*/
import org.jdom.Element;
import org.jetbrains.annotations.NotNull;
/**
* This base class provides empty implemtations for the interface methods.
* This base class provides empty implementations for the interface methods.
*/
public abstract class AbstractActionGroup implements ActionGroup {
/**
* Allows the group to save its state and any configuration. This does nothing.
* Allows the group to save its state and any configuration.
*
* @param element The plugin's root XML element that this group can add a child to
* @param element The root XML element of the plugin that this group can add a child to
*/
public void saveData(Element element) {
// no-op
public void saveData(@NotNull Element element) {
}
/**
* Allows the group to restore its state and any configuration. This does nothing.
* Allows the group to restore its state and any configuration.
*
* @param element The plugin's root XML element that this group can add a child to
* @param element The root XML element of the plugin that this group can add a child to
*/
public void readData(Element element) {
// no-op
public void readData(@NotNull Element element) {
}
}

View File

@@ -20,15 +20,16 @@ package com.maddyhome.idea.vim.group;
*/
import org.jdom.Element;
import org.jetbrains.annotations.NotNull;
/**
* This is the base for all key mapping groups
* The base for all key mapping groups.
*/
public interface ActionGroup {
/**
* Allows the group to save its state and any configuration.
*
* @param element The plugin's root XML element that this group can add a child to
* @param element The root XML element of the plugin that this group can add a child to
*/
void saveData(Element element);
void saveData(@NotNull Element element);
}

View File

@@ -1428,7 +1428,7 @@ public class ChangeGroup extends AbstractActionGroup {
return false;
}
if (type == null || CommandGroups.getInstance().getRegister().storeText(editor, context, range, type, true, false)) {
if (type == null || CommandGroups.getInstance().getRegister().storeText(editor, range, type, true)) {
final Document document = editor.getDocument();
final int[] startOffsets = range.getStartOffsets();
final int[] endOffsets = range.getEndOffsets();

View File

@@ -21,6 +21,7 @@ package com.maddyhome.idea.vim.group;
import com.intellij.openapi.diagnostic.Logger;
import org.jdom.Element;
import org.jetbrains.annotations.NotNull;
/**
* This singleton maintains the instances of all the key groups. All the key/action mappings get created the first
@@ -149,9 +150,9 @@ public class CommandGroups {
/**
* Tells each group to save its data.
*
* @param element The plugin's root element
* @param element The root XML element of the plugin
*/
public void saveData(Element element) {
public void saveData(@NotNull Element element) {
motion.saveData(element);
change.saveData(element);
copy.saveData(element);
@@ -168,9 +169,9 @@ public class CommandGroups {
/**
* Tells each group to read its data.
*
* @param element The plugin's root element
* @param element The root XML element of the plugin
*/
public void readData(Element element) {
public void readData(@NotNull Element element) {
logger.debug("readData");
motion.readData(element);
change.readData(element);

View File

@@ -94,7 +94,7 @@ public class CopyGroup extends AbstractActionGroup {
if (logger.isDebugEnabled()) {
logger.debug("yanking range: " + range);
}
boolean res = CommandGroups.getInstance().getRegister().storeText(editor, context, range, type, false, true);
boolean res = CommandGroups.getInstance().getRegister().storeText(editor, range, type, false);
if (moveCursor) {
MotionGroup.moveCaret(editor, range.normalize().getStartOffset());
}

View File

@@ -23,8 +23,8 @@ import com.intellij.openapi.diagnostic.Logger;
import com.maddyhome.idea.vim.helper.StringHelper;
import com.maddyhome.idea.vim.option.NumberOption;
import com.maddyhome.idea.vim.option.Options;
import org.jdom.CDATA;
import org.jdom.Element;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.HashMap;
@@ -37,7 +37,7 @@ public class HistoryGroup extends AbstractActionGroup {
public static final String EXPRESSION = "expr";
public static final String INPUT = "input";
public void addEntry(String key, String text) {
public void addEntry(String key, @NotNull String text) {
if (logger.isDebugEnabled()) {
logger.debug("Add entry '" + text + "' to " + key);
}
@@ -46,12 +46,6 @@ public class HistoryGroup extends AbstractActionGroup {
block.addEntry(text);
}
public String getEntry(String key, int index) {
HistoryBlock block = blocks(key);
return block.getEntry(index);
}
public List<HistoryEntry> getEntries(String key, int first, int last) {
HistoryBlock block = blocks(key);
@@ -103,12 +97,7 @@ public class HistoryGroup extends AbstractActionGroup {
return block;
}
/**
* Allows the group to save its state and any configuration. This does nothing.
*
* @param element The plugin's root XML element that this group can add a child to
*/
public void saveData(Element element) {
public void saveData(@NotNull Element element) {
logger.debug("saveData");
Element hist = new Element("history");
@@ -121,30 +110,23 @@ public class HistoryGroup extends AbstractActionGroup {
}
private void saveData(Element element, String key) {
HistoryBlock block = histories.get(key);
final HistoryBlock block = histories.get(key);
if (block == null) {
return;
}
Element root = new Element("history-" + key);
final Element root = new Element("history-" + key);
List<HistoryEntry> elems = block.getEntries();
for (HistoryEntry entry : elems) {
Element text = new Element("entry");
CDATA data = new CDATA(StringHelper.entities(entry.getEntry()));
text.addContent(data);
root.addContent(text);
for (HistoryEntry entry : block.getEntries()) {
final Element entryElement = new Element("entry");
StringHelper.setSafeXmlText(entryElement, entry.getEntry());
root.addContent(entryElement);
}
element.addContent(root);
}
/**
* Allows the group to restore its state and any configuration. This does nothing.
*
* @param element The plugin's root XML element that this group can add a child to
*/
public void readData(Element element) {
public void readData(@NotNull Element element) {
logger.debug("readData");
Element hist = element.getChild("history");
if (hist == null) {
@@ -166,11 +148,15 @@ public class HistoryGroup extends AbstractActionGroup {
block = new HistoryBlock();
histories.put(key, block);
Element root = element.getChild("history-" + key);
final Element root = element.getChild("history-" + key);
if (root != null) {
List items = root.getChildren("entry");
for (Object item : items) {
block.addEntry(StringHelper.unentities(((Element)item).getText()));
//noinspection unchecked
List<Element> items = root.getChildren("entry");
for (Element item : items) {
final String text = StringHelper.getSafeXmlText(item);
if (text != null) {
block.addEntry(text);
}
}
}
}
@@ -182,7 +168,7 @@ public class HistoryGroup extends AbstractActionGroup {
}
private static class HistoryBlock {
public void addEntry(String text) {
public void addEntry(@NotNull String text) {
for (int i = 0; i < entries.size(); i++) {
HistoryEntry entry = entries.get(i);
if (text.equals(entry.getEntry())) {
@@ -198,26 +184,17 @@ public class HistoryGroup extends AbstractActionGroup {
}
}
public String getEntry(int index) {
if (index < entries.size()) {
HistoryEntry entry = entries.get(index);
return entry.getEntry();
}
else {
return null;
}
}
@NotNull
public List<HistoryEntry> getEntries() {
return entries;
}
private List<HistoryEntry> entries = new ArrayList<HistoryEntry>();
@NotNull private final List<HistoryEntry> entries = new ArrayList<HistoryEntry>();
private int counter;
}
public static class HistoryEntry {
public HistoryEntry(int number, String entry) {
public HistoryEntry(int number, @NotNull String entry) {
this.number = number;
this.entry = entry;
}
@@ -226,12 +203,13 @@ public class HistoryGroup extends AbstractActionGroup {
return number;
}
@NotNull
public String getEntry() {
return entry;
}
private int number;
private String entry;
@NotNull private String entry;
}
private Map<String, HistoryBlock> histories = new HashMap<String, HistoryBlock>();

View File

@@ -347,12 +347,7 @@ public class MarkGroup extends AbstractActionGroup {
return marks;
}
/**
* Allows the group to save its state and any configuration.
*
* @param element The plugin's root XML element that this group can add a child to
*/
public void saveData(Element element) {
public void saveData(@NotNull Element element) {
Element marksElem = new Element("globalmarks");
for (Mark mark : globalMarks.values()) {
if (!mark.isClear()) {
@@ -423,12 +418,7 @@ public class MarkGroup extends AbstractActionGroup {
element.addContent(jumpsElem);
}
/**
* Allows the group to restore its state and any configuration.
*
* @param element The plugin's root XML element that this group can add a child to
*/
public void readData(Element element) {
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

View File

@@ -19,7 +19,6 @@ package com.maddyhome.idea.vim.group;
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
import com.intellij.openapi.actionSystem.DataContext;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Editor;
import com.maddyhome.idea.vim.command.CommandState;
@@ -29,9 +28,9 @@ import com.maddyhome.idea.vim.common.TextRange;
import com.maddyhome.idea.vim.helper.EditorHelper;
import com.maddyhome.idea.vim.helper.StringHelper;
import com.maddyhome.idea.vim.ui.ClipboardHandler;
import org.jdom.CDATA;
import org.jdom.Element;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.awt.event.KeyEvent;
@@ -45,27 +44,33 @@ import java.util.List;
*/
public class RegisterGroup extends AbstractActionGroup {
/**
* The regsister key for the default register
* The register key for the default register
*/
public static final char REGISTER_DEFAULT = '"';
/**
* Creates the group
*/
public RegisterGroup() {
}
private static final String WRITABLE_REGISTERS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-*+_/\"";
private static final String READONLY_REGISTERS = ":.%#=/";
private static final String RECORDABLE_REGISTER = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
private static final String PLAYBACK_REGISTER = RECORDABLE_REGISTER + "\".*+";
private static final String VALID_REGISTERS = WRITABLE_REGISTERS + READONLY_REGISTERS;
private static final Logger logger = Logger.getInstance(RegisterGroup.class.getName());
private char lastRegister = REGISTER_DEFAULT;
@NotNull private HashMap<Character, Register> registers = new HashMap<Character, Register>();
private char recordRegister = 0;
@Nullable private List<KeyStroke> recordList = null;
public RegisterGroup() {}
/**
* Check to see if the last selected register can be written to
*
* @return true if writable register, false if not
* Check to see if the last selected register can be written to.
*/
public boolean isRegisterWritable() {
return READONLY_REGISTERS.indexOf(lastRegister) < 0;
}
/**
* Stores which register the user wishes to work with
* Store which register the user wishes to work with.
*
* @param reg The register name
* @return true if a valid register name, false if not
@@ -83,7 +88,7 @@ public class RegisterGroup extends AbstractActionGroup {
}
/**
* Resets the selected register back to the default register
* Reset the selected register back to the default register.
*/
public void resetRegister() {
lastRegister = REGISTER_DEFAULT;
@@ -91,32 +96,27 @@ public class RegisterGroup extends AbstractActionGroup {
}
/**
* Stores text into the last register
* Store text into the last register.
*
* @param editor The editor to get the text from
* @param context The data context
* @param range The range of the text to store
* @param type The type of copy
* @param isDelete is from a delete
* @param isYank is from a yank
* @return true if able to store the text into the register, false if not
*/
public boolean storeText(Editor editor, DataContext context, TextRange range, @NotNull SelectionType type, boolean isDelete, boolean isYank) {
public boolean storeText(@NotNull Editor editor, @NotNull TextRange range, @NotNull SelectionType type,
boolean isDelete) {
if (isRegisterWritable()) {
String text = EditorHelper.getText(editor, range);
return storeTextInternal(editor, range, text, type, lastRegister, isDelete, isYank);
return storeTextInternal(editor, range, text, type, lastRegister, isDelete);
}
return false;
}
public void storeKeys(List<KeyStroke> strokes, @NotNull SelectionType type, char register) {
registers.put(register, new Register(register, type, strokes));
}
public boolean storeTextInternal(Editor editor, TextRange range, String text, @NotNull SelectionType type,
char register, boolean isDelete, boolean isYank) {
public boolean storeTextInternal(@NotNull Editor editor, @NotNull TextRange range, @NotNull String text,
@NotNull SelectionType type, char register, boolean isDelete) {
// Null register doesn't get saved
if (lastRegister == '_') return true;
@@ -199,10 +199,12 @@ public class RegisterGroup extends AbstractActionGroup {
*
* @return The register, null if no such register
*/
@Nullable
public Register getLastRegister() {
return getRegister(lastRegister);
}
@Nullable
public Register getPlaybackRegister(char r) {
if (PLAYBACK_REGISTER.indexOf(r) != 0) {
return getRegister(r);
@@ -212,6 +214,7 @@ public class RegisterGroup extends AbstractActionGroup {
}
}
@Nullable
public Register getRegister(char r) {
// Uppercase registers actually get the lowercase register
if (Character.isUpperCase(r)) {
@@ -241,6 +244,7 @@ public class RegisterGroup extends AbstractActionGroup {
return lastRegister;
}
@NotNull
public List<Register> getRegisters() {
ArrayList<Register> res = new ArrayList<Register>(registers.values());
Collections.sort(res, new Register.KeySorter<Register>());
@@ -261,13 +265,13 @@ public class RegisterGroup extends AbstractActionGroup {
}
public void addKeyStroke(KeyStroke key) {
if (recordRegister != 0) {
if (recordRegister != 0 && recordList != null) {
recordList.add(key);
}
}
public void addText(String text) {
if (recordRegister != 0) {
public void addText(@NotNull String text) {
if (recordRegister != 0 && recordList != null) {
recordList.addAll(StringHelper.stringToKeys(text));
}
}
@@ -279,12 +283,14 @@ public class RegisterGroup extends AbstractActionGroup {
reg = getRegister(recordRegister);
}
if (reg == null) {
reg = new Register(Character.toLowerCase(recordRegister), SelectionType.CHARACTER_WISE, recordList);
registers.put(Character.toLowerCase(recordRegister), reg);
}
else {
reg.addKeys(recordList);
if (recordList != null) {
if (reg == null) {
reg = new Register(Character.toLowerCase(recordRegister), SelectionType.CHARACTER_WISE, recordList);
registers.put(Character.toLowerCase(recordRegister), reg);
}
else {
reg.addKeys(recordList);
}
}
CommandState.getInstance(editor).setRecording(false);
}
@@ -292,100 +298,77 @@ public class RegisterGroup extends AbstractActionGroup {
recordRegister = 0;
}
/**
* Save all the registers
*
* @param element The plugin's root XML element that this group can add a child to
*/
public void saveData(Element element) {
public void saveData(@NotNull final Element element) {
logger.debug("saveData");
Element regs = new Element("registers");
final Element registersElement = new Element("registers");
for (Character key : registers.keySet()) {
Register register = registers.get(key);
Element reg = new Element("register");
reg.setAttribute("name", String.valueOf(key));
reg.setAttribute("type", Integer.toString(register.getType().getValue()));
if (register.isText()) {
Element text = new Element("text");
CDATA data = new CDATA(/*CDATA.normalizeString*/(StringHelper.entities(register.getText())));
text.addContent(data);
reg.addContent(text);
if (logger.isDebugEnabled()) {
logger.debug("register='" + register.getText() + "'");
logger.debug("data=" + data);
logger.debug("text=" + text);
}
final Register register = registers.get(key);
final Element registerElement = new Element("register");
registerElement.setAttribute("name", String.valueOf(key));
registerElement.setAttribute("type", Integer.toString(register.getType().getValue()));
final String text = register.getText();
if (text != null) {
final Element textElement = new Element("text");
StringHelper.setSafeXmlText(textElement, text);
registerElement.addContent(textElement);
}
else {
Element keys = new Element("keys");
List<KeyStroke> list = register.getKeys();
final Element keys = new Element("keys");
final List<KeyStroke> list = register.getKeys();
for (KeyStroke stroke : list) {
Element k = new Element("key");
final Element k = new Element("key");
k.setAttribute("char", Integer.toString(stroke.getKeyChar()));
k.setAttribute("code", Integer.toString(stroke.getKeyCode()));
k.setAttribute("mods", Integer.toString(stroke.getModifiers()));
keys.addContent(k);
}
reg.addContent(keys);
registerElement.addContent(keys);
}
regs.addContent(reg);
registersElement.addContent(registerElement);
}
element.addContent(regs);
element.addContent(registersElement);
}
/**
* Restore all the registers
*
* @param element The plugin's root XML element that this group can add a child to
*/
public void readData(Element element) {
public void readData(@NotNull final Element element) {
logger.debug("readData");
Element regs = element.getChild("registers");
if (regs != null) {
List<Element> list = regs.getChildren("register");
for (Element reg : list) {
Character key = reg.getAttributeValue("name").charAt(0);
Register register;
if (reg.getChild("text") != null) {
final SelectionType type = SelectionType.fromValue(Integer.parseInt(reg.getAttributeValue("type")));
register = new Register(key, type != null ? type : SelectionType.CHARACTER_WISE,
StringHelper.unentities(reg.getChild("text").getText/*Normalize*/()));
final Element registersElement = element.getChild("registers");
if (registersElement != null) {
//noinspection unchecked
final List<Element> registerElements = registersElement.getChildren("register");
for (Element registerElement : registerElements) {
final char key = registerElement.getAttributeValue("name").charAt(0);
final Register register;
final Element textElement = registerElement.getChild("text");
final String typeText = registerElement.getAttributeValue("type");
final SelectionType type = SelectionType.fromValue(Integer.parseInt(typeText));
if (textElement != null) {
final String text = StringHelper.getSafeXmlText(textElement);
if (text != null) {
register = new Register(key, type != null ? type : SelectionType.CHARACTER_WISE, text);
}
else {
register = null;
}
}
else {
Element keys = reg.getChild("keys");
List<Element> klist = keys.getChildren("key");
List<KeyStroke> strokes = new ArrayList<KeyStroke>();
for (Element kelem : klist) {
int code = Integer.parseInt(kelem.getAttributeValue("code"));
int mods = Integer.parseInt(kelem.getAttributeValue("mods"));
char ch = (char)Integer.parseInt(kelem.getAttributeValue("char"));
if (ch == KeyEvent.CHAR_UNDEFINED) {
strokes.add(KeyStroke.getKeyStroke(code, mods));
}
else {
strokes.add(KeyStroke.getKeyStroke(new Character(ch), mods));
}
final Element keysElement = registerElement.getChild("keys");
//noinspection unchecked
final List<Element> keyElements = keysElement.getChildren("key");
final List<KeyStroke> strokes = new ArrayList<KeyStroke>();
for (Element keyElement : keyElements) {
final int code = Integer.parseInt(keyElement.getAttributeValue("code"));
final int modifiers = Integer.parseInt(keyElement.getAttributeValue("mods"));
final char c = (char)Integer.parseInt(keyElement.getAttributeValue("char"));
//noinspection MagicConstant
strokes.add(c == KeyEvent.CHAR_UNDEFINED ?
KeyStroke.getKeyStroke(code, modifiers) :
KeyStroke.getKeyStroke(c));
}
final SelectionType type = SelectionType.fromValue(Integer.parseInt(reg.getAttributeValue("type")));
register = new Register(key, type != null ? type : SelectionType.CHARACTER_WISE, strokes);
}
registers.put(key, register);
}
}
}
private char lastRegister = REGISTER_DEFAULT;
private HashMap<Character, Register> registers = new HashMap<Character, Register>();
private char recordRegister = 0;
private List<KeyStroke> recordList = null;
private static final String WRITABLE_REGISTERS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-*+_/\"";
private static final String READONLY_REGISTERS = ":.%#=/";
private static final String RECORDABLE_REGISTER = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
private static final String PLAYBACK_REGISTER = RECORDABLE_REGISTER + "\".*+";
private static final String VALID_REGISTERS = WRITABLE_REGISTERS + READONLY_REGISTERS;
private static Logger logger = Logger.getInstance(RegisterGroup.class.getName());
}

View File

@@ -45,8 +45,9 @@ import com.maddyhome.idea.vim.regexp.CharHelper;
import com.maddyhome.idea.vim.regexp.CharPointer;
import com.maddyhome.idea.vim.regexp.CharacterClasses;
import com.maddyhome.idea.vim.regexp.RegExp;
import org.jdom.CDATA;
import org.jdom.Element;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.awt.event.ActionEvent;
@@ -87,10 +88,10 @@ public class SearchGroup extends AbstractActionGroup {
return lastPattern;
}
private void setLastPattern(Editor editor, String lastPattern) {
private void setLastPattern(Editor editor, @NotNull String lastPattern) {
this.lastPattern = lastPattern;
CommandGroups.getInstance().getRegister().storeTextInternal(editor, new TextRange(-1, -1),
lastPattern, SelectionType.CHARACTER_WISE, '/', false, false);
lastPattern, SelectionType.CHARACTER_WISE, '/', false);
CommandGroups.getInstance().getHistory().addEntry(HistoryGroup.SEARCH, lastPattern);
}
@@ -266,7 +267,9 @@ public class SearchGroup extends AbstractActionGroup {
}
lastSubstitute = pattern;
setLastPattern(editor, pattern);
if (pattern != null) {
setLastPattern(editor, pattern);
}
//int start = editor.logicalPositionToOffset(new LogicalPosition(line1, 0));
//int end = editor.logicalPositionToOffset(new LogicalPosition(line2, EditorHelper.getLineLength(editor, line2)));
@@ -559,7 +562,9 @@ public class SearchGroup extends AbstractActionGroup {
}
lastSearch = pattern;
setLastPattern(editor, pattern);
if (pattern != null) {
setLastPattern(editor, pattern);
}
lastOffset = offset;
lastDir = dir;
@@ -1107,43 +1112,23 @@ public class SearchGroup extends AbstractActionGroup {
EditorData.setLastSearch(editor, null);
}
/**
* Allows the group to save its state and any configuration. This does nothing.
*
* @param element The plugin's root XML element that this group can add a child to
*/
public void saveData(Element element) {
public void saveData(@NotNull Element element) {
logger.debug("saveData");
Element search = new Element("search");
if (lastSearch != null) {
Element text = new Element("last-search");
CDATA data = new CDATA(/*CDATA.normalizeString*/StringHelper.entities(lastSearch));
text.addContent(data);
search.addContent(text);
search.addContent(createElementWithText("last-search", lastSearch));
}
if (lastOffset != null) {
Element text = new Element("last-offset");
CDATA data = new CDATA(/*CDATA.normalizeString*/StringHelper.entities(lastOffset));
text.addContent(data);
search.addContent(text);
search.addContent(createElementWithText("last-offset", lastOffset));
}
if (lastPattern != null) {
Element text = new Element("last-pattern");
CDATA data = new CDATA(/*CDATA.normalizeString*/StringHelper.entities(lastPattern));
text.addContent(data);
search.addContent(text);
search.addContent(createElementWithText("last-pattern", lastPattern));
}
if (lastReplace != null) {
Element text = new Element("last-replace");
CDATA data = new CDATA(/*CDATA.normalizeString*/StringHelper.entities(lastReplace));
text.addContent(data);
search.addContent(text);
search.addContent(createElementWithText("last-replace", lastReplace));
}
if (lastSubstitute != null) {
Element text = new Element("last-substitute");
CDATA data = new CDATA(/*CDATA.normalizeString*/StringHelper.entities(lastSubstitute));
text.addContent(data);
search.addContent(text);
search.addContent(createElementWithText("last-substitute", lastSubstitute));
}
Element text = new Element("last-dir");
text.addContent(Integer.toString(lastDir));
@@ -1157,33 +1142,23 @@ public class SearchGroup extends AbstractActionGroup {
element.addContent(search);
}
/**
* Allows the group to restore its state and any configuration. This does nothing.
*
* @param element The plugin's root XML element that this group can add a child to
*/
public void readData(Element element) {
@NotNull
private static Element createElementWithText(@NotNull String name, @NotNull String text) {
return StringHelper.setSafeXmlText(new Element(name), text);
}
public void readData(@NotNull Element element) {
logger.debug("readData");
Element search = element.getChild("search");
if (search == null) {
return;
}
if (search.getChild("last-search") != null) {
lastSearch = StringHelper.unentities(search.getChildText/*Normalize*/("last-search"));
}
if (search.getChild("last-offset") != null) {
lastOffset = StringHelper.unentities(search.getChildText/*Normalize*/("last-offset"));
}
if (search.getChild("last-pattern") != null) {
lastPattern = StringHelper.unentities(search.getChildText/*Normalize*/("last-pattern"));
}
if (search.getChild("last-replace") != null) {
lastReplace = StringHelper.unentities(search.getChildText/*Normalize*/("last-replace"));
}
if (search.getChild("last-substitute") != null) {
lastSubstitute = StringHelper.unentities(search.getChildText/*Normalize*/("last-substitute"));
}
lastSearch = getSafeChildText(search, "last-search");
lastOffset = getSafeChildText(search, "last-offset");
lastPattern = getSafeChildText(search, "last-pattern");
lastReplace = getSafeChildText(search, "last-replace");
lastSubstitute = getSafeChildText(search, "last-substitute");
Element dir = search.getChild("last-dir");
lastDir = Integer.parseInt(dir.getText());
@@ -1196,6 +1171,12 @@ public class SearchGroup extends AbstractActionGroup {
}
}
@Nullable
private static String getSafeChildText(@NotNull Element element, @NotNull String name) {
final Element child = element.getChild(name);
return child != null ? StringHelper.getSafeXmlText(child) : null;
}
private class ButtonActionListener implements ActionListener {
public ButtonActionListener(int i) {
index = i;

View File

@@ -19,17 +19,21 @@ package com.maddyhome.idea.vim.helper;
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
import org.apache.commons.codec.binary.Base64;
import org.jdom.CDATA;
import org.jdom.Content;
import org.jdom.Element;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.awt.event.KeyEvent;
import java.util.ArrayList;
import java.util.List;
/**
*
*/
public class StringHelper {
private StringHelper() {}
public static String pad(String text, int len, char ch) {
int l = text.length();
StringBuffer res = new StringBuffer(text);
@@ -40,67 +44,46 @@ public class StringHelper {
return res.toString();
}
public static String escape(String text) {
StringBuffer res = new StringBuffer(text.length());
@NotNull
public static String escape(@NotNull String s) {
return escape(stringToKeys(s));
}
for (int i = 0; i < text.length(); i++) {
char ch = text.charAt(i);
if (ch < ' ') {
res.append('^').append((char)(ch + 'A' - 1));
@NotNull
public static String escape(@NotNull List<KeyStroke> keys) {
final StringBuffer res = new StringBuffer();
for (KeyStroke key : keys) {
final char c = key.getKeyChar();
final int modifiers = key.getModifiers();
final int code = key.getKeyCode();
if (c < ' ') {
res.append('^').append((char)(c + 'A' - 1));
}
else if (ch == '\n') {
else if (c == '\n') {
res.append("^J");
}
else if (ch == '\t') {
else if (c == '\t') {
res.append("^I");
}
else if (c == '\u0000') {
res.append("^@");
}
else if ((modifiers & KeyEvent.CTRL_DOWN_MASK) != 0) {
final char[] chars = Character.toChars(code);
if (chars.length == 1) {
res.append("^");
res.append(chars);
}
}
else {
res.append(ch);
res.append(c);
}
}
return res.toString();
}
public static String entities(String text) {
StringBuffer res = new StringBuffer(text.length());
for (int i = 0; i < text.length(); i++) {
char ch = text.charAt(i);
switch (ch) {
case '!':
res.append("&#33;");
break;
case '[':
res.append("&#91;");
break;
case ']':
res.append("&#93;");
break;
case ' ':
res.append("&#32;");
break;
case '&':
res.append("&amp;");
break;
case '\t':
res.append("&#9;");
break;
case '\n':
res.append("&#10;");
break;
case '\r':
res.append("&#13;");
break;
default:
res.append(ch);
}
}
return res.toString();
}
public static String unentities(String text) {
@Deprecated
private static String unentities(String text) {
StringBuffer res = new StringBuffer(text.length());
for (int i = 0; i < text.length(); i++) {
@@ -163,56 +146,6 @@ public class StringHelper {
return res;
}
public static String keysToString(List<KeyStroke> keys) {
StringBuffer res = new StringBuffer();
for (KeyStroke key : keys) {
if (key.getKeyChar() != KeyEvent.CHAR_UNDEFINED) {
res.append(key.getKeyChar());
}
else {
switch (key.getKeyCode()) {
case KeyEvent.VK_TAB:
res.append("\t");
break;
case KeyEvent.VK_ENTER:
res.append("\n");
break;
case KeyEvent.VK_BACK_SPACE:
res.append("\b");
break;
default:
res.append('<');
res.append(getModifiersText(key.getModifiers()));
res.append(KeyEvent.getKeyText(key.getKeyCode()));
res.append('>');
}
}
}
return res.toString();
}
public static String getModifiersText(int modifiers) {
StringBuffer buf = new StringBuffer();
if ((modifiers & KeyEvent.META_MASK) != 0) {
buf.append("M-");
}
if ((modifiers & KeyEvent.CTRL_MASK) != 0) {
buf.append("C-");
}
if ((modifiers & KeyEvent.ALT_MASK) != 0) {
buf.append("A-");
}
if ((modifiers & KeyEvent.SHIFT_MASK) != 0) {
buf.append("S-");
}
if ((modifiers & KeyEvent.ALT_GRAPH_MASK) != 0) {
buf.append("G-");
}
return buf.toString();
}
public static boolean containsUpperCase(String text) {
for (int i = 0; i < text.length(); i++) {
if (Character.isUpperCase(text.charAt(i)) && (i == 0 || text.charAt(i - 1) == '\\')) {
@@ -223,6 +156,79 @@ public class StringHelper {
return false;
}
private StringHelper() {
/**
* Set the text of an XML element, safely encode it if needed.
*/
public static Element setSafeXmlText(@NotNull Element element, @NotNull String text) {
final Character first = firstCharacter(text);
final Character last = lastCharacter(text);
if (!StringHelper.isXmlCharacterData(text) ||
first != null && Character.isWhitespace(first) ||
last != null && Character.isWhitespace(last)) {
element.setAttribute("encoding", "base64");
final String encoded = new String(Base64.encodeBase64(text.getBytes()));
element.setText(encoded);
}
else {
element.setText(text);
}
return element;
}
@Nullable
private static Character lastCharacter(@NotNull String text) {
return text.length() > 0 ? text.charAt(text.length() - 1) : null;
}
@Nullable
private static Character firstCharacter(@NotNull String text) {
return text.length() > 0 ? text.charAt(0) : null;
}
/**
* Get the (potentially safely encoded) text of an XML element.
*/
@Nullable
public static String getSafeXmlText(@NotNull Element element) {
final String text = element.getText();
//noinspection unchecked
final List<Content> contentItems = element.getContent();
for (Content content : contentItems) {
if (content instanceof CDATA) {
// TODO: Remove compatibility with the IdeaVim <= 0.24 settings style in the next versions
return unentities(text);
}
}
final String encoding = element.getAttributeValue("encoding");
if (encoding == null) {
return text;
}
else if (encoding.equals("base64")) {
return new String(Base64.decodeBase64(text.getBytes()));
}
return null;
}
/**
* Check if the text matches the CharData production from the XML grammar.
*
* This check is more restricted than CharData as it completely forbids '>'.
*/
public static boolean isXmlCharacterData(@NotNull String text) {
for (char c : text.toCharArray()) {
if (!isXmlChar(c) || c == '<' || c == '>' || c == '&') {
return false;
}
}
return true;
}
/**
* Check if a char matches the Char production from the XML grammar.
*
* Characters beyond the Basic Multilingual Plane are not supported.
*/
private static boolean isXmlChar(char c) {
return '\u0001' <= c && c <= '\uD7FF' || '\uE000' <= c && c <= '\uFFFD';
}
}

View File

@@ -23,6 +23,7 @@ public class MacroActionTest extends VimTestCase {
assertFalse(commandState.isRecording());
final RegisterGroup registerGroup = CommandGroups.getInstance().getRegister();
final Register register = registerGroup.getRegister('a');
assertNotNull(register);
assertEquals("3l", register.getText());
}
}