All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.termd.core.readline.LineBuffer Maven / Gradle / Ivy

Go to download

An open source terminal daemon library providing terminal handling in Java, back ported to Alibaba by core engine team to support running on JDK 6+.

There is a newer version: 1.1.7.14
Show newest version
/*
 * Copyright 2015 Julien Viet
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package io.termd.core.readline;

import io.termd.core.function.Consumer;
import io.termd.core.util.Vector;
import io.termd.core.util.Helper;
import io.termd.core.util.Wcwidth;

import java.util.Arrays;

/**
 * @author Julien Viet
 */
public class LineBuffer {

  private int[] data;
  private int cursor;
  private int size;

  public LineBuffer() {
    this(1000);
  }

  public LineBuffer(int cap) {
      data = new int[cap];
  }

  private LineBuffer(LineBuffer that) {
    data = that.data.clone();
    cursor = that.cursor;
    size = that.size;
  }

  public int[] toArray() {
    return Arrays.copyOf(data, size);
  }

  public int getAt(int index) {
    if (index < 0 | index >= size) {
      throw new IndexOutOfBoundsException();
    }
    return data[index];
  }

  /**
   * Insert a string in the buffer at the current cursor position.
   *
   * @see {@link #insert(int)}
   */
  public LineBuffer insert(String s) {
    return insert(Helper.toCodePoints(s));
  }

  /**
   * Insert an array of code points in the buffer at the current cursor position.
   */
  public LineBuffer insert(int... codePoints) {
    for (int cp : codePoints) {
      insert(cp);
    }
    return this;
  }

  public LineBuffer insert(Integer... codePoints) {
    for (int cp : codePoints) {
      insert(cp);
    }
    return this;
  }

  /**
   * Insert a codepoint in the buffer at the current cursor position.
   *
   * @param cp the codepoint to insert
   * @return this object
   * @throws IllegalArgumentException when an illegal character is inserted
   */
  public LineBuffer insert(int cp) {
    int w = Wcwidth.of(cp);
    if (w == -1) {
      if (cp != '\n') {
        throw new IllegalArgumentException("LineBuffer can only contain \\n control char");
      }
    } else if (w != 1) {
      throw new IllegalArgumentException("LineBuffer cannot contain chars of width!=1 for the moment");
    }
    if (cursor < size) {
      System.arraycopy(data, cursor, data, cursor + 1, size - cursor);
    }
    data[cursor++] = cp;
    size++;
    if (size >= data.length) {
        resize();
    }
    return this;
  }

  private void resize() {
      int[] tmpData = new int[data.length * 2];
      System.arraycopy(this.data, 0, tmpData, 0, data.length);
      this.data = tmpData;
  }

  public LineStatus.Ext insertEscaped(int... codePoints) {
    LineStatus.Ext status = new LineStatus.Ext();
    Helper.consumeTo(toArray(), status);
    status.buffer.clear();
    for (int cp : codePoints) {
      if (cp == 0) {
        continue;
      }
      if (cp < 32) {
        // Todo support \n with $'\n'
        throw new UnsupportedOperationException("todo");
      }
      switch (status.getQuote()) {
        case '"':
          switch (cp) {
            case '\\':
            case '"':
              if (!status.isEscaping()) {
                status.accept('\\');
              }
              status.accept(cp);
              break;
            default:
              if (status.isEscaping()) {
                // Should beep
              } else {
                status.accept(cp);
              }
              break;
          }
          break;
        case '\'':
          switch (cp) {
            case '\'':
              status.accept('\'');
              status.accept('\\');
              status.accept(cp);
              status.accept('\'');
              break;
            default:
              status.accept(cp);
              break;
          }
          break;
        case 0:
          if (status.isEscaping()) {
            status.accept(cp);
          } else {
            switch (cp) {
              case ' ':
              case '"':
              case '\'':
              case '\\':
                status.accept('\\');
                status.accept(cp);
                break;
              default:
                status.accept(cp);
                break;
            }
          }
          break;
        default:
          throw new UnsupportedOperationException("Todo " + status.getQuote());
      }
    }

    insert(status.buffer.toArray(new Integer[0]));
    return status;
  }

