Package org.jnode.command.system

Source Code of org.jnode.command.system.PageCommand$ScreenBuffer$ScreenLine

/*
* $Id$
*
* Copyright (C) 2003-2014 JNode.org
*
* This library is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* This library is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
* License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library; If not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package org.jnode.command.system;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.io.PipedReader;
import java.io.PipedWriter;
import java.io.PrintWriter;
import java.io.Reader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
import javax.naming.NameNotFoundException;
import org.jnode.driver.console.ConsoleManager;
import org.jnode.driver.console.TextConsole;
import org.jnode.driver.console.textscreen.TextScreenConsoleManager;
import org.jnode.driver.input.KeyboardEvent;
import org.jnode.driver.input.KeyboardListener;
import org.jnode.naming.InitialNaming;
import org.jnode.shell.AbstractCommand;
import org.jnode.shell.ShellManager;
import org.jnode.shell.syntax.Argument;
import org.jnode.shell.syntax.FileArgument;

/**
* This command is a simple analog of the UNIX/Linux 'more' and 'less' commands.
*
* @author crawley@jnode.org
*/
public class PageCommand extends AbstractCommand implements KeyboardListener {
   
    private static final String help_file = "the file to be paged";
    private static final String help_super = "output a file to the console one 'page' at a time";
    private static final String str_no_pipe = "Paging piped from the console is not supported";
    private static final String str_no_prev = "No previous search";
    private static final String str_inv_regex = "Invalid regex";
    private static final String str_not_found = "Not found";
    private static final String str_any_key = "Hit any key to continue";
    private static final String help_fw_page    = "Move forwards 1 page:         SPACE, f";
    private static final String help_bw_page    = "Move backwards 1 page:        b";
    private static final String help_fw_dline   = "Move forwards 1 data line:    ENTER";
    private static final String help_bw_dline   = "Move backwards 1 data line:   k, y";
    private static final String help_fw_sline   = "Move forwards 1 screen line:  d";
    private static final String help_bw_sline   = "Move backwards 1 screen line: u";
    private static final String help_go_start   = "Go to start of data:          <";
    private static final String help_go_end     = "Go to end of data:            >";
    private static final String help_fw_search1 = "Search forwards:              /regex";
    private static final String help_fw_search2 = "Repeat search forwards:       /";
    private static final String help_bw_search1 = "Search backwards:             ?regex";
    private static final String help_bw_search2 = "Repeat search backwards:      ?";
    private static final String help_help       = "Display this help screen:     h";
    private static final String help_quit       = "Quit:                         q, CTRL-D";
   
    private static boolean DEBUG = false;
    private static int LAST_SUBLINE = Integer.MAX_VALUE;
    private static int DEFAULT_COLOR = 0x07;
    private static int MATCH_COLOR = 0x04;
   
    private final FileArgument argFile;

    private PrintWriter err;
    private TextConsole console;
   
    private int pageHeight;
    private int pageWidth;
    private int pageSize;
    private int tabSize;
   
    // This is the line number of the top (data source) line displayed on the
    // screen page.
    private int topLineNo;
    private int topSublineNo;
   
   
    // This is the line number of the bottom (data source) line displayed on the
    // screen page.
    private int bottomLineNo;
    private int bottomSublineNo;
   
    // This pipe passes characters from the system thread that calls
    // our 'keyPressed' event method to the thread that runs the Page command.
    private PipedReader pr;
    private PipedWriter pw;

    private TextScreenConsoleManager manager;

    private LineStore lineStore;

    private Pattern regex;
    private Matcher matcher;

    private String prompt;

    private ScreenBuffer currentBuffer;
   
   
    public PageCommand() {
        super(help_super);
        argFile = new FileArgument("file", Argument.OPTIONAL, help_file);
        registerArguments(argFile);
    }

    /**
     * Classic java entry point
     */
    public static void main(String[] args) throws Exception {
        new PageCommand().execute(args);
    }

