@@ -148,6 +148,22 @@ const TerminalPage: FC = () => {
148148} ) ,
149149) ;
150150
151+ // Make shift+enter send ^[^M (escaped carriage return). Applications
152+ // typically take this to mean to insert a literal newline. There is no way
153+ // to remove this handler, so we must attach it once and rely on a ref to
154+ // send it to the current socket.
155+ terminal . attachCustomKeyEventHandler ( ( ev ) => {
156+ if ( ev . shiftKey && ev . key === "Enter" ) {
157+ if ( ev . type === "keydown" ) {
158+ websocketRef . current ?. send (
159+ new TextEncoder ( ) . encode ( JSON . stringify ( { data :"\x1b\r" } ) ) ,
160+ ) ;
161+ }
162+ return false ;
163+ }
164+ return true ;
165+ } ) ;
166+
151167terminal . open ( terminalWrapperRef . current ) ;
152168
153169// We have to fit twice here. It's unknown why, but the first fit will
@@ -190,6 +206,7 @@ const TerminalPage: FC = () => {
190206} , [ navigate , reconnectionToken , searchParams ] ) ;
191207
192208// Hook up the terminal through a web socket.
209+ const websocketRef = useRef < Websocket > ( ) ;
193210useEffect ( ( ) => {
194211if ( ! terminal ) {
195212return ;
@@ -270,6 +287,7 @@ const TerminalPage: FC = () => {
270287. withBackoff ( new ExponentialBackoff ( 1000 , 6 ) )
271288. build ( ) ;
272289websocket . binaryType = "arraybuffer" ;
290+ websocketRef . current = websocket ;
273291websocket . addEventListener ( WebsocketEvent . open , ( ) => {
274292// Now that we are connected, allow user input.
275293terminal . options = {
@@ -333,6 +351,7 @@ const TerminalPage: FC = () => {
333351d . dispose ( ) ;
334352}
335353websocket ?. close ( 1000 ) ;
354+ websocketRef . current = undefined ;
336355} ;
337356} , [
338357command ,