src/jdk.internal.le/share/classes/jdk/internal/jline/extra/AnsiInterpretingOutputStream.java
changeset 52974 ddbd9744a3d5
parent 52973 a659ccd1888d
parent 52961 d67b37917e82
child 52975 35e2bbea78b2
child 53179 760293737af0
equal deleted inserted replaced
52973:a659ccd1888d 52974:ddbd9744a3d5
     1 /*
       
     2  * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
       
     3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
       
     4  *
       
     5  * This code is free software; you can redistribute it and/or modify it
       
     6  * under the terms of the GNU General Public License version 2 only, as
       
     7  * published by the Free Software Foundation.  Oracle designates this
       
     8  * particular file as subject to the "Classpath" exception as provided
       
     9  * by Oracle in the LICENSE file that accompanied this code.
       
    10  *
       
    11  * This code is distributed in the hope that it will be useful, but WITHOUT
       
    12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
       
    13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
       
    14  * version 2 for more details (a copy is included in the LICENSE file that
       
    15  * accompanied this code).
       
    16  *
       
    17  * You should have received a copy of the GNU General Public License version
       
    18  * 2 along with this work; if not, write to the Free Software Foundation,
       
    19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
       
    20  *
       
    21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
       
    22  * or visit www.oracle.com if you need additional information or have any
       
    23  * questions.
       
    24  */
       
    25 package jdk.internal.jline.extra;
       
    26 
       
    27 import java.io.ByteArrayOutputStream;
       
    28 import java.io.IOException;
       
    29 import java.io.OutputStream;
       
    30 import java.util.HashMap;
       
    31 import java.util.Map;
       
    32 
       
    33 import jdk.internal.jline.internal.Ansi;
       
    34 
       
    35 /**A stream that interprets some escape code sequences, and ignores those it does not support.
       
    36  */
       
    37 public class AnsiInterpretingOutputStream extends OutputStream {
       
    38     private final String encoding;
       
    39     private final OutputStream out;
       
    40     private final Performer performer;
       
    41     private final Map<Character, AnsiCodeHandler> ESCAPE_CODE_ACTIONS = new HashMap<>();
       
    42 
       
    43     private boolean inEscapeSequence;
       
    44     private ByteArrayOutputStream escape = new ByteArrayOutputStream();
       
    45 
       
    46     public AnsiInterpretingOutputStream(String encoding, OutputStream output, Performer performer) {
       
    47         this.encoding = encoding;
       
    48         this.out = output;
       
    49         this.performer = performer;
       
    50         ESCAPE_CODE_ACTIONS.put('A', code -> {
       
    51             moveCursor(code, 0, -1);
       
    52         });
       
    53         ESCAPE_CODE_ACTIONS.put('B', code -> {
       
    54             moveCursor(code, 0, +1);
       
    55         });
       
    56         ESCAPE_CODE_ACTIONS.put('C', code -> {
       
    57             moveCursor(code, +1, 0);
       
    58         });
       
    59         ESCAPE_CODE_ACTIONS.put('D', code -> {
       
    60             moveCursor(code, -1, 0);
       
    61         });
       
    62         ESCAPE_CODE_ACTIONS.put('K', code -> {
       
    63             BufferState buffer = performer.getBufferState();
       
    64             switch (parseOutIntValue(code, 0)) {
       
    65                 case 0:
       
    66                     for (int i = buffer.cursorX; i < buffer.sizeX - 1; i++) {
       
    67                         out.write(' ');
       
    68                     }
       
    69                     performer.setCursorPosition(buffer.cursorX, buffer.cursorY);
       
    70                     break;
       
    71                 case 1:
       
    72                     performer.setCursorPosition(0, buffer.cursorY);
       
    73                     for (int i = 0; i < buffer.cursorX; i++) {
       
    74                         out.write(' ');
       
    75                     }
       
    76                     break;
       
    77                 case 2:
       
    78                     for (int i = 0; i < buffer.sizeX - 1; i++) {
       
    79                         out.write(' ');
       
    80                     }
       
    81                     performer.setCursorPosition(buffer.cursorX, buffer.cursorY);
       
    82                     break;
       
    83             }
       
    84             out.flush();
       
    85         });
       
    86     }
       
    87 
       
    88     @Override
       
    89     public void write(int d) throws IOException {
       
    90         if (inEscapeSequence) {
       
    91             escape.write(d);
       
    92             String escapeCandidate = new String(escape.toByteArray(), encoding);
       
    93             if (Ansi.ANSI_CODE_PATTERN.asPredicate().test(escapeCandidate)) {
       
    94                 //escape sequence:
       
    95                 char key = escapeCandidate.charAt(escapeCandidate.length() - 1);
       
    96                 AnsiCodeHandler handler =
       
    97                         ESCAPE_CODE_ACTIONS.get(key);
       
    98                 if (handler != null) {
       
    99                     handler.handle(escapeCandidate);
       
   100                 } else {
       
   101                     //unknown escape sequence, ignore
       
   102                 }
       
   103                 inEscapeSequence = false;
       
   104                 escape = null;
       
   105             }
       
   106         } else if (d == '\033') {
       
   107             inEscapeSequence = true;
       
   108             escape = new ByteArrayOutputStream();
       
   109             escape.write(d);
       
   110         } else {
       
   111             out.write(d);
       
   112         }
       
   113     }
       
   114     @Override
       
   115     public void flush() throws IOException {
       
   116         out.flush();
       
   117     }
       
   118 
       
   119     private void moveCursor(String code, int dx, int dy) throws IOException {
       
   120         int delta = parseOutIntValue(code, 1);
       
   121         BufferState buffer = performer.getBufferState();
       
   122         int tx = buffer.cursorX + dx * delta;
       
   123         int ty = buffer.cursorY + dy * delta;
       
   124 
       
   125         tx = Math.max(0, Math.min(buffer.sizeX - 1, tx));
       
   126         ty = Math.max(0, Math.min(buffer.sizeY - 1, ty));
       
   127 
       
   128         performer.setCursorPosition(tx, ty);
       
   129     }
       
   130 
       
   131     private int parseOutIntValue(String code, int def) {
       
   132         try {
       
   133             return Integer.parseInt(code.substring(code.indexOf('[') + 1, code.length() - 1));
       
   134         } catch (NumberFormatException ex) {
       
   135             return def;
       
   136         }
       
   137     }
       
   138 
       
   139     interface AnsiCodeHandler {
       
   140         public void handle(String code) throws IOException;
       
   141     }
       
   142 
       
   143     public interface Performer {
       
   144         public BufferState getBufferState() throws IOException;
       
   145         public void setCursorPosition(int cursorX, int cursorY) throws IOException;
       
   146     }
       
   147 
       
   148     public static class BufferState {
       
   149         public final int cursorX;
       
   150         public final int cursorY;
       
   151         public final int sizeX;
       
   152         public final int sizeY;
       
   153 
       
   154         public BufferState(int cursorX, int cursorY, int sizeX, int sizeY) {
       
   155             this.cursorX = cursorX;
       
   156             this.cursorY = cursorY;
       
   157             this.sizeX = sizeX;
       
   158             this.sizeY = sizeY;
       
   159         }
       
   160 
       
   161     }
       
   162 }