    /**
     * JNode command entry point.
     */
    @Override
    public void execute() throws Exception {
        err = getError().getPrintWriter();
        Reader r = null;
        boolean opened = false;
        try {
            if (argFile.isSet()) {
                r = new FileReader(argFile.getValue());
                opened = true;
            } else if (getInput().isTTY()) {
                // We cannot do this.  We need to use the console as the
                // source of command characters for the Page command.
                debugln(str_no_pipe);
                exit(1);
            } else {
                r = getInput().getReader();
            }
            setup();
            lineStore = new LineStore(r);
            pager();
           
        } catch (IOException ex) {
            debugln(ex.getMessage());
            exit(1);
        } finally {
            if (r != null && opened) {
                try {
                    r.close();
                } catch (IOException ex) {
                    // ignore
                }
            }
            tearDown();
        }
    }
   
    /**
     * Set up the pager's console and command pipe.
     */
    private void setup() throws NameNotFoundException, IOException {
        ShellManager sm = InitialNaming.lookup(ShellManager.NAME);
        manager = (TextScreenConsoleManager) sm.getCurrentShell().getConsole().getManager();
        console = manager.createConsole(
            "page",
            (ConsoleManager.CreateOptions.TEXT |
                ConsoleManager.CreateOptions.STACKED |
                ConsoleManager.CreateOptions.NO_LINE_EDITTING |
                ConsoleManager.CreateOptions.NO_SYSTEM_OUT_ERR));
        manager.focus(console);
       
        pageHeight = console.getDeviceHeight() - 1;
        pageWidth = console.getDeviceWidth();
        pageSize = pageHeight * pageWidth;
        tabSize = console.getTabSize();
       
        pw = new PipedWriter();
        pr = new PipedReader();
        pr.connect(pw);
       
        console.addKeyboardListener(this);
    }
   
    /**
     * Tear down the console and pipe.
     * @throws IOException
     */
    private void tearDown() throws IOException {
        if (manager != null && console != null) {
            manager.unregisterConsole(console);
        }
        if (pw != null) {
            pw.close();
        }
        if (pr != null) {
            pr.close();
        }
    }

    /**
     * Do the paging, reading commands from our private console input
     * pipe to figure out what to do next.
     *
     * @param r the source of data to be paged.
     * @throws IOException
     */
    private void pager() throws IOException {
        // Output first page.
        console.clear();
        bottomLineNo = -1;
        boolean exit = false;
        nextPage();
       
        // Process commands until we reach the EOF on the data source or
        // the command pipe.
        while (!exit) {
            prompt();
            int ch = pr.read();
            erasePrompt();
            switch (ch) {
                case -1:
                    exit = true;
                    break;
                case ' ':
                case 'f':
                    if (lineStore.isLastLineNo(bottomLineNo)) {
                        exit = true;
                    } else {
                        nextPage();
                    }
                    break;
                case 'b':
                    prevPage();
                    break;
                case 'k':
                case 'y':
                    prevLine();
                    break;
                case '\n':
                    if (lineStore.isLastLineNo(bottomLineNo)) {
                        exit = true;
                    } else {
                        nextLine();
                    }
                    break;
                case 'u':
                    prevScreenLine();
                    break;
                case 'd':
                    nextScreenLine();
                    break;
                case '<':
                    gotoPage(0);
                    break;
                case '>':
                    gotoLastPage();
                    break;
                case '/':
                    searchForwards();
                    break;
                case '?':
                    searchBackwards();
                    break;
                case '\004': // ^D
                case 'q':
                    exit = true;
                    break;
                case 'h':
                    help();
                default:
                    // ignore
            }
        }
    }
   
    private void searchBackwards() throws IOException {
        String input = readLine('?');
        int lineNo = bottomLineNo;
        if (input.length() <= 1) {
            if (regex == null) {
                setPrompt(str_no_prev);
                return;
            }
            lineNo--;
        } else {
            try {
                regex = Pattern.compile(input.substring(1));
            } catch (PatternSyntaxException ex) {
                setPrompt(str_inv_regex);
                return;
            }
            matcher = regex.matcher("");
        }
        while (true) {
            String line = lineStore.getLine(lineNo);
            if (line == null) {
                gotoPage(0);
                setPrompt(str_not_found);
                return;
            }
            matcher.reset(line);
            if (matcher.find()) {
                prepareReverse(lineNo, LAST_SUBLINE).output();
                return;
            }
            lineNo--;
        }
    }

