Skip to content

Commit 8716774

Browse files
committed
Enhance TerminalUI eventing
- New methods in an eventloop and message builder - Generic polish to use these methods - Relates #761
1 parent 9a5f189 commit 8716774

File tree

5 files changed

+138
-37
lines changed

5 files changed

+138
-37
lines changed

spring-shell-core/src/main/java/org/springframework/shell/component/view/TerminalUI.java

Lines changed: 14 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,6 @@
3333
import org.slf4j.LoggerFactory;
3434

3535
import org.springframework.lang.Nullable;
36-
import org.springframework.messaging.Message;
37-
import org.springframework.messaging.support.MessageBuilder;
3836
import org.springframework.shell.component.view.control.View;
3937
import org.springframework.shell.component.view.control.ViewService;
4038
import org.springframework.shell.component.view.event.DefaultEventLoop;
@@ -48,10 +46,8 @@
4846
import org.springframework.shell.component.view.event.MouseHandler.MouseHandlerResult;
4947
import org.springframework.shell.component.view.geom.Rectangle;
5048
import org.springframework.shell.component.view.message.ShellMessageBuilder;
51-
import org.springframework.shell.component.view.message.ShellMessageHeaderAccessor;
5249
import org.springframework.shell.component.view.screen.DefaultScreen;
5350
import org.springframework.util.Assert;
54-
import org.springframework.util.ObjectUtils;
5551
import org.springframework.util.StringUtils;
5652

5753
/**
@@ -236,40 +232,24 @@ private void display() {
236232
}
237233

238234
private void dispatchWinch() {
239-
Message<String> message = MessageBuilder.withPayload("WINCH")
240-
.setHeader(ShellMessageHeaderAccessor.EVENT_TYPE, EventLoop.Type.SIGNAL)
241-
.build();
242-
eventLoop.dispatch(message);
235+
eventLoop.dispatch(ShellMessageBuilder.ofSignal("WINCH"));
243236
}
244237

245238
private void registerEventHandling() {
246-
// XXX: think this again
247-
eventLoop.onDestroy(eventLoop.events()
248-
.filter(m -> {
249-
return ObjectUtils.nullSafeEquals(m.getHeaders().get(ShellMessageHeaderAccessor.EVENT_TYPE), EventLoop.Type.SIGNAL);
250-
})
251-
.doOnNext(m -> {
239+
eventLoop.onDestroy(eventLoop.signalEvents()
240+
.subscribe(event -> {
252241
display();
253-
})
254-
.subscribe());
242+
}));
255243

256-
// XXX: think this again
257-
eventLoop.onDestroy(eventLoop.events()
258-
.filter(m -> {
259-
return ObjectUtils.nullSafeEquals(m.getHeaders().get(ShellMessageHeaderAccessor.EVENT_TYPE), EventLoop.Type.SYSTEM);
260-
})
261-
.doOnNext(m -> {
262-
Object payload = m.getPayload();
263-
if (payload instanceof String s) {
264-
if ("redraw".equals(s)) {
265-
display();
266-
}
267-
else if ("int".equals(s)) {
268-
this.terminal.raise(Signal.INT);
269-
}
244+
eventLoop.onDestroy(eventLoop.systemEvents()
245+
.subscribe(event -> {
246+
if ("redraw".equals(event)) {
247+
display();
270248
}
271-
})
272-
.subscribe());
249+
else if ("int".equals(event)) {
250+
this.terminal.raise(Signal.INT);
251+
}
252+
}));
273253

274254
eventLoop.onDestroy(eventLoop.keyEvents()
275255
.doOnNext(m -> {
@@ -417,11 +397,8 @@ else if (operation == KeyEvent.Key.Mouse) {
417397
}
418398

419399
private void dispatchKeyEvent(KeyEvent event) {
420-
Message<KeyEvent> message = MessageBuilder
421-
.withPayload(event)
422-
.setHeader(ShellMessageHeaderAccessor.EVENT_TYPE, EventLoop.Type.KEY)
423-
.build();
424-
eventLoop.dispatch(message);
400+
log.debug("Dispatch key event: {}", event);
401+
eventLoop.dispatch(ShellMessageBuilder.ofKeyEvent(event));
425402
}
426403

427404
private void dispatchMouse(MouseEvent event) {

spring-shell-core/src/main/java/org/springframework/shell/component/view/event/DefaultEventLoop.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,22 @@ public Flux<MouseEvent> mouseEvents() {
150150
.ofType(MouseEvent.class);
151151
}
152152

153+
@Override
154+
public Flux<String> systemEvents() {
155+
return events()
156+
.filter(m -> EventLoop.Type.SYSTEM.equals(StaticShellMessageHeaderAccessor.getEventType(m)))
157+
.map(m -> m.getPayload())
158+
.ofType(String.class);
159+
}
160+
161+
@Override
162+
public Flux<String> signalEvents() {
163+
return events()
164+
.filter(m -> EventLoop.Type.SIGNAL.equals(StaticShellMessageHeaderAccessor.getEventType(m)))
165+
.map(m -> m.getPayload())
166+
.ofType(String.class);
167+
}
168+
153169
@Override
154170
public <T extends ViewEvent> Flux<T> viewEvents(Class<T> clazz) {
155171
return events(EventLoop.Type.VIEW, clazz);

spring-shell-core/src/main/java/org/springframework/shell/component/view/event/EventLoop.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,22 @@ public interface EventLoop {
6161
*/
6262
Flux<MouseEvent> mouseEvents();
6363

