From df602ccf5e32585c26dc618dd3b4a759b6820943 Mon Sep 17 00:00:00 2001 From: Kevin Lamonte Date: Mon, 14 Aug 2017 21:26:58 -0400 Subject: [PATCH] TEditor working --- docs/TODO.md | 11 +--- docs/worklog.md | 11 +++- src/jexer/TEditorWidget.java | 38 +++++++---- src/jexer/TEditorWindow.java | 98 ++++++++++++++++++++-------- src/jexer/TWindow.java | 1 - src/jexer/demos/DemoApplication.java | 16 +---- src/jexer/demos/DemoMainWindow.java | 33 ++++++++++ src/jexer/teditor/Document.java | 72 ++++++++++++++++++-- 8 files changed, 206 insertions(+), 74 deletions(-) diff --git a/docs/TODO.md b/docs/TODO.md index 20ce1fe6..95e1add9 100644 --- a/docs/TODO.md +++ b/docs/TODO.md @@ -8,21 +8,12 @@ Roadmap BUG: TTreeView.reflow() doesn't keep the vertical dot within the scrollbar. -0.0.5 - -- TEditor - - Document - - Filename - - Pick appropriate Highlighter: plain, Java, XML, ... - - TEditorWidget: - - Cut and Paste - - TTextArea extends TScrollableWidget - 0.0.6 - TEditor - True tokenization and syntax highlighting: Java, C, Clojure, XML - Tab character support + - Cut and Paste - Finish up multiscreen support: - cmAbort to cmScreenDisconnected diff --git a/docs/worklog.md b/docs/worklog.md index 57040409..6dde360a 100644 --- a/docs/worklog.md +++ b/docs/worklog.md @@ -1,6 +1,16 @@ Jexer Work Log ============== +August 14, 2017 + +TEditor is basically done. Mouse movement, keyboard movement, +backspace / delete / enter / etc. are all in. Things are starting to +look pretty good. + +I'm going to prep for a final cut and release tag tomorrow or the next +evening. I need to take a break and get some meatspace life dealt +with. + August 12, 2017 TEditor is stubbed in about 50% complete now. I have a Highlighter @@ -180,4 +190,3 @@ checklists. I think I will see if jexer is available at SourceForge, and if so grab it. Perhaps I can put together some good Turbo Vision resources too. At the very least direct people to the Borland-derived C++ releases and Free Vision. - diff --git a/src/jexer/TEditorWidget.java b/src/jexer/TEditorWidget.java index cf4d887f..8d2a0104 100644 --- a/src/jexer/TEditorWidget.java +++ b/src/jexer/TEditorWidget.java @@ -112,7 +112,6 @@ public final class TEditorWidget extends TWidget { } } } - } /** @@ -141,14 +140,14 @@ public final class TEditorWidget extends TWidget { // Set the row and column int newLine = topLine + mouse.getY(); int newX = leftColumn + mouse.getX(); - if (newLine > document.getLineCount()) { + if (newLine > document.getLineCount() - 1) { // Go to the end document.setLineNumber(document.getLineCount() - 1); document.end(); - if (document.getLineCount() > getHeight()) { - setCursorY(getHeight() - 1); + if (newLine > document.getLineCount() - 1) { + setCursorY(document.getLineCount() - 1 - topLine); } else { - setCursorY(document.getLineCount() - 1); + setCursorY(mouse.getY()); } alignCursor(); return; @@ -160,6 +159,7 @@ public final class TEditorWidget extends TWidget { document.end(); alignCursor(); } else { + document.setCursor(newX); setCursorX(mouse.getX()); } return; @@ -206,6 +206,7 @@ public final class TEditorWidget extends TWidget { */ private void alignDocument(final boolean topLineIsTop) { int line = document.getLineNumber(); + int cursor = document.getCursor(); if ((line < topLine) || (line > topLine + getHeight() - 1)) { // Need to move document to ensure it fits view. @@ -214,6 +215,9 @@ public final class TEditorWidget extends TWidget { } else { document.setLineNumber(topLine + (getHeight() - 1)); } + if (cursor < document.getCurrentLine().getDisplayLength()) { + document.setCursor(cursor); + } } /* @@ -244,7 +248,8 @@ public final class TEditorWidget extends TWidget { /* System.err.println("document cursor " + document.getCursor() + " leftColumn " + leftColumn); - */ + */ + setCursorX(document.getCursor() - leftColumn); } @@ -257,13 +262,11 @@ public final class TEditorWidget extends TWidget { @Override public void onKeypress(final TKeypressEvent keypress) { if (keypress.equals(kbLeft)) { - if (document.left()) { - alignCursor(); - } + document.left(); + alignTopLine(false); } else if (keypress.equals(kbRight)) { - if (document.right()) { - alignCursor(); - } + document.right(); + alignTopLine(true); } else if (keypress.equals(kbUp)) { document.up(); alignTopLine(false); @@ -302,14 +305,21 @@ public final class TEditorWidget extends TWidget { } else if (keypress.equals(kbIns)) { document.setOverwrite(!document.getOverwrite()); } else if (keypress.equals(kbDel)) { - // TODO: join lines document.del(); alignCursor(); } else if (keypress.equals(kbBackspace)) { document.backspace(); + alignTopLine(false); + } else if (keypress.equals(kbTab)) { + // TODO: tab character. For now just add spaces until we hit + // modulo 8. + for (int i = document.getCursor(); (i + 1) % 8 != 0; i++) { + document.addChar(' '); + } alignCursor(); } else if (keypress.equals(kbEnter)) { - // TODO: split lines + document.enter(); + alignTopLine(true); } else if (!keypress.getKey().isFnKey() && !keypress.getKey().isAlt() && !keypress.getKey().isCtrl() diff --git a/src/jexer/TEditorWindow.java b/src/jexer/TEditorWindow.java index f96b177f..69e254b9 100644 --- a/src/jexer/TEditorWindow.java +++ b/src/jexer/TEditorWindow.java @@ -114,6 +114,25 @@ public class TEditorWindow extends TScrollableWindow { setupAfterEditor(); } + /** + * Public constructor opens a file. + * + * @param parent the main application + * @param file the file to open + * @throws IOException if a java.io operation throws + */ + public TEditorWindow(final TApplication parent, + final File file) throws IOException { + + super(parent, file.getName(), 0, 0, parent.getScreen().getWidth(), + parent.getScreen().getHeight() - 2, RESIZABLE); + + filename = file.getName(); + String contents = readFileData(file); + editField = addEditor(contents, 0, 0, getWidth() - 2, getHeight() - 2); + setupAfterEditor(); + } + /** * Public constructor. * @@ -123,6 +142,39 @@ public class TEditorWindow extends TScrollableWindow { this(parent, "New Text Document"); } + /** + * Read file data into a string. + * + * @param file the file to open + * @return the file contents + * @throws IOException if a java.io operation throws + */ + private String readFileData(final File file) throws IOException { + StringBuilder fileContents = new StringBuilder(); + Scanner scanner = new Scanner(file); + String EOL = System.getProperty("line.separator"); + + try { + while (scanner.hasNextLine()) { + fileContents.append(scanner.nextLine() + EOL); + } + return fileContents.toString(); + } finally { + scanner.close(); + } + } + + /** + * Read file data into a string. + * + * @param filename the file to open + * @return the file contents + * @throws IOException if a java.io operation throws + */ + private String readFileData(final String filename) throws IOException { + return readFileData(new File(filename)); + } + /** * Draw the window. */ @@ -169,17 +221,18 @@ public class TEditorWindow extends TScrollableWindow { */ @Override public void onMouseDown(final TMouseEvent mouse) { + // Use TWidget's code to pass the event to the children. + super.onMouseDown(mouse); + if (mouseOnEditor(mouse)) { - editField.onMouseDown(mouse); + // The editor might have changed, update the scollbars. setBottomValue(editField.getMaximumRowNumber()); setVerticalValue(editField.getEditingRowNumber()); setRightValue(editField.getMaximumColumnNumber()); setHorizontalValue(editField.getEditingColumnNumber()); } else { - // Let the scrollbars get the event - super.onMouseDown(mouse); - if (mouse.isMouseWheelUp() || mouse.isMouseWheelDown()) { + // Vertical scrollbar actions editField.setEditingRowNumber(getVerticalValue()); } // TODO: horizontal scrolling @@ -220,30 +273,18 @@ public class TEditorWindow extends TScrollableWindow { if (command.equals(cmOpen)) { try { String filename = fileOpenBox("."); - if (filename != null) { - try { - File file = new File(filename); - StringBuilder fileContents = new StringBuilder(); - Scanner scanner = new Scanner(file); - String EOL = System.getProperty("line.separator"); - - try { - while (scanner.hasNextLine()) { - fileContents.append(scanner.nextLine() + EOL); - } - new TEditorWindow(getApplication(), filename, - fileContents.toString()); - } finally { - scanner.close(); - } - } catch (IOException e) { - // TODO: make this a message box - e.printStackTrace(); - } - } + if (filename != null) { + try { + String contents = readFileData(filename); + new TEditorWindow(getApplication(), filename, contents); + } catch (IOException e) { + messageBox("Error", "Error reading file: " + + e.getMessage()); + } + } } catch (IOException e) { - // TODO: make this a message box - e.printStackTrace(); + messageBox("Error", "Error opening file dialog: " + + e.getMessage()); } return; } @@ -253,8 +294,7 @@ public class TEditorWindow extends TScrollableWindow { try { editField.saveToFilename(filename); } catch (IOException e) { - // TODO: make this a message box - e.printStackTrace(); + messageBox("Error", "Error saving file: " + e.getMessage()); } } return; diff --git a/src/jexer/TWindow.java b/src/jexer/TWindow.java index 96213056..406eb789 100644 --- a/src/jexer/TWindow.java +++ b/src/jexer/TWindow.java @@ -1384,5 +1384,4 @@ public class TWindow extends TWidget { getScreen().hLineXY(x, y, n, ch, attr); } - } diff --git a/src/jexer/demos/DemoApplication.java b/src/jexer/demos/DemoApplication.java index 4d8671f9..5036dd01 100644 --- a/src/jexer/demos/DemoApplication.java +++ b/src/jexer/demos/DemoApplication.java @@ -29,7 +29,6 @@ package jexer.demos; import java.io.*; -import java.util.*; import jexer.*; import jexer.event.*; @@ -187,20 +186,7 @@ public class DemoApplication extends TApplication { String filename = fileOpenBox("."); if (filename != null) { try { - File file = new File(filename); - StringBuilder fileContents = new StringBuilder(); - Scanner scanner = new Scanner(file); - String EOL = System.getProperty("line.separator"); - - try { - while (scanner.hasNextLine()) { - fileContents.append(scanner.nextLine() + EOL); - } - new DemoTextWindow(this, filename, - fileContents.toString()); - } finally { - scanner.close(); - } + new TEditorWindow(this, new File(filename)); } catch (IOException e) { e.printStackTrace(); } diff --git a/src/jexer/demos/DemoMainWindow.java b/src/jexer/demos/DemoMainWindow.java index 8840605e..85d03414 100644 --- a/src/jexer/demos/DemoMainWindow.java +++ b/src/jexer/demos/DemoMainWindow.java @@ -28,7 +28,10 @@ */ package jexer.demos; +import java.io.*; + import jexer.*; +import jexer.event.*; import static jexer.TCommand.*; import static jexer.TKeypress.*; @@ -206,4 +209,34 @@ public class DemoMainWindow extends TWindow { statusBar.addShortcutKeypress(kbF3, cmOpen, "Open"); statusBar.addShortcutKeypress(kbF10, cmExit, "Exit"); } + + /** + * Method that subclasses can override to handle posted command events. + * + * @param command command event + */ + @Override + public void onCommand(final TCommandEvent command) { + if (command.equals(cmOpen)) { + try { + String filename = fileOpenBox("."); + if (filename != null) { + try { + new TEditorWindow(getApplication(), new File(filename)); + } catch (IOException e) { + messageBox("Error", "Error reading file: " + + e.getMessage()); + } + } + } catch (IOException e) { + messageBox("Error", "Error opening file dialog: " + + e.getMessage()); + } + return; + } + + // Didn't handle it, let children get it instead + super.onCommand(command); + } + } diff --git a/src/jexer/teditor/Document.java b/src/jexer/teditor/Document.java index 42082e55..c8ab7460 100644 --- a/src/jexer/teditor/Document.java +++ b/src/jexer/teditor/Document.java @@ -298,7 +298,15 @@ public class Document { * @return true if the cursor position changed */ public boolean left() { - return lines.get(lineNumber).left(); + if (!lines.get(lineNumber).left()) { + // We are on the leftmost column, wrap + if (up()) { + end(); + } else { + return false; + } + } + return true; } /** @@ -307,7 +315,15 @@ public class Document { * @return true if the cursor position changed */ public boolean right() { - return lines.get(lineNumber).right(); + if (!lines.get(lineNumber).right()) { + // We are on the rightmost column, wrap + if (down()) { + home(); + } else { + return false; + } + } + return true; } /** @@ -333,7 +349,19 @@ public class Document { */ public void del() { dirty = true; - lines.get(lineNumber).del(); + 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); + } } /** @@ -341,7 +369,43 @@ public class Document { */ public void backspace() { dirty = true; - lines.get(lineNumber).backspace(); + 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).getCursor(); + 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(); } /** -- 2.27.0