    private void searchForwards() throws IOException {
        String input = readLine('/');
        int lineNo = topLineNo;
        if (input.length() <= 1) {
            if (regex == null) {
                setPrompt(str_no_prev);
                return;
            }
            lineNo++;
        } else {
            try {
                regex = Pattern.compile(input.substring(1));
            } catch (PatternSyntaxException ex) {
                setPrompt(str_inv_regex);
                return;
            }
            matcher = regex.matcher("");
        }
       
        while (true) {
            String line = lineStore.getLine(lineNo);
            if (line == null) {
                gotoLastPage();
                setPrompt(str_not_found);
                return;
            }
            matcher.reset(line);
            if (matcher.find()) {
                prepare(lineNo, 0).output();
                return;
            }
            lineNo++;
        }
    }

    /**
     * Read a line up to the next newline and return it as a String.  The
     * line is echoed at the prompt location.
     *
     * @param ch a preread character.
     * @return
     * @throws IOException
     */
    private String readLine(int ch) throws IOException {
        StringBuffer sb = new StringBuffer();
        String line;
        do {
            sb.append((char) ch);
            line = sb.toString();
            prompt(line);
            ch = pr.read();
        } while (ch != -1 && ch != '\n');
        return line;
    }
   
    private void help() throws IOException {
        String[] help = new String[] {
            help_fw_page, help_bw_page, help_fw_dline, help_bw_dline, help_fw_sline, help_bw_sline,
            help_go_start, help_go_end, help_fw_search1, help_fw_search2, help_bw_search1, help_bw_search2,
            help_help, help_quit
        };
        // Remember the 'current' buffer so that we can repaint it
        // when we are done.
        ScreenBuffer prevBuffer = this.currentBuffer;
       
        // Prepare and paint the help screen
        ScreenBuffer buffer = new ScreenBuffer(true);
        for (int i = 0; i < help.length; i++) {
            prepareLine(help[i], i, buffer);
        }
        buffer.adjust(0, 0);
        buffer.output();
        prompt(str_any_key);
       
        // Wait till the user is done, then repaint the previous screen.
        pr.read();
        prompt();
        prevBuffer.output();
    }

    private void prompt() {
        if (prompt == null) {
            prompt(lineStore.isLastLineNo(bottomLineNo) ? "(END)" :
                (topLineNo + ", " + bottomLineNo + ": "));
        } else {
            prompt(prompt);
            prompt = null;
        }
    }

    private void prompt(String text) {
        console.clearRow(this.pageHeight);
        console.setChar(0, this.pageHeight, text.toCharArray(), DEFAULT_COLOR);
        console.setCursor(0, this.pageHeight);
    }
   
    private void setPrompt(String prompt) {
        this.prompt = prompt;
    }

    private void erasePrompt() {
        console.clearRow(this.pageHeight);
    }

    /**
     * Page forward by one page.  The implementation strategy is to 'prepare'
     * lines one at a time until we have got (at least) a whole screen full.
     * The preparation process involves copying characters to 'buffer', padding
     * lines with spaces, and doing BS and TAB interpretation.  Then we output
     * the prepared characters to the screen.  The 'output' process deals with
     * scrolling if we prepared less than a full screen, or truncation if we
     * prepared more than a full screen.
     *
     * @throws IOException
     */
    private void nextPage() throws IOException {
        prepare(bottomLineNo + 1, 0).output();
    }
   
    /**
     * Page backwards by one page.  The implementation strategy is similar to
     * page forward except that we use prepareReverse which paints lines starting
     * with the nominated line and working backwards.
     *
     * @throws IOException
     */
    private void prevPage() throws IOException {
        if (topLineNo > 0) {
            prepareReverse(topLineNo - 1, LAST_SUBLINE).output();
        }
    }
   
    private void gotoPage(int firstLineNo) throws IOException {
        prepare(firstLineNo, 0).output();
    }
   
