Skip to content

Commit 3201426

Browse files
javier-godoypaodb
authored andcommitted
feat: add support for command prompt
Close #12
1 parent 1d0c4e3 commit 3201426

File tree

5 files changed

+108
-28
lines changed

5 files changed

+108
-28
lines changed

src/main/java/com/flowingcode/vaadin/addons/xterm/ITerminalConsole.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,4 +68,14 @@ default CompletableFuture<String> getCurrentLine() {
6868
.thenApply(JsonValue::asString);
6969
}
7070

71+
/** Sets the command line prompt. */
72+
default void setPrompt(String prompt) {
73+
getElement().setProperty("prompt", prompt);
74+
}
75+
76+
/** Writes the command line prompt to the terminal. */
77+
default void writePrompt() {
78+
((XTermBase) this).executeJs("this.writePrompt()");
79+
}
80+
7181
}

src/main/java/com/flowingcode/vaadin/addons/xterm/TerminalHistory.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -108,8 +108,9 @@ private void handleArrowDown() {
108108

109109
private void write(String line) {
110110
if (line != null) {
111-
// erase logical line, cursor home in logical line
112-
terminal.write("\033[<2K\033[<H" + line);
111+
// erase logical line, cursor home in logical line, cursor horizontal absolute
112+
String prompt = terminal.getElement().getProperty("prompt", "");
113+
terminal.write("\033[<2K\033[<H\033[G" + prompt + line);
113114
lastRet = line;
114115
}
115116
}

src/main/resources/META-INF/frontend/fc-xterm/xterm-console-mixin.ts

Lines changed: 74 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,13 @@ interface IConsoleMixin extends TerminalMixin {
2424
escapeEnabled: Boolean;
2525
insertMode: Boolean;
2626
readonly currentLine: string;
27+
prompt: string;
2728
}
2829

2930
class ConsoleAddon extends TerminalAddon<IConsoleMixin> {
3031

32+
__yPrompt : Number = -1;
33+
3134
get currentLine() : string {
3235
let inputHandler = ((this.$core) as any)._inputHandler;
3336
let buffer = inputHandler._bufferService.buffer;
@@ -37,37 +40,48 @@ class ConsoleAddon extends TerminalAddon<IConsoleMixin> {
3740
line += buffer.lines.get(i).translateToString();
3841
}
3942
line = line.replace(/\s+$/,"");
43+
if (this.__yPrompt==range.first) line = line.substring(this.$.prompt.length);
4044
return line;
4145
}
4246

4347
activateCallback(terminal: Terminal): void {
4448

4549
var inputHandler = ((this.$core) as any)._inputHandler;
4650

51+
let promptLength = () => this.$.prompt ? this.$.prompt.length : 0;
52+
4753
let scanEOL = (function() {
4854
let buffer = this._bufferService.buffer;
4955
let col = this._bufferService.buffer.lines.get(buffer.ybase+buffer.y).getTrimmedLength();
5056
this.cursorCharAbsolute({params:[col+1]});
5157
}).bind(inputHandler);
5258

53-
let cursorForwardWrapped = (function() {
59+
let cursorForwardWrapped = (function(params: any) {
5460
let buffer = this._bufferService.buffer;
55-
if (buffer.x==this._bufferService.cols-1) {
56-
let next = buffer.lines.get(buffer.y+buffer.ybase+1);
57-
if (next && next.isWrapped) {
58-
this.cursorNextLine({params:1});
61+
let x = buffer.x+(params && params[0] || 1);
62+
do {
63+
if (x>=this._bufferService.cols) {
64+
let next = buffer.lines.get(buffer.y+buffer.ybase);
65+
if (next && next.isWrapped) {
66+
this.cursorNextLine({params:1});
67+
}
68+
x-=this._bufferService.cols;
69+
} else {
70+
x = Math.min(x, this._bufferService.buffer.lines.get(buffer.y+buffer.ybase).getTrimmedLength());
71+
x>0 && this.cursorCharAbsolute({params:[x+1]});
5972
}
60-
} else if (buffer.x<this._bufferService.buffer.lines.get(buffer.y+buffer.ybase).getTrimmedLength()) {
61-
this.cursorForward({params:[1]});
62-
}
73+
} while (x>=this._bufferService.cols);
6374
}).bind(inputHandler);
6475

6576
let cursorBackwardWrapped = (function() {
6677
let buffer = this._bufferService.buffer;
67-
if (buffer.x>0) {
78+
let line = buffer.lines.get(buffer.y+buffer.ybase);
79+
if (!line.isWrapped && buffer.x< 1+promptLength()) {
80+
return false;
81+
} else if (buffer.x>0) {
6882
this.cursorBackward({params:[1]});
6983
return true;
70-
} else if (buffer.lines.get(buffer.y+buffer.ybase).isWrapped) {
84+
} else if (line.isWrapped) {
7185
this.cursorPrecedingLine({params:[1]});
7286
scanEOL();
7387
return true;
@@ -76,25 +90,27 @@ class ConsoleAddon extends TerminalAddon<IConsoleMixin> {
7690
}
7791
}).bind(inputHandler);
7892

79-
let deleteChar = (function() {
93+
let deleteChar = (function(params: any) {
8094
let buffer = this._bufferService.buffer;
81-
this.deleteChars({params:[1]});
8295
let x = buffer.x;
8396
let y = buffer.y;
84-
let line = buffer.lines.get(buffer.ybase+buffer.y);
85-
let range = buffer.getWrappedRangeForLine(buffer.y + buffer.ybase)
86-
for (let i=buffer.y+buffer.ybase; i<range.last; i++) {
87-
let next = buffer.lines.get(buffer.ybase+buffer.y+1);
88-
line.set(line.length-1, next.get(0));
89-
this.cursorNextLine({params:1});
97+
for (let i=0; i< (params && params[0] || 1); i++) {
9098
this.deleteChars({params:[1]});
91-
line = next;
92-
}
93-
if (line.isWrapped && line.getTrimmedLength()==0) {
94-
line.isWrapped=false;
95-
if (y==range.last) {
96-
y--;
97-
x=this._bufferService.cols-1;
99+
let line = buffer.lines.get(buffer.ybase+buffer.y);
100+
let range = buffer.getWrappedRangeForLine(buffer.y + buffer.ybase)
101+
for (let i=buffer.y+buffer.ybase; i<range.last; i++) {
102+
let next = buffer.lines.get(buffer.ybase+buffer.y+1);
103+
line.set(line.length-1, next.get(0));
104+
this.cursorNextLine({params:1});
105+
this.deleteChars({params:[1]});
106+
line = next;
107+
}
108+
if (line.isWrapped && line.getTrimmedLength()==0) {
109+
line.isWrapped=false;
110+
if (y==range.last) {
111+
y--;
112+
x=this._bufferService.cols-1;
113+
}
98114
}
99115
}
100116
buffer.y=y;
@@ -116,6 +132,7 @@ class ConsoleAddon extends TerminalAddon<IConsoleMixin> {
116132
} else {
117133
this.cursorCharAbsolute({params:[1]});
118134
}
135+
promptLength() && cursorForwardWrapped([promptLength()]);
119136
}).bind(inputHandler);
120137

121138
let backspace = (function() {
@@ -212,6 +229,32 @@ class ConsoleAddon extends TerminalAddon<IConsoleMixin> {
212229

213230
}
214231

232+
writePrompt() {
233+
if (!this.$.prompt) return;
234+
235+
let inputHandler = ((this.$core) as any)._inputHandler;
236+
let buffer = inputHandler._bufferService.buffer;
237+
let range = buffer.getWrappedRangeForLine(buffer.y + buffer.ybase);
238+
239+
let prepare = "";
240+
let restore = this.$.insertMode ? "\x1b[4h" : "\x1b[4l"
241+
242+
console.error(this.__yPrompt+" "+range.first+" "+buffer.y+" "+buffer.ybase);
243+
if (this.__yPrompt == range.first) {
244+
//prompt has been written in this line
245+
prepare+="\x1b[4l"; //Override mode
246+
} else {
247+
//prompt has not been written in this line
248+
this.__yPrompt = range.first;
249+
prepare+="\x1b[s"; //Save cursor position
250+
prepare+="\x1b[4h"; //Insert mode
251+
restore+="\x1b[u"; //Restore cursor position
252+
restore+="\x1b[<"+this.$.prompt.length+"R"; //cursor forward wrapped
253+
}
254+
255+
prepare+="\x1b[<H\x1b[G"; //Cursor Home Logical, Cursor Horizontal Absolute
256+
this.$.prompt && this.$.node.terminal.write(prepare+this.$.prompt+restore);
257+
}
215258
}
216259

217260
type Constructor<T = {}> = new (...args: any[]) => T;
@@ -220,6 +263,7 @@ export function XTermConsoleMixin<TBase extends Constructor<TerminalMixin>>(Base
220263

221264
_addon? : ConsoleAddon;
222265
escapeEnabled: Boolean;
266+
prompt: string;
223267

224268
connectedCallback() {
225269
super.connectedCallback();
@@ -245,5 +289,10 @@ export function XTermConsoleMixin<TBase extends Constructor<TerminalMixin>>(Base
245289
return this._addon.currentLine;
246290
}
247291

292+
writePrompt() {
293+
//execute writePrompt with blocking semantics
294+
this.node.terminal.write('', ()=>this._addon.writePrompt());
295+
}
296+
248297
}
249298
}

src/test/java/com/flowingcode/vaadin/addons/xterm/XtermDemoView.java

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,12 @@ public XtermDemoView() {
4444
getElement().getStyle().set("background", "black");
4545

4646
xterm = new XTerm();
47+
xterm.setPrompt("[user@xterm ~]$ ");
48+
4749
xterm.writeln("xterm add-on by Flowing Code S.A.\n\n");
48-
xterm.writeln("Commands: time, date, beep, color, history\n");
50+
xterm.writeln("Commands: time, date, beep, color, history, prompt\n");
51+
xterm.writePrompt();
52+
4953
xterm.setCursorBlink(true);
5054
xterm.setCursorStyle(CursorStyle.UNDERLINE);
5155
xterm.setBellStyle(BellStyle.SOUND);
@@ -94,11 +98,23 @@ public XtermDemoView() {
9498
case "history":
9599
showHistory();
96100
break;
101+
case "prompt":
102+
if (line.length == 1) {
103+
xterm.writeln("Write prompt off for disabling the prompt");
104+
xterm.writeln("Write prompt <text> for setting the prompt");
105+
} else if (line[1].equals("off")) {
106+
xterm.setPrompt(null);
107+
} else {
108+
xterm.setPrompt(line[1].trim() + " ");
109+
}
110+
break;
97111
default:
98112
if (!ev.getLine().trim().isEmpty()) {
99113
xterm.writeln("Unknown command: " + line[0]);
100114
}
101115
}
116+
117+
xterm.writePrompt();
102118
});
103119

104120
xterm.focus();

src/test/java/com/flowingcode/vaadin/addons/xterm/integration/XTermElement.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,10 @@ public void setUseSystemClipboard(boolean value) {
5454
setProperty("useSystemClipboard", value);
5555
}
5656

57+
public void setPrompt(String value) {
58+
setProperty("prompt", value);
59+
}
60+
5761
@Override
5862
public void sendKeys(CharSequence... keysToSend) {
5963
input.sendKeys(keysToSend);

0 commit comments

Comments
 (0)