64+
/**
65+
* Specialisation of {@link #events()} which returns type safe
66+
* {code system} events.
67+
*
68+
* @return the system events from an event loop
69+
*/
70+
Flux<String> systemEvents();
71+
72+
/**
73+
* Specialisation of {@link #events()} which returns type safe
74+
* {code signal} events.
75+
*
76+
* @return the signal events from an event loop
77+
*/
78+
Flux<String> signalEvents();
79+
6480
/**
6581
* Specialisation of {@link #events()} which returns type safe {@link ViewEvent}s.
6682
*

spring-shell-core/src/main/java/org/springframework/shell/component/view/message/ShellMessageBuilder.java

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import org.springframework.messaging.support.GenericMessage;
2121
import org.springframework.shell.component.view.control.View;
2222
import org.springframework.shell.component.view.event.EventLoop;
23+
import org.springframework.shell.component.view.event.KeyEvent;
2324
import org.springframework.shell.component.view.event.MouseEvent;
2425
import org.springframework.util.Assert;
2526

@@ -83,6 +84,30 @@ public static Message<String> ofInterrupt() {
8384
.build();
8485
}
8586

87+
/**
88+
* Create a {@code signal} message.
89+
*
90+
* @return a signal message
91+
*/
92+
public static Message<String> ofSignal(String signal) {
93+
return new ShellMessageBuilder<>(signal, null)
94+
.setEventType(EventLoop.Type.SIGNAL)
95+
.setPriority(0)
96+
.build();
97+
}
98+
99+
/**
100+
* Create a message of a {@link KeyEvent}.
101+
*
102+
* @param event the event type
103+
* @return a message with {@link KeyEvent} as a payload
104+
*/
105+
public static Message<KeyEvent> ofKeyEvent(KeyEvent event) {
106+
return new ShellMessageBuilder<>(event, null)
107+
.setEventType(EventLoop.Type.KEY)
108+
.build();
109+
}
110+
86111
/**
87112
* Create a message of a {@link MouseEvent}.
88113
*

spring-shell-core/src/test/java/org/springframework/shell/component/view/event/DefaultEventLoopTests.java

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
import java.time.Duration;
1919
import java.util.Arrays;
20+
import java.util.EnumSet;
2021

2122
import org.junit.jupiter.api.AfterEach;
2223
import org.junit.jupiter.api.Test;
@@ -190,4 +191,70 @@ public void run() {
190191
}
191192
}
192193

194+
@Test
195+
void keyEvents() {
196+
initDefault();
197+
198+
KeyEvent event = KeyEvent.of(KeyEvent.Key.a);
199+
Message<KeyEvent> message = ShellMessageBuilder.ofKeyEvent(event);
200+
201+
StepVerifier verifier1 = StepVerifier.create(loop.keyEvents())
202+
.expectNextCount(1)
203+
.thenCancel()
204+
.verifyLater();
205+
206+
loop.dispatch(message);
207+
verifier1.verify(Duration.ofSeconds(1));
208+
}
209+
210+
@Test
211+
void mouseEvents() {
212+
initDefault();
213+
214+
org.jline.terminal.MouseEvent jlineMouseEvent = new org.jline.terminal.MouseEvent(
215+
org.jline.terminal.MouseEvent.Type.Released,
216+
org.jline.terminal.MouseEvent.Button.Button1,
217+
EnumSet.noneOf(org.jline.terminal.MouseEvent.Modifier.class), 0, 0);
218+
MouseEvent event = MouseEvent.of(jlineMouseEvent);
219+
Message<MouseEvent> message = ShellMessageBuilder.ofMouseEvent(event);
220+
221+
StepVerifier verifier1 = StepVerifier.create(loop.mouseEvents())
222+
.expectNextCount(1)
223+
.thenCancel()
224+
.verifyLater();
225+
226+
loop.dispatch(message);
227+
verifier1.verify(Duration.ofSeconds(1));
228+
}
229+
230+
@Test
231+
void systemEvents() {
232+
initDefault();
233+
234+
Message<String> message = ShellMessageBuilder.ofRedraw();
235+
236+
StepVerifier verifier1 = StepVerifier.create(loop.systemEvents())
237+
.expectNextCount(1)
238+
.thenCancel()
239+
.verifyLater();
240+
241+
loop.dispatch(message);
242+
verifier1.verify(Duration.ofSeconds(1));
243+
}
244+
245+
@Test
246+
void signalEvents() {
247+
initDefault();
248+
249+
Message<String> message = ShellMessageBuilder.ofSignal("WINCH");
250+
251+
StepVerifier verifier1 = StepVerifier.create(loop.signalEvents())
252+
.expectNextCount(1)
253+
.thenCancel()
254+
.verifyLater();
255+
256+
loop.dispatch(message);
257+
verifier1.verify(Duration.ofSeconds(1));
258+
}
259+
193260
}

0 commit comments

Comments
 (0)