    private void gotoLastPage() throws IOException {
        prepareReverse(lineStore.getLastLineNo(), LAST_SUBLINE).output();
    }
   
    /**
     * Page forward by one line.  We use the 'prepare' and 'output'
     * strategy as described in {@link nextPage}.
     *
     * @throws IOException
     */
    private void nextLine() throws IOException {
        prepare(topLineNo + 1, 0).output();
    }

    /**
     * Page backwards by one line.  We use the 'prepare' and 'output'
     * strategy as described in {@link nextPage}.
     *
     * @throws IOException
     */
    private void prevLine() throws IOException {
        prepareReverse(bottomLineNo - 1, LAST_SUBLINE).output();
    }
   
    private void nextScreenLine() throws IOException {
        prepare(topLineNo, topSublineNo + 1).output();
    }
   
    private void prevScreenLine() throws IOException {
        if (bottomSublineNo == 0) {
            prepareReverse(bottomLineNo - 1, LAST_SUBLINE).output();
        } else {
            prepareReverse(bottomLineNo, bottomSublineNo + 1).output();
        }
    }

    /**
     * Prepare lines for output by painting them to our private buffer in the forward
     * direction starting at a given line number and (rendered) subline number.
     *
     * @param startLineNo
     */
    private ScreenBuffer prepare(int startLineNo, int startSublineNo) {
        while (true) {
            ScreenBuffer buffer = new ScreenBuffer(true);
            int lineNo = startLineNo;
            boolean more;
            do {
                String line = lineStore.getLine(lineNo);
                if (line == null) {
                    break;
                }
                more = prepareLine(line, lineNo, buffer);
                lineNo++;
            } while (more);
            if (buffer.adjust(startLineNo, startSublineNo) || startLineNo == 0) {
                return buffer;
            } else {
                startLineNo -= 1;
                startSublineNo = LAST_SUBLINE;
            }
        }
    }

    /**
     *
     * Prepare lines for output by painting them to our private buffer in the reverse
     * direction starting at a given end line number and (rendered) subline number.
     */
    private ScreenBuffer prepareReverse(int endLineNo, int endSublineNo) {
        ScreenBuffer buffer = new ScreenBuffer(false);
        int lineNo = endLineNo;
        String line = null;
        boolean more = true;
        while (more && lineNo >= 0) {
            line = lineStore.getLine(lineNo);
            more = prepareLine(line, lineNo, buffer);
            lineNo--;
        }
        if (buffer.adjust(endLineNo, endSublineNo)) {
            return buffer;
        } else {
            return prepare(0, 0);
        }
    }
   
    /**
     * Prepare lines for output by painting them to our private buffer
     * starting at the supplied bufferLineOffset
     *
     * @param line the line to be prepared
     * @param lineNo the line's line number
     * @param buffer the ScreenBuffer we are preparing
     */
    private boolean prepareLine(String line, int lineNo, ScreenBuffer buffer) {
        buffer.startLine(lineNo);
        int pos = 0;
        int len = line.length();
        int startMatchPos = len;
        int endMatchPos = len;
        if (matcher != null) {
            matcher.reset(line);
            if (matcher.find(0)) {
                startMatchPos = matcher.start();
                endMatchPos = matcher.end();
            }
        }
        for (int i = 0; i < len; i++) {
            if (i == startMatchPos) {
                buffer.setColor(MATCH_COLOR);
            } else if (i == endMatchPos) {
                if (matcher.find(i)) {
                    startMatchPos = matcher.start();
                    endMatchPos = matcher.end();
                    if (startMatchPos > i) {
                        buffer.setColor(DEFAULT_COLOR);
                    }
                } else {
                    buffer.setColor(DEFAULT_COLOR);
                }
            }
            // FIXME - support different renderings, including ones where
            // control characters are rendered as visible characters?
            char ch = line.charAt(i);
            switch (ch) {
                case '\n':
                    throw new AssertionError("no newlines expected");
                case '\r':
                    // ignore bare CRs.
                    break;
                case '\t':
                    int fill = tabSize - pos % pageWidth % tabSize;
                    for (int j = 0; j < fill; j++) {
                        buffer.putChar(' ');
                    }
                    pos += fill;
                    break;
                default:
                    if (ch >= ' ' && ch <= '\377' && ch != '\177') {
                        buffer.putChar(ch);
                    } else {
                        buffer.putChar('?');
                    }
                    pos++;
            }
        }
        buffer.setColor(DEFAULT_COLOR);
        buffer.endLine();
        return !buffer.isComplete();
    }

