*
* The MIT License (MIT)
*
- * Copyright (C) 2017 Kevin Lamonte
+ * Copyright (C) 2019 Kevin Lamonte
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
*/
public class Document {
+ // ------------------------------------------------------------------------
+ // Variables --------------------------------------------------------------
+ // ------------------------------------------------------------------------
+
/**
* The list of lines.
*/
*/
private Highlighter highlighter = new Highlighter();
+ /**
+ * The tab stop size.
+ */
+ private int tabSize = 8;
+
+ /**
+ * If true, backspace at an indent level goes back a full indent level.
+ * If false, backspace always goes back one column.
+ */
+ private boolean backspaceUnindents = false;
+
+ /**
+ * If true, save files with tab characters. If false, convert tabs to
+ * spaces when saving files.
+ */
+ private boolean saveWithTabs = false;
+
+ // ------------------------------------------------------------------------
+ // Constructors -----------------------------------------------------------
+ // ------------------------------------------------------------------------
+
+ /**
+ * Construct a new Document from an existing text string.
+ *
+ * @param str the text string
+ * @param defaultColor the color for unhighlighted text
+ */
+ public Document(final String str, final CellAttributes defaultColor) {
+ this.defaultColor = defaultColor;
+
+ // Set colors to resemble the Borland IDE colors, but for Java
+ // language keywords.
+ highlighter.setJavaColors();
+
+ String [] rawLines = str.split("\n");
+ for (int i = 0; i < rawLines.length; i++) {
+ lines.add(new Line(rawLines[i], this.defaultColor, highlighter));
+ }
+ }
+
+ /**
+ * Private constructor used by dup().
+ */
+ private Document() {
+ // NOP
+ }
+
+ // ------------------------------------------------------------------------
+ // Document ---------------------------------------------------------------
+ // ------------------------------------------------------------------------
+
+ /**
+ * Create a duplicate instance.
+ *
+ * @return duplicate intance
+ */
+ public Document dup() {
+ Document other = new Document();
+ for (Line line: lines) {
+ other.lines.add(line.dup());
+ }
+ other.lineNumber = lineNumber;
+ other.overwrite = overwrite;
+ other.dirty = dirty;
+ other.defaultColor = defaultColor;
+ other.highlighter.setTo(highlighter);
+ return other;
+ }
+
/**
* Get the overwrite flag.
*
* @return true if addChar() overwrites data, false if it inserts
*/
- public boolean getOverwrite() {
+ public boolean isOverwrite() {
return overwrite;
}
return dirty;
}
+ /**
+ * Unset the dirty flag.
+ */
+ public void setNotDirty() {
+ dirty = false;
+ }
+
/**
* Save contents to file.
*
"UTF-8");
for (Line line: lines) {
- output.write(line.getRawString());
+ if (saveWithTabs) {
+ output.write(convertSpacesToTabs(line.getRawString()));
+ } else {
+ output.write(line.getRawString());
+ }
output.write("\n");
}
}
/**
- * Set the current cursor position of the editing line. 0-based.
+ * Get the character at the current cursor position in the text.
*
- * @param cursor the new cursor position
+ * @return the character, or -1 if the cursor is at the end of the line
*/
- public void setCursor(final int cursor) {
- lines.get(lineNumber).setCursor(cursor);
+ public int getChar() {
+ return lines.get(lineNumber).getChar();
}
/**
- * Construct a new Document from an existing text string.
+ * Set the current cursor position of the editing line. 0-based.
*
- * @param str the text string
- * @param defaultColor the color for unhighlighted text
+ * @param cursor the new cursor position
*/
- public Document(final String str, final CellAttributes defaultColor) {
- this.defaultColor = defaultColor;
-
- // TODO: set different colors based on file extension
- highlighter.setJavaColors();
-
- String [] rawLines = str.split("\n");
- for (int i = 0; i < rawLines.length; i++) {
- lines.add(new Line(rawLines[i], this.defaultColor, highlighter));
+ public void setCursor(final int cursor) {
+ if (cursor >= lines.get(lineNumber).getDisplayLength()) {
+ lines.get(lineNumber).end();
+ } else {
+ lines.get(lineNumber).setCursor(cursor);
}
}
if (lineNumber < lines.size() - 1) {
int x = lines.get(lineNumber).getCursor();
lineNumber++;
- if (x > lines.get(lineNumber).getDisplayLength()) {
+ if (x >= lines.get(lineNumber).getDisplayLength()) {
lines.get(lineNumber).end();
} else {
lines.get(lineNumber).setCursor(x);
if (lineNumber > lines.size() - 1) {
lineNumber = lines.size() - 1;
}
- if (x > lines.get(lineNumber).getDisplayLength()) {
+ if (x >= lines.get(lineNumber).getDisplayLength()) {
lines.get(lineNumber).end();
} else {
lines.get(lineNumber).setCursor(x);
if (lineNumber > 0) {
int x = lines.get(lineNumber).getCursor();
lineNumber--;
- if (x > lines.get(lineNumber).getDisplayLength()) {
+ if (x >= lines.get(lineNumber).getDisplayLength()) {
lines.get(lineNumber).end();
} else {
lines.get(lineNumber).setCursor(x);
if (lineNumber < 0) {
lineNumber = 0;
}
- if (x > lines.get(lineNumber).getDisplayLength()) {
+ if (x >= lines.get(lineNumber).getDisplayLength()) {
lines.get(lineNumber).end();
} else {
lines.get(lineNumber).setCursor(x);
}
/**
- * Decrement the cursor by one. If at the first column, do nothing.
+ * Decrement the cursor by one. If at the first column on the first
+ * line, do nothing.
*
* @return true if the cursor position changed
*/
}
/**
- * Increment the cursor by one. If at the last column, do nothing.
+ * Increment the cursor by one. If at the last column on the last line,
+ * do nothing.
*
* @return true if the cursor position changed
*/
return true;
}
+ /**
+ * Go back to the beginning of this word if in the middle, or the
+ * beginning of the previous word.
+ */
+ public void backwardsWord() {
+
+ // If at the beginning of a word already, push past it.
+ if ((getChar() != -1)
+ && (getRawLine().length() > 0)
+ && !Character.isWhitespace((char) getChar())
+ ) {
+ left();
+ }
+
+ // int line = lineNumber;
+ while ((getChar() == -1)
+ || (getRawLine().length() == 0)
+ || Character.isWhitespace((char) getChar())
+ ) {
+ if (left() == false) {
+ return;
+ }
+ }
+
+
+ assert (getChar() != -1);
+
+ if (!Character.isWhitespace((char) getChar())
+ && (getRawLine().length() > 0)
+ ) {
+ // Advance until at the beginning of the document or a whitespace
+ // is encountered.
+ while (!Character.isWhitespace((char) getChar())) {
+ int line = lineNumber;
+ if (left() == false) {
+ // End of document, bail out.
+ return;
+ }
+ if (lineNumber != line) {
+ // We wrapped a line. Here that counts as whitespace.
+ right();
+ return;
+ }
+ }
+ }
+
+ // We went one past the word, push back to the first character of
+ // that word.
+ right();
+ return;
+ }
+
+ /**
+ * Go to the beginning of the next word.
+ */
+ public void forwardsWord() {
+ int line = lineNumber;
+ while ((getChar() == -1)
+ || (getRawLine().length() == 0)
+ ) {
+ if (right() == false) {
+ return;
+ }
+ if (lineNumber != line) {
+ // We wrapped a line. Here that counts as whitespace.
+ if (!Character.isWhitespace((char) getChar())) {
+ // We found a character immediately after the line.
+ // Done!
+ return;
+ }
+ // Still looking...
+ line = lineNumber;
+ }
+ }
+ assert (getChar() != -1);
+
+ if (!Character.isWhitespace((char) getChar())
+ && (getRawLine().length() > 0)
+ ) {
+ // Advance until at the end of the document or a whitespace is
+ // encountered.
+ while (!Character.isWhitespace((char) getChar())) {
+ line = lineNumber;
+ if (right() == false) {
+ // End of document, bail out.
+ return;
+ }
+ if (lineNumber != line) {
+ // We wrapped a line. Here that counts as whitespace.
+ if (!Character.isWhitespace((char) getChar())
+ && (getRawLine().length() > 0)
+ ) {
+ // We found a character immediately after the line.
+ // Done!
+ return;
+ }
+ break;
+ }
+ }
+ }
+
+ while ((getChar() == -1)
+ || (getRawLine().length() == 0)
+ ) {
+ if (right() == false) {
+ return;
+ }
+ if (lineNumber != line) {
+ // We wrapped a line. Here that counts as whitespace.
+ if (!Character.isWhitespace((char) getChar())) {
+ // We found a character immediately after the line.
+ // Done!
+ return;
+ }
+ // Still looking...
+ line = lineNumber;
+ }
+ }
+ assert (getChar() != -1);
+
+ if (Character.isWhitespace((char) getChar())) {
+ // Advance until at the end of the document or a non-whitespace
+ // is encountered.
+ while (Character.isWhitespace((char) getChar())) {
+ if (right() == false) {
+ // End of document, bail out.
+ return;
+ }
+ }
+ return;
+ }
+
+ // We wrapped the line to get here.
+ return;
+ }
+
+ /**
+ * Get the raw string that matches this line.
+ *
+ * @return the string
+ */
+ public String getRawLine() {
+ return lines.get(lineNumber).getRawString();
+ }
+
/**
* Go to the first column of this line.
*
dirty = true;
int cursor = lines.get(lineNumber).getCursor();
if (cursor > 0) {
- lines.get(lineNumber).backspace();
+ lines.get(lineNumber).backspace(tabSize, backspaceUnindents);
} else if (lineNumber > 0) {
// Join two lines
lineNumber--;
*/
public void enter() {
dirty = true;
- int cursor = lines.get(lineNumber).getCursor();
+ int cursor = lines.get(lineNumber).getRawCursor();
String original = lines.get(lineNumber).getRawString();
String firstLine = original.substring(0, cursor);
String secondLine = original.substring(cursor);
*
* @param ch the character to replace or insert
*/
- public void addChar(final char ch) {
+ public void addChar(final int ch) {
dirty = true;
if (overwrite) {
lines.get(lineNumber).replaceChar(ch);
}
}
+ /**
+ * Get the tab stop size.
+ *
+ * @return the tab stop size
+ */
+ public int getTabSize() {
+ return tabSize;
+ }
+
+ /**
+ * Set the tab stop size.
+ *
+ * @param tabSize the new tab stop size
+ */
+ public void setTabSize(final int tabSize) {
+ this.tabSize = tabSize;
+ }
+
+ /**
+ * Set the backspace unindent option.
+ *
+ * @param backspaceUnindents If true, backspace at an indent level goes
+ * back a full indent level. If false, backspace always goes back one
+ * column.
+ */
+ public void setBackspaceUnindents(final boolean backspaceUnindents) {
+ this.backspaceUnindents = backspaceUnindents;
+ }
+
+ /**
+ * Set the save with tabs option.
+ *
+ * @param saveWithTabs If true, save files with tab characters. If
+ * false, convert tabs to spaces when saving files.
+ */
+ public void setSaveWithTabs(final boolean saveWithTabs) {
+ this.saveWithTabs = saveWithTabs;
+ }
+
+ /**
+ * Handle the tab character.
+ */
+ public void tab() {
+ if (overwrite) {
+ del();
+ }
+ lines.get(lineNumber).tab(tabSize);
+ }
+
+ /**
+ * Handle the backtab (shift-tab) character.
+ */
+ public void backTab() {
+ lines.get(lineNumber).backTab(tabSize);
+ }
+
/**
* Get a (shallow) copy of the list of lines.
*
return n;
}
+ /**
+ * Get the current line length.
+ *
+ * @return the number of cells needed to display the current line
+ */
+ public int getLineLength() {
+ return lines.get(lineNumber).getDisplayLength();
+ }
+
+ /**
+ * Get the entire contents of the document as one string.
+ *
+ * @return the document contents
+ */
+ public String getText() {
+ StringBuilder sb = new StringBuilder();
+ for (Line line: getLines()) {
+ sb.append(line.getRawString());
+ sb.append("\n");
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Trim trailing whitespace from lines and trailing empty
+ * lines from the document.
+ */
+ public void cleanWhitespace() {
+ for (Line line: getLines()) {
+ line.trimRight();
+ }
+ if (lines.size() == 0) {
+ return;
+ }
+ while (lines.get(lines.size() - 1).length() == 0) {
+ lines.remove(lines.size() - 1);
+ }
+ if (lineNumber > lines.size() - 1) {
+ lineNumber = lines.size() - 1;
+ }
+ }
+
+ /**
+ * Set keyword highlighting.
+ *
+ * @param enabled if true, enable keyword highlighting
+ */
+ public void setHighlighting(final boolean enabled) {
+ highlighter.setEnabled(enabled);
+ for (Line line: getLines()) {
+ line.scanLine();
+ }
+ }
+
+ /**
+ * Convert a string with leading spaces to a mix of tabs and spaces.
+ *
+ * @param string the string to convert
+ */
+ private String convertSpacesToTabs(final String string) {
+ if (string.length() == 0) {
+ return string;
+ }
+
+ int start = 0;
+ while (string.charAt(start) == ' ') {
+ start++;
+ }
+ int tabCount = start / 8;
+ if (tabCount == 0) {
+ return string;
+ }
+
+ StringBuilder sb = new StringBuilder(string.length());
+
+ for (int i = 0; i < tabCount; i++) {
+ sb.append('\t');
+ }
+ sb.append(string.substring(tabCount * 8));
+ return sb.toString();
+ }
+
}