@@ -5,9 +5,11 @@ import sharedbAce from 'sharedb-ace';
55
66import 'ace-builds/src-noconflict/ext-language_tools' ;
77import 'ace-builds/src-noconflict/ext-searchbox' ;
8+ import { createContext , getAllOccurrencesInScope } from 'js-slang' ;
89import { HighlightRulesSelector , ModeSelector } from 'js-slang/dist/editors/ace/modes/source' ;
910import 'js-slang/dist/editors/ace/theme/source' ;
1011import { LINKS } from '../../utils/constants' ;
12+ import AceRange from './AceRange' ;
1113import { checkSessionIdExists } from './collabEditing/helper' ;
1214
1315/**
@@ -45,18 +47,21 @@ export interface IPosition {
4547class Editor extends React . PureComponent < IEditorProps , { } > {
4648 public ShareAce : any ;
4749 public AceEditor : React . RefObject < AceEditor > ;
50+ private markerIds : number [ ] ;
4851 private onChangeMethod : ( newCode : string ) => void ;
4952 private onValidateMethod : ( annotations : IAnnotation [ ] ) => void ;
5053
5154 constructor ( props : IEditorProps ) {
5255 super ( props ) ;
5356 this . AceEditor = React . createRef ( ) ;
5457 this . ShareAce = null ;
58+ this . markerIds = [ ] ;
5559 this . onChangeMethod = ( newCode : string ) => {
5660 if ( this . props . handleUpdateHasUnsavedChanges ) {
5761 this . props . handleUpdateHasUnsavedChanges ( true ) ;
5862 }
5963 this . props . handleEditorValueChange ( newCode ) ;
64+ this . handleVariableHighlighting ( ) ;
6065 } ;
6166 this . onValidateMethod = ( annotations : IAnnotation [ ] ) => {
6267 if ( this . props . isEditorAutorun && annotations . length === 0 ) {
@@ -116,6 +121,8 @@ class Editor extends React.PureComponent<IEditorProps, {}> {
116121 if ( this . props . editorSessionId !== '' ) {
117122 this . handleStartCollabEditing ( editor ) ;
118123 }
124+
125+ this . handleVariableHighlighting ( ) ;
119126 }
120127
121128 public componentWillUnmount ( ) {
@@ -181,6 +188,14 @@ class Editor extends React.PureComponent<IEditorProps, {}> {
181188 mac : 'Command-B'
182189 } ,
183190 exec : this . handleNavigate
191+ } ,
192+ {
193+ name : 'refactor' ,
194+ bindKey : {
195+ win : 'Ctrl-M' ,
196+ mac : 'Command-M'
197+ } ,
198+ exec : this . handleRefactor
184199 }
185200 ] }
186201 editorProps = { {
@@ -193,6 +208,7 @@ class Editor extends React.PureComponent<IEditorProps, {}> {
193208 highlightActiveLine = { false }
194209 mode = { this . chapterNo ( ) } // select according to props.sourceChapter
195210 onChange = { this . onChangeMethod }
211+ onCursorChange = { this . handleVariableHighlighting }
196212 onValidate = { this . onValidateMethod }
197213 theme = "source"
198214 value = { this . props . editorValue }
@@ -230,6 +246,59 @@ class Editor extends React.PureComponent<IEditorProps, {}> {
230246 }
231247 } ;
232248
249+ private handleRefactor = ( ) => {
250+ const editor = ( this . AceEditor . current as any ) . editor ;
251+ if ( ! editor ) {
252+ return ;
253+ }
254+ const code = this . props . editorValue ;
255+ const chapter = this . props . sourceChapter ;
256+ const position = editor . getCursorPosition ( ) ;
257+
258+ const sourceLocations = getAllOccurrencesInScope ( code , createContext ( chapter ) , {
259+ line : position . row + 1 , // getCursorPosition returns 0-indexed row, function here takes in 1-indexed row
260+ column : position . column
261+ } ) ;
262+
263+ const selection = editor . getSelection ( ) ;
264+ const ranges = sourceLocations . map (
265+ loc => new AceRange ( loc . start . line - 1 , loc . start . column , loc . end . line - 1 , loc . end . column )
266+ ) ;
267+ ranges . forEach ( range => selection . addRange ( range ) ) ;
268+ } ;
269+
270+ private handleVariableHighlighting = ( ) => {
271+ // using Ace Editor's way of highlighting as seen here: https://github.com/ajaxorg/ace/blob/master/lib/ace/editor.js#L497
272+ // We use async blocks so we don't block the browser during editing
273+
274+ setTimeout ( ( ) => {
275+ const editor = ( this . AceEditor . current as any ) . editor ;
276+ const session = editor . session ;
277+ const code = this . props . editorValue ;
278+ const chapterNumber = this . props . sourceChapter ;
279+ const position = editor . getCursorPosition ( ) ;
280+ if ( ! session || ! session . bgTokenizer ) {
281+ return ;
282+ }
283+ this . markerIds . forEach ( id => {
284+ session . removeMarker ( id ) ;
285+ } ) ;
286+ const ranges = getAllOccurrencesInScope ( code , createContext ( chapterNumber ) , {
287+ line : position . row + 1 ,
288+ column : position . column
289+ } ) . map (
290+ loc => new AceRange ( loc . start . line - 1 , loc . start . column , loc . end . line - 1 , loc . end . column )
291+ ) ;
292+
293+ const markerType = 'ace_variable_highlighting' ;
294+ const markerIds = ranges . map ( range => {
295+ // returns the marker ID for removal later
296+ return session . addMarker ( range , markerType , 'text' ) ;
297+ } ) ;
298+ this . markerIds = markerIds ;
299+ } , 10 ) ;
300+ } ;
301+
233302 private handleGutterClick = ( e : any ) => {
234303 const target = e . domEvent . target ;
235304 if (
0 commit comments