    /**
     * Capture keyboard input events and write all character data to the
     * private pipe.  The command thread will read them from
     * the other end as required.
     */
    @Override
    public void keyPressed(KeyboardEvent event) {
        if (!event.isConsumed()) {
            char ch = event.getKeyChar();
            if (ch != KeyboardEvent.NO_CHAR) {
                try {
                    pw.write(ch);
                    pw.flush();
                } catch (IOException ex) {
                    // ignore it
                }
                event.consume();
            }
        }
    }

    @Override
    public void keyReleased(KeyboardEvent event) {
        // ignore
    }
   
    private void debugln(String msg) {
        if (DEBUG) {
            err.println(msg);
        }
    }
   
    /**
     * This class provides an in-memory buffer for lines read from the data source
     * being paged.  In the future, this could be enhanced to cut down on memory
     * usage.  When paging a seekable Reader, it could use seek/tell to record file
     * offsets rather than actual file lines.  When paging a non-seekable Reader
     * (e.g. a pipe) it could store the lines read from the reader in a temporary file.
     *
     * @author crawley@jnode.org
     */
    private final class LineStore {
        private final BufferedReader reader;
        private boolean reachedEOF;
        private List<String> lines = new ArrayList<String>(100);
       
        private LineStore(Reader reader) {
            this.reader = new BufferedReader(reader);
        }
       
        /**
         * Get a line identified by line number.
         * @param lineNo the line number
         * @return the requested line, or <code>null</code> if the EOF
         * was reached before the requested line could be reached.
         */
        private String getLine(int lineNo) {
            if (lineNo < 0) {
                return null;
            } else if (lineNo < lines.size()) {
                return lines.get(lineNo);
            } else if (lineNo > lines.size()) {
                throw new AssertionError(
                        "Reading at wrong place (" + lineNo + " > " + lines.size() + ")");
            } else if (reachedEOF) {
                return null;
            } else {
                try {
                    String line = reader.readLine();
                    if (line == null) {
                        reachedEOF = true;
                    } else {
                        lines.add(line);
                    }
                    return line;
                } catch (IOException ex) {
                    ex.printStackTrace(err);
                    reachedEOF = true;
                    return null;
                }
            }
        }
       
        /**
         * Get the last line number for the data source.  This requires that
         * all lines of the data source are read up to the EOF position.
         * @return the last line number
         * @throws IOException
         */
        private int getLastLineNo() throws IOException {
            while (!reachedEOF) {
                String line = reader.readLine();
                if (line == null) {
                    reachedEOF = true;
                } else {
                    lines.add(line);
                }
            }
            return lines.size() - 1;
        }
       
        /**
         * Check if a given line number is known to be the last line
         * of the data source.  This method does not do any reading
         * ahead of the data source to find the last line.
         * @param lineNo the line number to test
         * @return Returns <code>true</code> if the given line number
         * is known to be the last line, and <code>false</code> if it is
         * not, or if we don't know.
         */
        private boolean isLastLineNo(int lineNo) {
            return reachedEOF && lineNo == lines.size() - 1;
        }
    }
   
    /**
     * The ScreenBuffer class holds the screen image that we are building.
     * It takes care of wrapping long lines over multiple screen lines.
     * <p>
     * In the javadoc and embedded comments, a 'line' refers to an arbitrarily
     * long data source line, and a 'subline' refers to a screen line.  Sublines
     * are 'pageWidth' characters wide and are filled with SP characters when
     * completed.
     *
     * @author crawley@jnode.org
     */
    private final class ScreenBuffer {
        private final class ScreenLine {
            private final char[] chars = new char[pageWidth];
            private final int[] colors = new int[pageWidth];
            private final int lineNo;
            private final int sublineNo;
           
