@@ -3,9 +3,49 @@ import { useSelector, useDispatch } from 'react-redux';
33
44import * as actions from './actions' ;
55import AdvancedEditor from './AdvancedEditor' ;
6- import { CommonEditorProps , Editor as EditorType } from './types' ;
6+ import { CommonEditorProps , Editor as EditorType , Position , Selection } from './types' ;
77import { State } from './reducers' ;
88
9+ class CodeByteOffsets {
10+ readonly code : string ;
11+ readonly lines : string [ ] ;
12+
13+ constructor ( code : string ) {
14+ this . code = code ;
15+ this . lines = code . split ( '\n' ) ;
16+ }
17+
18+ public lineToOffsets ( line : number ) {
19+ const precedingBytes = this . bytesBeforeLine ( line ) ;
20+
21+ const highlightedLine = this . lines [ line ] ;
22+ const highlightedBytes = highlightedLine . length ;
23+
24+ return [ precedingBytes , precedingBytes + highlightedBytes ] ;
25+ }
26+
27+ public rangeToOffsets ( start : Position , end : Position ) {
28+ const startBytes = this . positionToBytes ( start ) ;
29+ const endBytes = this . positionToBytes ( end ) ;
30+ return [ startBytes , endBytes ] ;
31+ }
32+
33+ private positionToBytes ( position : Position ) {
34+ // Subtract one as this logic is zero-based and the columns are one-based
35+ return this . bytesBeforeLine ( position . line ) + position . column - 1 ;
36+ }
37+
38+ private bytesBeforeLine ( line : number ) {
39+ // Subtract one as this logic is zero-based and the lines are one-based
40+ line -= 1 ;
41+
42+ const precedingLines = this . lines . slice ( 0 , line ) ;
43+
44+ // Add one to account for the newline we split on and removed
45+ return precedingLines . map ( l => l . length + 1 ) . reduce ( ( a , b ) => a + b ) ;
46+ }
47+ }
48+
949class SimpleEditor extends React . PureComponent < CommonEditorProps > {
1050 private _editor : HTMLTextAreaElement ;
1151
@@ -35,35 +75,41 @@ class SimpleEditor extends React.PureComponent<CommonEditorProps> {
3575
3676 public componentDidUpdate ( prevProps , _prevState ) {
3777 this . gotoPosition ( prevProps . position , this . props . position ) ;
78+ this . setSelection ( prevProps . selection , this . props . selection ) ;
3879 }
3980
40- private gotoPosition ( oldPosition , newPosition ) {
81+ private gotoPosition ( oldPosition : Position , newPosition : Position ) {
4182 const editor = this . _editor ;
4283
4384 if ( ! newPosition || ! editor ) { return ; }
4485 if ( newPosition === oldPosition ) { return ; }
4586
46- // Subtract one as this logix is zero-based and the lines are one-based
47- const line = newPosition . line - 1 ;
48- const { code } = this . props ;
87+ const offsets = new CodeByteOffsets ( this . props . code ) ;
88+ const [ startBytes , endBytes ] = offsets . lineToOffsets ( newPosition . line ) ;
4989
50- const lines = code . split ( '\n' ) ;
90+ editor . focus ( ) ;
91+ editor . setSelectionRange ( startBytes , endBytes ) ;
92+ }
5193
52- const precedingLines = lines . slice ( 0 , line ) ;
53- const highlightedLine = lines [ line ] ;
94+ private setSelection ( oldSelection : Selection , newSelection : Selection ) {
95+ const editor = this . _editor ;
5496
55- // Add one to account for the newline we split on and removed
56- const precedingBytes = precedingLines . map ( l => l . length + 1 ) . reduce ( ( a , b ) => a + b ) ;
57- const highlightedBytes = highlightedLine . length ;
97+ if ( ! newSelection || ! editor ) { return ; }
98+ if ( newSelection === oldSelection ) { return ; }
99+
100+ const offsets = new CodeByteOffsets ( this . props . code ) ;
101+ const [ startBytes , endBytes ] = offsets . rangeToOffsets ( newSelection . start , newSelection . end ) ;
58102
59- editor . setSelectionRange ( precedingBytes , precedingBytes + highlightedBytes ) ;
103+ editor . focus ( ) ;
104+ editor . setSelectionRange ( startBytes , endBytes ) ;
60105 }
61106}
62107
63108const Editor : React . SFC = ( ) => {
64109 const code = useSelector ( ( state : State ) => state . code ) ;
65110 const editor = useSelector ( ( state : State ) => state . configuration . editor ) ;
66111 const position = useSelector ( ( state : State ) => state . position ) ;
112+ const selection = useSelector ( ( state : State ) => state . selection ) ;
67113 const crates = useSelector ( ( state : State ) => state . crates ) ;
68114
69115 const dispatch = useDispatch ( ) ;
@@ -76,6 +122,7 @@ const Editor: React.SFC = () => {
76122 < div className = "editor" >
77123 < SelectedEditor code = { code }
78124 position = { position }
125+ selection = { selection }
79126 crates = { crates }
80127 onEditCode = { onEditCode }
81128 execute = { execute } />
0 commit comments