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 } |
|