X-Git-Url: http://git.nikiroo.be/?a=blobdiff_plain;f=src%2Fjexer%2Fteditor%2FDocument.java;h=2abfef6635f3c1877fc733ee36ea8c67d01160b6;hb=12b90437b5f22c2ae6e9b9b14c3b62b60f6143e5;hp=cae6f47106db0ae9a3656dd1d318f7916771be12;hpb=12b55d76e3473407bf37fca3667860240cb8f3be;p=fanfix.git diff --git a/src/jexer/teditor/Document.java b/src/jexer/teditor/Document.java index cae6f47..2abfef6 100644 --- a/src/jexer/teditor/Document.java +++ b/src/jexer/teditor/Document.java @@ -3,7 +3,7 @@ * * 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"), @@ -28,14 +28,23 @@ */ package jexer.teditor; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStreamWriter; import java.util.ArrayList; import java.util.List; +import jexer.bits.CellAttributes; + /** * A Document represents a text file, as a collection of lines. */ public class Document { + // ------------------------------------------------------------------------ + // Variables -------------------------------------------------------------- + // ------------------------------------------------------------------------ + /** * The list of lines. */ @@ -52,6 +61,47 @@ public class Document { */ private boolean overwrite = false; + /** + * If true, the document has been edited. + */ + private boolean dirty = false; + + /** + * The default color for the TEditor class. + */ + private CellAttributes defaultColor = null; + + /** + * The text highlighter to use. + */ + private Highlighter highlighter = new Highlighter(); + + // ------------------------------------------------------------------------ + // 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; + + // 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)); + } + } + + // ------------------------------------------------------------------------ + // Document --------------------------------------------------------------- + // ------------------------------------------------------------------------ + /** * Get the overwrite flag. * @@ -61,6 +111,41 @@ public class Document { return overwrite; } + /** + * Get the dirty value. + * + * @return true if the buffer is dirty + */ + public boolean isDirty() { + return dirty; + } + + /** + * Save contents to file. + * + * @param filename file to save to + * @throws IOException if a java.io operation throws + */ + public void saveToFilename(final String filename) throws IOException { + OutputStreamWriter output = null; + try { + output = new OutputStreamWriter(new FileOutputStream(filename), + "UTF-8"); + + for (Line line: lines) { + output.write(line.getRawString()); + output.write("\n"); + } + + dirty = false; + } + finally { + if (output != null) { + output.close(); + } + } + } + /** * Set the overwrite flag. * @@ -81,6 +166,15 @@ public class Document { return lineNumber; } + /** + * Get the current editing line. + * + * @return the line + */ + public Line getCurrentLine() { + return lines.get(lineNumber); + } + /** * Get a specific line by number. * @@ -100,19 +194,60 @@ public class Document { */ public void setLineNumber(final int n) { if ((n < 0) || (n > lines.size())) { - throw new IndexOutOfBoundsException("Line size is " + lines.size() + - ", requested index " + n); + throw new IndexOutOfBoundsException("Lines array size is " + + lines.size() + ", requested index " + n); } lineNumber = n; } + /** + * Get the current cursor position of the editing line. + * + * @return the cursor position + */ + public int getCursor() { + return lines.get(lineNumber).getCursor(); + } + + /** + * Get the character at the current cursor position in the text. + * + * @return the character, or -1 if the cursor is at the end of the line + */ + public int getChar() { + return lines.get(lineNumber).getChar(); + } + + /** + * Set the current cursor position of the editing line. 0-based. + * + * @param cursor the new cursor position + */ + public void setCursor(final int cursor) { + if (cursor >= lines.get(lineNumber).getDisplayLength()) { + lines.get(lineNumber).end(); + } else { + lines.get(lineNumber).setCursor(cursor); + } + } + /** * Increment the line number by one. If at the last line, do nothing. + * + * @return true if the editing line changed */ - public void down() { + public boolean down() { if (lineNumber < lines.size() - 1) { + int x = lines.get(lineNumber).getCursor(); lineNumber++; + if (x >= lines.get(lineNumber).getDisplayLength()) { + lines.get(lineNumber).end(); + } else { + lines.get(lineNumber).setCursor(x); + } + return true; } + return false; } /** @@ -120,21 +255,42 @@ public class Document { * increment only to the last line. * * @param n the number of lines to increment by + * @return true if the editing line changed */ - public void down(final int n) { - lineNumber += n; - if (lineNumber > lines.size() - 1) { - lineNumber = lines.size() - 1; + public boolean down(final int n) { + if (lineNumber < lines.size() - 1) { + int x = lines.get(lineNumber).getCursor(); + lineNumber += n; + if (lineNumber > lines.size() - 1) { + lineNumber = lines.size() - 1; + } + if (x >= lines.get(lineNumber).getDisplayLength()) { + lines.get(lineNumber).end(); + } else { + lines.get(lineNumber).setCursor(x); + } + return true; } + return false; } /** * Decrement the line number by one. If at the first line, do nothing. + * + * @return true if the editing line changed */ - public void up() { + public boolean up() { if (lineNumber > 0) { + int x = lines.get(lineNumber).getCursor(); lineNumber--; + if (x >= lines.get(lineNumber).getDisplayLength()) { + lines.get(lineNumber).end(); + } else { + lines.get(lineNumber).setCursor(x); + } + return true; } + return false; } /** @@ -142,54 +298,286 @@ public class Document { * decrement only to the first line. * * @param n the number of lines to decrement by + * @return true if the editing line changed + */ + public boolean up(final int n) { + if (lineNumber > 0) { + int x = lines.get(lineNumber).getCursor(); + lineNumber -= n; + if (lineNumber < 0) { + lineNumber = 0; + } + if (x >= lines.get(lineNumber).getDisplayLength()) { + lines.get(lineNumber).end(); + } else { + lines.get(lineNumber).setCursor(x); + } + return true; + } + return false; + } + + /** + * Decrement the cursor by one. If at the first column on the first + * line, do nothing. + * + * @return true if the cursor position changed + */ + public boolean left() { + if (!lines.get(lineNumber).left()) { + // We are on the leftmost column, wrap + if (up()) { + end(); + } else { + return false; + } + } + return true; + } + + /** + * Increment the cursor by one. If at the last column on the last line, + * do nothing. + * + * @return true if the cursor position changed */ - public void up(final int n) { - lineNumber -= n; - if (lineNumber < 0) { - lineNumber = 0; + public boolean right() { + if (!lines.get(lineNumber).right()) { + // We are on the rightmost column, wrap + if (down()) { + home(); + } else { + return false; + } + } + 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.isSpace((char) getChar()) + ) { + left(); + } + + // int line = lineNumber; + while ((getChar() == -1) + || (getRawLine().length() == 0) + || Character.isSpace((char) getChar()) + ) { + if (left() == false) { + return; + } + } + + + assert (getChar() != -1); + + if (!Character.isSpace((char) getChar()) + && (getRawLine().length() > 0) + ) { + // Advance until at the beginning of the document or a whitespace + // is encountered. + while (!Character.isSpace((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; } /** - * Decrement the cursor by one. If at the first column, do nothing. + * Go to the beginning of the next word. */ - public void left() { - lines.get(lineNumber).left(); + 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.isSpace((char) getChar())) { + // We found a character immediately after the line. + // Done! + return; + } + // Still looking... + line = lineNumber; + } + } + assert (getChar() != -1); + + if (!Character.isSpace((char) getChar()) + && (getRawLine().length() > 0) + ) { + // Advance until at the end of the document or a whitespace is + // encountered. + while (!Character.isSpace((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.isSpace((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.isSpace((char) getChar())) { + // We found a character immediately after the line. + // Done! + return; + } + // Still looking... + line = lineNumber; + } + } + assert (getChar() != -1); + + if (Character.isSpace((char) getChar())) { + // Advance until at the end of the document or a non-whitespace + // is encountered. + while (Character.isSpace((char) getChar())) { + if (right() == false) { + // End of document, bail out. + return; + } + } + return; + } + + // We wrapped the line to get here. + return; } /** - * Increment the cursor by one. If at the last column, do nothing. + * Get the raw string that matches this line. + * + * @return the string */ - public void right() { - lines.get(lineNumber).right(); + public String getRawLine() { + return lines.get(lineNumber).getRawString(); } /** * Go to the first column of this line. + * + * @return true if the cursor position changed */ - public void home() { - lines.get(lineNumber).home(); + public boolean home() { + return lines.get(lineNumber).home(); } /** * Go to the last column of this line. + * + * @return true if the cursor position changed */ - public void end() { - lines.get(lineNumber).end(); + public boolean end() { + return lines.get(lineNumber).end(); } /** * Delete the character under the cursor. */ public void del() { - lines.get(lineNumber).del(); + dirty = true; + int cursor = lines.get(lineNumber).getCursor(); + if (cursor < lines.get(lineNumber).getDisplayLength() - 1) { + lines.get(lineNumber).del(); + } else if (lineNumber < lines.size() - 2) { + // Join two lines + StringBuilder newLine = new StringBuilder(lines. + get(lineNumber).getRawString()); + newLine.append(lines.get(lineNumber + 1).getRawString()); + lines.set(lineNumber, new Line(newLine.toString(), + defaultColor, highlighter)); + lines.get(lineNumber).setCursor(cursor); + lines.remove(lineNumber + 1); + } } /** * Delete the character immediately preceeding the cursor. */ public void backspace() { - lines.get(lineNumber).backspace(); + dirty = true; + int cursor = lines.get(lineNumber).getCursor(); + if (cursor > 0) { + lines.get(lineNumber).backspace(); + } else if (lineNumber > 0) { + // Join two lines + lineNumber--; + String firstLine = lines.get(lineNumber).getRawString(); + if (firstLine.length() > 0) { + // Backspacing combining two lines + StringBuilder newLine = new StringBuilder(firstLine); + newLine.append(lines.get(lineNumber + 1).getRawString()); + lines.set(lineNumber, new Line(newLine.toString(), + defaultColor, highlighter)); + lines.get(lineNumber).setCursor(firstLine.length()); + lines.remove(lineNumber + 1); + } else { + // Backspacing an empty line + lines.remove(lineNumber); + lines.get(lineNumber).setCursor(0); + } + } + } + + /** + * Split the current line into two, like pressing the enter key. + */ + public void enter() { + dirty = true; + int cursor = lines.get(lineNumber).getRawCursor(); + String original = lines.get(lineNumber).getRawString(); + String firstLine = original.substring(0, cursor); + String secondLine = original.substring(cursor); + lines.add(lineNumber + 1, new Line(secondLine, defaultColor, + highlighter)); + lines.set(lineNumber, new Line(firstLine, defaultColor, highlighter)); + lineNumber++; + lines.get(lineNumber).home(); } /** @@ -198,8 +586,13 @@ public class Document { * * @param ch the character to replace or insert */ - public void addChar(final char ch) { - lines.get(lineNumber).addChar(ch); + public void addChar(final int ch) { + dirty = true; + if (overwrite) { + lines.get(lineNumber).replaceChar(ch); + } else { + lines.get(lineNumber).addChar(ch); + } } /** @@ -236,15 +629,12 @@ public class Document { } /** - * Construct a new Document from an existing text string. + * Get the current line length. * - * @param str the text string + * @return the number of cells needed to display the current line */ - public Document(final String str) { - String [] rawLines = str.split("\n"); - for (int i = 0; i < rawLines.length; i++) { - lines.add(new Line(rawLines[i])); - } + public int getLineLength() { + return lines.get(lineNumber).getDisplayLength(); } }