1+ /*-
2+ * #%L
3+ * XTerm Selection Addon
4+ * %%
5+ * Copyright (C) 2020 - 2021 Flowing Code
6+ * %%
7+ * Licensed under the Apache License, Version 2.0 (the "License");
8+ * you may not use this file except in compliance with the License.
9+ * You may obtain a copy of the License at
10+ *
11+ * http://www.apache.org/licenses/LICENSE-2.0
12+ *
13+ * Unless required by applicable law or agreed to in writing, software
14+ * distributed under the License is distributed on an "AS IS" BASIS,
15+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+ * See the License for the specific language governing permissions and
17+ * limitations under the License.
18+ * #L%
19+ */
20+ import { Terminal } from 'xterm'
21+ import { TerminalMixin , TerminalAddon } from '@vaadin/flow-frontend/fc-xterm/xterm-element' ;
22+ import { IConsoleMixin } from '@vaadin/flow-frontend/fc-xterm/xterm-console-mixin' ;
23+
24+ interface ISelectionMixin extends TerminalMixin {
25+ keyboardSelectionEnabled : boolean ;
26+ }
27+
28+ class SelectionAddon extends TerminalAddon < ISelectionMixin > {
29+
30+ __selectionLength : number ;
31+ __selectionAnchor : number ;
32+ __selectionRight : boolean = true ;
33+
34+ activateCallback ( terminal : Terminal ) : void {
35+
36+ var inputHandler = ( ( this . $core ) as any ) . _inputHandler ;
37+
38+ let resetSelection = ( ) => {
39+ this . __selectionAnchor = undefined ;
40+ }
41+
42+ let clearSelection = ( ) => {
43+ if ( ! this . $ . keyboardSelectionEnabled ) return ;
44+ resetSelection ( ) ;
45+ terminal . clearSelection ( ) ;
46+ }
47+
48+ let ensureSelection = ( ) => {
49+ if ( this . __selectionAnchor === undefined ) {
50+ let buffer = inputHandler . _bufferService . buffer ;
51+ this . __selectionAnchor = buffer . y * terminal . cols + buffer . x ; ;
52+ this . __selectionLength = 0 ;
53+ }
54+ } ;
55+
56+ let moveSelection = ( dx :number , dy :number = 0 ) => {
57+ if ( ! this . $ . keyboardSelectionEnabled ) return ;
58+ ensureSelection ( ) ;
59+
60+ let newSelectionLength = this . __selectionLength ;
61+ if ( this . __selectionRight ) {
62+ newSelectionLength += dx + dy * terminal . cols ;
63+ } else {
64+ newSelectionLength -= dx + dy * terminal . cols ;
65+ }
66+
67+ if ( newSelectionLength < 0 ) {
68+ newSelectionLength = - newSelectionLength ;
69+ this . __selectionRight = ! this . __selectionRight ;
70+ }
71+
72+ let newSelectionStart = this . __selectionAnchor ;
73+ if ( ! this . __selectionRight ) {
74+ newSelectionStart -= newSelectionLength ;
75+ }
76+
77+ if ( newSelectionStart < 0 ) return ;
78+ if ( newSelectionStart + newSelectionLength > terminal . buffer . active . length * terminal . cols ) return ;
79+
80+ let row = Math . floor ( newSelectionStart / terminal . cols ) ;
81+ let col = newSelectionStart % terminal . cols ;
82+
83+ this . __selectionLength = newSelectionLength ;
84+ terminal . select ( col , row , newSelectionLength ) ;
85+ } ;
86+
87+ let selectLeft = ( ) => moveSelection ( - 1 ) ;
88+ let selectRight = ( ) => moveSelection ( + 1 ) ;
89+ let selectUp = ( ) => moveSelection ( 0 , - 1 ) ;
90+ let selectDown = ( ) => moveSelection ( 0 , + 1 ) ;
91+
92+ let promptLength = ( ) => ( this . $ as unknown as IConsoleMixin ) . prompt ?. length || 0 ;
93+
94+ let selectHome = ( ) => {
95+ if ( ! this . $ . keyboardSelectionEnabled ) return ;
96+
97+ let buffer = ( terminal . buffer . active as any ) . _buffer ;
98+ let range = buffer . getWrappedRangeForLine ( buffer . ybase + buffer . y ) ;
99+
100+ let pos = terminal . getSelectionPosition ( ) || { startRow : buffer . ybase + buffer . y , startColumn : buffer . x } ;
101+
102+ resetSelection ( ) ;
103+ ensureSelection ( ) ;
104+ let dx = range . first * terminal . cols - this . __selectionAnchor ;
105+ if ( pos . startRow != range . first || pos . startColumn != promptLength ( ) ) {
106+ dx += promptLength ( ) ;
107+ }
108+
109+ moveSelection ( dx ) ;
110+ } ;
111+
112+ let selectEnd = ( ) => {
113+ if ( ! this . $ . keyboardSelectionEnabled ) return ;
114+
115+ let buffer = ( terminal . buffer . active as any ) . _buffer ;
116+ let range = buffer . getWrappedRangeForLine ( buffer . ybase + buffer . y ) ;
117+
118+ resetSelection ( ) ;
119+ ensureSelection ( ) ;
120+ moveSelection ( range . last * terminal . cols + buffer . lines . get ( range . last ) . getTrimmedLength ( ) - this . __selectionAnchor ) ;
121+ } ;
122+
123+ let deleteSelection = ( ev : KeyboardEvent ) => {
124+ if ( ! this . $ . keyboardSelectionEnabled ) return ;
125+ if ( this . __selectionAnchor !== undefined ) {
126+ let buffer = ( terminal . buffer . active as any ) . _buffer ;
127+ let range = buffer . getWrappedRangeForLine ( buffer . ybase + buffer . y ) ;
128+ let pos = terminal . getSelectionPosition ( ) ;
129+ if ( pos && pos . startRow >= range . first && pos . endRow <= range . last ) {
130+ //let selectionStart = pos.startRow * terminal.cols + pos.startColumn;
131+ if ( ! this . __selectionRight ) {
132+ //cursor backward wrapped
133+ terminal . write ( "\x1b[<" + this . __selectionLength + "L" ) ;
134+ }
135+ //delete characters wrapped
136+ terminal . write ( "\x1b[<" + this . __selectionLength + "D" ) ;
137+ ev . stopImmediatePropagation ( ) ;
138+ }
139+ }
140+ clearSelection ( ) ;
141+ } ;
142+
143+ let hasModifiers = ( ev :KeyboardEvent ) => ev . shiftKey || ev . altKey || ev . metaKey || ev . ctrlKey ;
144+
145+ this . _disposables = [
146+ ( this . $core as any ) . coreService . onUserInput ( ( ) => clearSelection ) ,
147+ this . $node . customKeyEventHandlers . register ( ev => ev . key == 'ArrowLeft' && ev . shiftKey , selectLeft ) ,
148+ this . $node . customKeyEventHandlers . register ( ev => ev . key == 'ArrowRight' && ev . shiftKey , selectRight ) ,
149+ this . $node . customKeyEventHandlers . register ( ev => ev . key == 'ArrowUp' && ev . shiftKey , selectUp ) ,
150+ this . $node . customKeyEventHandlers . register ( ev => ev . key == 'ArrowDown' && ev . shiftKey , selectDown ) ,
151+ this . $node . customKeyEventHandlers . register ( ev => ev . key == 'Home' && ev . shiftKey , selectHome ) ,
152+ this . $node . customKeyEventHandlers . register ( ev => ev . key == 'End' && ev . shiftKey , selectEnd ) ,
153+
154+ this . $node . customKeyEventHandlers . register ( ev => ev . key == 'ArrowLeft' && ! hasModifiers ( ev ) , clearSelection ) ,
155+ this . $node . customKeyEventHandlers . register ( ev => ev . key == 'ArrowRight' && ! hasModifiers ( ev ) , clearSelection ) ,
156+ this . $node . customKeyEventHandlers . register ( ev => ev . key == 'ArrowUp' && ! hasModifiers ( ev ) , clearSelection ) ,
157+ this . $node . customKeyEventHandlers . register ( ev => ev . key == 'ArrowDown' && ! hasModifiers ( ev ) , clearSelection ) ,
158+ this . $node . customKeyEventHandlers . register ( ev => ev . key == 'Home' && ! hasModifiers ( ev ) , clearSelection ) ,
159+ this . $node . customKeyEventHandlers . register ( ev => ev . key == 'End' && ! hasModifiers ( ev ) , clearSelection ) ,
160+
161+ this . $node . customKeyEventHandlers . register ( ev => ev . key == 'Delete' && ! hasModifiers ( ev ) , deleteSelection ) ,
162+ this . $node . customKeyEventHandlers . register ( ev => ev . key == 'Backspace' && ! hasModifiers ( ev ) , deleteSelection ) ,
163+ ] ;
164+
165+ }
166+
167+ }
168+
169+ type Constructor < T = { } > = new ( ...args : any [ ] ) => T ;
170+ export function XTermSelectionMixin < TBase extends Constructor < TerminalMixin > > ( Base : TBase ) {
171+ return class XTermSelectionMixin extends Base implements ISelectionMixin {
172+
173+ keyboardSelectionEnabled : boolean = true ;
174+
175+ connectedCallback ( ) {
176+ super . connectedCallback ( ) ;
177+ let addon = new SelectionAddon ( ) ;
178+ addon . $ = this ;
179+ this . node . terminal . loadAddon ( addon ) ;
180+ }
181+
182+ }
183+ }
0 commit comments