CharReader.java
- /*
- * Copyright (c) 2016, Stein Eldar Johnsen
- *
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you 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 net.morimekta.strings.chr;
- import net.morimekta.strings.io.Utf8StreamReader;
- import java.io.IOException;
- import java.io.InputStream;
- import java.io.Reader;
- import static net.morimekta.strings.EscapeUtil.javaEscape;
- import static net.morimekta.strings.chr.Unicode.unicode;
- /**
- * A keystroke char reader. returns a Char object at a time related to the
- * single keystroke the user typed. Note that since this does not implement
- * the {@link Reader} base class, it does not belong in the 'io' package.
- */
- public class CharReader {
- private final Reader in;
- /**
- * Create a char reader.
- *
- * @param in Input stream to read from.
- */
- public CharReader(InputStream in) {
- this(new Utf8StreamReader(in));
- }
- /**
- * Create a char reader.
- *
- * @param in Reader to read from.
- */
- public CharReader(Reader in) {
- this.in = in;
- }
- /**
- * Read a single char if ready (something is available in the input
- * stream), otherwise return null.
- *
- * @return The read char or null of not ready.
- * @throws IOException If failed to read from input.
- */
- public Char readIfAvailable() throws IOException {
- if (in.ready()) {
- return read();
- }
- return null;
- }
- /**
- * Read the next char.
- *
- * @return The next char, or null of input stream is closed.
- * @throws IOException If unable to read a char.
- */
- public Char read() throws IOException {
- int cp = in.read();
- if (cp < 0) {
- return null;
- }
- if (cp == '\033') {
- // We have received an 'esc' and nothing else...
- if (!in.ready()) {
- return unicode(cp);
- }
- // 'esc' was last char in stream.
- int c2 = in.read();
- if (c2 < 0) {
- return unicode(cp);
- }
- StringBuilder charBuilder = new StringBuilder();
- charBuilder.append((char) cp);
- charBuilder.append((char) c2);
- if (c2 == '\033') {
- // Treat double 'esc' as a single 'esc'. Otherwise pressing 'esc'
- // will consistently crash the application.
- return unicode(cp);
- } else if (c2 == '[') {
- char c3 = expect();
- charBuilder.append(c3);
- if ('A' <= c3 && c3 <= 'Z') {
- // \033 [ A-Z
- return new Control(charBuilder.toString());
- }
- if (c3 == '?') {
- c3 = expect();
- charBuilder.append(c3);
- }
- while (('0' <= c3 && c3 <= '9') || c3 == ';') {
- c3 = expect();
- charBuilder.append(c3);
- }
- if (c3 == '~' ||
- ('a' <= c3 && c3 <= 'z') ||
- ('A' <= c3 && c3 <= 'Z')) {
- // \033 [ (number) ~ (F1, F2 ... Fx)
- // \033 [ (number...;) [A-D] (numbered cursor movement)
- // \033 [ (number...;) [su] (cursor save / restore, ...)
- // \033 [ (number...;) m (color)
- if (c3 == 'm') {
- try {
- return new Color(charBuilder.toString());
- } catch (IllegalArgumentException e) {
- throw new IOException(e.getMessage(), e);
- }
- }
- return new Control(charBuilder.toString());
- }
- } else if (c2 == 'O') {
- char c3 = expect();
- charBuilder.append(c3);
- if ('A' <= c3 && c3 <= 'Z') {
- // \033 O [A-Z]
- return new Control(charBuilder.toString());
- }
- } else if (('a' <= c2 && c2 <= 'z') ||
- ('0' <= c2 && c2 <= '9') ||
- ('A' <= c2 && c2 <= 'Z')) {
- // \033 [a-z]: <alt-{c}> aka <M-{c}>.
- // \033 [0-9]: <alt-{c}> aka <M-{c}>.
- // \033 [A-N]: <alt-shift-{c}> aka <M-S-{c}>.
- // \033 [P-Z]: <alt-shift-{c}> aka <M-S-{c}>.
- return new Control(charBuilder.toString());
- }
- throw new IOException("Invalid escape sequence: \"" + javaEscape(charBuilder.toString()) + "\"");
- } else {
- // Make sure to consume both surrogates on 32-bit code-points.
- if (Character.isHighSurrogate((char) cp)) {
- cp = Character.toCodePoint((char) cp, expect());
- }
- return unicode(cp);
- }
- }
- private char expect() throws IOException {
- int cp = in.read();
- if (cp < 0) {
- throw new IOException("Unexpected end of stream.");
- }
- return (char) cp;
- }
- }