2020 * Martin Oberhuber (Wind River) - [265352][api] Allow setting fonts programmatically
2121 * Anton Leherbauer (Wind River) - [434749] UnhandledEventLoopException when copying to clipboard while the selection is empty
2222 * Davy Landman (CWI) - [475267][api] Allow custom mouse listeners
23+ * Philipp Kurrle (Advantest) - [2131] Implement word and line selection
2324 *******************************************************************************/
2425package org .eclipse .terminal .internal .textcanvas ;
2526
4344import org .eclipse .swt .graphics .Rectangle ;
4445import org .eclipse .swt .widgets .Composite ;
4546import org .eclipse .terminal .control .ITerminalMouseListener ;
47+ import org .eclipse .terminal .model .ITerminalTextDataReadOnly ;
4648import org .eclipse .terminal .model .TerminalColor ;
4749
4850/**
@@ -60,6 +62,11 @@ public class TextCanvas extends GridCanvas {
6062 private boolean fHasSelection ;
6163 private ResizeListener fResizeListener ;
6264 private final List <ITerminalMouseListener > fMouseListeners ;
65+ private SelectionMode fSelMode = SelectionMode .NONE ;
66+
67+ private enum SelectionMode {
68+ NONE , DRAG , WORD , LINE
69+ }
6370
6471 // The minSize is meant to determine the minimum size of the backing store
6572 // (grid) into which remote data is rendered. If the viewport is smaller
@@ -151,13 +158,43 @@ public void mouseDoubleClick(MouseEvent e) {
151158 for (ITerminalMouseListener l : fMouseListeners ) {
152159 l .mouseDoubleClick (fCellCanvasModel .getTerminalText (), pt .y , pt .x , e .button , e .stateMask );
153160 }
161+ selectWord (pt );
154162 }
155163 }
156164 }
157165
166+ /**
167+ * Select the word at the given point.
168+ * If no word is detected, select the single character at the point.
169+ *
170+ * @param pt
171+ * the point in cell coordinates
172+ */
173+ private void selectWord (Point pt ) {
174+ fSelMode = SelectionMode .WORD ;
175+
176+ fCellCanvasModel .expandHoverSelectionAt (pt .y , pt .x );
177+ Point start = fCellCanvasModel .getHoverSelectionStart ();
178+ Point end = fCellCanvasModel .getHoverSelectionEnd ();
179+ if (start != null && end != null ) {
180+ fHasSelection = true ;
181+ fDraggingStart = start ; // anchor at word start for drag-extend by words
182+ fCellCanvasModel .setSelectionAnchor (start );
183+ fCellCanvasModel .setSelection (start .y , end .y , start .x , end .x );
184+ } else {
185+ fHasSelection = true ;
186+ fDraggingStart = pt ;
187+ fCellCanvasModel .setSelectionAnchor (pt );
188+ fCellCanvasModel .setSelection (pt .y , pt .y , pt .x , pt .x );
189+ }
190+ // clear hover highlight after using it
191+ fCellCanvasModel .expandHoverSelectionAt (-1 , -1 );
192+ }
193+
158194 @ Override
159195 public void mouseDown (MouseEvent e ) {
160196 if (e .button == 1 ) { // left button
197+ fSelMode = SelectionMode .DRAG ;
161198 fDraggingStart = screenPointToCell (e .x , e .y );
162199 fHasSelection = false ;
163200 if ((e .stateMask & SWT .SHIFT ) != 0 ) {
@@ -178,18 +215,48 @@ public void mouseDown(MouseEvent e) {
178215 }
179216 }
180217 }
218+ if (e .count == 3 ) {
219+ selectLine (e );
220+ }
221+ }
222+
223+ private void selectLine (MouseEvent e ) {
224+ fSelMode = SelectionMode .LINE ;
225+ Point pt = screenPointToCell (e .x , e .y );
226+ if (pt != null ) {
227+ ITerminalTextDataReadOnly term = fCellCanvasModel .getTerminalText ();
228+ int row = Math .max (0 , Math .min (pt .y , term .getHeight () - 1 ));
229+ int startCol = 0 ;
230+ int endCol = Math .max (0 , term .getWidth () - 1 ); // inclusive end column
231+ Point start = new Point (startCol , row );
232+ Point end = new Point (endCol , row );
233+ fHasSelection = true ;
234+ // keep anchor at the start of the clicked line for drag-extend by lines
235+ fDraggingStart = start ;
236+ fCellCanvasModel .setSelectionAnchor (start );
237+ fCellCanvasModel .setSelection (start .y , end .y , start .x , end .x );
238+ // notify custom listeners
239+ if (fMouseListeners .size () > 0 ) {
240+ for (ITerminalMouseListener l : fMouseListeners ) {
241+ l .mouseDown (term , pt .y , pt .x , e .button , e .stateMask );
242+ }
243+ }
244+ }
181245 }
182246
183247 @ Override
184248 public void mouseUp (MouseEvent e ) {
185249 if (e .button == 1 ) { // left button
186- updateHasSelection (e );
187- if (fHasSelection ) {
188- setSelection (screenPointToCell (e .x , e .y ));
189- } else {
190- fCellCanvasModel .setSelection (-1 , -1 , -1 , -1 );
250+ if (fSelMode == SelectionMode .DRAG ) {
251+ updateHasSelection (e );
252+ if (fHasSelection ) {
253+ setSelection (screenPointToCell (e .x , e .y ));
254+ } else {
255+ fCellCanvasModel .setSelection (-1 , -1 , -1 , -1 );
256+ }
191257 }
192258 fDraggingStart = null ;
259+ fSelMode = SelectionMode .NONE ;
193260 }
194261 if (fMouseListeners .size () > 0 ) {
195262 Point pt = screenPointToCell (e .x , e .y );
@@ -203,8 +270,20 @@ public void mouseUp(MouseEvent e) {
203270 });
204271 addMouseMoveListener (e -> {
205272 if (fDraggingStart != null ) {
273+ Point curr = screenPointToCell (e .x , e .y );
206274 updateHasSelection (e );
207- setSelection (screenPointToCell (e .x , e .y ));
275+ switch (fSelMode ) {
276+ case WORD :
277+ updateWordSelection (curr );
278+ break ;
279+ case LINE :
280+ updateLineSelection (curr );
281+ break ;
282+ case DRAG :
283+ default :
284+ setSelection (curr );
285+ break ;
286+ }
208287 fCellCanvasModel .expandHoverSelectionAt (-1 , -1 );
209288 } else if ((e .stateMask & SWT .MODIFIER_MASK ) == SWT .MOD1 ) {
210289 // highlight (underline) word that would be used by MOD1 + mouse click
@@ -215,10 +294,80 @@ public void mouseUp(MouseEvent e) {
215294 }
216295 redraw ();
217296 });
218- setVerticalBarVisible (true );
297+ serVerticalBarVisible (true );
219298 setHorizontalBarVisible (false );
220299 }
221300
301+ private static class Range {
302+ final Point start ;
303+ final Point end ;
304+
305+ Range (Point start , Point end ) {
306+ this .start = start ;
307+ this .end = end ;
308+ }
309+
310+ boolean isValid () {
311+ return start != null && end != null ;
312+ }
313+ }
314+
315+ private Range wordRangeAt (Point pt ) {
316+ fCellCanvasModel .expandHoverSelectionAt (pt .y , pt .x );
317+ return new Range (fCellCanvasModel .getHoverSelectionStart (), fCellCanvasModel .getHoverSelectionEnd ());
318+ }
319+
320+ private void setNormalizedSelection (Point start , Point end ) {
321+ if (start == null || end == null ) {
322+ return ;
323+ }
324+
325+ Point s = start ;
326+ Point e = end ;
327+ if (compare (e , s ) < 0 ) { // normalize order
328+ s = end ;
329+ e = start ;
330+ }
331+ int sCol = Math .max (0 , s .x );
332+ int sRow = Math .max (0 , s .y );
333+ fCellCanvasModel .setSelection (sRow , e .y , sCol , e .x );
334+ }
335+
336+ private void updateWordSelection (Point curr ) {
337+ if (fDraggingStart == null || curr == null ) {
338+ return ;
339+ }
340+ // Determine word boundaries at anchor and current positions
341+
342+ Range anchor = wordRangeAt (fDraggingStart );
343+ Range current = wordRangeAt (curr );
344+ if (!anchor .isValid () || !current .isValid ()) {
345+ return ;
346+ }
347+
348+ if (compare (current .start , anchor .start ) < 0 ) {
349+ setNormalizedSelection (current .start , anchor .end );
350+ } else {
351+ setNormalizedSelection (anchor .start , current .end );
352+ }
353+ fHasSelection = true ;
354+ // clear hover underline
355+ fCellCanvasModel .expandHoverSelectionAt (-1 , -1 );
356+ }
357+
358+ private void updateLineSelection (Point curr ) {
359+ if (fDraggingStart == null || curr == null ) {
360+ return ;
361+ }
362+ ITerminalTextDataReadOnly term = fCellCanvasModel .getTerminalText ();
363+ int startRow = Math .max (0 , Math .min (fDraggingStart .y , curr .y ));
364+ int endRow = Math .max (0 , Math .max (fDraggingStart .y , curr .y ));
365+ int endCol = Math .max (0 , term .getWidth () - 1 );
366+ setNormalizedSelection (new Point (0 , startRow ), new Point (endCol , endRow ));
367+ fCellCanvasModel .setSelectionAnchor (new Point (0 , fDraggingStart .y ));
368+ fHasSelection = true ;
369+ }
370+
222371 /**
223372 * The user has to drag the mouse to at least one character to make a selection.
224373 * Once this is done, even a one char selection is OK.
@@ -544,5 +693,4 @@ public void removeTerminalMouseListener(ITerminalMouseListener listener) {
544693 public String getHoverSelection () {
545694 return fCellCanvasModel .getHoverSelectionText ();
546695 }
547-
548696}
0 commit comments