Permalink
Browse files

Added :retab command. This is mostly working, except it needs to modify

the Eclipse preferences of spaces and tabs to work fully as expected.
  • Loading branch information...
bdetweiler committed Mar 9, 2013
1 parent b6e9b46 commit b9f09caeb7ac9fc0011013ece174f1c80b494aa4
@@ -22,6 +22,8 @@
import net.sourceforge.vrapper.vim.commands.LineRangeOperationCommand;
import net.sourceforge.vrapper.vim.commands.LineWiseSelection;
import net.sourceforge.vrapper.vim.commands.MotionCommand;
+import net.sourceforge.vrapper.vim.commands.RetabOperation;
+import net.sourceforge.vrapper.vim.commands.SimpleTextOperation;
import net.sourceforge.vrapper.vim.commands.SortOperation;
import net.sourceforge.vrapper.vim.commands.TextOperationTextObjectCommand;
import net.sourceforge.vrapper.vim.modes.NormalMode;
@@ -102,6 +104,23 @@ public void testCommandLineParser() {
assertTrue(command instanceof LineRangeOperationCommand);
}
+ @Test
+ public void testRetab() throws CommandExecutionException {
+ SimpleTextOperation retabCommand = (SimpleTextOperation) new RetabOperation(null);
+
+ content.setText("\t");
+ retabCommand.execute(adaptor, null, ContentType.LINES);
+ assertEquals(" ", content.getText());
+
+ content.setText("line\n\t\t\tnew\tline\n\t\tABC");
+ retabCommand.execute(adaptor, null, ContentType.LINES);
+ assertEquals("line\n new line\n ABC", content.getText());
+
+ content.setText("\t\t\t\n\t\n\n\t\t\t\n\t");
+ retabCommand.execute(adaptor, null, ContentType.LINES);
+ assertEquals(" \n \n\n \n ", content.getText());
+ }
+
@Test
public void testSort() throws CommandExecutionException {
content.setText("single line");
@@ -27,10 +27,13 @@
public static final Option<Boolean> IM_DISABLE = bool("imdisable", false, "imd");
public static final Option<Boolean> VISUAL_MOUSE = bool("visualmouse", true, "vm");
public static final Option<Boolean> AUTO_CHDIR = bool("autochdir", false, "acd");
+ // TODO: This is an Eclipse setting under Window->Preferences->Editors->Text Editors->"Insert spaces for tabs"
+ // Changing this value should change the Eclipse configuration too. -- BRD
+ public static final Option<Boolean> EXPAND_TAB = bool("expandtab", true, "et");
@SuppressWarnings("unchecked")
public static final Set<Option<Boolean>> BOOLEAN_OPTIONS = set(
- SMART_INDENT, AUTO_INDENT, ATOMIC_INSERT, IGNORE_CASE, SMART_CASE,
+ EXPAND_TAB, SMART_INDENT, AUTO_INDENT, ATOMIC_INSERT, IGNORE_CASE, SMART_CASE,
SANE_CW, SANE_Y, SEARCH_HIGHLIGHT, SEARCH_REGEX,
INCREMENTAL_SEARCH, LINE_NUMBERS, SHOW_WHITESPACE, IM_DISABLE,
VISUAL_MOUSE, AUTO_CHDIR);
@@ -47,6 +50,10 @@
public static final Option<Integer> SCROLL_JUMP = integer("scrolljump", 1, "sj");
public static final Option<Integer> TEXT_WIDTH = integer("textwidth", 80, "tw");
public static final Option<Integer> SOFT_TAB = integer("softtabstop", 0, "sts");
+ // TODO: This is an Eclipse setting under Window->Preferences->Editors->Text Editors->"Displayed tab width"
+ // Changing this value should change the Eclipse configuration too. -- BRD
+ public static final Option<Integer> TAB_STOP = integer("tabstop", 8, "ts");
+
@SuppressWarnings("unchecked")
- public static final Set<Option<Integer>> INT_OPTIONS = set(SCROLL_JUMP, SCROLL_OFFSET, TEXT_WIDTH, SOFT_TAB);
-}
+ public static final Set<Option<Integer>> INT_OPTIONS = set(SCROLL_JUMP, SCROLL_OFFSET, TEXT_WIDTH, SOFT_TAB, TAB_STOP);
+}
@@ -117,10 +117,14 @@ private TextOperationTextObjectCommand parseRangeDefinition(String command, Edit
return null;
}
}
-
+
+ /*
+ * When parsing a range, this determines the first letter of the actual operation we're
+ * trying to do, so stop parsing the range. Range operations include :sort, :retab, :yank, :delete
+ */
private boolean isOperationChar(char c) {
//what other operations do we support?
- return c == 'd' || c == 'y' || c == 's' || c == 'v' || c == 'g';
+ return c == 'd' || c == 'y' || c == 's' || c == 'v' || c == 'g' || c == 'r';
}
/**
@@ -313,6 +317,9 @@ else if(operation == 's') {
else if(operation == 'g' || operation == 'v') {
return new ExCommandOperation(remainingChars);
}
+ else if(operation == 'r' && (remainingChars.startsWith("ret"))) {
+ return new RetabOperation(remainingChars);
+ }
else {
editorAdaptor.getUserInterfaceService().setErrorMessage("Unknown operation for range: " + operation);
return null;
@@ -0,0 +1,211 @@
+package net.sourceforge.vrapper.vim.commands;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import net.sourceforge.vrapper.platform.TextContent;
+import net.sourceforge.vrapper.utils.ContentType;
+import net.sourceforge.vrapper.utils.LineInformation;
+import net.sourceforge.vrapper.utils.TextRange;
+import net.sourceforge.vrapper.vim.EditorAdaptor;
+import net.sourceforge.vrapper.vim.Options;
+
+/*
+ * *:ret* *:retab* *:retab!*
+ * :[range]ret[ab][!] [new_tabstop]
+ * Replace all sequences of white-space containing a
+ * <Tab> with new strings of white-space using the new
+ * tabstop value given. If you do not specify a new
+ * tabstop size or it is zero, Vim uses the current value
+ * of 'tabstop'.
+ * The current value of 'tabstop' is always used to
+ * compute the width of existing tabs.
+ * With !, Vim also replaces strings of only normal
+ * spaces with tabs where appropriate.
+ * With 'expandtab' on, Vim replaces all tabs with the
+ * appropriate number of spaces.
+ * This command sets 'tabstop' to the new value given,
+ * and if performed on the whole file, which is default,
+ * should not make any visible change.
+ * Careful: This command modifies any <Tab> characters
+ * inside of strings in a C program. Use "\t" to avoid
+ * this (that's a good habit anyway).
+ * `:retab!` may also change a sequence of spaces by
+ * <Tab> characters, which can mess up a printf().
+ * {not in Vi}
+ */
+/**
+ * RetabOperation
+ * @author bdetweiler
+ *
+ * :retab changes the tabstop value in the current configuration. It also will
+ * apply those changes to the current file. This works in a two different ways.
+ *
+ * If expandtab (et) is turned on, it will replace any tab characters with the
+ * equivalent number of spaces in the TAB_STOP option. This is useful for getting
+ * rid of unwanted tabs in a text file.
+ *
+ * If expandtab (et) is NOT turned on, the TAB_STOP value is applied to the current file.
+ *
+ * If a number is passed to :retab, the TAB_STOP value for the current session is
+ * changed to that value and applied to the current file. Tabs are replaced with
+ * the new value, and spaces are applied where a tab does not fit.
+ *
+ * TODO: Eclipse uses its own tabstop value under
+ * Window->Preferences->General->Editors->Text Editors->"Displayed tab width"
+ * And expandtab is also an Eclipse setting under
+ * Window->Preferences->General->Editors->Text Editors->"Insert spaces for tabs"
+ *
+ */
+public class RetabOperation extends SimpleTextOperation {
+
+ /** ! - replace strings of normal spaces */
+ private static final String REPLACE_NORMAL_SPACE = "!";
+
+ private static final String SPACE = " ";
+
+ // Possible configurations for sort
+ private boolean replaceNormalSpace = false;
+
+ private Integer newTab = 0;
+
+ public RetabOperation(String commandStr) {
+ super();
+
+ // If no command was given, we're done with setup
+ if(commandStr == null)
+ return;
+
+ commandStr = commandStr.replace("retab", "");
+ commandStr = commandStr.replace("reta", "");
+ commandStr = commandStr.replace("ret", "");
+
+ // Replaces equivalent number of spaces with tab if expandtab is not on
+ if(commandStr.contains(REPLACE_NORMAL_SPACE))
+ replaceNormalSpace = true;
+
+ // New tab character
+ if(commandStr.length() > 0) {
+ try {
+ newTab = Integer.parseInt(commandStr.trim());
+ } catch(Exception e) {
+ // Invalid value, do nothing
+ return;
+ }
+ }
+ }
+
+ @Override
+ public void execute(EditorAdaptor editorAdaptor, TextRange region, ContentType contentType) throws CommandExecutionException {
+ try {
+
+ TextContent content = editorAdaptor.getModelContent();
+ LineInformation startLine;
+ LineInformation endLine;
+ int length;
+
+ if(region == null) {
+ startLine = content.getLineInformation(0);
+ endLine = content.getLineInformation(content.getNumberOfLines() - 1);
+ length = endLine.getEndOffset() - startLine.getBeginOffset();
+ }
+ else {
+ startLine = content.getLineInformationOfOffset(region.getLeftBound().getModelOffset());
+ endLine = content.getLineInformationOfOffset(region.getRightBound().getModelOffset() - 1);
+ length = region.getModelLength();
+ }
+
+ doIt(editorAdaptor, startLine, endLine, length);
+
+ } catch (Exception e) {
+ throw new CommandExecutionException("retab failed: " + e.getMessage());
+ }
+ }
+
+ /**
+ * This is where the action happens.
+ *
+ * @param editorAdaptor
+ * @throws Exception
+ */
+ public void doIt(EditorAdaptor editorAdaptor,
+ LineInformation startLine,
+ LineInformation endLine,
+ int totalLengthOfRange) throws Exception {
+
+ int tabStop = editorAdaptor.getConfiguration().get(Options.TAB_STOP);
+ boolean expandTab = editorAdaptor.getConfiguration().get(Options.EXPAND_TAB);
+
+ if(tabStop == 0)
+ tabStop = 8;
+
+ Integer currenTabStop = editorAdaptor.getConfiguration().get(Options.TAB_STOP);
+
+ // Can't have 0 for TAB_STOP. Leave it the same.
+ if(newTab == 0)
+ newTab = currenTabStop;
+
+ // Tabs are converted to spaces in place, then the TAB_STOP value is changed
+ // so future tabs will have the new value of spaces
+ String replacementSpaces = "";
+ for(int i = 0; i < currenTabStop; ++i)
+ replacementSpaces += SPACE;
+
+ editorAdaptor.getConfiguration().set(Options.TAB_STOP, newTab);
+
+ // Throw the whole editor into an array separated by newlines
+ String newline = editorAdaptor.getConfiguration().getNewLine();
+ TextContent content = editorAdaptor.getModelContent();
+ int totalLinesInEditor = content.getNumberOfLines();
+ List<String> editorContentList = new ArrayList<String>();
+ LineInformation line = null;
+
+ /*
+ * Step 1: Go through and retab each line and put it into an
+ * array list so we can replace the contents of the editor
+ */
+ for(int i = startLine.getNumber(); i <= endLine.getNumber(); ++i) {
+ line = content.getLineInformation(i);
+ String lineStr = content.getText(line.getBeginOffset(), line.getLength());
+
+ if(expandTab) {
+ lineStr = lineStr.replaceAll("\\t", replacementSpaces);
+ } else if(replaceNormalSpace) {
+ // If TAB_STOP number of spaces are encountered and expandTab is off,
+ // replace each occurrence of those spaces with a tab
+ lineStr = lineStr.replaceAll(replacementSpaces, "\t");
+ }
+
+ editorContentList.add(lineStr);
+ }
+
+ /*
+ * Step 2: Append newlines to everything but the very last line of the editor
+ */
+ StringBuilder replacementText = new StringBuilder();
+ int count = startLine.getNumber();
+ for (String editorLine : editorContentList) {
+ if(count != totalLinesInEditor - 1)
+ editorLine += newline;
+
+ ++count;
+ replacementText.append(editorLine);
+ }
+
+ /*
+ * Step 3: Replace the contents of the editor with the freshly retabbed text
+ * This applies to a range, or the whole editor
+ */
+ editorAdaptor.getModelContent().replace(startLine.getBeginOffset(),
+ totalLengthOfRange,
+ replacementText.toString());
+ //put cursor at beginning of sorted text
+ editorAdaptor.setPosition(
+ editorAdaptor.getCursorService().newPositionForModelOffset(startLine.getBeginOffset()),
+ true);
+ }
+
+ public TextOperation repetition() {
+ return null;
+ }
+}
@@ -24,6 +24,7 @@
import net.sourceforge.vrapper.vim.commands.MotionCommand;
import net.sourceforge.vrapper.vim.commands.RedoCommand;
import net.sourceforge.vrapper.vim.commands.RepeatLastSubstitutionCommand;
+import net.sourceforge.vrapper.vim.commands.RetabOperation;
import net.sourceforge.vrapper.vim.commands.SaveAllCommand;
import net.sourceforge.vrapper.vim.commands.SaveCommand;
import net.sourceforge.vrapper.vim.commands.SetOptionCommand;
@@ -146,6 +147,22 @@ public Object evaluate(EditorAdaptor vim, Queue<String> command) {
return null;
}
};
+ Evaluator retab = new Evaluator() {
+ public Object evaluate(EditorAdaptor vim, Queue<String> command) {
+ String commandStr = "";
+ while(command.size() > 0)
+ // attempt to preserve spacing
+ commandStr += command.poll() + " ";
+
+ try {
+ new RetabOperation(commandStr).execute(vim, null, ContentType.LINES);
+ } catch (CommandExecutionException e) {
+ vim.getUserInterfaceService().setErrorMessage(e.getMessage());
+ }
+
+ return null;
+ }
+ };
Evaluator chDir = new Evaluator() {
public Object evaluate(EditorAdaptor vim, Queue<String> command) {
String dir = command.isEmpty() ? "/" : command.poll();
@@ -264,6 +281,12 @@ public Object evaluate(EditorAdaptor vim, Queue<String> command) {
mapping.add("sor", sort);
mapping.add("sort", sort);
mapping.add("sort!", sort);
+ mapping.add("ret", retab);
+ mapping.add("reta", retab);
+ mapping.add("retab", retab);
+ mapping.add("ret!", retab);
+ mapping.add("reta!", retab);
+ mapping.add("retab!", retab);
// Display the ascii values of the character under the cursor
mapping.add("as", ascii);
mapping.add("ascii", ascii);
@@ -354,6 +377,18 @@ public Command parseAndExecute(String first, String command) {
if(command.startsWith("sort!")) {
command = command.replace("sort!", "sort !");
}
+
+ // TODO: Need a parser for partial commands so we can avoid this redundancy
+ // Replace all spaces, not just tabs
+ if(command.startsWith("ret!")) {
+ command = command.replace("ret!", "ret !");
+ }
+ if(command.startsWith("reta!")) {
+ command = command.replace("reta!", "reta !");
+ }
+ if(command.startsWith("retab!")) {
+ command = command.replace("retab!", "retab !");
+ }
/** Now check against list of known commands (whitespace-delimited) **/
@@ -424,6 +459,20 @@ public Object evaluate(EditorAdaptor vim, Queue<String> command) {
return null;
}
},
+ EXPAND_TAB {
+ public Object evaluate(EditorAdaptor vim, Queue<String> command) {
+ vim.getConfiguration().set(Options.EXPAND_TAB, Boolean.TRUE);
+ vim.getEditorSettings().setShowLineNumbers(true);
+ return null;
+ }
+ },
+ NO_EXPAND_TAB {
+ public Object evaluate(EditorAdaptor vim, Queue<String> command) {
+ vim.getConfiguration().set(Options.EXPAND_TAB, Boolean.FALSE);
+ vim.getEditorSettings().setShowLineNumbers(false);
+ return null;
+ }
+ },
LINE_NUMBERS {
public Object evaluate(EditorAdaptor vim, Queue<String> command) {
vim.getConfiguration().set(Options.LINE_NUMBERS, Boolean.TRUE);

3 comments on commit b9f09ca

@bdetweiler

This comment has been minimized.

Show comment Hide comment
@bdetweiler

bdetweiler Mar 9, 2013

Contributor

Kevin, as always, if you see any glaringly stupid mistakes, feel free to correct them. As I mentioned in the comments, expandtab and tabstop won't have any effect other than for retab until it modifies the Eclipse configs. I think this is pretty useful even without it though just for getting rid of unwanted tabs in a file. Particularly since there is a bug with some version of eclipse where autoindent will not replace the tab with spaces.

Contributor

bdetweiler replied Mar 9, 2013

Kevin, as always, if you see any glaringly stupid mistakes, feel free to correct them. As I mentioned in the comments, expandtab and tabstop won't have any effect other than for retab until it modifies the Eclipse configs. I think this is pretty useful even without it though just for getting rid of unwanted tabs in a file. Particularly since there is a bug with some version of eclipse where autoindent will not replace the tab with spaces.

@keforbes

This comment has been minimized.

Show comment Hide comment
@keforbes

keforbes Mar 12, 2013

Contributor

Honestly, I feel the same way about expandtab and tabstop as I do about softtabstop or textwidth. While it'd be nice to integrate with Eclipse settings, these really aren't values that users tweak on a regular basis. I think it's acceptable for a user to define those values in their .vrapperrc file to match their settings in Eclipse and then never modify those values again. It seems like it'd be a lot of extra work for us to sync our values with the current Eclipse config all for a value that the user will never change.

If you can find a way to sync with Eclipse settings then by all means, go for it. I just wouldn't spend too much time on it myself. I think your current solution of separate values which mimic Eclipse settings is perfectly fine.

Contributor

keforbes replied Mar 12, 2013

Honestly, I feel the same way about expandtab and tabstop as I do about softtabstop or textwidth. While it'd be nice to integrate with Eclipse settings, these really aren't values that users tweak on a regular basis. I think it's acceptable for a user to define those values in their .vrapperrc file to match their settings in Eclipse and then never modify those values again. It seems like it'd be a lot of extra work for us to sync our values with the current Eclipse config all for a value that the user will never change.

If you can find a way to sync with Eclipse settings then by all means, go for it. I just wouldn't spend too much time on it myself. I think your current solution of separate values which mimic Eclipse settings is perfectly fine.

@bdetweiler

This comment has been minimized.

Show comment Hide comment
@bdetweiler

bdetweiler Mar 12, 2013

Contributor

No, and it's not something I tweak in Vim ever. I set it once in my .vimrc and it's done. The same thought would apply here, where the user sets it in their .vrapperrc and they get the expected results. I was working on making it sync the settings, but haven't been able to figure that out yet. I'm kind of hating the Eclipse API documentation, or lack thereof rather. If you know of any good resources on the subject, I could sure use it. As for the :retab command, I mostly use it for replacing tab characters with spaces. It's super useful for that purpose.

Contributor

bdetweiler replied Mar 12, 2013

No, and it's not something I tweak in Vim ever. I set it once in my .vimrc and it's done. The same thought would apply here, where the user sets it in their .vrapperrc and they get the expected results. I was working on making it sync the settings, but haven't been able to figure that out yet. I'm kind of hating the Eclipse API documentation, or lack thereof rather. If you know of any good resources on the subject, I could sure use it. As for the :retab command, I mostly use it for replacing tab characters with spaces. It's super useful for that purpose.

Please sign in to comment.