            private ScreenLine(int lineNo, int sublineNo) {
                this.lineNo = lineNo;
                this.sublineNo = sublineNo;
            }
        }
       
        ArrayList<ScreenLine> lines = new ArrayList<ScreenLine>();
       
        // The direction of filling ...
        private final boolean forwards;
       
        // The current color.
        private int color;
       
        // The character pos in the current subline
        private int charPos;
       
        // The current subline no
        private int linePos;
       
        // The current data source line number
        private int lineNo;
        private int sublineNo;
        private int firstLinePos;
        private int lastLinePos;
       
        ScreenBuffer(boolean forwards) {
            this.forwards = forwards;
            this.linePos = 0;
            this.color = DEFAULT_COLOR;
        }

        void setColor(int color) {
            this.color = color;
        }

        /**
         * Start a new line.  If we are filling forwards, this will be
         * after the last subline of the current line.  If we are filling
         * backwards it will be before first subline of the current line.
         * It is an error to start a new line when the buffer is full, so
         * this method won't grow the buffer.
         *
         * @param lineNo the line number of the datastream line we are starting.
         */
        void startLine(int lineNo) throws IllegalArgumentException, IllegalStateException {
            if (lineNo < 0) {
                throw new IllegalArgumentException("lineNo < 0");
            }
           
            // Record current line number and allocate the first screen line.
            this.lineNo = lineNo;
            this.sublineNo = 0;
            lines.add(linePos, new ScreenLine(lineNo, 0));
            charPos = 0;
        }
       
        /**
         * End the current line.  This fills the remainder of the subline, then
         * moves 'linePos' to the position for the next line.
         */
        void endLine() {
            // Fill to the end of the current subline with a spaces
            ScreenLine line = lines.get(linePos);
            while (charPos < pageWidth) {
                line.chars[charPos] = ' ';
                line.colors[charPos] = color;
                charPos++;
            }
            // Move 'linepos' to the position for the next line.
            if (forwards) {
                linePos++;
            } else {
                while (linePos > 0 && lines.get(linePos - 1).lineNo == lineNo) {
                    linePos--;
                }
            }
        }
       
        /**
         * Put a character to the current line, allocating a new subline
         * if we wrap past 'pageWidth'.
         *
         * @param ch
         */
        void putChar(char ch) {
            if (charPos >= pageWidth) {
                newSubline();
                charPos = 0;
            }
            ScreenLine line = lines.get(linePos);
            line.chars[charPos] = ch;
            line.colors[charPos] = color;
            charPos++;
        }
       
        /**
         * Add a new subline for the current line.  The subline
         * goes into the buffer after the current subline.  If
         * we are filling forward, the current subline stays where it is.
         * If we are filling backwards, other sublines in the current line
         * are moves 'upwards' to make space.
         */
        private void newSubline() {
            sublineNo++;
            linePos++;
            lines.add(linePos, new ScreenLine(lineNo, sublineNo));
        }
       