  /**
   * Delete a specified number of chars relative to the current cursor position:
   * 
    *
  • a positive value deletes the range {@literal [cursor,cursor + delta]}
  • *
  • a negative value deletes the range {@literal [cursor + delta - 1,cursor - 1]}
  • *
* * @param delta the number of chars to delete * @return the number of delete chars */ public int delete(int delta) { if (delta > 0) { delta = Math.min(delta, size - cursor); System.arraycopy(data, cursor + delta, data, cursor, size - cursor + delta); size -= delta; return delta; } else if (delta < 0) { delta = - Math.min(- delta, cursor); System.arraycopy(data, cursor, data, cursor + delta, size - cursor); size += delta; cursor += delta; return - delta; } else { return 0; } } public int getSize() { return size; } public void setSize(int size) { this.size = size >= 0 ? size : 0; if (cursor > size) { cursor = size; } } public int getCapacity() { return this.data.length; } public int getCursor() { return cursor; } public LineBuffer setCursor(int next) { this.cursor = next < 0 ? 0 : (next > size ? size : next); return this; } public LineBuffer copy() { return new LineBuffer(this); } public void clear() { size = 0; cursor = 0; } public int moveCursor(int delta) { int prev = cursor; setCursor(cursor + delta); return cursor - prev; } public String toString() { StringBuilder sb = new StringBuilder(); for (int i = 0; i < size; i++) { sb.appendCodePoint(data[i]); } return sb.toString(); } /** * Compute the current cursor position of this line buffer given a {@literal width} and a relative {@literal origin} * position. * * @param width the screen width * @return the height */ public Vector getCursorPosition(int width) { return getPosition(cursor, width); } /** * Compute the position of the char at the specified {@literal offset} of this line buffer given a * {@literal width} and a relative {@literal start} position. * * @param width the screen width * @return the height */ public Vector getPosition(int offset, int width) { if (offset > size) { throw new IndexOutOfBoundsException("Offset cannot bebe greater than the buffer size"); } return Helper.computePosition(data, new Vector(0, 0), offset, width); } private int findEndOfLine(int offset) { while (offset < size) { int c = data[offset]; int w = Wcwidth.of(c); if (w == -1) { if (c == '\n') { break; // ?? unsure } else { throw new UnsupportedOperationException(); } } offset++; } return offset; } public void update(LineBuffer dst, Consumer out, int width) { new Update(out, width).perform(dst); } // The update algorithm encapsulated in an inner class // todo : use term capabilities instead of hardcoded ansi programming // todo : support other control chars // todo : support codepoint of with != 1 (like combining chars, etc...) // todo : issue existing chars for moving right instead of cursor left movement private class Update { private final Consumer out; private final int width; private int scrCol, scrRow; // The current screen cursor position private int srcIdx, srcCol, srcRow; // The source state private int dstIdx, dstCol, dstRow; // The destination state public Update(Consumer out, int width) { this.out = out; this.width = width; this.scrCol = getCursorPosition(width).x(); this.scrRow = getCursorPosition(width).y(); } public void perform(LineBuffer dst) { while (dstIdx < dst.size) { int eol = dst.findEndOfLine(dstIdx); boolean needGlitchCorrection = dstIdx < eol; // Handle one dest line at a time while (dstIdx < eol) { int c = dst.data[dstIdx]; int w = Wcwidth.of(c); if (w != 1) { throw new UnsupportedOperationException(); } if (srcIdx < size && new Vector(srcCol, srcRow).equals(new Vector(dstCol, dstRow))) { if (data[srcIdx] == dst.data[dstIdx]) { dstCol += w; if (dstCol == width) { dstCol = 0; dstRow++; } } else { moveCursor(dstCol, dstRow); out.accept(new int[]{c}); dstCol += w; if (dstCol == width) { dstCol = 0; dstRow++; } scrCol = dstCol; scrRow = dstRow; } dstIdx++; } else { moveCursor(dstCol, dstRow); dstIdx++; out.accept(new int[]{c}); dstCol += w; if (dstCol == width) { dstCol = 0; dstRow++; } scrCol = dstCol; scrRow = dstRow; } ensure(dstCol, dstRow); } // Glitch correction if needed if (needGlitchCorrection && dstCol == 0) { out.accept(new int[]{' ','\r'}); } // Remove extra chars if needed if (dstIdx < dst.size) { dstIdx++; dstCol = 0; ++dstRow; int _col = srcCol, _row = srcRow; if (ensure(dstCol, dstRow)) { moveCursor(_col, _row); out.accept(new int[]{'\033','[', 'K'}); } } // We may need to issue some \n after we are done while (scrRow < dstRow) { out.accept(new int[]{'\n'}); scrRow++; scrCol = 0; } } // Erase extra remaining chars if (srcIdx < size) { int _col = srcCol; int _row = srcRow; int count = 0; while (srcIdx < size) { int c = data[srcIdx++]; if (c == '\n') { if (count > 0) { moveCursor(_col, _row); out.accept(new int[]{'\033', '[', 'K'}); count = 0; } _col = srcCol = 0; _row = ++srcRow; } else { int w = Wcwidth.of(c); if (w != 1) { throw new UnsupportedOperationException(); } srcCol++; count++; if (srcCol == width) { if (count > 0) { moveCursor(_col, _row); out.accept(new int[]{'\033', '[', 'K'}); count = 0; _col = srcCol = 0; _row = ++srcRow; } else { srcCol = 0; srcRow++; } } } } if (count > 0) { moveCursor(_col, _row); out.accept(new int[]{'\033', '[', 'K'}); } } // Move cursor to initial position moveCursor(dst.getCursorPosition(width).x(), dst.getCursorPosition(width).y()); // Update internal state data = dst.data.clone(); cursor = dst.cursor; size = dst.size; } /** * Ensure the source pointers are at least matching the specified column and row * * @param col the column * @param row the row * @return true if we skipped some source chars when moving the source pointers */ private boolean ensure(int col, int row) { boolean ret = false; while (srcIdx < size) { if (srcRow > row || (srcRow == row && srcCol >= col)) { break; } int c = data[srcIdx]; int w = Wcwidth.of(c); if (w == 1) { ret = true; srcCol++; if (srcCol == width) { srcRow++; srcCol = 0; } } else if (c == '\n') { srcCol = 0; srcRow++; } else { throw new UnsupportedOperationException(); } srcIdx++; } return ret; } /** * Move the cursor to the specified coordinates, this updates the internal physical cursor. * * @param col the column * @param row the row */ private void moveCursor(int col, int row) { if (scrCol != col) { if (col == 0) { out.accept(new int[]{'\r'}); scrCol = 0; } else { while (scrCol != col) { if (scrCol < col) { scrCol++; out.accept(new int[]{'\033','[','1','C'}); } else { scrCol--; out.accept(new int[]{'\b'}); } } } } while (scrRow != row) { if (row < scrRow) { scrRow--; out.accept(new int[]{27,'[','1','A'}); } else { scrRow++; out.accept(new int[]{27,'[','1','B'}); } } } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy