Skip to content

Commit

Permalink
Improve performances when pasting huge strings, fixes #479
Browse files Browse the repository at this point in the history
  • Loading branch information
gnodet committed Nov 25, 2019
1 parent 7fce4d3 commit fea903c
Show file tree
Hide file tree
Showing 9 changed files with 185 additions and 20 deletions.
68 changes: 68 additions & 0 deletions reader/src/main/java/org/jline/keymap/BindingReader.java
Expand Up @@ -117,6 +117,33 @@ public <T> T readBinding(KeyMap<T> keys, KeyMap<T> local, boolean block) {
return null;
}

public String readStringUntil(String sequence) {
StringBuilder sb = new StringBuilder();
if (!pushBackChar.isEmpty()) {
pushBackChar.forEach(sb::appendCodePoint);
}
try {
char[] buf = new char[64];
while (true) {
int idx = sb.indexOf(sequence, Math.max(0, sb.length() - buf.length - sequence.length()));
if (idx >= 0) {
String rem = sb.substring(idx + sequence.length());
runMacro(rem);
return sb.substring(0, idx);
}
int l = reader.readBuffered(buf);
if (l < 0) {
throw new ClosedException();
}
sb.append(buf, 0, l);
}
} catch (ClosedException e) {
throw new EndOfFileException(e);
} catch (IOException e) {
throw new IOError(e);
}
}

/**
* Read a codepoint from the terminal.
*
Expand Down Expand Up @@ -144,6 +171,47 @@ public int readCharacter() {
}
}

public int readCharacterBuffered() {
try {
if (pushBackChar.isEmpty()) {
char[] buf = new char[32];
int l = reader.readBuffered(buf);
if (l <= 0) {
return -1;
}
int s = 0;
for (int i = 0; i < l; ) {
int c = buf[i++];
if (Character.isHighSurrogate((char) c)) {
s = c;
if (i < l) {
c = buf[i++];
pushBackChar.addLast(Character.toCodePoint((char) s, (char) c));
} else {
break;
}
} else {
s = 0;
pushBackChar.addLast(c);
}
}
if (s != 0) {
int c = reader.read();
if (c >= 0) {
pushBackChar.addLast(Character.toCodePoint((char) s, (char) c));
} else {
return -1;
}
}
}
return pushBackChar.pop();
} catch (ClosedException e) {
throw new EndOfFileException(e);
} catch (IOException e) {
throw new IOError(e);
}
}

public int peekCharacter(long timeout) {
if (!pushBackChar.isEmpty()) {
return pushBackChar.peek();
Expand Down
35 changes: 15 additions & 20 deletions reader/src/main/java/org/jline/reader/impl/LineReaderImpl.java
Expand Up @@ -914,6 +914,19 @@ protected <T> T doReadBinding(KeyMap<T> keys, KeyMap<T> local) {
}
}

protected String doReadStringUntil(String sequence) {
if (lock.isHeldByCurrentThread()) {
try {
lock.unlock();
return bindingReader.readStringUntil(sequence);
} finally {
lock.lock();
}
} else {
return bindingReader.readStringUntil(sequence);
}
}

/**
* Read from the input stream and decode an operation from the key map.
*
Expand Down Expand Up @@ -5625,28 +5638,10 @@ public boolean mouse() {
}

public boolean beginPaste() {
final Object SELF_INSERT = new Object();
final Object END_PASTE = new Object();
KeyMap<Object> keyMap = new KeyMap<>();
keyMap.setUnicode(SELF_INSERT);
keyMap.setNomatch(SELF_INSERT);
keyMap.setAmbiguousTimeout(0);
keyMap.bind(END_PASTE, BRACKETED_PASTE_END);
StringBuilder sb = new StringBuilder();
while (true) {
Object b = doReadBinding(keyMap, null);
if (b == END_PASTE) {
break;
}
String s = getLastBinding();
if ("\r".equals(s)) {
s = "\n";
}
sb.append(s);
}
String str = doReadStringUntil(BRACKETED_PASTE_END);
regionActive = RegionType.PASTE;
regionMark = getBuffer().cursor();
getBuffer().write(sb);
getBuffer().write(str.replace('\r', '\n'));
return true;
}

Expand Down
8 changes: 8 additions & 0 deletions reader/src/test/java/org/jline/keymap/BindingReaderTest.java
Expand Up @@ -74,4 +74,12 @@ public void testBindingReaderUnicode() {
assertEquals("b", reader.getLastBinding());
assertNull(reader.readBinding(keyMap));
}

@Test
public void testBindingReaderReadString() {
in.setIn(new ByteArrayInputStream("\uD834\uDD21abc0123456789defg".getBytes(StandardCharsets.UTF_8)));
BindingReader reader = new BindingReader(terminal.reader());
String str = reader.readStringUntil("fg");
assertEquals("\uD834\uDD21abc0123456789de", str);
}
}
Expand Up @@ -86,6 +86,11 @@ public int read(long timeout, boolean isPeek) throws IOException {
}
}

@Override
public int readBuffered(byte[] b) throws IOException {
return in.read(b);
}

private void setNonBlocking() {
if (current == null
|| current.getControlChar(Attributes.ControlChar.VMIN) != 0
Expand Down
27 changes: 27 additions & 0 deletions terminal/src/main/java/org/jline/utils/NonBlocking.java
Expand Up @@ -197,6 +197,33 @@ protected int read(long timeout, boolean isPeek) throws IOException {
}
}

@Override
public int readBuffered(char[] b) throws IOException {
if (b == null) {
throw new NullPointerException();
} else if (b.length == 0) {
return 0;
} else {
if (chars.hasRemaining()) {
int r = Math.min(b.length, chars.remaining());
chars.get(b);
return r;
} else {
byte[] buf = new byte[b.length];
int l = input.readBuffered(buf);
if (l < 0) {
return l;
} else {
ByteBuffer bytes = ByteBuffer.wrap(buf, 0, l);
CharBuffer chars = CharBuffer.wrap(b);
decoder.decode(bytes, chars, false);
chars.flip();
return chars.remaining();
}
}
}
}

@Override
public void shutdown() {
input.shutdown();
Expand Down
10 changes: 10 additions & 0 deletions terminal/src/main/java/org/jline/utils/NonBlockingInputStream.java
Expand Up @@ -78,6 +78,16 @@ public int read(byte b[], int off, int len) throws IOException {
return 1;
}

public int readBuffered(byte[] b) throws IOException {
if (b == null) {
throw new NullPointerException();
} else if (b.length == 0) {
return 0;
} else {
return super.read(b, 0, b.length);
}
}

/**
* Shuts down the thread that is handling blocking I/O if any. Note that if the
* thread is currently blocked waiting for I/O it may not actually
Expand Down
22 changes: 22 additions & 0 deletions terminal/src/main/java/org/jline/utils/NonBlockingPumpReader.java
Expand Up @@ -106,6 +106,28 @@ protected synchronized int read(long timeout, boolean isPeek) throws IOException
return res;
}

@Override
public int readBuffered(char[] b) throws IOException {
if (b == null) {
throw new NullPointerException();
} else if (b.length == 0) {
return 0;
} else {
int r = Math.min(b.length, readBuffer.remaining());
if (r > 0) {
readBuffer.get(b);
return r;
} else {
r = read(-1, false);
if (r >= 0) {
b[0] = (char) r;
return 1;
}
return r;
}
}
}

synchronized void write(char[] cbuf, int off, int len) throws IOException {
while (len > 0) {
// Blocks until there is new space available for buffering or the
Expand Down
2 changes: 2 additions & 0 deletions terminal/src/main/java/org/jline/utils/NonBlockingReader.java
Expand Up @@ -85,6 +85,8 @@ public int read(char[] b, int off, int len) throws IOException {
return 1;
}

public abstract int readBuffered(char[] b) throws IOException;

public int available() {
return 0;
}
Expand Down
28 changes: 28 additions & 0 deletions terminal/src/main/java/org/jline/utils/NonBlockingReaderImpl.java
Expand Up @@ -90,6 +90,34 @@ public synchronized boolean ready() throws IOException {
return ch >= 0 || in.ready();
}

@Override
public int readBuffered(char[] b) throws IOException {
if (b == null) {
throw new NullPointerException();
} else if (b.length == 0) {
return 0;
} else if (exception != null) {
assert ch == READ_EXPIRED;
IOException toBeThrown = exception;
exception = null;
throw toBeThrown;
} else if (ch >= -1) {
b[0] = (char) ch;
ch = READ_EXPIRED;
return 1;
} else if (!threadIsReading) {
return in.read(b);
} else {
int c = read(-1, false);
if (c >= 0) {
b[0] = (char) c;
return 1;
} else {
return -1;
}
}
}

/**
* Attempts to read a character from the input stream for a specific
* period of time.
Expand Down

0 comments on commit fea903c

Please sign in to comment.