        /**
         * This method calculates the adjusted start/end linePos values corresponding
         * to the supplied lineNo/sublineNo and the opposite end of the screen buffer.
         * Then it resets the top/bottom lineNo and sublineNo fields.
         *
         * @param lineNo
         * @param sublineNo
         */
        boolean adjust(final int lineNo, final int sublineNo) {
            debugln(lineNo + ", " + sublineNo);
            debugln(topLineNo + ", " + topSublineNo + ", " +
                    bottomLineNo + ", " + bottomSublineNo);
            int linePos;
            int len = lines.size();
            if (len == 0) {
                firstLinePos = 0;
                lastLinePos = -1;
                topLineNo = 0;
                topSublineNo = 0;
                bottomLineNo = -1;
                bottomSublineNo = 0;
            } else {
                for (linePos = 0; linePos < len - 1; linePos++) {
                    ScreenLine line = lines.get(linePos);
                    if (line.lineNo == lineNo && line.sublineNo == sublineNo) {
                        break;
                    }
                    if (line.lineNo > lineNo) {
                        linePos--;
                        break;
                    }
                }
                debugln(linePos + " : " + len);
                firstLinePos = forwards ? linePos : Math.max(0, linePos - pageHeight + 1);
                lastLinePos = forwards ? Math.min(len, linePos + pageHeight) - 1 : linePos;

                if (lastLinePos >= len) {
                    firstLinePos = Math.max(0, firstLinePos - (len - lastLinePos));
                    lastLinePos = len - 1;
                }

                debugln(firstLinePos + ", " + lastLinePos);
                ScreenLine topLine = lines.get(firstLinePos);
                topLineNo = topLine.lineNo;
                topSublineNo = topLine.sublineNo;
                ScreenLine bottomLine = lines.get(lastLinePos);
                bottomLineNo = bottomLine.lineNo;
                bottomSublineNo = bottomLine.sublineNo;
            }
            debugln(topLineNo + ", " + topSublineNo + ", " +
                    bottomLineNo + ", " + bottomSublineNo);
            return lastLinePos - firstLinePos == (pageHeight - 1);
        }

        /**
         * Test if the buffer is 'full'; i.e. if 'pageHeight' lines
         * have been populated.  This should only be called after
         * we have called {@line #endLine()}.
         *
         * @return
         */
        boolean isComplete() {
            if (charPos != pageWidth) {
                throw new IllegalStateException(
                        "line is still active (" + charPos + ", " + pageWidth + ")");
            }
            // Since we've called endLine(), linePos should be at the point where
            // the next line will be added to the buffer.
            if (forwards) {
                return linePos >= pageHeight;
            } else {
                return lines.size() - linePos - 1 >= pageHeight;
            }
        }

        /**
         * Output the buffer to the screen.  When we're done, we make this buffer
         * the 'current' buffer.
         */
        void output() {
            // This is probably the best I can do given the current console APIs.  The
            // problem are:
            // 1) there is no 'console.setChar(x, y, chars, colors, x, 1)' method, and
            // 2) a call to setChar(...) will sync the screen, which currently repaints
            //    every character to the screen device.
           
            // First we build a single char array for all characters on the screen, populate
            // from the lines, and pad out with spaces to the screen height.
            char[] tmp = new char[pageSize];
            debugln("output: " + firstLinePos + ", " + lastLinePos);
            for (int y = firstLinePos; y <= lastLinePos; y++) {
                ScreenLine line = lines.get(y);
                System.arraycopy(line.chars, 0, tmp, (y - firstLinePos) * pageWidth, pageWidth);
            }
            Arrays.fill(tmp, (lastLinePos - firstLinePos + 1) * pageWidth, pageSize, ' ');
           
            // Next, output the characters in the default color
            console.setChar(0, 0, tmp, 0, pageSize, 0x7);
           
            // Finally, go back and repaint any characters that have a different color
            // to the default.  We do this in runs, to avoid doing too many screen syncs.
            int color = DEFAULT_COLOR;
            int colorStartX = -1;
            int colorStartY = -1;
            int colorStartPos = -1;
            for (int y = firstLinePos; y <= lastLinePos; y++) {
                ScreenLine line = lines.get(y);
                int[] colors = line.colors;
                for (int x = 0; x < pageWidth; x++) {
                    if (colors[x] != color) {
                        if (color != DEFAULT_COLOR) {
                            int pos = x + y * pageWidth;
                            console.setChar(colorStartX, colorStartY,
                                    tmp, colorStartPos, pos - colorStartPos, color);
                        } else {
                            colorStartX = x;
                            colorStartY = y;
                            colorStartPos = colorStartX + colorStartY * pageWidth;
                        }
                        color = colors[x];
                    }
                }
                if (color != DEFAULT_COLOR) {
                    console.setChar(colorStartX, colorStartY,
                            tmp, colorStartPos, pageSize - colorStartPos, color);
                }
            }
            currentBuffer = this;
        }
    }
}
TOP

Related Classes of org.jnode.command.system.PageCommand$ScreenBuffer$ScreenLine

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.