otsdaq_utilities  v2_05_02_indev
CodeEditor.js
1 
2 
3 
4  // Description of Code Editor Functionality/Behavior:
5  //
6  // Folder Icon in top-left (and again if split pane for right side, or bottom)
7  // (when file open) Save Icon in top-left (and again if split pane for right side, or bottom)
8  //
9  // Split Pane Icon in top-right (toggle horiz/vert split)
10  // Incremental Build Icon in top-right (mrb b)
11  // Clean Build Icon in top-right (mrb z)
12  //
13  // Use console to view build results
14  //
15  // Recent files at start of folder display
16  //
17  // Selecting a file in folder, opens the Code Editor Box for that file.
18  // - Save Icon then appears next to Folder Icon
19 
20 
21 
22 var CodeEditor = CodeEditor || {}; //define CodeEditor namespace
23 
24 if (typeof DesktopContent == 'undefined')
25  throw('ERROR: DesktopContent is undefined! Must include DesktopContent.js before CodeEditor.js');
26 
27 
28 CodeEditor.MENU_PRIMARY_COLOR = "rgb(220, 187, 165)";
29 CodeEditor.MENU_SECONDARY_COLOR = "rgb(130, 51, 51)";
30 
31 
32 CodeEditor.editor; //this is THE CodeEditor variable
33 
34 
35 
36 //htmlOpen(tag,attObj,innerHTML,closeTag)
37 //htmlClearDiv()
38 
39 //=====================================================================================
40 //htmlOpen ~~
41 // tab name and attribute/value map object
42 function htmlOpen(tag,attObj,innerHTML,doCloseTag)
43 {
44  var str = "";
45  var attKeys = attObj?Object.keys(attObj):[];
46  str += "<" + tag + " ";
47  for(var i=0;i<attKeys.length;++i)
48  str += " " + attKeys[i] + "='" +
49  attObj[attKeys[i]] + "' ";
50  str += ">";
51  if(innerHTML) str += innerHTML;
52  if(doCloseTag)
53  str += "</" + tag + ">";
54  return str;
55 } // end htmlOpen()
56 
57 //=====================================================================================
58 //htmlClearDiv ~~
59 function htmlClearDiv()
60 {
61  return "<div id='clearDiv'></div>";
62 } //end htmlClearDiv()
63 
64 
65 
66 //=====================================================================================
67 //define scrollIntoViewIfNeeded for Firefox
68 // NOTE: Chrome broke this functionality in Version 76.0.3809.100 (Official Build) (64-bit)
69 // so just always redefine this behavior.
70 if (1 || !Element.prototype.scrollIntoViewIfNeeded)
71 {
72  Element.prototype.scrollIntoViewIfNeeded = function (centerIfNeeded)
73  {
74  centerIfNeeded = arguments.length === 0 ? true : !!centerIfNeeded;
75 
76  var parent = this.parentNode,
77  tdParent = parent.parentNode,
78  editorParent = parent.parentNode.parentNode.parentNode.parentNode.parentNode, //"textEditorBody" which limits view
79  parentComputedStyle = window.getComputedStyle(parent, null),
80  parentBorderTopWidth = parseInt(parentComputedStyle.getPropertyValue('border-top-width')),
81  parentBorderLeftWidth = parseInt(parentComputedStyle.getPropertyValue('border-left-width')),
82  overTop = this.offsetTop - tdParent.offsetTop < editorParent.scrollTop,//this.offsetTop - parent.offsetTop < parent.scrollTop,
83  overBottom = (this.offsetTop - tdParent.offsetTop + this.clientHeight - parentBorderTopWidth) > (editorParent.scrollTop + editorParent.clientHeight), //(this.offsetTop - parent.offsetTop + this.clientHeight - parentBorderTopWidth) > (parent.scrollTop + parent.clientHeight),
84  overLeft = this.offsetLeft - tdParent.offsetLeft < editorParent.scrollLeft, //this.offsetLeft - parent.offsetLeft < parent.scrollLeft,
85  overRight = (this.offsetLeft + tdParent.offsetLeft + this.clientWidth - parentBorderLeftWidth) > (editorParent.scrollLeft + editorParent.clientWidth), // (parent.scrollLeft + parent.clientWidth),// (this.offsetLeft - parent.offsetLeft + this.clientWidth - parentBorderLeftWidth) > (parent.scrollLeft + editorParent.clientWidth - tdParent.offsetLeft), // (parent.scrollLeft + parent.clientWidth),
86  alignWithTop = overTop && !overBottom;
87 
88  if ((overTop || overBottom) && centerIfNeeded)
89  {
90  editorParent.scrollTop = this.offsetTop - tdParent.offsetTop - editorParent.clientHeight / 2 - parentBorderTopWidth + this.clientHeight / 2;
91  //parent.scrollTop = this.offsetTop - parent.offsetTop - parent.clientHeight / 2 - parentBorderTopWidth + this.clientHeight / 2;
92  }
93 
94  if ((overLeft || overRight) && centerIfNeeded)
95  {
96  editorParent.scrollLeft = this.offsetLeft + tdParent.offsetLeft - editorParent.clientWidth / 2 - parentBorderLeftWidth + this.clientWidth / 2;
97  //parent.scrollLeft = this.offsetLeft - parent.offsetLeft - parent.clientWidth / 2 - parentBorderLeftWidth + this.clientWidth / 2;
98  }
99 
100  if ((overTop || overBottom || overLeft || overRight) && !centerIfNeeded)
101  {
102  this.scrollIntoView(alignWithTop);
103  }
104  };
105 } //end define scrollIntoViewIfNeeded
106 
107 
108 //=====================================================================================
109 //showTooltip ~~
110 var windowTooltip = "Welcome to the Code Editor user interface. " +
111  "Edit your code, save it, and compile!\n\n" +
112  "Hover your mouse over the icons and buttons to see what they do. " +
113  "If you hover your mouse over the filename additional icons will appear for changing the filename, downloading, uploading, undo, and redo. The buttons in the top corners are described below followed by hot-keys:\n\n" +
114  "<INDENT>" +
115  "<b>Open a file:</b>\n<INDENT>Use the folder icon in the top-left to navigate to a code file to edit.</INDENT>\n" +
116  "<b>Toggle view:</b>\n<INDENT>Use the split-pane icon in the top-right to toggle another code editor in the same window.</INDENT>\n" +
117  "<b>Save:</b>\n<INDENT>Use the save icon in the top-left to save your changes.</INDENT>\n" +
118  "<b>Compile:</b>\n<INDENT>Use the Incremmental Build or Clean Build icons in the top-right.</INDENT>\n" +
119 
120  "<b>Global Hot Keys:</b>\n<INDENT>" +
121 
122  "<table border=0 cellspacing=0 cellpadding=0 style='border: 1px solid grey;'>" +
123  "<tr style='background-color: rgb(106, 102, 119);'><td style='white-space: nowrap; padding:5px;'> " +
124  "Ctrl + B </td><td style='padding:5px'> ==> </td><td style='padding:5px'> Incremental Build</td></tr>" +
125  "<tr><td style='white-space: nowrap; padding:5px;'> " +
126  "Ctrl + N </td><td style='padding:5px'> ==> </td><td style='padding:5px'> Clean Build</td></tr>" +
127  "<tr style='background-color: rgb(106, 102, 119);'><td style='white-space: nowrap; padding:5px;'> " +
128  "Ctrl + 2 </td><td style='padding:5px'> ==> </td><td style='padding:5px'> Toggle Split-View Mode (single, dual-vertical, dual-horizontal)</td></tr>" +
129  "</table></INDENT>\n" +
130 
131 
132  "<b>Editor Pane Hot Keys:</b>\n<INDENT>" +
133 
134  "<table border=0 cellspacing=0 cellpadding=0 style='border: 1px solid grey;'>" +
135 
136  "<tr style='background-color: rgb(106, 102, 119);'><td style='white-space: nowrap; padding:5px;'> " +
137  "Ctrl + S </td><td style='padding:5px'> ==> </td><td style='padding:5px'> Save File</td></tr>" +
138 
139  "<tr><td style='white-space: nowrap; padding:5px;'> " +
140  "Ctrl + D </td><td style='padding:5px'> ==> </td><td style='padding:5px'> Toggle Directory Navigation</td></tr>" +
141 
142  "<tr style='background-color: rgb(106, 102, 119);'><td style='white-space: nowrap; padding:5px;'> " +
143  "Ctrl + F </td><td style='padding:5px'> ==> </td><td style='padding:5px'> Find & Replace</td></tr>" +
144 
145  "<tr><td style='white-space: nowrap; padding:5px;'> " +
146  "Ctrl + U </td><td style='padding:5px'> ==> </td><td style='padding:5px'> Undo Text Editing</td></tr>" +
147 
148  "<tr style='background-color: rgb(106, 102, 119);'><td style='white-space: nowrap; padding:5px;'> " +
149  "Shift + Ctrl + U </td><td style='padding:5px'> ==> </td><td style='padding:5px'> Redo Text Editing</td></tr>" +
150 
151  "<tr><td style='white-space: nowrap; padding:5px;'> " +
152  "Ctrl + L or G </td><td style='padding:5px'> ==> </td><td style='padding:5px'> Goto Line Number</td></tr>" +
153 
154  "<tr style='background-color: rgb(106, 102, 119);'><td style='white-space: nowrap; padding:5px;'> " +
155  "Ctrl + 1 or ; </td><td style='padding:5px'> ==> </td><td style='padding:5px'> Switch to Related File (associated .h or .cc)</td></tr>" +
156 
157  "<tr><td style='white-space: nowrap; padding:5px;'> " +
158  "Ctrl + 0 or &apos; </td><td style='padding:5px'> ==> </td><td style='padding:5px'> Reload Current File from Server</td></tr>" +
159 
160  "</table></INDENT>\n" +
161 
162 
163  "<b>Selected-Text Hot Keys:</b>\n<INDENT>" +
164 
165  "<table border=0 cellspacing=0 cellpadding=0 style='border: 1px solid grey;'>" +
166  "<tr style='background-color: rgb(106, 102, 119);'><td style='white-space: nowrap; padding:5px;'> TAB</td><td style='padding:5px'> ==> </td><td style='padding:5px'> Add leading TAB character to all highlighted lines.</td></tr>" +
167  "<tr><td style='white-space: nowrap; padding:5px;'> Shift + TAB </td><td style='padding:5px'> ==> </td><td style='padding:5px'> Remove leading TAB character from all highlighted lines.</td></tr>" +
168  "<tr style='background-color: rgb(106, 102, 119);'><td style='white-space: nowrap; padding:5px;'> Ctrl + T or Y </td><td style='padding:5px'> ==> </td><td style='padding:5px'> Add TAB character at starting cursor position of all highlighted line (i.e. Block Tab effect).</td></tr>" +
169  "<tr><td style='white-space: nowrap; padding:5px;'> Shift + Ctrl + T or Y</td><td style='padding:5px'> ==> </td><td style='padding:5px'> Remove TAB character from starting cursor position of all highlighted line (i.e. reverse Block Tab effect).</td></tr>" +
170  "<tr style='background-color: rgb(106, 102, 119);'><td style='white-space: nowrap; padding:5px;'> Ctrl + / </td><td style='padding:5px'> ==> </td><td style='padding:5px'> Add leading comment character(s) to all highlighted lines.</td></tr>" +
171  "<tr><td style='white-space: nowrap; padding:5px;'> Shift + Ctrl + / </td><td style='padding:5px'> ==> </td><td style='padding:5px'> Remove leading comment character(s) to all highlighted lines.</td></tr>" +
172  "<tr style='background-color: rgb(106, 102, 119);'><td style='white-space: nowrap; padding:5px;'> Ctrl + I </td><td style='padding:5px'> ==> </td><td style='padding:5px'> Auto-indent all highlighted lines.</td></tr>" +
173  "</table></INDENT>\n" +
174  "If you are an admin and want to set Code Editor in viewer mode for other users i,e. 'Code Viewer,' go to 'Desktop Icon Table' in Configure to set the parameter 'readOnlyMode.'" +
175  "</INDENT>"
176  ;
177 
178 var windowViewModeTooltip = "Welcome to the Code Viewer user interface. " +
179  "You will only be able to view codes without modifying. Your text inputs won't be saved. " +
180  "Contact your administrator if you think you should have modification access."+
181  "<INDENT>\n" +
182  "<b>Open a file:</b>\n<INDENT>Use the folder icon in the top-left to navigate to a code file to edit.</INDENT>\n" +
183  "<b>Toggle view:</b>\n<INDENT>Use the split-pane icon in the top-right to toggle another code editor in the same window.</INDENT>\n" +
184 
185  "<b>Viewer Hot Keys:</b>\n<INDENT>" +
186 
187  "<table border=0 cellspacing=0 cellpadding=0 style='border: 1px solid grey;'>" +
188 
189  "<tr style='background-color: rgb(106, 102, 119);'><td style='white-space: nowrap; padding:5px;'> " +
190  "Ctrl + 2 </td><td style='padding:5px'> ==> </td><td style='padding:5px'> Toggle Split-View Mode (single, dual-vertical, dual-horizontal)</td></tr>" +
191 
192  "<tr><td style='white-space: nowrap; padding:5px;'> " +
193  "Ctrl + L or G </td><td style='padding:5px'> ==> </td><td style='padding:5px'> Goto Line Number</td></tr>" +
194 
195  "<tr style='background-color: rgb(106, 102, 119);'><td style='white-space: nowrap; padding:5px;'> " +
196  "Ctrl + F </td><td style='padding:5px'> ==> </td><td style='padding:5px'> Find</td></tr>" +
197  "</table></INDENT>\n" +
198 
199  "</INDENT>";
200 
201 appMode = "Code Editor";
202 
203 CodeEditor.showTooltip = function(alwaysShow)
204 {
205  DesktopContent.tooltip(
206  (alwaysShow? "ALWAYS" : appMode), windowTooltip);
207 
208  DesktopContent.setWindowTooltip(windowTooltip);
209 } //end showTooltip()
210 
211 
212 
215 //call create to create instance of a SmartLaunch
218 CodeEditor.create = function(standAlone) {
219 
220  //STAND ALONE mode allows for single upload/download file features only
221  // no directory notion.
222  console.log("standAlone",standAlone);
223  var _STAND_ALONE = standAlone;
224 
225  //outline:
226  //
227  // "private":
228  // ================
229  // init()
230  // redrawWindow()
231  // createElements()
232  // localCreatePaneControls()
233  // createTextEditor(forPrimary)
234  // createDirectoryNav(forPrimary)
235 
236  // "public":
237  // ================
238  // showTooltip(alwaysShow)
239  // toggleDirectoryNav(forPrimary,v)
240  // saveFile(forPrimary)
241  // toggleView(v)
242  // build(cleanBuild)
243  // undo(forPrimary,redo)
244  // openDirectory(forPrimary,path,doNotOpenPane)
245  // handleDirectoryContent(forPrimary,req)
246  // openFile(forPrimary,path,extension,doConfirm,gotoLine,altPaths,altExtensions,propagateErr)
247  // openRelatedFile(forPrimary,inOtherPane)
248  // gotoLine(forPrimary,line,selectionCursor,topOfView)
249  // handleFileContent(forPrimary,req,fileObj)
250  // getLine(forPrimary)
251  // setCursor(el,cursor,scrollIntoView)
252  // createCursorFromContentPosition(el,startPos,endPos)
253  // getCursor(el)
254  // updateDecorations(forPrimary,forceDisplayComplete,forceDecorations)
255  // localInsertLabel(startPos)
256  // autoIndent(forPrimary,cursor)
257  // updateDualView(forPrimary)
258  // updateOutline(forPrimary,{text,time})
259  // localHandleStackManagement()
260  // localHandleCcOutline()
261  // localHandleJsOutline()
262  // handleOutlineSelect(forPrimary)
263  // updateLastSave(forPrimary)
264  // keyDownHandler(e,forPrimary,shortcutsOnly)
265  // localInsertCharacter(c)
266  // handleFileNameMouseMove(forPrimary,doNotStartTimer)
267  // startEditFileName(forPrimary)
268  // editCellOK(forPrimary)
269  // editCellCancel(forPrimary)
270  // updateFileHistoryDropdowns(forPrimarySelect)
271  // handleFileNameHistorySelect(forPrimary)
272  // showFindAndReplace(forPrimary)
273  // showFindAndReplaceSelection(forPrimary)
274  // doFindAndReplaceAction(forPrimary,action)
275  // displayFileHeader(forPrimary)
276  // updateFileSnapshot(forPrimary,{text,time},ignoreTimeDelta)
277  // startUpdateHandling(forPrimary)
278  // stopUpdateHandling(event)
279  // updateTimeoutHandler()
280  // doubleClickHandler(forPrimary)
281  // download(forPrimary)
282  // upload(forPrimary)
283  // uploadTextFromFile(forPrimary)
284 
285 
286  //for display
287  var _WINDOW_MIN_SZ = 525;
288 
289  var _ALLOWED_FILE_EXTENSIONS = [];
290 
291  var _needEventListeners = true;
292 
293  var _viewMode = 0; //0: only primary, 1: vertical split, 2: horizontal split
294  var _navMode = [0,0]; //1 for showing directory nav
295  var _filePath = ["",""]; //file path for primary and secondary
296  var _fileExtension = ["",""]; //file extension for primary and secondary
297  var _fileLastSave = [0,0]; //file last save time for primary and secondary
298  var _fileWasModified = [false,false]; //file wasModified for primary and secondary
299  var _numberOfLines = [0,0];
300 
301  var _eel = [undefined,undefined]; //editor elements for primary and secondary
302 
303  var _updateTimerHandle = 0;
304  var _updateHandlerTargetPane = [false,false];
305  var _commandKeyDown = false;
306  var _lastPageUpDownLine = -1;
307  var _startPageUpDownLine = -1;
308  var _startPageUpDownNodeIndex = -1;
309  var _startPageUpDownPos = -1;
310 
311  var _fileNameMouseMoveTimerHandle = 0;
312  var _fileNameEditing = [false,false]; //for primary and secondary
313  var _fileUploadString;
314 
315  var _activePaneIsPrimary = 1; //default to primary, and switch based on last click
316 
317  var _undoStackLatestIndex = [-1,-1]; //when empty, -1, for secondary/primary
318  var _undoStack_MAX_SIZE = 10;
319  var _undoStack = [[],[]]; //newest,time are placed at _undoStackLatestIndex+1, for secondary/primary
320 
321  var _fileHistoryStack = {}; //map of filename => [content,timestamp ms,fileWasModified,fileLastSave]
322 
323  var _findAndReplaceCursorInContent = [undefined,undefined];
324 
325  var _fileStringHoverEl = 0; //element for hoverable buttons to open file
326  var _fileStringHoverTimeout = 0; //timeout to remove hoverable buttons to open file
327 
328  var _UPDATE_DECOR_TIMEOUT = 2000; //ms
329 
330  var _TAB_SIZE = 4; //to match eclipse!
331 
332  var _READ_ONLY = false;
333  var _requestPreamble = ""; // to choose readonly request or fullaccess
334 
337  // end variable declaration
338  CodeEditor.editor = this;
339  Debug.log("CodeEditor.editor constructed");
340 
341  // start "public" members
342  //this.lastFileNameHistorySelectIndex = -1; //now unused (?)
343  this.findAndReplaceFind = ["",""]; //save find & replace state
344  this.findAndReplaceReplace = ["",""]; //save find & replace state
345  this.findAndReplaceScope = [0,0]; //save find & replace state
346  this.findAndReplaceDirection = [0,0]; //save find & replace state
347  this.findAndReplaceCaseSensitive = [0,0]; //save find & replace state
348  this.findAndReplaceWholeWord = [1,1]; //save find & replace state
349  this.findAndReplaceLastButton = [-1,-1]; //1,2,3,4 := Find, Replace, Find&Replace, Replace All //save find & replace state
350  // end "public" members
351 
352  init();
353  Debug.log("CodeEditor.editor initialized");
354 
355 
356  //=====================================================================================
357  //init ~~
358  function init()
359  {
360  Debug.log("Code Editor init ");
361 
362  //extract GET parameters
363  var parameterStartFile = [
364  //"/otsdaq/otsdaq-core/CoreSupervisors/version.h",
365  //"/otsdaq_components/otsdaq-components/FEInterfaces/FEOtsUDPTemplateInterface.h",
366  //"/otsdaq_components/otsdaq-components/FEInterfaces/FEOtsUDPTemplateInterface_interface.cc",
367  //"/CMakeLists.txt",
368  //"/CMakeLists.txt",
369  DesktopContent.getParameter(0,"startFilePrimary"),
370  DesktopContent.getParameter(0,"startFileSecondary")
371  ];
372  var parameterGotoLine = [
373  DesktopContent.getParameter(0,"gotoLinePrimary"),
374  DesktopContent.getParameter(0,"gotoLineSecondary")
375  ];
376  var parameterOpenDirectory = [
377  DesktopContent.getParameter(0,"openDirectoryPrimary"),
378  DesktopContent.getParameter(0,"openDirectorySecondary")
379  ];
380  if(parameterOpenDirectory[0] === undefined)
381  parameterOpenDirectory[0] = "/";
382  if(parameterOpenDirectory[1] === undefined)
383  parameterOpenDirectory[1] = "/";
384 
385  var parameterViewMode = DesktopContent.getParameter(0,"startViewMode");
386  if(parameterViewMode !== undefined) //set view mode if parameter
387  {
388  _viewMode = parameterViewMode|0;
389  }
390 
391 
392  var readOnlyMode = DesktopContent.getParameter(0, "readOnlyMode");
393  if (readOnlyMode !== undefined) //set read mode if parameter
394  {
395  Debug.log("Launching readonly mode to true!");
396  _READ_ONLY = true; //readOnlyMode | 0;
397 
398 
399  }
400  console.log("parameterStartFile",parameterStartFile);
401  console.log("parameterGotoLine",parameterGotoLine);
402  console.log("parameterViewMode",parameterViewMode);
403  console.log("parameterOpenDirectory", parameterOpenDirectory);
404  console.log("_READ_ONLY", _READ_ONLY);
405 
406  if(_READ_ONLY == true)
407  {
408  _requestPreamble = "readOnly";
409  appMode = "Code Viewer";
410  windowTooltip = windowViewModeTooltip;
411 
412 
413  //_viewMode = 2; for debugging
414  }
415 
416 
417  CodeEditor.showTooltip(_STAND_ALONE);
418 
419  //proceed
420 
421  createElements();
422  redrawWindow();
423 
424  if(_needEventListeners)
425  {
426  window.addEventListener("resize",redrawWindow);
427  _needEventListeners = false;
428  }
429 
430  //stop here if standalone with no server
431  if(_STAND_ALONE)
432  {
433 
434  //set directory situation after CodeEditor has been fully instantiated
435  window.setTimeout(
436  function()
437  {
438  //at this point, CodeEditor is intantiated
439  Debug.log("CodeEditor stand alone setup...");
440  CodeEditor.editor.toggleDirectoryNav(true /*forPrimary*/,false /*value*/);
441 
442  //open upload window to get the ball rolling
443  CodeEditor.editor.upload(true /*forPrimary*/);
444  }, 100);
445 
446 
447  return;
448  }
449 
450  DesktopContent.XMLHttpRequest("Request?RequestType=" + _requestPreamble +
451  "codeEditor" +
452  "&option=getAllowedExtensions"
453  , "" /* data */,
454  function(req, reqParam, errStr)
455  {
456  console.log("getAllowedExtensions",req,errStr);
457 
458  if(!_READ_ONLY && !req)
459  {
460  if(DesktopContent._sequence)
461  Debug.log("Assuming invalid permissions (remember the wiz mode sequence access code must be at least 8 characters to allow write access)!\n\nReverting to read-only mode.", Debug.WARN_PRIORITY);
462  else
463  Debug.log("Assuming invalid permissions (remember only named users can have write access, not the anonymous admin user)!\n\nReverting to read-only mode.", Debug.WARN_PRIORITY);
464  _READ_ONLY = true;
465  init();
466  return;
467  }
468 
469  if(errStr && errStr != "")
470  Debug.log(errStr,Debug.HIGH_PRIORITY);
471 
472  if(!req) //request failed
473  {
474  Debug.log("Does the Code Editor Supervisor exist? You must connect the web editor to a valid Code Editor Supervisor application (please check your Configuration Tree and then restart ots).",Debug.HIGH_PRIORITY);
475  return;
476  }
477  else //check for error
478  {
479  if(!errStr || errStr == "")
480  errStr = DesktopContent.getXMLValue(req,"Error");
481 
482  if(errStr && errStr != "")
483  {
484  Debug.log(errStr,Debug.HIGH_PRIORITY);
485  Debug.log("Does the Code Editor Supervisor exist? You must connect the web editor to a valid Code Editor Supervisor application (please check your Configuration Tree and then restart ots).",Debug.HIGH_PRIORITY);
486  return;
487  }
488  }
489 
490  _ALLOWED_FILE_EXTENSIONS = DesktopContent.getXMLValue(req,"AllowedExtensions");
491  console.log("_ALLOWED_FILE_EXTENSIONS",_ALLOWED_FILE_EXTENSIONS);
492  _ALLOWED_FILE_EXTENSIONS = _ALLOWED_FILE_EXTENSIONS.split(',');
493  console.log("_ALLOWED_FILE_EXTENSIONS",_ALLOWED_FILE_EXTENSIONS);
494 
495  DesktopContent.XMLHttpRequest("Request?RequestType=" + _requestPreamble +
496  "codeEditor" +
497  "&option=getDirectoryContent" +
498  "&path=/"
499  , "" /* data */,
500  function(req)
501  {
502  var fileSplit;
503 
504  //console.log("getDirectoryContent",req);
505 
506 
507  CodeEditor.editor.handleDirectoryContent(1 /*forPrimary*/, req);
508  CodeEditor.editor.handleDirectoryContent(0 /*forPrimary*/, req);
509 
510  //decide how to start display(file or directory)
511  fileSplit = [];
512  if(parameterStartFile[0] && parameterStartFile[0] != "")
513  fileSplit = parameterStartFile[0].split('.');
514 
515 
516 
517  if(fileSplit.length == 2) //show shortcut file
518  CodeEditor.editor.openFile(
519  1 /*forPrimary*/,
520  fileSplit[0] /*path*/,
521  fileSplit[1] /*extension*/,
522  false /*doConfirm*/,
523  parameterGotoLine[0 /*primary goto line*/] /*gotoLine*/);
524  else //show base directory nav
525  {
526  CodeEditor.editor.openDirectory(
527  1 /*forPrimary*/,
528  parameterOpenDirectory[0] /*path*/,
529  true /*doNotOpenPane*/
530  );
531  //CodeEditor.editor.toggleDirectoryNav(1 /*forPrimary*/, 1 /*showNav*/);
532  }
533 
534  //for secondary pane
535  fileSplit = [];
536  if(parameterStartFile[1] && parameterStartFile[1] != "")
537  fileSplit = parameterStartFile[1].split('.');
538 
539  if(fileSplit.length == 2) //show shortcut file
540  CodeEditor.editor.openFile(
541  0 /*forPrimary*/,
542  fileSplit[0] /*path*/,
543  fileSplit[1] /*extension*/,
544  false /*doConfirm*/,
545  parameterGotoLine[1 /*secondary goto line*/] /*gotoLine*/);
546  else //show base directory nav
547  {
548 
549  CodeEditor.editor.openDirectory(
550  0 /*forPrimary*/,
551  parameterOpenDirectory[1] /*path*/,
552  true /*doNotOpenPane*/
553  );
554  //CodeEditor.editor.toggleDirectoryNav(0 /*forPrimary*/, 1 /*showNav*/);
555  }
556 
557 
558  _activePaneIsPrimary = 1; //default active pane to primary
559 
560  }); //end get directory contents
561  },
562  0 /*reqParam*/, 0 /*progressHandler*/,
563  true /*callHandlerOnErr*/,
564  0 /*doNotShowLoadingOverlay*/, 0 /*targetSupervisor*/,
565  0 /*ignoreSystemBlock*/,
566  true /*doNotOfferSequenceChange*/ //so read-only switch can happen
567  ); //end get allowed file extensions
568 
569  } //end init()
570 
571  //=====================================================================================
572  //createElements ~~
573  // called initially to create checkbox and button elements
574  function createElements()
575  {
576  Debug.log("createElements");
577 
578 
579 
580 
581  // <!-- body content populated by javascript -->
582  // <div id='content'>
583  // <div id='primaryPane'></div> <!-- for primary view -->
584  // <div id='secondaryPane'></div> <!-- for horiz/vert split view -->
585  // <div id='controlsPane'></div> <!-- for view toggle and compile -->
586  // </div>
587 
588  var cel,el,al,sl,str;
589 
590  cel = document.getElementById("content");
591  if(!cel)
592  {
593  cel = document.createElement("div");
594  cel.setAttribute("id","content");
595  }
596 
597  //clear all elements
598  cel.innerHTML = "";
599 
600  { //content div
601 
602  //================
603  //primaryPane and secondaryPane div
604  var forPrimary;
605  for(forPrimary=1;forPrimary >= 0;--forPrimary)
606  {
607  el = document.createElement("div");
608  el.setAttribute("class","editorPane");
609  el.setAttribute("id","editorPane" + forPrimary);
610  {
611  var str = "";
612  str += createTextEditor(forPrimary);
613  str += createDirectoryNav(forPrimary);
614  str += localCreatePaneControls(forPrimary);
615  el.innerHTML = str;
616  } //end fill editor pane
617  cel.appendChild(el);
618  }
619 
620  //================
621  function localCreatePaneControls(forPrimary)
622  {
623  //add folder, and save buttons
624  //add directory nav and editor divs
625 
626  str = "";
627 
628  //local pane controls
629  str += htmlOpen("div",
630  {
631  "class":"controlsPane",
632  },"" /*innerHTML*/, 0 /*doCloseTag*/);
633  {
634  //folder
635  str += htmlOpen("div",
636  {
637  "id":"directoryNavToggle",
638  "class":"controlsButton",
639  "style":"float:left;" +
640  (_STAND_ALONE ?
641  "display:none":""),
642  "onclick":"CodeEditor.editor.toggleDirectoryNav(" + forPrimary + ");",
643  "title": "Open a file... (Ctrl + D)",
644  },"" /*innerHTML*/, 0 /*doCloseTag*/);
645  {
646  str += htmlOpen("div",
647  {
648  "id":"directoryNavToggleTop",
649 
650  },"" /*innerHTML*/, 1 /*doCloseTag*/);
651  str += htmlOpen("div",
652  {
653  "id":"directoryNavToggleBottom",
654 
655  },"" /*innerHTML*/, 1 /*doCloseTag*/);
656  } //end directoryNavToggle
657  str += "</div>"; //close directoryNavToggle
658 
659  //save
660  str += htmlOpen("div",
661  {
662  "id": "saveFile",
663  "class": "controlsButton",
664  "style": "float:left;" +
665  ((_STAND_ALONE || _READ_ONLY)?
666  "display:none":""),
667  "onclick": "CodeEditor.editor.saveFile(" + forPrimary + ");",
668  "title": "Click to Save the File (Ctrl + S)\nUndo changes (Ctrl + U)\nRedo changes (Shift + Ctrl + U)",
669  }, "" /*innerHTML*/, 0 /*doCloseTag*/);
670  {
671  str += htmlOpen("div",
672  {
673  "id":"saveFileMain",
674 
675  },"" /*innerHTML*/, 1 /*doCloseTag*/);
676  str += htmlOpen("div",
677  {
678  "id":"saveFileMainTop",
679 
680  },"" /*innerHTML*/, 1 /*doCloseTag*/);
681  str += htmlOpen("div",
682  {
683  "id":"saveFileMainBottom",
684 
685  },"" /*innerHTML*/, 1 /*doCloseTag*/);
686  } //end save content
687  str += "</div>"; //close saveFile
688 
689  } //end locals controlsPane
690  str += "</div>"; //close controlsPane
691  return str;
692  } //end localCreatePaneControls
693 
694 
695  //================
696  //controlsPane div
697  el = document.createElement("div");
698  el.setAttribute("class", "controlsPane");
699 
700  {
701  //add view toggle, incremental compile, and clean compile buttons
702 
703  str = "";
704 
705  //view toggle
706  str += htmlOpen("div",
707 
708  {
709  "id":"viewToggle",
710  "class":"controlsButton",
711  "style":"float:right",
712  "onclick":"CodeEditor.editor.toggleView();",
713  "title":"Toggle Verical/Horizontal Split-view (Ctrl + W)",
714  },"" /*innerHTML*/, 0 /*doCloseTag*/);
715 
716  {
717 
718  str += htmlOpen("div",
719  {
720  "id":"viewToggleRight",
721 
722  },"" /*innerHTML*/, 1 /*doCloseTag*/);
723  str += htmlOpen("div",
724  {
725  "id":"viewToggleLeftTop",
726 
727  },"" /*innerHTML*/, 1 /*doCloseTag*/);
728  str += htmlOpen("div",
729  {
730  "id":"viewToggleLeftBottom",
731 
732  },"" /*innerHTML*/, 1 /*doCloseTag*/);
733  }
734  str += "</div>"; //close viewToggle
735 
736  //incremental compile
737 
738  str += htmlOpen("div",
739  {
740  "id": "incrementalBuild",
741  "class": "controlsButton",
742  "style": "float:right;" +
743  ((_STAND_ALONE || _READ_ONLY)?
744  "display:none":""),
745  "onclick": "CodeEditor.editor.build(0 /*cleanBuild*/);",
746  "title": "Incremental Build... (Ctrl + B)",
747  }, "" /*innerHTML*/, 0 /*doCloseTag*/);
748  {
749 
750  str += htmlOpen("div",
751  {
752  "style":"margin:11px 0 0 13px;",
753  },"b" /*innerHTML*/, 1 /*doCloseTag*/);
754  }
755  str += "</div>"; //close incrementalBuild
756 
757  //clean compile
758  str += htmlOpen("div",
759  {
760  "id": "cleanBuild",
761  "class": "controlsButton",
762  "style": "float:right;" +
763  ((_STAND_ALONE || _READ_ONLY)?
764  "display:none":""),
765  "onclick": "CodeEditor.editor.build(1 /*cleanBuild*/);",
766  "title": "Clean Build... (Ctrl + N)",
767  }, "" /*innerHTML*/, 0 /*doCloseTag*/);
768  {
769 
770  str += htmlOpen("div",
771  {
772  "style":"margin:10px 0 0 13px;",
773  },"z" /*innerHTML*/, 1 /*doCloseTag*/);
774  }
775  str += "</div>"; //close cleanBuild
776 
777 
778  el.innerHTML = str;
779  }
780  cel.appendChild(el);
781 
782  } //end content div
783 
784 
785  document.body.appendChild(cel);
786  _eel = [document.getElementById("editableBox" + 0),
787  document.getElementById("editableBox" + 1)];
788 
789 
791  //add event listeners
792  var box;
793  for(var i=0;i<2;++i)
794  {
795  _eel[i].addEventListener("input",
796  function(e)
797  {
798  e.stopPropagation();
799 
800  var forPrimary = this.id[this.id.length-1]|0;
801  forPrimary = forPrimary?1:0;
802 
803  Debug.log("input forPrimary=" + forPrimary);
804 
805  _fileWasModified[forPrimary] = true;
806  CodeEditor.editor.updateLastSave(forPrimary);
807 
808  CodeEditor.editor.startUpdateHandling(forPrimary);
809 
810  }); //end addEventListener
811 
812  _eel[i].addEventListener("keydown",
813  function(e)
814  {
815  if(e.keyCode == 91 || e.keyCode == 93 ||
816  e.keyCode == 224) //apple command keys chrome left/right and firefox
817  _commandKeyDown = true;
818 
819  var forPrimary = this.id[this.id.length-1]|0;
820  forPrimary = forPrimary?1:0;
821 
822  //Debug.log("keydown handler for editableBox" + forPrimary);
823  CodeEditor.editor.keyDownHandler(e,forPrimary);
824  e.stopPropagation();
825  }); //end addEventListener
826 
827  _eel[i].addEventListener("keyup",
828  function(e)
829  {
830  if(e.keyCode == 91 || e.keyCode == 93 ||
831  e.keyCode == 224) //apple command keys chrome left/right and firefox
832  _commandKeyDown = false;
833  }); //end addEventListener
834 
835  _eel[i].addEventListener("click",
836  function(e)
837  {
838 
839  if(e.which > 1)
840  {
841  Debug.log("Special mouse click handling");
842 
843  e.preventDefault();
844  e.stopPropagation();
845  return;
846  }
847 
848  e.stopPropagation(); //to stop click body behavior
849  }); //end addEventListener
850 
851  _eel[i].addEventListener("dblclick",
852  function(e)
853  {
854 
855  var forPrimary = this.id[this.id.length-1]|0;
856  forPrimary = forPrimary?1:0;
857 
858  Debug.log("dblclick handler for editor" + forPrimary);
859  e.stopPropagation(); //to stop click body behavior
860 
861  CodeEditor.editor.doubleClickHandler(forPrimary);
862  }); //end addEventListener
863 
864  _eel[i].addEventListener("contextmenu",
865  function(e)
866  {
867 
868  if(e.which > 1)
869  {
870  Debug.log("Special context menu handling");
871 
872  e.preventDefault();
873  e.stopPropagation();
874  return;
875  }
876 
877  }); //end addEventListener
878 
879  _eel[i].addEventListener("mousedown",
880  function(e)
881  {
882  if(e.which > 1)
883  {
884  Debug.log("Special mouse down handling");
885 
886  e.preventDefault();
887  e.stopPropagation();
888  return;
889  }
890 
891  CodeEditor.editor.stopUpdateHandling(e);
892 
893  var forPrimary = this.id[this.id.length-1]|0;
894  forPrimary = forPrimary?1:0;
895 
896  Debug.log("mousedown handler for editor" + forPrimary + " " + e.which);
897 
898  //update dual view in case other has been modified
899  if(_activePaneIsPrimary != forPrimary)
900  CodeEditor.editor.updateDualView(!forPrimary);
901 
902  _activePaneIsPrimary = forPrimary;
903 
904 
905  }); //end addEventListener
906 
907  _eel[i].addEventListener("mouseup",
908  function(e)
909  {
910  var forPrimary = this.id[this.id.length-1]|0;
911  forPrimary = forPrimary?1:0;
912 
913  Debug.log("mouseup handler for editor" + forPrimary);
914 
915  if(e.which > 1)
916  {
917  Debug.log("Special mouse up handling");
918 
919  e.preventDefault();
920  e.stopPropagation();
921  return;
922  }
923 
924  CodeEditor.editor.startUpdateHandling(forPrimary);
925 
926  }); //end addEventListener
927 
928  _eel[i].addEventListener('paste',
929  function(e)
930  {
931  Debug.log("paste handler() for editor" + forPrimary);
932  var paste = (e.clipboardData || window.clipboardData).getData('text');
933 
934  var selection = window.getSelection();
935  if (!selection.rangeCount) return false;
936  selection.deleteFromDocument();
937  selection.getRangeAt(0).insertNode(document.createTextNode(paste));
938 
939  e.preventDefault();
940  }); //end addEventListener
941 
942  //add click handler to track active pane
943  box = document.getElementById("editorPane" + i);
944  box.addEventListener("click",
945  function(e)
946  {
947  var forPrimary = this.id[this.id.length-1]|0;
948  forPrimary = forPrimary?1:0;
949 
950  Debug.log("click handler for pane" + forPrimary);
951 
952 
953  //update dual view in case other has been modified
954  if(_activePaneIsPrimary != forPrimary)
955  CodeEditor.editor.updateDualView(!forPrimary);
956 
957  _activePaneIsPrimary = forPrimary;
958 
959 
960  CodeEditor.editor.showFindAndReplaceSelection(forPrimary);
961 
962  //focus on edit box
963  var el = document.getElementById("textEditorBody" + forPrimary);
964  var scrollLeft = el.scrollLeft;
965  var scrollTop = el.scrollTop;
966  //var cursor = CodeEditor.editor.getCursor(el);
967  _eel[forPrimary].focus();
968  //CodeEditor.editor.setCursor(el,cursor);
969  el.scrollLeft = scrollLeft;
970  el.scrollTop = scrollTop;
971 
972 
973  }); //end addEventListener
974 
975 
976 
977  } //end handler creation
978  box = document.body;
979  box.addEventListener("keydown",
980  function(e)
981  {
982  if(e.keyCode == 91 || e.keyCode == 93 ||
983  e.keyCode == 224) //apple command keys chrome left/right and firefox
984  _commandKeyDown = true;
985 
986  var forPrimary = _activePaneIsPrimary; //take last active pane
987  Debug.log("keydown handler for body" + forPrimary);
988  CodeEditor.editor.keyDownHandler(e,forPrimary,true /*shortcutsOnly*/);
989  //e.stopPropagation();
990  }); //end addEventListener
991  box.addEventListener("keyup",
992  function(e)
993  {
994  if(e.keyCode == 91 || e.keyCode == 93 ||
995  e.keyCode == 224) //apple command keys chrome left/right and firefox
996  _commandKeyDown = false;
997  }); //end addEventListener
998  box.addEventListener("mouseover",
999  function()
1000  {
1001  //Debug.log("body onmouseover");
1002  if(_fileStringHoverEl.parentNode) //then delete the element
1003  {
1004  Debug.log("body removing string hover");
1005  window.clearTimeout(_fileStringHoverTimeout);
1006  _fileStringHoverTimeout = window.setTimeout(
1007  function()
1008  {
1009  Debug.log("body removed string hover");
1010  try
1011  {
1012  _fileStringHoverEl.parentNode.removeChild(_fileStringHoverEl);
1013  }
1014  catch(e)
1015  {} //ignore error
1016  }, 1000 /* 1 sec*/);
1017  }
1018 
1019  }); //end addEventListener
1020 
1021  } //end createElements()
1022 
1023  //=====================================================================================
1024  //createTextEditor ~~
1025  function createTextEditor(forPrimary)
1026  {
1027  forPrimary = forPrimary?1:0;
1028 
1029  Debug.log("createTextEditor forPrimary=" + forPrimary);
1030 
1031  var str = "";
1032 
1033  str += htmlOpen("div",
1034  {
1035  "class":"textEditor",
1036  "id":"textEditor" + forPrimary,
1037  "style":"overflow:hidden;",
1038  },0 /*innerHTML*/, false /*doCloseTag*/);
1039 
1040  //add header
1041  //add body with overflow:auto
1042  //add leftMargin
1043  //add editorBox
1044 
1045 
1046  str += htmlOpen("div",
1047  {
1048  "class":"textEditorHeader",
1049  "id":"textEditorHeader" + forPrimary,
1050  },0 /*"<div>File</div><div>Save Date</div>"*/ /*innerHTML*/,
1051  true /*doCloseTag*/);
1052 
1053  str += htmlOpen("div",
1054  {
1055  "class":"textEditorBody",
1056  "id":"textEditorBody" + forPrimary,
1057  },0 /*innerHTML*/, false /*doCloseTag*/);
1058 
1059  str += "<table class='editableBoxTable' style='margin-bottom:200px'>" + //add white space to bottom for expected scroll behavior
1060  "<tr><td valign='top'>";
1061  str += htmlOpen("div",
1062  {
1063  "class":"editableBoxLeftMargin",
1064  "id":"editableBoxLeftMargin" + forPrimary,
1065  },"0\n1\n2" /*html*/,true /*closeTag*/);
1066  str += "</td><td valign='top'>";
1067  str += htmlOpen("div",
1068  {
1069  "class":"editableBox",
1070  "id":"editableBox" + forPrimary,
1071  "contenteditable":"true",
1072  "autocomplete":"off",
1073  "autocorrect":"off",
1074  "autocapitalize":"off",
1075  "spellcheck":"false",
1076  },0 /*html*/,true /*closeTag*/);
1077  str += "</td></tr></table>"; //close table
1078 
1079  str += "</div>"; //close textEditorBody tag
1080 
1081  str += "</div>"; //close textEditor tag
1082 
1083  return str;
1084  } //end createTextEditor()
1085 
1086  //=====================================================================================
1087  //createDirectoryNav ~~
1088  function createDirectoryNav(forPrimary)
1089  {
1090  forPrimary = forPrimary?1:0;
1091 
1092  Debug.log("createDirectoryNav forPrimary=" + forPrimary);
1093 
1094  var str = "";
1095 
1096  str += htmlOpen("div",
1097  {
1098  "class":"directoryNav",
1099  "id":"directoryNav" + forPrimary,
1100  },"Directory" /*innerHTML*/, 1 /*doCloseTag*/);
1101 
1102  return str;
1103 
1104  } //end createDirectoryNav()
1105 
1106  //=====================================================================================
1107  //redrawWindow ~~
1108  // called when page is resized
1109  function redrawWindow()
1110  {
1111  //adjust link divs to proper size
1112  // use ratio of new-size/original-size to determine proper size
1113 
1114  var w = window.innerWidth | 0;
1115  var h = window.innerHeight | 0;
1116 
1117  if(w < _WINDOW_MIN_SZ)
1118  w = _WINDOW_MIN_SZ;
1119  if(h < _WINDOW_MIN_SZ)
1120  h = _WINDOW_MIN_SZ;
1121 
1122  Debug.log("redrawWindow to " + w + " - " + h);
1123 
1124  var eps = document.getElementsByClassName("editorPane");
1125  var epHdrs = document.getElementsByClassName("textEditorHeader");
1126  var epBdys = document.getElementsByClassName("textEditorBody");
1127  var dns = document.getElementsByClassName("directoryNav");
1128  var rect = [{},{}];
1129 
1130 
1131  eps[0].style.position = "absolute";
1132  eps[1].style.position = "absolute";
1133 
1134  var DIR_NAV_MARGIN = 50;
1135  var EDITOR_MARGIN = 20;
1136  var EDITOR_HDR_H = 56;
1137  switch(_viewMode)
1138  {
1139  case 0: //only primary
1140 
1141  rect = [{"left":0,"top":0,"w":w,"h":h},
1142  undefined];
1143  break;
1144  case 1: //vertical split
1145 
1146  rect = [{"left":0,"top":0,"w":((w/2)|0),"h":h},
1147  {"left":((w/2)|0),"top":0,"w":(w-((w/2)|0)),"h":h}];
1148 
1149  break;
1150  case 2: //horizontal split
1151 
1152  rect = [{"left":0,"top":0,"h":((h/2)|0),"w":w},
1153  {"top":((h/2)|0),"left":0,"h":(h-((h/2)|0)),"w":w}];
1154 
1155  break;
1156  default:
1157  Debug.log("Invalid view mode encountered: " + _viewMode);
1158  } //end switch
1159 
1160  //place all editor components
1161  for(var i=0;i<2;++i)
1162  {
1163  if(!rect[i])
1164  {
1165  eps[i].style.display = "none";
1166  continue;
1167  }
1168 
1169  dns[i].style.left = (DIR_NAV_MARGIN) + "px";
1170  dns[i].style.top = (DIR_NAV_MARGIN) + "px";
1171  dns[i].style.width = (rect[i].w - 2*DIR_NAV_MARGIN) + "px";
1172  dns[i].style.height = (rect[i].h - 2*DIR_NAV_MARGIN) + "px";
1173 
1174  eps[i].style.left = rect[i].left + "px";
1175  eps[i].style.top = rect[i].top + "px";
1176  eps[i].style.height = rect[i].h + "px";
1177  eps[i].style.width = rect[i].w + "px";
1178 
1179  epHdrs[i].style.left = EDITOR_MARGIN + "px";
1180  epHdrs[i].style.top = (DIR_NAV_MARGIN - 2*EDITOR_MARGIN) + "px";
1181  epHdrs[i].style.height = (EDITOR_HDR_H + 2*EDITOR_MARGIN) + "px";
1182  epHdrs[i].style.width = (rect[i].w - 2*EDITOR_MARGIN) + "px";
1183 
1184  //offset body by left and top, but extend to border and allow scroll
1185  epBdys[i].style.left = 0 + "px";
1186  epBdys[i].style.top = (DIR_NAV_MARGIN + EDITOR_HDR_H) + "px";
1187  epBdys[i].style.height = (rect[i].h - DIR_NAV_MARGIN - EDITOR_HDR_H) + "px";
1188  epBdys[i].style.width = (rect[i].w - 0) + "px";
1189 
1190  eps[i].style.display = "block";
1191  } //end place editor components
1192 
1193  } //end redrawWindow()
1194 
1195 
1196  //=====================================================================================
1197  //toggleView ~~
1198  // does primary only, vertical, or horizontal
1199  this.toggleView = function(v)
1200  {
1201  if(v !== undefined)
1202  _viewMode = v;
1203  else
1204  _viewMode = (_viewMode+1)%3;
1205  Debug.log("toggleView _viewMode=" + _viewMode);
1206  redrawWindow();
1207  } //end toggleView()
1208 
1209  //=====================================================================================
1210  //toggleDirectoryNav ~~
1211  // toggles directory nav
1212  this.toggleDirectoryNav = function(forPrimary, v)
1213  {
1214  forPrimary = forPrimary?1:0;
1215  _activePaneIsPrimary = forPrimary;
1216 
1217  Debug.log("toggleDirectoryNav forPrimary=" + forPrimary);
1218 
1219  if(v !== undefined) //if being set, take value
1220  _navMode[forPrimary] = v?1:0;
1221  else //else toggle
1222  _navMode[forPrimary] = _navMode[forPrimary]?0:1;
1223  Debug.log("toggleDirectoryNav _navMode=" + _navMode[forPrimary]);
1224 
1225  var el = document.getElementById("directoryNav" + forPrimary);
1226  var wasHidden = el.style.display == "none";
1227  el.style.display =
1228  _navMode[forPrimary]?"block":"none";
1229 
1230  if(_navMode[forPrimary] && wasHidden)
1231  {
1232  var paths = document.getElementById("directoryNav" +
1233  forPrimary).getElementsByClassName("dirNavPath");
1234  var buildPath = "/";
1235  for(var i=1;i<paths.length;++i)
1236  buildPath += (i>1?"/":"") + paths[i].textContent;
1237  Debug.log("refresh " + buildPath);
1238 
1239  CodeEditor.editor.openDirectory(forPrimary,buildPath);
1240  }
1241  } //end toggleDirectoryNav()
1242 
1243  //=====================================================================================
1244  //saveFile ~~
1245  // save file for pane
1246  this.saveFile = function(forPrimary, quiet)
1247  {
1248  forPrimary = forPrimary?1:0;
1249 
1250  Debug.log("saveFile forPrimary=" + forPrimary);
1251 
1252  Debug.log("saveFile _filePath=" + _filePath[forPrimary]);
1253  Debug.log("saveFile _fileExtension=" + _fileExtension[forPrimary]);
1254 
1255  if(_filePath[forPrimary] == "")
1256  {
1257  Debug.log("Error, can not save to empty file name!",
1258  Debug.HIGH_PRIORITY);
1259  return;
1260  }
1261 
1262  if(!quiet)
1263  {
1264  DesktopContent.popUpVerification(
1265  "Are you sure you want to save...<br>" +
1266  _filePath[forPrimary] + "." + _fileExtension[forPrimary] + "?",
1267  localDoIt,
1268  undefined,undefined,undefined,
1269  undefined,undefined,//val, bgColor, textColor, borderColor, getUserInput,
1270  "90%" /*dialogWidth*/
1271  );
1272  return;
1273  }
1274  else
1275  localDoIt();
1276 
1277  function localDoIt()
1278  {
1279  //Note: innerText is the same as textContent, except it is the only
1280  // the human readable text (ignores hidden elements, scripts, etc.)
1281  var textObj = {"text":
1282  _eel[forPrimary].innerText,
1283  "time":undefined};
1284 
1285  //console.log(content,content.length);
1286 
1287  //remove crazy characters
1288  // (looks like they come from emacs tabbing -- they seem to be backwards (i.e. 2C and 0A are real characters))
1289  textObj.text = textObj.text.replace(/%20%20/g,"%20%20").replace(/%20/g, //convert two to tab, otherwise space
1290  "%20").replace(/%20/g,"%20").replace(/%20/g,"%20");
1291 
1292 
1293 
1294  DesktopContent.XMLHttpRequest("Request?RequestType=codeEditor" +
1295  "&option=saveFileContent" +
1296  "&path=" + _filePath[forPrimary] +
1297  "&ext=" + _fileExtension[forPrimary]
1298  , "content=" + encodeURIComponent(textObj.text) /* data */,
1299  function(req)
1300  {
1301  Debug.log("Successfully saved " +
1302  _filePath[forPrimary] + "." +
1303  _fileExtension[forPrimary],quiet?Debug.LOW_PRIORITY:Debug.INFO_PRIORITY);
1304 
1305  _fileWasModified[forPrimary] = false;
1306  textObj.time = Date.now();
1307  _fileLastSave[forPrimary] = textObj.time; //record last Save time
1308 
1309  //update last save field
1310  CodeEditor.editor.updateLastSave(forPrimary);
1311 
1312  if(_filePath[0] == _filePath[1] &&
1313  _fileExtension[0] == _fileExtension[1])
1314  {
1315  CodeEditor.editor.updateDualView(forPrimary);
1316  //capture right now if different, ignore time delta
1317  CodeEditor.editor.updateFileSnapshot(!forPrimary,
1318  textObj,
1319  true /*ignoreTimeDelta*/);
1320  }
1321 
1322  //capture right now if different, ignore time delta
1323  CodeEditor.editor.updateFileSnapshot(forPrimary,
1324  textObj,
1325  true /*ignoreTimeDelta*/);
1326 
1327  }); // end codeEditor saveFileContent handler
1328 
1329  } //end localDoIt()
1330  } //end saveFile()
1331 
1332  //=====================================================================================
1333  //build ~~
1334  // launch compile
1335  this.build = function(cleanBuild)
1336  {
1337  cleanBuild = cleanBuild?1:0;
1338 
1339  Debug.log("build cleanBuild=" + cleanBuild);
1340 
1341  if(cleanBuild)
1342  {
1343  DesktopContent.popUpVerification(
1344  "Are you sure you want to do a clean build?!",
1345  localDoIt
1346  );
1347  return;
1348  }
1349  else
1350  localDoIt();
1351 
1352  function localDoIt()
1353  {
1354  DesktopContent.XMLHttpRequest("Request?RequestType=codeEditor" +
1355  "&option=build" +
1356  "&clean=" + (cleanBuild?1:0)
1357  , "" /* data */,
1358  function(req)
1359  {
1360  Debug.log("Build was launched! Check " +
1361  "<a onclick='DesktopContent.openNewBrowserTab(" +
1362  "\"Console\");' " +
1363  "title='Click to open the Console web app in a new browser tab.'>" +
1364  "console</a> for result!", Debug.INFO_PRIORITY);
1365 
1366  }); //end codeEditor build handler
1367  } //end localDoIt()
1368 
1369  } //end build()
1370 
1371  //=====================================================================================
1372  //undo ~~
1373  // manage undo stack
1374  this.undo = function(forPrimary,redo)
1375  {
1376  DesktopContent.showLoading(localDoIt);
1377  return;
1378 
1379  function localDoIt()
1380  {
1381  forPrimary = forPrimary?1:0;
1382 
1383  Debug.log("undo() forPrimary=" + forPrimary + " redo=" + redo);
1384  console.log("undo stack index",_undoStackLatestIndex[forPrimary]);
1385  console.log("undo stack length",_undoStack[forPrimary].length);
1386 
1387  console.log("undo stack",_undoStack[forPrimary]);
1388 
1389  var el = _eel[forPrimary];
1390 
1391  //capture right now if different, ignore time delta
1392  CodeEditor.editor.updateFileSnapshot(forPrimary,
1393  {"text":el.textContent,
1394  "time":Date.now()},
1395  true /*ignoreTimeDelta*/);
1396 
1397  var newIndex = _undoStackLatestIndex[forPrimary];
1398  newIndex += redo?1:-1;
1399  if(newIndex >= _undoStack_MAX_SIZE)
1400  newIndex = 0; //wrap around
1401  else if(newIndex < 0)
1402  newIndex = _undoStack[forPrimary].length-1; //wrap around
1403 
1404  console.log("new stack index",newIndex);
1405 
1406  //do not allow wrap around in time
1407  if(!redo && //assert back in time
1408  _undoStack[forPrimary][newIndex][1] >=
1409  _undoStack[forPrimary][_undoStackLatestIndex[forPrimary]][1])
1410  {
1411  Debug.log("Reached end of undo history...",Debug.WARN_PRIORITY);
1412  return;
1413  }
1414  if(redo && //assert forward in time
1415  (newIndex >= _undoStack[forPrimary].length ||
1416  _undoStack[forPrimary][newIndex][1] <=
1417  _undoStack[forPrimary][_undoStackLatestIndex[forPrimary]][1]))
1418  {
1419  Debug.log("Reached end of redo history...",Debug.WARN_PRIORITY);
1420  return;
1421  }
1422 
1423  //here, accept change!
1424  _undoStackLatestIndex[forPrimary] = newIndex;
1425  console.log("result stack index",newIndex);
1426 
1427  var cursor = CodeEditor.editor.getCursor(el);
1428 
1429  el.textContent =
1430  _undoStack[forPrimary][_undoStackLatestIndex[forPrimary]][0];
1431  _fileWasModified[forPrimary] = true;
1432 
1433  CodeEditor.editor.updateDecorations(forPrimary,
1434  false /*forceDisplayComplete*/,
1435  true /*forceDecorations*/);
1436 
1437  CodeEditor.editor.setCursor(el,cursor,true /*scrollIntoView*/);
1438  } //end localDoIt()
1439  } //end undo()
1440 
1441  //=====================================================================================
1442  //handleDirectoryContent ~~
1443  // redraw directory content based on req response
1444  this.handleDirectoryContent = function(forPrimary,req)
1445  {
1446  forPrimary = forPrimary?1:0;
1447 
1448  Debug.log("handleDirectoryContent forPrimary=" + forPrimary);
1449  console.log(req);
1450 
1451  var path = DesktopContent.getXMLValue(req,"path");
1452  if(path == "/") path = ""; //default to empty to avoid //
1453 
1454  var specials = req.responseXML.getElementsByTagName("special");
1455  var dirs = req.responseXML.getElementsByTagName("directory");
1456  var files = req.responseXML.getElementsByTagName("file");
1457  var specialFiles = req.responseXML.getElementsByTagName("specialFile");
1458 
1459  Debug.log("handleDirectoryContent path=" + path);
1460  console.log(dirs);console.log(files);
1461 
1462  var str = "";
1463  var i;
1464  var name;
1465  str += htmlOpen("div",
1466  {
1467  "style":"margin:20px;" +
1468  "white-space: nowrap;",
1469  });
1470 
1472  //show path with links
1473  {
1474  var pathSplit = path.split('/');
1475  var buildPath = "";
1476  var pathSplitName;
1477 
1478  str += "Path: <a class='dirNavPath' onclick='CodeEditor.editor.openDirectory(" +
1479  forPrimary + ",\"" +
1480  "/" + "\"" +
1481  ")'>" +
1482  "srcs</a>";
1483 
1484  name = "srcs"; //take last name encoutered as folder name
1485  for(i=0;i<pathSplit.length;++i)
1486  {
1487  pathSplitName = pathSplit[i].trim();
1488  if(pathSplitName == "") continue; //skip blanks
1489  Debug.log("pathSplitName " + pathSplitName);
1490  name = pathSplitName; //take last name encoutered as folder name
1491 
1492  buildPath += "/" + pathSplitName;
1493 
1494  str += "/";
1495  str += "<a class='dirNavPath' onclick='CodeEditor.editor.openDirectory(" +
1496  forPrimary + ",\"" +
1497  buildPath + "\"" +
1498  ")' title='Open folder: \nsrcs" + buildPath +
1499  "' >" +
1500  pathSplitName + "</a>";
1501 
1502  }
1503  str += "/";
1504 
1505  //open in other pane
1506  str += htmlOpen("a",
1507  {
1508  "title":"Open folder in the other editor pane of the split-view: \n" +
1509  "srcs" + buildPath,
1510  "onclick":"CodeEditor.editor.openDirectory(" +
1511  (!forPrimary) + ",\"" +
1512  buildPath+ "\");", //end onclick
1513  },
1514  "<img class='dirNavFileNewWindowImgNewPane' " +
1515  "src='/WebPath/images/windowContentImages/CodeEditor-openInOtherPane.png'>"
1516  /*innerHTML*/, true /*doCloseTag*/);
1517 
1518  //open in new window
1519  str += htmlOpen("a",
1520  {
1521  "title":"Open folder in a new browser tab: \n" +
1522  "srcs" + buildPath,
1523  "onclick":"DesktopContent.openNewBrowserTab(" +
1524  "\"" + name + "\",\"\"," +
1525  "\"/WebPath/html/CodeEditor.html?urn=" +
1526  DesktopContent._localUrnLid + "&" +
1527  "openDirectoryPrimary=" +
1528  buildPath + "\",0 /*unique*/);' ", //end onclick
1529  },
1530  "<img class='dirNavFileNewWindowImgNewWindow' " +
1531  "src='/WebPath/images/windowContentImages/CodeEditor-openInNewWindow.png'>"
1532  /*innerHTML*/, true /*doCloseTag*/);
1533 
1534 
1535  str += "<br><br>";
1536  }
1537 
1539  //show specials
1540  for(i=0;i<specials.length;++i)
1541  {
1542  name = specials[i].getAttribute('value');
1543 
1544  //open in new window
1545  str += htmlOpen("a",
1546  {
1547  "title":"Open folder in a new browser tab: \n" +
1548  "srcs" + path + "/" + name,
1549  "onclick":"DesktopContent.openNewBrowserTab(" +
1550  "\"" + name + "\",\"\"," +
1551  "\"/WebPath/html/CodeEditor.html?urn=" +
1552  DesktopContent._localUrnLid + "&" +
1553  "openDirectoryPrimary=" +
1554  path + "/" + name + "\",0 /*unique*/);' ", //end onclick
1555  },
1556  "<img class='dirNavFileNewWindowImgNewWindow' " +
1557  "src='/WebPath/images/windowContentImages/CodeEditor-openInNewWindow.png'>"
1558  /*innerHTML*/, true /*doCloseTag*/);
1559 
1560  //open in other pane
1561  str += htmlOpen("a",
1562  {
1563  "title":"Open folder in the other editor pane of the split-view: \n" +
1564  "srcs" + path + "/" + name,
1565  "onclick":"CodeEditor.editor.openDirectory(" +
1566  (!forPrimary) + ",\"" +
1567  path + "/" + name + "\");", //end onclick
1568  },
1569  "<img class='dirNavFileNewWindowImgNewPane' " +
1570  "src='/WebPath/images/windowContentImages/CodeEditor-openInOtherPane.png'>"
1571  /*innerHTML*/, true /*doCloseTag*/);
1572 
1573  //open in this pane
1574  str += "<a class='dirNavSpecial' onclick='CodeEditor.editor.openDirectory(" +
1575  forPrimary + ",\"" +
1576  path + "/" + name + "\"" +
1577  ")' title='Open folder: \nsrcs" + path + "/" + name + "' >" +
1578  name + "</a>";
1579 
1580 
1581  str += "<br>";
1582 
1583  }
1585  //show special files
1586  var nameSplit;
1587  if(specialFiles.length)
1588  {
1589  str += "<table>";
1590  str += "<tr><th>" + path.substr(1,path.length-2) + " Files</th><th style='padding-left:20px'>Repository</th></tr>";
1591  }
1592  for(i=0;i<specialFiles.length;++i)
1593  {
1594  name = specialFiles[i].getAttribute('value');
1595  nameSplit = name.split('/');
1596 
1597  str += "<tr><td>";
1598 
1599  //open in new window
1600  str += htmlOpen("a",
1601  {
1602  "title":"Open file in a new browser tab: \n" +
1603  "srcs" + name,
1604  "onclick":"DesktopContent.openNewBrowserTab(" +
1605  "\"" + nameSplit[nameSplit.length-1] + "\",\"\"," +
1606  "\"/WebPath/html/CodeEditor.html?urn=" +
1607  DesktopContent._localUrnLid + "&" +
1608  "startFilePrimary=" +
1609  name + "\",0 /*unique*/);' ", //end onclick
1610  },
1611  "<img class='dirNavFileNewWindowImgNewWindow' " +
1612  "src='/WebPath/images/windowContentImages/CodeEditor-openInNewWindow.png'>"
1613  /*innerHTML*/, true /*doCloseTag*/);
1614 
1615  //open in other pane
1616  str += htmlOpen("a",
1617  {
1618  "title":"Open file in the other editor pane of the split-view: \n" +
1619  "srcs" + name,
1620  "onclick":"CodeEditor.editor.openFile(" +
1621  (!forPrimary) + ",\"" +
1622  name + "\", \"" +
1623  name.substr(name.lastIndexOf('.')+1) + "\"" + //extension
1624  ");", //end onclick
1625  },
1626  "<img class='dirNavFileNewWindowImgNewPane' " +
1627  "src='/WebPath/images/windowContentImages/CodeEditor-openInOtherPane.png'>"
1628  /*innerHTML*/, true /*doCloseTag*/);
1629 
1630  //open in this pane
1631  str += "<a class='dirNavFile' onclick='CodeEditor.editor.openFile(" +
1632  forPrimary + ",\"" +
1633  name + "\",\"" +
1634  name.substr(name.lastIndexOf('.')+1) + "\"" + //extension
1635  ")' title='Open file: \nsrcs" + name + "' >";
1636 
1637  str += nameSplit[nameSplit.length-1] + "</a>";
1638 
1639 
1640 
1641  str += "</td><td style='padding-left:20px'>" + nameSplit[1] + "</td></tr>";
1642 
1643  }
1644  if(specialFiles.length)
1645  {
1646  str += "</table>";
1647  }
1649  //show folders
1650  for(i=0;i<dirs.length;++i)
1651  {
1652  name = dirs[i].getAttribute('value');
1653 
1654  //open in new window
1655  str += htmlOpen("a",
1656  {
1657  "title":"Open folder in a new browser tab: \n" +
1658  "srcs" + path + "/" + name,
1659  "onclick":"DesktopContent.openNewBrowserTab(" +
1660  "\"" + name + "\",\"\"," +
1661  "\"/WebPath/html/CodeEditor.html?urn=" +
1662  DesktopContent._localUrnLid + "&" +
1663  "openDirectoryPrimary=" +
1664  path + "/" + name + "\",0 /*unique*/);' ", //end onclick
1665  },
1666  "<img class='dirNavFileNewWindowImgNewWindow' " +
1667  "src='/WebPath/images/windowContentImages/CodeEditor-openInNewWindow.png'>"
1668  /*innerHTML*/, true /*doCloseTag*/);
1669 
1670  //open in other pane
1671  str += htmlOpen("a",
1672  {
1673  "title":"Open folder in the other editor pane of the split-view: \n" +
1674  "srcs" + path + "/" + name,
1675  "onclick":"CodeEditor.editor.openDirectory(" +
1676  (!forPrimary) + ",\"" +
1677  path + "/" + name + "\");", //end onclick
1678  },
1679  "<img class='dirNavFileNewWindowImgNewPane' " +
1680  "src='/WebPath/images/windowContentImages/CodeEditor-openInOtherPane.png'>"
1681  /*innerHTML*/, true /*doCloseTag*/);
1682 
1683  //open in this pane
1684  str += "<a class='dirNavFolder' onclick='CodeEditor.editor.openDirectory(" +
1685  forPrimary + ",\"" +
1686  path + "/" + name + "\"" +
1687  ")' title='Open folder: \nsrcs" + path + "/" + name + "' >" +
1688  name + "</a>";
1689 
1690 
1691  str += "<br>";
1692 
1693  }
1695  //show files
1696  for(i=0;i<files.length;++i)
1697  {
1698  name = files[i].getAttribute('value');
1699 
1700  //open in new window
1701  str += htmlOpen("a",
1702  {
1703  "title":"Open file in a new browser tab: \n" +
1704  "srcs" + path + "/" + name,
1705  "onclick":"DesktopContent.openNewBrowserTab(" +
1706  "\"" + name + "\",\"\"," +
1707  "\"/WebPath/html/CodeEditor.html?urn=" +
1708  DesktopContent._localUrnLid + "&" +
1709  "startFilePrimary=" +
1710  path + "/" + name + "\",0 /*unique*/);' ", //end onclick
1711  },
1712  "<img class='dirNavFileNewWindowImgNewWindow' " +
1713  "src='/WebPath/images/windowContentImages/CodeEditor-openInNewWindow.png'>"
1714  /*innerHTML*/, true /*doCloseTag*/);
1715 
1716  //open in other pane
1717  str += htmlOpen("a",
1718  {
1719  "title":"Open file in the other editor pane of the split-view: \n" +
1720  "srcs" + path + "/" + name,
1721  "onclick":"CodeEditor.editor.openFile(" +
1722  (!forPrimary) + ",\"" +
1723  path + "/" + name + "\", \"" +
1724  name.substr(name.lastIndexOf('.')+1) + "\"" + //extension
1725  ");", //end onclick
1726  },
1727  "<img class='dirNavFileNewWindowImgNewPane' " +
1728  "src='/WebPath/images/windowContentImages/CodeEditor-openInOtherPane.png'>"
1729  /*innerHTML*/, true /*doCloseTag*/);
1730 
1731 
1732  //open in this pane
1733  str += "<a class='dirNavFile' onclick='CodeEditor.editor.openFile(" +
1734  forPrimary + ",\"" +
1735  path + "/" + name + "\", \"" +
1736  name.substr(name.lastIndexOf('.')+1) + "\"" + //extension
1737  ")' title='Open file: \nsrcs" + path + "/" + name + "' >" +
1738  name + "</a>";
1739 
1740 
1741 
1742  str += "<br>";
1743 
1744  }
1745  str += "</div>";
1746  document.getElementById("directoryNav" + forPrimary).innerHTML = str;
1747  } //end handleDirectoryContent()
1748 
1749  //=====================================================================================
1750  //openDirectory ~~
1751  // open directory to directory nav
1752  this.openDirectory = function(forPrimary,path,doNotOpenPane)
1753  {
1754  forPrimary = forPrimary?1:0;
1755 
1756  if(!path || path == "") path = "/"; //defualt to root
1757  Debug.log("openDirectory forPrimary=" + forPrimary +
1758  " path=" + path);
1759 
1760  DesktopContent.XMLHttpRequest("Request?RequestType=" + _requestPreamble +
1761  "codeEditor" +
1762  "&option=getDirectoryContent" +
1763  "&path=" + path
1764  , "" /* data */,
1765  function(req)
1766  {
1767  CodeEditor.editor.handleDirectoryContent(forPrimary, req);
1768  CodeEditor.editor.toggleDirectoryNav(forPrimary,1 /*set nav mode*/);
1769 
1770 
1771  //if secondary and not shown, show
1772  if(!doNotOpenPane && !forPrimary && _viewMode == 0)
1773  CodeEditor.editor.toggleView();
1774 
1775  }); // end codeEditor getDirectoryContent handler
1776  } //end openDirectory()
1777 
1778  //=====================================================================================
1779  //openRelatedFile ~~
1780  // open the related file in text editor
1781  this.openRelatedFile = function(forPrimary,inOtherPane)
1782  {
1783  Debug.log("openRelatedFile forPrimary=" + forPrimary +
1784  " path=" + _filePath[forPrimary]);
1785 
1786  var relatedPath = _filePath[forPrimary];
1787  var relatedExtension = _fileExtension[forPrimary];
1788  var targetPane = inOtherPane?!forPrimary:forPrimary;
1789 
1790  var altPaths = [];
1791  var altExtensions = [];
1792 
1793  if(relatedExtension == "html")
1794  {
1795  relatedExtension = "js";
1796  var i = relatedPath.indexOf("/html/");
1797  if(i >= 0)
1798  {
1799  altPaths.push(relatedPath.substr(0,i) + "/css/" +
1800  relatedPath.substr(i + ("/html/").length));
1801  altExtensions.push("css");
1802 
1803  relatedPath = relatedPath.substr(0,i) + "/js/" +
1804  relatedPath.substr(i + ("/html/").length);
1805  }
1806  else
1807  {
1808  altPaths.push(relatedPath);
1809  altExtensions.push("css");
1810  }
1811 
1812  CodeEditor.editor.openFile(targetPane,relatedPath,relatedExtension,
1813  undefined /*doConfirm*/, undefined/*gotoLine*/,
1814  altPaths /*altPaths*/, altExtensions/*altExtensions*/);
1815  return;
1816  }
1817  else if(relatedExtension[0] == "h")
1818  {
1819  relatedExtension = "cc";
1820 
1821  altPaths.push(relatedPath);
1822  altExtensions.push("cc");
1823 
1824  altPaths.push(relatedPath+"_interface");
1825  altExtensions.push("cc");
1826  altPaths.push(relatedPath+"_processor");
1827  altExtensions.push("cc");
1828  altPaths.push(relatedPath+"_slowcontrols");
1829  altExtensions.push("cc");
1830  altPaths.push(relatedPath+"_table");
1831  altExtensions.push("cc");
1832 
1833  altPaths.push(relatedPath);
1834  altExtensions.push("cpp");
1835  altPaths.push(relatedPath);
1836  altExtensions.push("CC");
1837  altPaths.push(relatedPath);
1838  altExtensions.push("cxx");
1839  altPaths.push(relatedPath);
1840  altExtensions.push("c");
1841  altPaths.push(relatedPath);
1842  altExtensions.push("C");
1843  altPaths.push(relatedPath);
1844  altExtensions.push("icc");
1845 
1846  //try special plugin addons
1847  if(relatedPath.indexOf("Interface") >= 0)
1848  relatedPath += "_interface";
1849  else if(relatedPath.indexOf("Processor") >= 0)
1850  relatedPath += "_processor";
1851  else if(relatedPath.indexOf("Consumer") >= 0)
1852  relatedPath += "_processor";
1853  else if(relatedPath.indexOf("Producer") >= 0)
1854  relatedPath += "_processor";
1855  else if(relatedPath.indexOf("Controls") >= 0)
1856  relatedPath += "_slowcontrols";
1857  else if(relatedPath.indexOf("Table") >= 0)
1858  relatedPath += "_table";
1859 
1860  CodeEditor.editor.openFile(targetPane,relatedPath,relatedExtension,
1861  undefined /*doConfirm*/, undefined/*gotoLine*/,
1862  altPaths /*altPaths*/, altExtensions/*altExtensions*/);
1863  return;
1864  }
1865  else if(relatedExtension == "css")
1866  {
1867  relatedExtension = "js";
1868  var i = relatedPath.indexOf("/css/");
1869 
1870  if(i >= 0)
1871  {
1872  altPaths.push(relatedPath.substr(0,i) + "/html/" +
1873  relatedPath.substr(i + ("/css/").length));
1874  altExtensions.push("html");
1875 
1876  relatedPath = relatedPath.substr(0,i) + "/js/" +
1877  relatedPath.substr(i + ("/css/").length);
1878  }
1879  else
1880  {
1881  altPaths.push(relatedPath);
1882  altExtensions.push("html");
1883  }
1884 
1885  CodeEditor.editor.openFile(targetPane,relatedPath,relatedExtension,
1886  undefined /*doConfirm*/, undefined/*gotoLine*/,
1887  altPaths /*altPaths*/, altExtensions/*altExtensions*/);
1888  return;
1889  }
1890  else if(relatedExtension[0] == 'c' ||
1891  relatedExtension[0] == 'C' ||
1892  relatedExtension == "icc")
1893  {
1894  relatedExtension = "h";
1895 
1896  altPaths.push(relatedPath); //with _interface left in
1897  altExtensions.push("h");
1898 
1899  var i;
1900  if((i = relatedPath.indexOf("_interface")) > 0 &&
1901  i == relatedPath.length-("_interface").length)
1902  relatedPath = relatedPath.substr(0,i); //remove interface
1903  if((i = relatedPath.indexOf("_processor")) > 0 &&
1904  i == relatedPath.length-("_processor").length)
1905  relatedPath = relatedPath.substr(0,i); //remove processor
1906  if((i = relatedPath.indexOf("_slowcontrols")) > 0 &&
1907  i == relatedPath.length-("_slowcontrols").length)
1908  relatedPath = relatedPath.substr(0,i); //remove controls
1909  if((i = relatedPath.indexOf("_table")) > 0 &&
1910  i == relatedPath.length-("_table").length)
1911  relatedPath = relatedPath.substr(0,i); //remove table
1912 
1913  altPaths.push(relatedPath);
1914  altExtensions.push("hh");
1915  altPaths.push(relatedPath);
1916  altExtensions.push("hpp");
1917  altPaths.push(relatedPath);
1918  altExtensions.push("hxx");
1919  altPaths.push(relatedPath);
1920  altExtensions.push("H");
1921 
1922  CodeEditor.editor.openFile(targetPane,relatedPath,relatedExtension,
1923  undefined /*doConfirm*/, undefined/*gotoLine*/,
1924  altPaths /*altPaths*/, altExtensions/*altExtensions*/);
1925  return;
1926  }
1927  else if(relatedExtension == "js")
1928  {
1929  relatedExtension = "css";
1930  var i = relatedPath.indexOf("/js/");
1931 
1932  if(i >= 0)
1933  {
1934  altPaths.push(relatedPath.substr(0,i) + "/html/" +
1935  relatedPath.substr(i + ("/js/").length));
1936  altExtensions.push("html");
1937 
1938  relatedPath = relatedPath.substr(0,i) + "/css/" +
1939  relatedPath.substr(i + ("/js/").length);
1940  }
1941  else
1942  {
1943  altPaths.push(relatedPath);
1944  altExtensions.push("html");
1945  }
1946 
1947  CodeEditor.editor.openFile(targetPane,relatedPath,relatedExtension,
1948  undefined /*doConfirm*/, undefined/*gotoLine*/,
1949  altPaths /*altPaths*/, altExtensions/*altExtensions*/);
1950  return;
1951  }
1952 
1953  Debug.log("Giving up on attempt to open a related file for " +
1954  relatedPath + "." + relatedExtension +
1955  "... no known related file.", Debug.HIGH_PRIORITY);
1956 
1957  } //end openRelatedFile ()
1958 
1959  //=====================================================================================
1960  //openFile ~~
1961  // open the file in text editor
1962  //
1963  // Before opening on disk, check the file history stack.
1964  //
1965  // If altPaths provided, they are tried on error
1966  this.openFile = function(forPrimary,path,extension,doConfirm,gotoLine,
1967  altPaths,altExtensions,propagateErr)
1968  {
1969  forPrimary = forPrimary?1:0;
1970 
1971  Debug.log("openFile forPrimary=" + forPrimary +
1972  " path=" + path);
1973  var i = path.lastIndexOf('.');
1974  if(i > 0) //up to extension
1975  path = path.substr(0,i);
1976 
1977  if(!propagateErr) propagateErr = "";
1978 
1979  if(doConfirm)
1980  {
1981  DesktopContent.popUpVerification(
1982  "Do you want to reload the file from the server (and discard your changes)?",
1983  localDoIt
1984  );
1985  return;
1986  }
1987  else
1988  {
1989  //check the file history stack first
1990  var keys = Object.keys(_fileHistoryStack);
1991  var filename = path + "." + extension;
1992  for(i;i<keys.length;++i)
1993  if(filename == keys[i])
1994  {
1995  Debug.log("Found " + filename + " in file history.");
1996 
1997  //do not open file, just cut to the existing content in stack
1998 
1999  var fileObj = {};
2000  fileObj.path = path;
2001  fileObj.extension = extension;
2002  fileObj.text = _fileHistoryStack[filename][0];
2003  fileObj.fileWasModified = _fileHistoryStack[filename][2];
2004  fileObj.fileLastSave = _fileHistoryStack[filename][3];
2005 
2006  console.log("fileObj",fileObj);
2007 
2008  CodeEditor.editor.handleFileContent(forPrimary,0,fileObj);
2009 
2010  CodeEditor.editor.toggleDirectoryNav(forPrimary, false /*set val*/);
2011 
2012  //if secondary and not shown, show
2013  if(!forPrimary && _viewMode == 0)
2014  CodeEditor.editor.toggleView();
2015 
2016  return;
2017  }
2018 
2019  localDoIt();
2020  }
2021 
2022  function localDoIt()
2023  {
2024  CodeEditor.editor.toggleDirectoryNav(forPrimary,false /*set val*/);
2025 
2026  DesktopContent.XMLHttpRequest("Request?RequestType=" + _requestPreamble +
2027  "codeEditor" +
2028  "&option=getFileContent" +
2029  "&path=" + path +
2030  "&ext=" + extension
2031  , "" /* data */,
2032  function(req)
2033  {
2034 
2035  var err = DesktopContent.getXMLValue(req,"Error"); //example application level error
2036  if(err)
2037  {
2038  if(altPaths && altPaths.length &&
2039  altExtensions && altExtensions.length) //if other files to try, try them
2040  {
2041  //Debug.log(err,Debug.INFO_PRIORITY); //do not call error until final attempt
2042  CodeEditor.editor.openFile(forPrimary,
2043  altPaths.splice(0,1)[0], //try first alt path
2044  altExtensions.splice(0,1)[0], //try first alt extension
2045  undefined /*doConfirm*/, undefined/*gotoLine*/,
2046  altPaths /*altPaths*/, altExtensions/*altExtensions*/,
2047  propagateErr + err /* propagateErr */ );
2048  }
2049  else //not alt files, so this is an error
2050  Debug.log(propagateErr + err,Debug.HIGH_PRIORITY); //log error and create pop-up error box
2051 
2052 
2053  return;
2054  }
2055 
2056 
2057  try
2058  {
2059  CodeEditor.editor.toggleDirectoryNav(forPrimary,0 /*set nav mode*/);
2060  CodeEditor.editor.handleFileContent(forPrimary, req);
2061 
2062  //if secondary and not shown, show
2063  if(!forPrimary && _viewMode == 0)
2064  CodeEditor.editor.toggleView();
2065 
2066  if(gotoLine !== undefined)
2067  CodeEditor.editor.gotoLine(forPrimary,gotoLine);
2068  }
2069  catch(e)
2070  {
2071  Debug.log("Ignoring error handling file open:[" + DesktopContent.getExceptionLineNumber(e) + "]: " + e);
2072  }
2073  console.log(DesktopContent._loadBox.style.display);
2074 
2075  }, //end codeEditor getFileContent handler
2076  0 /*reqParam*/, 0 /*progressHandler*/, 1 /*callHandlerOnErr*/);
2077  } //end localDoIt()
2078  } //end openFile()
2079 
2080  //=====================================================================================
2081  //getLine ~~
2082  // returns current cursor with line number
2083  this.getLine = function(forPrimary)
2084  {
2085  Debug.log("getLine() forPrimary=" + forPrimary);
2086 
2087 
2088  var el = _eel[forPrimary];
2089  var cursor = CodeEditor.editor.getCursor(el);
2090  cursor.line = 1;
2091  if(cursor.startNodeIndex === undefined)
2092  {
2093  Debug.log("No cursor, so defaulting to top");
2094  return cursor;
2095  }
2096 
2097 
2098  var i,n,node,val;
2099  for(n=0; n<el.childNodes.length; ++n)
2100  {
2101  node = el.childNodes[n];
2102  val = node.textContent;
2103 
2104 
2105  for(i=0;i<val.length;++i)
2106  {
2107  //want to be one character past new line
2108  if(!cursor.focusAtEnd &&
2109  n == cursor.startNodeIndex &&
2110  i == cursor.startPos)
2111  break; //done counting lines in this value
2112  else if(cursor.focusAtEnd &&
2113  n == cursor.endNodeIndex &&
2114  i == cursor.endPos)
2115  break; //done counting lines in this value
2116 
2117  if(val[i] == '\n')
2118  ++cursor.line;
2119  }
2120 
2121  //when completed start node, done
2122  if(!cursor.focusAtEnd &&
2123  n == cursor.startNodeIndex)
2124  {
2125  Debug.log("Found cursor at line " + cursor.line);
2126  break; //done!
2127  }
2128  else if(cursor.focusAtEnd &&
2129  n == cursor.endNodeIndex)
2130  {
2131  Debug.log("Found cursor at line " + cursor.line);
2132  break; //done!
2133  }
2134  } //end line count loop
2135 
2136  return cursor;
2137  } //end getLine()
2138 
2139  //=====================================================================================
2140  //gotoLine ~~
2141  this.gotoLine = function(forPrimary,line,selectionCursor,topOfView)
2142  {
2143  line = line | 0;
2144  if(line < 1) line = 1;
2145  if(line > _numberOfLines[forPrimary])
2146  line = _numberOfLines[forPrimary];
2147  console.log("Goto line number ",line,selectionCursor);
2148 
2149  if(topOfView)
2150  window.location.href = "#" + forPrimary + "L" + line;
2151 
2152  //then set cursor, so moving the cursor does not lose position
2153  // steps:
2154  // count new lines while going through elements, then set cursor there
2155 
2156  var el = _eel[forPrimary];
2157 
2158  if(line < 2)
2159  {
2160  //cursor is placed at line 1
2161  //0 element and index, set cursor
2162  var cursor = {
2163  "startNodeIndex": 0,
2164  "startPos":0,
2165  "endNodeIndex":0,
2166  "endPos":0,
2167  };
2168 
2169  //if selection cursor, handle highlighting
2170  if(selectionCursor)
2171  {
2172  cursor.endNodeIndex = selectionCursor.startNodeIndex;
2173  cursor.endPos = selectionCursor.startPos;
2174  cursor.focusAtEnd = selectionCursor.focusAtEnd;
2175  }
2176  CodeEditor.editor.setCursor(el,cursor,true /*scrollIntoView*/);
2177 
2178  return line;
2179  }
2180 
2181  var i,n,node,el,val;
2182  var lineCount = 1;
2183  var found = false;
2184  var newLine = false;
2185 
2186  var lastNode = 0;
2187  var lastPos = 0;
2188  for(n=0; n<el.childNodes.length; ++n)
2189  {
2190  node = el.childNodes[n];
2191  val = node.textContent;
2192 
2193 
2194  for(i=0;i<val.length;++i)
2195  {
2196  if(newLine)
2197  {
2198  lastNode = n;
2199  lastPos = i;
2200  }
2201 
2202  //want to be one character past new line
2203  if(line == lineCount)
2204  {
2205 
2206  Debug.log("Found line " + line);
2207  found = true;
2208  break;
2209  }
2210 
2211  newLine = false;
2212  if(val[i] == '\n')
2213  {
2214  ++lineCount;
2215  newLine = true;
2216  }
2217  }
2218  if(found) break;
2219  } //end line count loop
2220 
2221 
2222  //have element in index, set cursor
2223  var cursor = {
2224  "startNodeIndex":lastNode,
2225  "startPos":lastPos,
2226  "endNodeIndex":lastNode,
2227  "endPos":lastPos,
2228  };
2229 
2230 
2231  //if selection cursor, handle highlighting
2232  if(selectionCursor)
2233  {
2234  cursor.focusAtEnd = selectionCursor.focusAtEnd;
2235 
2236  if(lastNode < selectionCursor.startNodeIndex ||
2237  ( lastNode == selectionCursor.startNodeIndex &&
2238  lastPos < selectionCursor.startPos))
2239  {
2240  cursor.endNodeIndex = selectionCursor.startNodeIndex;
2241  cursor.endPos = selectionCursor.startPos;
2242  }
2243  else
2244  {
2245  cursor.startNodeIndex = selectionCursor.startNodeIndex;
2246  cursor.startPos = selectionCursor.startPos;
2247  }
2248 
2249 
2250  CodeEditor.editor.setCursor(el,cursor,
2251  true /*scrollIntoView*/);
2252  return line;
2253  }
2254 
2255 
2256  CodeEditor.editor.setCursor(el,cursor,true /*scrollIntoView*/);
2257 
2258  return line;
2259 
2260  } //end gotoLine
2261 
2262  //=====================================================================================
2263  //handleFileContent ~~
2264  // redraw text editor based on file content in req response
2265  //
2266  // if req is undefined, attempts to use
2267  // fileObj:={path, extension, text, fileWasModified, fileLastSave}
2268  this.handleFileContent = function(forPrimary,req,fileObj)
2269  {
2270  forPrimary = forPrimary?1:0;
2271 
2272  Debug.log("handleFileContent forPrimary=" + forPrimary);
2273  console.log(req);
2274 
2275  var path;
2276  var extension;
2277  var text;
2278  var fileWasModified, fileLastSave;
2279 
2280  if(req)
2281  {
2282  path = DesktopContent.getXMLValue(req,"path");
2283  extension = DesktopContent.getXMLValue(req,"ext");
2284  text = DesktopContent.getXMLValue(req,"content");
2285  fileWasModified = false;
2286  fileLastSave = 0; //default time to 0
2287  }
2288  else
2289  {
2290  path = fileObj.path;
2291  extension = fileObj.extension;
2292  text = fileObj.text;
2293  fileWasModified = fileObj.fileWasModified;
2294  fileLastSave = fileObj.fileLastSave;
2295  }
2296 
2297  //replace weird space characters (e.g. from emacs tab character two &#160's)
2298  // with spaces
2299  text = text.replace(new RegExp(
2300  String.fromCharCode(160),'g'),' ');//String.fromCharCode(160,160),'g'),'\t');
2301 
2302  //console.log(text);
2303 
2304  _filePath[forPrimary] = path;
2305  _fileExtension[forPrimary] = extension;
2306  _fileWasModified[forPrimary] = fileWasModified;
2307  _fileLastSave[forPrimary] = fileLastSave;
2308 
2309  _undoStack[forPrimary] = []; //clear undo stack
2310  _undoStackLatestIndex[forPrimary] = -1; //reset latest undo index
2311 
2312  var el = _eel[forPrimary];
2313 
2314  //do decor in timeout to show loading
2315  DesktopContent.showLoading(function()
2316  {
2317  try
2318  {
2319  el.textContent = text;
2320  CodeEditor.editor.displayFileHeader(forPrimary);
2321  }
2322  catch(e)
2323  { Debug.log("Ignoring error:[" + DesktopContent.getExceptionLineNumber(e) + "]: " + e); }
2324  }); //end show loading
2325 
2326  } //end handleFileContent()
2327 
2328  //=====================================================================================
2329  //setCursor ~~
2330  this.setCursor = function(el,inCursor,scrollIntoView)
2331  {
2332  if(inCursor.startNodeIndex !== undefined)
2333  {
2334  //make a copy of cursor, in case modifications are needed
2335  var cursor = {
2336  "startNodeIndex": inCursor.startNodeIndex,
2337  "startPos": inCursor.startPos,
2338  "endNodeIndex": inCursor.endNodeIndex,
2339  "endPos": inCursor.endPos,
2340  "focusAtEnd": inCursor.focusAtEnd,
2341  };
2342 
2343  //if focus is at end, set scrollEndIntoView
2344  var scrollEndIntoView = cursor.focusAtEnd?true:false;
2345 
2346  try
2347  {
2348  console.log("set cursor",cursor,"scrollIntoView=",scrollIntoView,
2349  "scrollEndIntoView=",scrollEndIntoView);
2350 
2351  var range = document.createRange();
2352 
2353  var firstEl = el.childNodes[cursor.startNodeIndex];
2354  //if(firstEl.firstChild)
2355  // firstEl = firstEl.firstChild;
2356 
2357  var secondEl = el.childNodes[cursor.endNodeIndex];
2358  //if(secondEl.firstChild)
2359  // secondEl = secondEl.firstChild;
2360 
2361  if(scrollIntoView)
2362  {
2363  Debug.log("scrollIntoView");
2364 
2365  //try to scroll end element and then first element,
2366  // but if it fails then it is likely text
2367  //Note: for scrollIntoView() to work, it seems
2368  // browser requires at least one character in the
2369  // scrolling element.
2370 
2371 
2372  Debug.log("inserting scroll 2nd element");
2373  try
2374  {
2375  //add an element to scroll into view and then remove it
2376  var val = secondEl.textContent;
2377  if(cursor.endPos < val.length)
2378  {
2379  var newNode1 = document.createTextNode(
2380  val.substr(0,cursor.endPos)); //pre-special text
2381  el.insertBefore(newNode1,secondEl);
2382 
2383  var newNode = document.createElement("label");
2384  newNode.textContent = val[cursor.endPos]; //special text
2385  el.insertBefore(newNode,secondEl);
2386 
2387  if(cursor.endPos+1 < val.length)
2388  secondEl.textContent = val.substr(cursor.endPos+1); //post-special text
2389  else
2390  secondEl.textContent = "";
2391 
2392  newNode.scrollIntoViewIfNeeded(); //target the middle special text
2393 
2394  el.removeChild(newNode);
2395  el.removeChild(newNode1);
2396  secondEl.textContent = val;
2397  }
2398  else //if is the end of the first element, focus on next element
2399  {
2400  el.childNodes[cursor.endNodeIndex+1].scrollIntoViewIfNeeded();
2401  }
2402 
2403  }
2404  catch(e)
2405  {
2406  Debug.log("Failed to scroll to inserted 2nd element:[" + DesktopContent.getExceptionLineNumber(e) + "]: " + e);
2407  try
2408  {
2409  secondEl.scrollIntoViewIfNeeded();
2410  }
2411  catch(e)
2412  {
2413  Debug.log("Failed to scroll 2nd element:[" + DesktopContent.getExceptionLineNumber(e) + "]: " + e);
2414  }
2415  }
2416 
2417 
2418  Debug.log("inserting scroll 1st element");
2419  try
2420  {
2421 
2422 
2423  if(!scrollEndIntoView)
2424  {
2425  //add an element to scroll into view and then remove it
2426  firstEl = el.childNodes[cursor.startNodeIndex];
2427  var val = firstEl.textContent;
2428  var newNode1 = document.createTextNode(
2429  val.substr(0,cursor.startPos)); //pre-special text
2430 
2431  el.insertBefore(newNode1,firstEl);
2432 
2433  var newNode = document.createElement("label");
2434  newNode.textContent = val[cursor.startPos]; //special text
2435  el.insertBefore(newNode,firstEl);
2436 
2437  firstEl.textContent = val.substr(cursor.startPos+1); //post-special text
2438 
2439  newNode.scrollIntoViewIfNeeded();
2440 
2441  //now remove new nodes
2442  el.removeChild(newNode);
2443  el.removeChild(newNode1);
2444  firstEl.textContent = val;
2445  }
2446  else
2447  Debug.log("scrollEndIntoView only");
2448  }
2449  catch(e)
2450  {
2451  Debug.log("Failed to scroll to inserted 1st element:[" + DesktopContent.getExceptionLineNumber(e) + "]: " + e);
2452  try
2453  {
2454  firstEl.scrollIntoViewIfNeeded();
2455  }
2456  catch(e)
2457  {
2458  Debug.log("Failed to scroll 1st element:[" + DesktopContent.getExceptionLineNumber(e) + "]: " + e);
2459  }
2460  }
2461 
2462 
2463 
2464 
2465  } //end scrollIntoView
2466 
2467  if(firstEl.firstChild)
2468  firstEl = firstEl.firstChild;
2469  if(secondEl.firstChild)
2470  secondEl = secondEl.firstChild;
2471 
2472  range.setStart(firstEl,
2473  cursor.startPos);
2474  range.setEnd(secondEl,
2475  cursor.endPos);
2476 
2477  //el.focus();
2478  var selection = window.getSelection();
2479  selection.removeAllRanges();
2480  selection.addRange(range);
2481 
2482  //if selecting forward, then place focus on end element
2483  if(scrollEndIntoView)
2484  selection.extend(secondEl,cursor.endPos);
2485  //else
2486  // selection.extend(firstEl,cursor.startPos);
2487 
2488  if(scrollIntoView)
2489  el.focus();
2490 
2491 
2492 
2493  }
2494  catch(err)
2495  {
2496  console.log("set cursor err:",err);
2497  }
2498  } //end set cursor placement
2499  } //end setCursor()
2500 
2501  //=====================================================================================
2502  //createCursorFromContentPosition ~~
2503  this.createCursorFromContentPosition = function(el,startPos,endPos)
2504  {
2505  //handle get cursor location
2506  var cursor = {
2507  "startNodeIndex":undefined,
2508  "startPos":undefined,
2509  "endNodeIndex":undefined,
2510  "endPos":undefined
2511  };
2512 
2513 
2514  var sum = 0;
2515  var oldSum = 0;
2516 
2517  try
2518  {
2519  //find start and end node index
2520  for(i=0;i<el.childNodes.length;++i)
2521  {
2522  sum += el.childNodes[i].textContent.length;
2523 
2524  if(cursor.startNodeIndex === undefined &&
2525  startPos >= oldSum &&
2526  startPos < sum)
2527  {
2528  //found start node
2529  cursor.startNodeIndex = i;
2530  cursor.startPos = startPos - oldSum;
2531  }
2532  if(endPos >= oldSum &&
2533  endPos < sum)
2534  {
2535  //found start node
2536  cursor.endNodeIndex = i;
2537  cursor.endPos = endPos - oldSum;
2538  break; //done!
2539  }
2540 
2541  oldSum = sum;
2542  }
2543 
2544  console.log("createCursorFromContentPosition:",cursor);
2545 
2546  }
2547  catch(err)
2548  {
2549  console.log("get cursor err:",err);
2550  }
2551  return cursor;
2552 
2553  } //end createCursorFromContentPosition()
2554 
2555  //=====================================================================================
2556  //getCursor ~~
2557  this.getCursor = function(el)
2558  {
2559  //handle get cursor location
2560  var cursor = {
2561  "startNodeIndex":undefined,
2562  "startPos":undefined,
2563  "endNodeIndex":undefined,
2564  "endPos":undefined,
2565  "startPosInContent":undefined,
2566  "endPosInContent":undefined,
2567  "focusAtEnd":undefined
2568  };
2569 
2570  var sum = 0;
2571  try
2572  {
2573  var selection = window.getSelection();
2574  var range = selection.getRangeAt(0);
2575  var focusNode = selection.focusNode;
2576  var extentNode = selection.extentNode;
2577 
2578  cursor.startPos = range.startOffset;
2579  cursor.endPos = range.endOffset;
2580 
2581  //find start and end node index
2582  for(i=0;i<el.childNodes.length;++i)
2583  {
2584  if(cursor.startNodeIndex === undefined &&
2585  (
2586  el.childNodes[i] == range.startContainer ||
2587  el.childNodes[i] == range.startContainer.parentNode ||
2588  el.childNodes[i] == range.startContainer.parentNode.parentNode ||
2589  el.childNodes[i] == range.startContainer.parentNode.parentNode.parentNode) )
2590  {
2591  cursor.startNodeIndex = i;
2592  cursor.startPosInContent = sum + cursor.startPos;
2593 
2594  if(focusNode == range.startContainer ||
2595  extentNode == range.startContainer)
2596  cursor.focusAtEnd = false; //focus is at start
2597  }
2598 
2599  if(el.childNodes[i] == range.endContainer ||
2600  el.childNodes[i] == range.endContainer.parentNode ||
2601  el.childNodes[i] == range.endContainer.parentNode.parentNode ||
2602  el.childNodes[i] == range.startContainer.parentNode.parentNode.parentNode)
2603  {
2604  cursor.endNodeIndex = i;
2605  cursor.endPosInContent = sum + cursor.endPos;
2606 
2607  if(cursor.focusAtEnd == undefined &&
2608  (focusNode == range.endContainer ||
2609  extentNode == range.endContainer))
2610  cursor.focusAtEnd = true; //focus is at end
2611 
2612  break; //done!
2613  }
2614 
2615  sum += el.childNodes[i].textContent.length;
2616  }
2617 
2618  //console.log("get cursor",cursor);
2619 
2620 
2621  }
2622  catch(err)
2623  {
2624  console.log("get cursor err:",err);
2625  }
2626  return cursor;
2627  } //end getCursor()
2628 
2629  //=====================================================================================
2630  //updateDecorations ~~
2631  // redraw text editor based on file content in req response
2632  var _DECORATION_RED = "rgb(202, 52, 52)";
2633  var _DECORATION_BLUE = "rgb(64, 86, 206)";
2634  var _DECORATION_GREEN = "rgb(33, 175, 60)";
2635  var _DECORATION_BLACK = "rgb(5, 5, 5)";
2636  var _DECORATION_GRAY = "rgb(162, 179, 158)";
2637  var _DECORATIONS = {
2638  "txt": {
2639  "ADD_SUBDIRECTORY" : _DECORATION_RED,
2640  "include_directories" : _DECORATION_RED,
2641  "simple_plugin" : _DECORATION_RED,
2642  "set" : _DECORATION_RED,
2643  "install_headers" : _DECORATION_RED,
2644  "install_source" : _DECORATION_RED,
2645  "enable_testing" : _DECORATION_RED,
2646  "CMAKE_MINIMUM_REQUIRED": _DECORATION_RED,
2647  "include" : _DECORATION_RED,
2648  "create_doxygen_documentation": _DECORATION_RED,
2649  },
2650  "c++": {
2651  "#define" : _DECORATION_RED,
2652  "#undef" : _DECORATION_RED,
2653  "#include" : _DECORATION_RED,
2654  "#ifndef" : _DECORATION_RED,
2655  "#if" : _DECORATION_RED,
2656  "#else" : _DECORATION_RED,
2657  "#endif" : _DECORATION_RED,
2658  "using" : _DECORATION_RED,
2659  "namespace" : _DECORATION_RED,
2660  "class" : _DECORATION_RED,
2661  "public" : _DECORATION_RED,
2662  "private" : _DECORATION_RED,
2663  "protected" : _DECORATION_RED,
2664  "static" : _DECORATION_RED,
2665  "virtual" : _DECORATION_RED,
2666  "override" : _DECORATION_RED,
2667  "const" : _DECORATION_RED,
2668  "void" : _DECORATION_RED,
2669  "bool" : _DECORATION_RED,
2670  "unsigned" : _DECORATION_RED,
2671  "int" : _DECORATION_RED,
2672  "uint64_t" : _DECORATION_RED,
2673  "uint32_t" : _DECORATION_RED,
2674  "uint16_t" : _DECORATION_RED,
2675  "uint8_t" : _DECORATION_RED,
2676  "size_t" : _DECORATION_RED,
2677  "time_t" : _DECORATION_RED,
2678  "long" : _DECORATION_RED,
2679  "float" : _DECORATION_RED,
2680  "double" : _DECORATION_RED,
2681  "return" : _DECORATION_RED,
2682  "char" : _DECORATION_RED,
2683  "if" : _DECORATION_RED,
2684  "else" : _DECORATION_RED,
2685  "for" : _DECORATION_RED,
2686  "while" : _DECORATION_RED,
2687  "do" : _DECORATION_RED,
2688  "switch" : _DECORATION_RED,
2689  "case" : _DECORATION_RED,
2690  "default" : _DECORATION_RED,
2691  "try" : _DECORATION_RED,
2692  "catch" : _DECORATION_RED,
2693  "this" : _DECORATION_RED,
2694  "true" : _DECORATION_RED,
2695  "false" : _DECORATION_RED,
2696  "auto" : _DECORATION_RED,
2697 
2698  "fstream" : _DECORATION_GREEN,
2699  "sstream" : _DECORATION_GREEN,
2700  "istream" : _DECORATION_GREEN,
2701  "ostream" : _DECORATION_GREEN,
2702 
2703  "std" : _DECORATION_GREEN,
2704  "ots" : _DECORATION_GREEN,
2705  "ConfigurationTree" : _DECORATION_GREEN,
2706  "string" : _DECORATION_GREEN,
2707  "set" : _DECORATION_GREEN,
2708  "vector" : _DECORATION_GREEN,
2709  "array" : _DECORATION_GREEN,
2710  "pair" : _DECORATION_GREEN,
2711  "get" : _DECORATION_GREEN,
2712  "map" : _DECORATION_GREEN,
2713  "endl" : _DECORATION_GREEN,
2714  "runtime_error" : _DECORATION_GREEN,
2715  "memcpy" : _DECORATION_GREEN,
2716  "cout" : _DECORATION_GREEN,
2717  },
2718  "js": {
2719  "this" : _DECORATION_RED,
2720  "var" : _DECORATION_RED,
2721  "return" : _DECORATION_RED,
2722  "function" : _DECORATION_RED,
2723  "if" : _DECORATION_RED,
2724  "else" : _DECORATION_RED,
2725  "for" : _DECORATION_RED,
2726  "while" : _DECORATION_RED,
2727  "do" : _DECORATION_RED,
2728  "switch" : _DECORATION_RED,
2729  "case" : _DECORATION_RED,
2730  "default" : _DECORATION_RED,
2731  "try" : _DECORATION_RED,
2732  "catch" : _DECORATION_RED,
2733  "new" : _DECORATION_RED,
2734  "instanceof" : _DECORATION_RED,
2735  "true" : _DECORATION_RED,
2736  "false" : _DECORATION_RED,
2737 
2738  "Debug" : _DECORATION_GREEN,
2739  "DesktopContent" : _DECORATION_GREEN,
2740  "HIGH_PRIORITY" : _DECORATION_GREEN,
2741  "WARN_PRIORITY" : _DECORATION_GREEN,
2742  "INFO_PRIORITY" : _DECORATION_GREEN,
2743  "LOW_PRIORITY" : _DECORATION_GREEN,
2744 
2745  "Math" : _DECORATION_GREEN,
2746  "String" : _DECORATION_GREEN,
2747  "window" : _DECORATION_GREEN,
2748  "document" : _DECORATION_GREEN,
2749  "textContent" : _DECORATION_GREEN,
2750  "innerHTML" : _DECORATION_GREEN,
2751  },
2752  "sh" : {
2753  "if" : _DECORATION_RED,
2754  "then" : _DECORATION_RED,
2755  "else" : _DECORATION_RED,
2756  "fi" : _DECORATION_RED,
2757  "for" : _DECORATION_RED,
2758  "in" : _DECORATION_RED,
2759  "while" : _DECORATION_RED,
2760  "do" : _DECORATION_RED,
2761  "done" : _DECORATION_RED,
2762  "switch" : _DECORATION_RED,
2763  "case" : _DECORATION_RED,
2764  "default" : _DECORATION_RED,
2765  "export" : _DECORATION_RED,
2766 
2767  "echo" : _DECORATION_GREEN,
2768  "cd" : _DECORATION_GREEN,
2769  "cp" : _DECORATION_GREEN,
2770  "rm" : _DECORATION_GREEN,
2771  "cat" : _DECORATION_GREEN,
2772  "wget" : _DECORATION_GREEN,
2773  "chmod" : _DECORATION_GREEN,
2774  "sleep" : _DECORATION_GREEN,
2775  }
2776  };
2777  this.updateDecorations = function(forPrimary,forceDisplayComplete,forceDecorations)
2778  {
2779  forPrimary = forPrimary?1:0;
2780 
2781  Debug.log("updateDecorations forPrimary=" + forPrimary + " forceDisplayComplete=" + forceDisplayComplete);
2782 
2783  var el = _eel[forPrimary];
2784  var elTextObj = {"text":el.textContent,"time":Date.now()};
2785  var wasSnapshot = CodeEditor.editor.updateFileSnapshot(forPrimary,
2786  elTextObj);
2787 
2788  if(wasSnapshot || forceDisplayComplete)
2789  CodeEditor.editor.updateOutline(forPrimary,elTextObj);
2790 
2791  if(!forceDecorations && !wasSnapshot)
2792  {
2793  Debug.log("unchanged, skipping decorations");
2794 
2795  return;
2796  }
2797 
2798 
2799  var i, j;
2800  var val;
2801 
2802  //get cursor location
2803  var cursor = CodeEditor.editor.getCursor(el);
2804 
2805 
2806  //update last save field
2807  CodeEditor.editor.updateLastSave(forPrimary);
2808 
2809 
2810  var n;
2811  var decor, fontWeight;
2812  var specialString;
2813  var commentString = "#";
2814  //var blockCommentStartString,blockCommentEndString; //TODO
2815 
2816  if(_fileExtension[forPrimary][0] == 'c' ||
2817  _fileExtension[forPrimary][0] == 'C' ||
2818  _fileExtension[forPrimary][0] == 'h' ||
2819  _fileExtension[forPrimary][0] == 'j' ||
2820  _fileExtension[forPrimary] == "icc")
2821  {
2822  commentString = "//"; //comment string
2823  //blockCommentStartString = "/*"; //TODO
2824  //blockCommentEndString = "*/"; //TODO
2825  }
2826 
2827 // if(_fileExtension[forPrimary].length > 2 &&
2828 // _fileExtension[forPrimary].substr(0,3) == "htm")
2829 // {
2830 // blockCommentStartString = "<!--"; //TODO
2831 // blockCommentEndString = "-->"; //TODO
2832 // }
2833 
2834 
2835  var fileDecorType = "txt";
2836  if( _fileExtension[forPrimary] == "html" ||
2837  _fileExtension[forPrimary] == "js")
2838  fileDecorType = "js"; //js style
2839  else if(_fileExtension[forPrimary][0] == 'c' ||
2840  _fileExtension[forPrimary][0] == 'C' ||
2841  _fileExtension[forPrimary][0] == 'h' ||
2842  _fileExtension[forPrimary][0] == 'j' ||
2843  _fileExtension[forPrimary] == "icc")
2844  fileDecorType = "c++"; //c++ style
2845  else if(_fileExtension[forPrimary] == 'sh' ||
2846  _fileExtension[forPrimary] == 'py')
2847  fileDecorType = "sh"; //script style
2848 
2849  var newNode;
2850  var node;
2851 
2852  var startOfWord = -1;
2853  var startOfString = -1;
2854  var stringQuoteChar; // " or '
2855  var escapeCount; //to identify escape \\ even number
2856  var startOfComment = -1;
2857  var firstSpecialStringStartHandling = true;
2858  var firstSpecialStringEndHandling = true;
2859  var endPositionCache; //used to restore end position if special string not closed
2860 
2861  var done = false; //for debuggin
2862 
2863  var eatNode;
2864  var eatVal;
2865  var closedString;
2866 
2867  var prevChar;
2868 
2870  function localInsertLabel(startPos, isQuote)
2871  {
2872  //split text node into 3 nodes.. text | label | text
2873 
2874  newNode = document.createTextNode(val.substr(0,startPos)); //pre-special text
2875  el.insertBefore(newNode,node);
2876 
2877  newNode = document.createElement("label");
2878  newNode.style.fontWeight = fontWeight; //bold or normal
2879  newNode.style.color = decor;
2880  newNode.textContent = specialString; //special text
2881 
2882  el.insertBefore(newNode,node);
2883 
2884  if(isQuote)
2885  {
2886  var str = newNode.textContent;
2887  str = str.substr(str.lastIndexOf('.')+1);
2888 
2889  //note that trailing " or ' is still there
2890  if(str.length > 0 && str.length <= 4 && (
2891  str[0] == 'c' ||
2892  str[0] == 'C' ||
2893  str[0] == 'h' ||
2894  str.substr(0,3) == "txt" ||
2895  str.substr(0,2) == "py" ||
2896  str.substr(0,2) == "sh" ||
2897  str[0] == "j"
2898  ))
2899  {
2900  Debug.log("is quote " + str);
2901 
2902  newNode.onmouseover = function(e)
2903  {
2904  window.clearTimeout(_fileStringHoverTimeout);
2905 
2906  var x = this.offsetWidth + this.offsetLeft + 64;
2907  var y = this.offsetTop;
2908  e.stopPropagation();//to stop body behavior
2909  //Debug.log("loc " + x + " " + y);
2910 
2911  if(_fileStringHoverEl.parentNode)
2912  { //then delete the element
2913  _fileStringHoverEl.parentNode.removeChild(_fileStringHoverEl);
2914  }
2915  else
2916  {
2917  //make the element
2918  _fileStringHoverEl = document.createElement("div");
2919  _fileStringHoverEl.setAttribute("id","fileStringHoverEl");
2920  _fileStringHoverEl.setAttribute("contentEditable","false");
2921  _fileStringHoverEl.onmouseover = function(e)
2922  { //prevent body mouseover handling
2923  window.clearTimeout(_fileStringHoverTimeout);
2924  e.stopPropagation();
2925  };
2926  }
2927 
2928  _fileStringHoverEl.style.display = 'none';
2929 
2930  var str = "";
2931  var name = this.textContent;
2932 
2933  //translate to proper path
2934  name = name.substr(1,name.length-2); //remove quotes
2935  var nameArr = name.split('/');
2936  if(nameArr.length == 0)
2937  {
2938  Debug.log("empty name array, error! name = " + name);
2939  return;
2940  }
2941  else if(nameArr.length > 1 && nameArr[0] == "" &&
2942  nameArr[1] == "WebPath")
2943  {
2944  name = "/otsdaq_utilities/WebGUI" +
2945  name.substr(("/WebPath").length);
2946  }
2947  else if(nameArr[0] != "")
2948  {
2949  //look-up first entry
2950  var i = nameArr[0].indexOf('-');
2951  if(i > 0) //change -'s to _
2952  {
2953  var repo = "";
2954  if(nameArr[0] != "otsdaq-core")
2955  {
2956  nameArr[0] = nameArr[0].substr(0,i) + '_'
2957  + nameArr[0].substr(i+1); //change - to _
2958  }
2959  else
2960  nameArr[0] = "otsdaq";
2961 
2962 
2963  }
2964  //add repo name first
2965  name = "/" + nameArr[0] + "/" + name;
2966  }
2967 
2968 
2969  Debug.log("name " + name);
2970 
2971 
2972  //open in this pane
2973  str += htmlOpen("a",
2974  {
2975  "title":"Open file in this editor pane: \n" +
2976  "srcs" + name,
2977  "onclick":"CodeEditor.editor.openFile(" +
2978  (forPrimary) + ",\"" +
2979  name + "\", \"" +
2980  name.substr(name.lastIndexOf('.')+1) + "\"" + //extension
2981  ");", //end onclick
2982  },
2983  "<div " +
2984  "style='float: left; padding: 1px 0 1px 6px;'>" +
2985  "<div " +
2986  "style='border:1px solid rgb(99, 98, 98); border-radius: 2px; width: 9px;" +
2987  "height: 9px; '></div></div>"
2988  /*innerHTML*/, true /*doCloseTag*/);
2989  //open in other pane
2990  str += htmlOpen("a",
2991  {
2992  "title":"Open file in the other editor pane of the split-view: \n" +
2993  "srcs" + name,
2994  "onclick":"CodeEditor.editor.openFile(" +
2995  (!forPrimary) + ",\"" +
2996  name + "\", \"" +
2997  name.substr(name.lastIndexOf('.')+1) + "\"" + //extension
2998  ");", //end onclick
2999  },
3000  "<div " +
3001  "style='float: left; padding: 0;'>" +
3002  "<img class='dirNavFileNewWindowImgNewPane' " +
3003  "src='/WebPath/images/windowContentImages/CodeEditor-openInOtherPane.png'></div>"
3004  /*innerHTML*/, true /*doCloseTag*/);
3005  //open in new window
3006  str += htmlOpen("a",
3007  {
3008  "title":"Open file in a new browser tab: \n" +
3009  "srcs" + name,
3010  "onclick":"DesktopContent.openNewBrowserTab(" +
3011  "\"" + nameArr[nameArr.length-1] + "\",\"\"," +
3012  "\"/WebPath/html/CodeEditor.html?urn=" +
3013  DesktopContent._localUrnLid + "&" +
3014  "startFilePrimary=" +
3015  name + "\",0 /*unique*/);' ", //end onclick
3016  },
3017  "<div " +
3018  "style='float: left; padding: 0 6px 0 0;'>" +
3019  "<img class='dirNavFileNewWindowImgNewWindow' " +
3020  "src='/WebPath/images/windowContentImages/CodeEditor-openInNewWindow.png'></div>"
3021  /*innerHTML*/, true /*doCloseTag*/);
3022 
3023  _fileStringHoverEl.innerHTML = str;
3024 
3025  this.parentNode.appendChild(_fileStringHoverEl);
3026 
3027  //position + left for line numbers
3028  _fileStringHoverEl.style.left = x + "px";
3029  _fileStringHoverEl.style.top = y + "px";
3030 
3031  _fileStringHoverEl.style.display = 'block';
3032 
3033  } //end special file name string mouseover
3034  } //end special file string handling
3035  } //end quote handling
3036 
3037 
3038  node.textContent = val.substr(i); //post-special text
3039 
3040 
3041  //handle cursor position update
3042  if(cursor.startNodeIndex !== undefined)
3043  {
3044  if(n < cursor.startNodeIndex)
3045  {
3046  //cursor is in a later node
3047  cursor.startNodeIndex += 2; //two nodes were inserted
3048  cursor.endNodeIndex += 2; //two nodes were inserted
3049  }
3050  else
3051  {
3052  //handle start and stop independently
3053 
3054  //handle start
3055  if(n == cursor.startNodeIndex)
3056  {
3057  //determine if cursor is in one of of new nodes
3058  if(cursor.startPos < startPos)
3059  {
3060  //then in first new element, and
3061  // start Node and Pos are still correct
3062  }
3063  else if(cursor.startPos < i)
3064  {
3065  //then in second new element, adjust Node and Pos
3066  ++cursor.startNodeIndex;
3067  cursor.startPos -= startPos;
3068  }
3069  else
3070  {
3071  //then in original last element, adjust Node and Pos
3072  cursor.startNodeIndex += 2;
3073  if(val[cursor.startPos-1] == '\r') --cursor.startPos; //get before any \r
3074  cursor.startPos -= i;
3075  }
3076  } //end start handling
3077 
3078  //handle end
3079  if(n == cursor.endNodeIndex)
3080  {
3081  //determine if cursor is in one of of new nodes
3082  if(cursor.endPos < startPos)
3083  {
3084  //then in first new element, and
3085  // end Node and Pos are still correct
3086  }
3087  else if(cursor.endPos < i)
3088  {
3089  //then in second new element, adjust Node and Pos
3090  ++cursor.endNodeIndex;
3091  cursor.endPos -= startPos;
3092  }
3093  else
3094  {
3095  //then in original last element, adjust Node and Pos
3096  cursor.endNodeIndex += 2;
3097  if(val[cursor.endPos-1] == '\r') --cursor.endPos; //get before any \r
3098  cursor.endPos -= i;
3099 
3100  }
3101  }
3102  else if(n < cursor.endNodeIndex)
3103  {
3104  cursor.endNodeIndex += 2; //two new elements were added in front
3105  }//end end handling
3106 
3107  }
3108  } //end cursor position update
3109 
3110  n += 1; //to return to same modified text node
3111 
3112  } //end localInsertLabel
3113 
3114  for(n=0;!done && n<el.childNodes.length;++n)
3115  {
3116  node = el.childNodes[n];
3117  val = node.textContent; //.nodeValue; //.wholeText
3118 
3119  if(node.nodeName == "LABEL" ||
3120  node.nodeName == "FONT" ||
3121  node.nodeName == "SPAN" ||
3122  node.nodeName == "PRE")
3123  {
3124  //console.log("Label handling...",val);
3125 
3126  //if value is no longer a special word, quote, nor comment, then remove label
3127  if((_DECORATIONS[fileDecorType][val] === undefined &&
3128  (val[0] != commentString[0] || //break up comment if there is a new line
3129  val.indexOf('\n') >= 0) &&
3130  val[0] != '"') ||
3131  //or if cursor is nearby
3132  (n+1 >= cursor.startNodeIndex && n-1 <= cursor.endNodeIndex))
3133  {
3134  //Debug.log("val lost " + val);
3135 
3136  //add text node and delete node
3137  newNode = document.createTextNode(val);
3138  el.insertBefore(newNode,node);
3139  el.removeChild(node);
3140 
3141  //should have no effect on cursor
3142 
3143  --n; //revisit this same node for text rules, now that it is not a special label
3144  continue;
3145  }
3146 
3147  } //end LABEL type handling
3148  else if(node.nodeName == "DIV" ||
3149  node.nodeName == "BR") //adding new lines causes chrome to make DIVs and BRs
3150  {
3151  //get rid of divs as soon as possible
3152  //convert div to text node and then have it reevaluated
3153  eatVal = node.innerHTML; //if html <br> then add another new line
3154  //console.log("div/br",eatVal,val);
3155 
3156  i = 1;
3157  if(node.nodeName == "DIV") //for DIV there may be more or less new lines to add
3158  { // depending on previous new line and <br> tag
3159  if(n > 0) //check for \n in previous node
3160  {
3161  specialString = el.childNodes[n-1].textContent;
3162  if(specialString[specialString.length-1] == '\n')
3163  --i; //remove a new line, because DIV does not cause a new line if already done
3164  }
3165  //check for new line at start of this node
3166  if(eatVal.indexOf("<br>") == 0 ||
3167  eatVal[0] == '\n')
3168  ++i;
3169  }
3170 
3171  if(i == 2)
3172  val = "\n\n" + val;
3173  else if(i == 1)
3174  val = "\n" + val;
3175  //else sometimes i == 0
3176 
3177  //add text node and delete node
3178  newNode = document.createTextNode(val); //add new line to act like div
3179  el.insertBefore(newNode,node);
3180  el.removeChild(node);
3181 
3182  //if cursor was here, then advance to account for added newline
3183  if(n == cursor.startNodeIndex)
3184  cursor.startPos += i;
3185  if(n == cursor.endNodeIndex)
3186  cursor.endPos += i;
3187 
3188  --n; //revisit this same node for text rules
3189  continue;
3190 
3191  } //end DIV type handling
3192  else if(node.nodeName == "#text")
3193  {
3194  if(n > 0 &&
3195  el.childNodes[n-1].nodeName == "#text")
3196  {
3197  //if prev child is text, go back!
3198  n -= 2;
3199  continue;
3200  }
3201 
3202  //merge text nodes
3203  if(n + 1 < el.childNodes.length &&
3204  el.childNodes[n+1].nodeName == "#text")
3205  {
3206  //Debug.log("Merging nodes at " + n);
3207 
3208 
3209  //merging may have an effect on cursor!
3210  //handle cursor position update
3211  if(cursor.startNodeIndex !== undefined)
3212  {
3213  if(n+1 < cursor.startNodeIndex)
3214  {
3215  //cursor is in a later node
3216  cursor.startNodeIndex -= 1; //one node was removed
3217  cursor.endNodeIndex -= 1; //one node was removed
3218  }
3219  else
3220  {
3221  //handle start and stop independently
3222 
3223  //handle start
3224  if(n+1 == cursor.startNodeIndex)
3225  {
3226  //then cursor is in second part of merger
3227  --cursor.startNodeIndex;
3228  cursor.startPos += val.length;
3229  } //end start handling
3230 
3231  //handle end
3232  if(n+1 == cursor.endNodeIndex)
3233  {
3234  //then cursor is in second part of merger
3235  --cursor.endNodeIndex;
3236  cursor.endPos += val.length;
3237  }
3238  else if(n+1 < cursor.endNodeIndex)
3239  {
3240  //then cursor is in a later node
3241  --cursor.endNodeIndex; //one node was removed
3242  }//end end handling
3243  }
3244  } //end cursor position update
3245 
3246  //place in next node and delete this one
3247  newNode = el.childNodes[n+1];
3248  val += newNode.textContent;
3249  newNode.textContent = val;
3250  el.removeChild(node);
3251 
3252  --n; //revisit this same node for text rules, now that it is merged
3253  continue;
3254  } //end merge text nodes
3255 
3256  startOfWord = -1;
3257 
3258  for(i=0;i<val.length;++i)
3259  {
3260 
3261  //for each character:
3262  // check if in quoted string
3263  // then if in comment
3264  // then if special word
3265  if(startOfComment == -1 && ( //string handling
3266  startOfString != -1 ||
3267  (prevChar != '\\' && val[i] == '"') ||
3268  (prevChar != '\\' && val[i] == "'")
3269  ))
3270  {
3271  if(startOfString == -1 && //start string
3272  (val[i] == '"' || val[i] == "'"))
3273  {
3274  startOfString = i;
3275  stringQuoteChar = val[i];
3276 
3277  firstSpecialStringStartHandling = true;
3278  firstSpecialStringEndHandling = true;
3279  }
3280  else if(prevChar != '\\' && val[i] == stringQuoteChar) //end string
3281  {
3282  ++i; //include " in label
3283  specialString = val.substr(startOfString,i-startOfString);
3284  //console.log("string",startOfString,val.length,specialString);
3285 
3286  decor = _DECORATION_BLUE;
3287  fontWeight = "normal";
3288  localInsertLabel(startOfString, true /*isQuote*/);
3289  startOfString = -1;
3290  //done = true; //for debugging
3291  break;
3292  }
3293  }
3294  else if(startOfString == -1 && ( //comment handling
3295  startOfComment != -1 ||
3296  (i+commentString.length-1 < val.length &&
3297  val.substr(i,commentString.length) ==
3298  commentString)))
3299  {
3300  if(startOfComment == -1 && val[i] == commentString[0]) //start comment
3301  {
3302  startOfComment = i;
3303  firstSpecialStringStartHandling = true;
3304  firstSpecialStringEndHandling = true;
3305  }
3306  else if(val[i] == '\n') //end comment
3307  {
3308  //++i; //do not include \n in label
3309  specialString = val.substr(startOfComment,i-startOfComment);
3310  //console.log("comment",startOfComment,val.length,specialString);
3311 
3312  decor = _DECORATION_GRAY;
3313  fontWeight = "normal";
3314  localInsertLabel(startOfComment);
3315  startOfComment = -1;
3316  //done = true; //for debugging
3317  break;
3318  }
3319  }
3320  else if( //special word handling
3321  (val[i] >= 'a' && val[i] <= 'z') ||
3322  (val[i] >= 'A' && val[i] <= 'Z') ||
3323  (val[i] >= '0' && val[i] <= '9') ||
3324  (val[i] == '_' || val[i] == '-') ||
3325  val[i] == '#')
3326  {
3327  if(startOfWord == -1)
3328  startOfWord = i;
3329  //else still within word
3330  }
3331  else if(startOfWord != -1) //found end of word, check for special word
3332  {
3333  specialString = val.substr(startOfWord,i-startOfWord);
3334  decor = _DECORATIONS[fileDecorType][specialString];
3335  //console.log(specialString);
3336 
3337  if(decor) //found special word
3338  {
3339  //console.log(specialString);
3340  fontWeight = "bold";
3341  localInsertLabel(startOfWord);
3342  startOfWord = -1;
3343  //done = true; //for debugging
3344  break;
3345  }
3346  else
3347  startOfWord = -1;
3348  }
3349 
3350  //track previous character and handle escape count
3351  if(prevChar == '\\' && val[i] == '\\')
3352  {
3353  ++escapeCount; //increase escape count
3354  if(escapeCount%2 == 0) //if even, then not an escape
3355  prevChar = ''; //clear escape
3356  else //if odd, then treat as an escape
3357  prevChar = '\\';
3358  }
3359  else
3360  {
3361  escapeCount = 1;
3362  prevChar = val[i]; //save previous character (e.g. to check for quote escape)
3363  }
3364  } //end node string value loop
3365 
3366 
3367 
3368 
3369 
3371  //if still within string or comment, handle crossing nodes
3372  if(startOfString != -1 || startOfComment != -1)
3373  {
3374  console.log("In string/comment crossing Nodes!");
3375  //acquire nodes into string until a quote is encountered
3376 
3377  closedString = false;
3378  for(++n;n<el.childNodes.length;++n)
3379  {
3380  eatNode = el.childNodes[n];
3381  eatVal = eatNode.textContent; //.nodeValue; //.wholeText
3382 
3383  //merging may have an effect on cursor!
3384  //handle cursor position update
3385  if(cursor.startNodeIndex !== undefined)
3386  {
3387  if(firstSpecialStringStartHandling) //do nothing, for now
3388  firstSpecialStringStartHandling = false;
3389 
3390  if(firstSpecialStringEndHandling)
3391  {
3392  //first time, add initial comment node string contribution to endPos
3393 
3394  endPositionCache = cursor.endPos; //cache end position in case this is not the last line
3395  firstSpecialStringEndHandling = false;
3396  }
3397 
3398  if(n < cursor.startNodeIndex)
3399  {
3400  //cursor is in a later node
3401  cursor.startNodeIndex -= 1; //one node was removed
3402  cursor.endNodeIndex -= 1; //one node was removed
3403  }
3404  else
3405  {
3406  //handle start and stop independently
3407 
3408  //handle start
3409  if(n == cursor.startNodeIndex)
3410  {
3411  //then cursor is in second part of merger
3412  --cursor.startNodeIndex;
3413  cursor.startPos += val.length;
3414  } //end start handling
3415 
3416  //handle end
3417  if(n == cursor.endNodeIndex)
3418  {
3419  //then cursor is in second part of merger
3420  --cursor.endNodeIndex;
3421  cursor.endPos += val.length;
3422  }
3423  else if(n < cursor.endNodeIndex)
3424  {
3425  //then cursor is in a later node
3426  --cursor.endNodeIndex; //one node was removed
3427  }//end end handling
3428  }
3429  } //end cursor position update
3430 
3431 // //deleteing the node may have an effect on cursor!
3432 // //handle cursor position update
3433 // if(cursor.startNodeIndex !== undefined)
3434 // {
3435 // if(firstSpecialStringStartHandling)
3436 // {
3437 // //first time, add initial comment node string contribution to startPos
3438 //
3439 // cursor.startPos += val.length;
3440 // if(startOfString != -1)
3441 // cursor.startPos -= startOfString;
3442 // else if(startOfComment != -1)
3443 // cursor.startPos -= startOfComment;
3444 //
3445 // firstSpecialStringStartHandling = false;
3446 // }
3447 //
3448 // if(firstSpecialStringEndHandling)
3449 // {
3450 // //first time, add initial comment node string contribution to endPos
3451 //
3452 // endPositionCache = cursor.endPos; //cache end position in case this is not the last line
3453 //
3454 // cursor.endPos += val.length+1;
3455 // if(startOfString != -1)
3456 // cursor.endPos -= startOfString;
3457 // else if(startOfComment != -1)
3458 // cursor.endPos -= startOfComment;
3459 //
3460 // firstSpecialStringEndHandling = false;
3461 // }
3462 //
3463 //
3464 // if(n < cursor.startNodeIndex)
3465 // {
3466 // //cursor is in a later node
3467 // cursor.startNodeIndex -= 1; //one node was removed
3468 // cursor.endNodeIndex -= 1; //one node was removed
3469 //
3470 // cursor.startPos += eatVal.length; //add cursor position in preparation for concat text
3471 // cursor.endPos += eatVal.length;
3472 // }
3473 // else
3474 // {
3475 // //handle start and stop independently
3476 //
3477 // //handle start
3478 // if(n == cursor.startNodeIndex)
3479 // {
3480 // //then cursor is in second part of merger
3481 // --cursor.startNodeIndex;
3482 // } //end start handling
3483 //
3484 // //handle end
3485 // if(n == cursor.endNodeIndex)
3486 // {
3487 // //then cursor is in second part of merger
3488 // --cursor.endNodeIndex;
3489 // }
3490 // else if(n < cursor.endNodeIndex)
3491 // {
3492 // //then cursor is in a later node
3493 // --cursor.endNodeIndex; //one node was removed
3494 // cursor.endPos += eatVal.length;
3495 // }//end end handling
3496 // }
3497 // } //end cursor position update
3498 
3499 
3500  //eat text and delete node
3501  val += eatVal;
3502  el.removeChild(eatNode);
3503  --n; //after removal, move back index for next node
3504 
3505 
3506  //look for quote close or comment close
3507  for(i;i<val.length;++i)
3508  {
3509  //string handling
3510  if(startOfString != -1 &&
3511  (prevChar != '\\' && val[i] == '"')) //end string
3512  {
3513  Debug.log("Closing node crossed string.");
3514 
3515  ++i; //include " in label
3516  specialString = val.substr(startOfString,i-startOfString);
3517  //console.log("string",startOfString,val.length,specialString);
3518 
3519  decor = _DECORATION_BLUE;
3520  fontWeight = "normal";
3521  localInsertLabel(startOfString,true /*isQuote*/);
3522  startOfString = -1;
3523  closedString = true;
3524  break;
3525  }
3526 
3527  //comment handling
3528  if(startOfComment != -1 && val[i] == '\n') //end comment
3529  {
3530  Debug.log("Closing node crossed comment.");
3531 
3532  //++i; //do not include \n in label
3533 
3534  specialString = val.substr(startOfComment,i-startOfComment);
3535  //console.log("string",startOfComment,val.length,specialString);
3536 
3537  decor = _DECORATION_GRAY;
3538  fontWeight = "normal";
3539  localInsertLabel(startOfComment);
3540  startOfComment = -1;
3541 
3542  closedString = true;
3543  break; //exit inner loop
3544 
3545  }
3546 
3547  //track previous character and handle escape count
3548  if(prevChar == '\\' && val[i] == '\\')
3549  {
3550  ++escapeCount; //increase escape count
3551  if(escapeCount%2 == 0) //if even, then not an escape
3552  prevChar = ''; //clear escape
3553  else //if odd, then treat as an escape
3554  prevChar = '\\';
3555  }
3556  else
3557  {
3558  escapeCount = 1;
3559  prevChar = val[i]; //save previous character (e.g. to check for quote escape)
3560  }
3561 
3562  } //end node string value loop
3563 
3564  if(closedString) break; //exit outer loop
3565 
3566  } //end string node crossing node loop
3567 
3568  if(!closedString && startOfString != -1)
3569  {
3570  Debug.log("String is never closed!");
3571  specialString = val.substr(startOfString,i-startOfString);
3572  //console.log("string",startOfString,val.length,specialString);
3573 
3574  decor = _DECORATION_BLUE;
3575  --n; //move back index (because it was incremented past bounds in end search)
3576  localInsertLabel(startOfString, true /*isQuote*/);
3577  startOfString = -1;
3578  }
3579  if(!closedString && startOfComment != -1)
3580  {
3581  Debug.log("Comment is never closed!");
3582  specialString = val.substr(startOfComment,i-startOfComment);
3583  //console.log("string",startOfString,val.length,specialString);
3584 
3585  decor = _DECORATION_GRAY;
3586  --n; //move back index (because it was incremented past bounds in end search)
3587  localInsertLabel(startOfComment);
3588  startOfComment = -1;
3589  }
3590 
3591  if(n < cursor.endNodeIndex)
3592  {
3593  //if did not close string including the endNodeIndex,
3594  // then reset the end of string handling
3595  firstSpecialStringEndHandling = true;
3596  cursor.endPos = endPositionCache;
3597  }
3598 
3599 
3600  } //end crossing nodes with string
3601 
3602  } //end #text type node handling
3603  else
3604  {
3605  console.log("unknown node.nodeName",node.nodeName);
3606  throw("node error!");
3607  }
3608  } //end node loop
3609 
3610  //set cursor placement
3611  CodeEditor.editor.setCursor(el,cursor);
3612 
3613  CodeEditor.editor.updateDualView(forPrimary);
3614 
3615  } //end updateDecorations()
3616 
3617  //=====================================================================================
3618  //autoIndent ~~
3619  this.autoIndent = function(forPrimary, cursor)
3620  {
3621  if(!cursor || cursor.startNodeIndex === undefined)
3622  {
3623  Debug.log("Invalid text selection for auto-indent. Please select text in the text editor.",
3624  Debug.HIGH_PRIORITY);
3625  return;
3626  }
3627  forPrimary = forPrimary?1:0;
3628 
3629  Debug.log("autoIndent " + forPrimary);
3630 
3631  DesktopContent.showLoading(localDoIt);
3632  return;
3633 // window.setTimeout(function()
3634 // {
3635 // localDoIt();
3636 // DesktopContent.hideLoading();
3637 // },100);
3638 
3640  function localDoIt()
3641  {
3642 
3643  //steps:
3644  // get text content for selection back to start of previous new line
3645  // tab it properly
3646  // replace selected lines with modified text
3647 
3648 
3649 
3650  var el = _eel[forPrimary];
3651  var node,val;
3652  var found = false;
3653  var n,i;
3654 
3655  //reverse-find new line
3656  for(n=cursor.startNodeIndex;n>=0; --n)
3657  {
3658  node = el.childNodes[n];
3659  val = node.textContent;
3660 
3661  for(i=(n==cursor.startNodeIndex?cursor.startPos-1:
3662  val.length-1); i>=0; --i)
3663  {
3664  if(val[i] == '\n')
3665  {
3666  //found start of line
3667  found = true;
3668  break;
3669  }
3670  }
3671  if(found) break;
3672  } //end reverse find new line loop
3673 
3674  //assume at new line point (or start of file)
3675  console.log("at leading newline - n",n,"i",i);
3676 
3677  if(n < 0) n = 0;
3678  if(i < 0) i = 0;
3679  else ++i; //skip past new line
3680 
3681  cursor.startNodeIndex = n;
3682  cursor.startPos = i;
3683 
3684  //preText and postText are to be left unchanged
3685  // text is to be auto-indented
3686  var preText = "";
3687  var text = "";
3688  var postText = "";
3689 
3690  for(n=0; n<el.childNodes.length; ++n)
3691  {
3692  val = el.childNodes[n].textContent;
3693  if(n < cursor.startNodeIndex)
3694  preText += val;
3695  else if(n == cursor.startNodeIndex)
3696  {
3697  preText += val.substr(0,cursor.startPos);
3698 
3699  if(n < cursor.endNodeIndex)
3700  text += val.substr(cursor.startPos);
3701  else //n == cursor.endNodeIndex
3702  {
3703  text += val.substr(cursor.startPos,
3704  cursor.endPos-cursor.startPos);
3705  postText += val.substr(cursor.endPos);
3706  }
3707  }
3708  else if(n < cursor.endNodeIndex)
3709  text += val;
3710  else if(n == cursor.endNodeIndex)
3711  {
3712  text += val.substr(0,cursor.endPos);
3713  postText += val.substr(cursor.endPos);
3714  }
3715  else // n > cursor.endNodeIndex
3716  postText += val;
3717  }
3718 
3719  //Debug.log("preText " + preText);
3720  //Debug.log("postText " + postText);
3721  //Debug.log("text " + text);
3722 
3723  var fileExtension = _fileExtension[forPrimary];
3724  if(1
3725 // fileExtension == "cc" ||
3726 // fileExtension == "cpp" ||
3727 // fileExtension == "h" ||
3728 // fileExtension == "js" ||
3729 // fileExtension == "html"
3730  )
3731  {
3732  // find leading whitespace count
3733  var x = 0;
3734  for(i=0;i<text.length;++i)
3735  if(text[i] == ' ')
3736  ++x;
3737  else if(text[i] == '\t')
3738  x += _TAB_SIZE - (x+_TAB_SIZE)%_TAB_SIZE;
3739  else
3740  break; //found white space
3741  Debug.log("Whitespace size =" + x + " tabs=" + ((x/_TAB_SIZE)|0));
3742 
3743  //have starting point in units of number of tabs
3744  var tabStr = "";
3745  for(n=0;n<((x/_TAB_SIZE)|0);++n)
3746  tabStr += '\t';
3747 
3748  //continue through text string, setting leading whitespace as tabStr
3749  newText = "";
3750  i = -1; //start at beginning
3751 
3752  var nextTabStr;
3753 
3754  var lastChar,firstChar;
3755  var prevLastChar,prevFirstChar;
3756 
3757  var inCmdTabStr = "";
3758  var nextInCmdTabStr = "";
3759  var isCmdTabStr = "";
3760  var nextIsCmdTabStr = "";
3761 
3762 
3763 
3764  var foundComment;
3765  var firstColonCommand = false;
3766  var lastColonCommand = false;
3767  var foundDoubleQuote,foundSingleQuote;
3768  var tradeInCmdStack = [];
3769  var tradeIsCmdStack = [];
3770 
3771  do
3772  {
3773  //start of each loop text[i] is \n
3774  //find next newline and last char
3775 
3776  lastChar = '';
3777  firstChar = '';
3778 
3779  foundComment = false;
3780  foundDoubleQuote = false;
3781  foundSingleQuote = false;
3782 
3783  for(n=i+1;n<text.length;++n)
3784  {
3785  if(text[n] == '\n')
3786  break;
3787 
3788  if(foundComment)
3789  continue; //do not proceed with command tabbing if in comment
3790 
3791  if(!foundSingleQuote && text[n] == '"')
3792  foundDoubleQuote = !foundDoubleQuote;
3793  else if(!foundDoubleQuote && text[n] == "'")
3794  foundSingleQuote = !foundSingleQuote;
3795  else if(text[n] == '/' && n+1 < text.length &&
3796  text[n+1] == '/')
3797  {
3798  foundComment = true;
3799  continue; //do not proceed with command tabbing if in comment
3800  }
3801 
3802  if(foundDoubleQuote || foundSingleQuote)
3803  continue; //skip if in quote
3804 
3805  if(text[n] != ' ' && text[n] != '\t')
3806  {
3807  lastChar = text[n];
3808  if(firstChar == '')
3809  firstChar = text[n];
3810  }
3811 
3812 
3813  if(text[n] == '(') //add in-command tab
3814  inCmdTabStr += '\t';
3815  else if(text[n] == ')') //remove in-command tab
3816  inCmdTabStr = inCmdTabStr.substr(0, inCmdTabStr.length-1);
3817  else if(inCmdTabStr.length == 0 &&
3818  text[n] == ';') //clear all in-command tabs
3819  {
3820  inCmdTabStr = "";
3821  isCmdTabStr = "";
3822  firstColonCommand = false;
3823  lastColonCommand = false;
3824  }
3825 
3826  } //end main loop looking for next newline
3827 
3828  nextTabStr = tabStr;
3829 
3830  //handle tabStr before putting together string
3831  if(firstChar == '}')
3832  {
3833  nextIsCmdTabStr = "";
3834 
3835  //trade back if closing } at traded tab point
3836  if(tradeInCmdStack.length &&
3837  tradeInCmdStack[tradeInCmdStack.length-1][0] ==
3838  tabStr.length)
3839  {
3840  //remove a tab
3841  inCmdTabStr = tradeInCmdStack.pop()[1];
3842  nextInCmdTabStr = inCmdTabStr;
3843  isCmdTabStr = tradeIsCmdStack.pop();
3844  tabStr = tabStr.substr(0,tabStr.length-1);
3845  }
3846 
3847  //remove a tab
3848  isCmdTabStr = "";
3849  firstColonCommand = false;
3850  //lastColonCommand = false;
3851  tabStr = tabStr.substr(0,tabStr.length-1);
3852  nextTabStr = tabStr;
3853  }
3854  else if(lastChar == ':' && //ends with :
3855  (firstChar == 'p' || //and start with public/private/protected
3856  firstChar == 'd' || //or default
3857  firstChar == 'c')) //or case
3858  {
3859  nextIsCmdTabStr = "";
3860 
3861  //remove a tab for now
3862  isCmdTabStr = "";
3863  firstColonCommand = false;
3864  lastColonCommand = false;
3865  nextTabStr = tabStr.substr(0,tabStr.length-1);
3866  }
3867  else if(firstChar == ':') //starts with :
3868  {
3869  //remove cmd tab
3870  nextIsCmdTabStr = "";
3871  isCmdTabStr = "";
3872  firstColonCommand = true;
3873  }
3874  else if(firstChar == '#' || //starts with #, i.e. pragma
3875  firstChar == '{') //starts with {
3876  {
3877  //remove a tab, if no open (), things were lined up
3878  if(lastColonCommand)
3879  tabStr = tabStr.substr(0,tabStr.length-1);
3880 
3881  //only remove command tab, if we think we are not in a command
3882  // ending in comma previously, seems like an array
3883  if(nextInCmdTabStr.length != 0 ||
3884  nextIsCmdTabStr.length != 1 ||
3885  prevLastChar != ',')
3886  {
3887  //remove cmd tab
3888  nextIsCmdTabStr = "";
3889  isCmdTabStr = "";
3890  }
3891  }
3892  else if(!firstColonCommand &&
3893  !lastColonCommand &&
3894  lastChar != '' &&
3895  lastChar != ';' &&
3896  firstChar != '"' &&
3897  firstChar != "'") //do one and only tab for a command content that should stack up
3898  isCmdTabStr = '\t';
3899  else if(lastColonCommand &&
3900  prevLastChar == ',' &&
3901  inCmdTabStr.length == 0)
3902  {
3903  //remove a tab for json style definition
3904  lastColonCommand = false;
3905  tabStr = tabStr.substr(0,tabStr.length-1);
3906  isCmdTabStr = "\t";
3907  nextIsCmdTabStr = "\t";
3908  }
3909  else
3910  firstColonCommand = false;
3911 
3912  //if command ended
3913  if(lastChar == ';') //clear all in-command tabs
3914  {
3915  inCmdTabStr = "";
3916  isCmdTabStr = "";
3917  firstColonCommand = false;
3918  lastColonCommand = false;
3919  }
3920 
3921  console.log(
3922  "firstChar = " + firstChar +
3923  "lastChar = " + lastChar +
3924  "prevFirstChar = " + prevFirstChar +
3925  "prevLastChar = " + prevLastChar +
3926  " ... nextTab = " +
3927  tabStr.length +
3928  " + " +
3929  inCmdTabStr.length +
3930  " + " +
3931  isCmdTabStr.length +
3932  " ... nowTab = " +
3933  nextTabStr.length +
3934  " + " +
3935  nextInCmdTabStr.length +
3936  " + " +
3937  nextIsCmdTabStr.length +
3938  " stack=" +
3939  tradeInCmdStack.length +
3940  " " + firstColonCommand +
3941  " " + lastColonCommand);
3942 
3943  if(i >= 0)
3944  newText += text[i];
3945  newText += nextTabStr + nextInCmdTabStr + nextIsCmdTabStr;
3946  //add up to next newline
3947  newText += text.substr(i+1,n-(i+1)).trimLeft();
3948 
3949 
3950  //handle tabStr after putting together string
3951  if(lastChar == '{') //add a tab
3952  {
3953  tabStr += '\t';
3954  isCmdTabStr = "";
3955 
3956  if(inCmdTabStr.length) //if in command then trade to tabStr
3957  {
3958  tabStr += '\t';
3959 
3960  tradeInCmdStack.push([tabStr.length,inCmdTabStr]); //push to stack
3961  tradeIsCmdStack.push(isCmdTabStr); //push to stack
3962 
3963  inCmdTabStr = ""; //clear
3964  isCmdTabStr = ""; //clear
3965  }
3966  }
3967  // else if(lastChar == ':')
3968  // {
3969  // if(inCmdTabStr.length == 0) //this is like a case:, where we want to indent from here
3970  // tabStr += '\t';
3971  // isCmdTabStr = "";
3972  // lastColonCommand = true;
3973  // }
3974  // else
3975  // lastColonCommand = false;
3976 
3977  nextInCmdTabStr = inCmdTabStr;
3978  nextIsCmdTabStr = isCmdTabStr;
3979 
3980  if(lastChar != '')
3981  prevLastChar = lastChar;
3982  if(firstChar != '')
3983  prevFirstChar = firstChar;
3984 
3985  i = n;
3986 
3987  } while(i+1<text.length);
3988 
3989  //if last character is new line, then add it
3990  // otherwise, all characters before next new line were added already
3991  if(text[i] == '\n') newText += '\n';
3992 
3993  //Debug.log("Done newText\n" + newText);
3994  }
3995  else
3996  {
3997  Debug.log("Unknown operation to auto-indent file with extension " +
3998  fileExtension,Debug.HIGH_PRIORITY);
3999  return;
4000  }
4001 
4002  //place text back
4003  el.textContent = preText + newText + postText;
4004 
4005  _fileWasModified[forPrimary] = true;
4006 
4007  CodeEditor.editor.updateDecorations(forPrimary,
4008  false /*forceDisplayComplete*/,
4009  true /*forceDecorations*/);
4010 
4011  } //end localDoIt()
4012  } //end autoIndent
4013 
4014  //=====================================================================================
4015  //updateDualView ~~
4016  this.updateDualView = function(forPrimary)
4017  {
4018  forPrimary = forPrimary?1:0;
4019 
4020  Debug.log("updateDualView " + forPrimary);
4021 
4022 
4023  //if other pane is same path and extension, update it too
4024  if(_filePath[0] == _filePath[1] &&
4025  _fileExtension[0] == _fileExtension[1])
4026  {
4027  var val,node, newNode;
4028  var el = _eel[forPrimary];
4029 
4030  Debug.log("Update dual view");
4031 
4032  _fileLastSave[(!forPrimary)?1:0] = _fileLastSave[forPrimary];
4033  _fileWasModified[(!forPrimary)?1:0] = _fileWasModified[forPrimary];
4034  CodeEditor.editor.updateLastSave(!forPrimary);
4035 
4036  //copy all elements over
4037 
4038  var elAlt = _eel[(!forPrimary)?1:0];
4039  elAlt.innerHTML = ""; //clear all children
4040  for(i=0;i<el.childNodes.length;++i)
4041  {
4042  node = el.childNodes[i];
4043  val = node.textContent;
4044  if(node.nodeName == "LABEL")
4045  {
4046  newNode = document.createElement("label");
4047  newNode.style.fontWeight = node.style.fontWeight; //bold or normal
4048  newNode.style.color = node.style.color;
4049  newNode.textContent = val; //special text
4050  }
4051  else if(node.nodeName == "#text")
4052  {
4053  newNode = document.createTextNode(val);
4054  }
4055  else
4056  Debug.log("Skipping unknown node " + node.nodeName);
4057  elAlt.appendChild(newNode);
4058  }
4059  }
4060 
4061  } //end updateDualView()
4062 
4063  //=====================================================================================
4064  //updateOutline ~~
4065  // elTextObj := {text,time}
4066  this.updateOutline = function(forPrimary,elTextObj)
4067  {
4068  forPrimary = forPrimary?1:0;
4069 
4070  Debug.log("updateOutline " + forPrimary);
4071 
4072  var starti;
4073  var endi;
4074  var strLength;
4075  var str;
4076  var endPi, startCi, lastWhiteSpacei;
4077  var newLinei;
4078  var localNewLineCount;
4079 
4080  var newLineCount = 0;
4081  var outline = []; //line number and name
4082  outline.push([1,"Top"]); //always include top
4083  var i,j,k;
4084  var fail, found;
4085 
4086  var isCcSource = _fileExtension[forPrimary][0] == 'c' ||
4087  _fileExtension[forPrimary][0] == 'C' ||
4088  _fileExtension[forPrimary] == "icc";
4089  var isJsSource = _fileExtension[forPrimary] == "js" ||
4090  _fileExtension[forPrimary] == "html";
4091 
4092  var indicatorIndex = 0;
4093  var indicator = "";
4094  if(isCcSource) indicator = "::";
4095  if(isJsSource) indicator = "function";
4096 
4097  var inComment = false; //ignore indicator in comment
4098  var inBlockComment = false; //ignore indicator in comment
4099 
4100  for(i=0;i<elTextObj.text.length;++i)
4101  {
4102  if(elTextObj.text[i] == '\n')
4103  {
4104  ++newLineCount;
4105  indicatorIndex = 0; //reset
4106  inComment = false; //reset
4107  continue;
4108  }
4109  else if(inBlockComment && i+1 < elTextObj.text.length &&
4110  elTextObj.text[i] == '*' &&
4111  elTextObj.text[i+1] == '/')
4112  inBlockComment = false;
4113  else if(inComment || inBlockComment) continue;
4114  else if(i+1 < elTextObj.text.length)
4115  {
4116  if(elTextObj.text[i] == '/' &&
4117  elTextObj.text[i+1] == '*')
4118  {
4119  inBlockComment = true;
4120  continue;
4121  }
4122  else if(elTextObj.text[i] == '/' &&
4123  elTextObj.text[i+1] == '/')
4124  {
4125  inComment = true;
4126  continue;
4127  }
4128  } //end comment handling
4129 
4130  //find indicators
4131  if(elTextObj.text[i] == indicator[indicatorIndex])
4132  {
4133  ++indicatorIndex;
4134  if(indicatorIndex == indicator.length)
4135  {
4136  //found entire indicator!
4137  //Debug.log("found indicator " + indicator + " i:" + i);
4138 
4139  //look for thing to outline
4140  if(isCcSource)
4141  str = localHandleCcOutline();
4142  else if(isJsSource)
4143  str = localHandleJsOutline();
4144 
4145  if(str)
4146  {
4147  //have a new outline thing
4148  outline.push([newLineCount+1,
4149  str +
4150  "()"]);
4151  }
4152  else
4153  {
4154  inComment = false;
4155  inBlockComment = false;
4156  }
4157  }
4158  }
4159  else
4160  indicatorIndex = 0; //reset
4161 
4162  } // end text content char loop
4163 
4164  ++newLineCount; //always add 1 for good luck
4165 
4166  Debug.log("Number of lines " + newLineCount);
4167  console.log("Done with outline", outline);
4168 
4169  //handle create line numbers
4170  str = "";
4171  for(i=0;i<newLineCount;++i)
4172  {
4173  str += "<a name='" + forPrimary + "L" + (i+1) + "'></a>"; //add anchor tag
4174  str += (i+1);
4175  str += "<br>";
4176  }
4177  document.getElementById("editableBoxLeftMargin" + forPrimary).innerHTML = str;
4178 
4179  _numberOfLines[forPrimary] = newLineCount;
4180  //window.location.href = "#L220";
4181 
4182  if(!isCcSource && !isJsSource)
4183  {
4184  //generate simple outline for non C++ source
4185  i = (newLineCount/2)|0;
4186  if(i > 40)
4187  {
4188  outline.push([i,"Middle"]);
4189  }
4190  } //end simple outline generate
4191  outline.push([newLineCount,"Bottom"]); //always include bottom
4192 
4193  var text;
4194  //handle create outline
4195  str = "";
4196  str += "<center>";
4197  str += "<table><td>"
4198  str += "Outline: ";
4199  str += "</td><td>"; //do select in table so that width plays nice
4200  str += htmlOpen("select",
4201  {
4202  "class":"textEditorOutlineSelect",
4203  "id":"textEditorOutlineSelect" + forPrimary,
4204  "style":"text-align-last: center; width: 100%;",
4205  "title":"Jump to a section of code.",
4206  "onchange":
4207  "CodeEditor.editor.handleOutlineSelect(" + forPrimary + ");",
4208  "onclick":
4209  "CodeEditor.editor.stopUpdateHandling(event);",
4210  },0 /*innerHTML*/, false /*doCloseTag*/);
4211  str += "<option value='0'>Jump to a Line Number (Ctrl + L)</option>"; //blank option
4212 
4213  found = false;
4214  for(i=0;i<outline.length;++i)
4215  {
4216  str += "<option value='" + (outline[i][0]-2) + "'>";
4217  text = "#" + outline[i][0];
4218  str += text;
4219 
4220  //if local then put more spacing
4221  found = (outline[i][1].indexOf("local") == 0);
4222 
4223  for(j=text.length;j<(found?20:12);++j)
4224  str += "&nbsp;"; //create fixed spacing for name
4225  str += outline[i][1];
4226  str += "</option>";
4227  }
4228  str += "</select>"; //end textEditorOutlineSelect
4229  str += "</td></table>";
4230  str += "</center>";
4231  try
4232  {
4233  document.getElementById("textEditorOutline" + forPrimary).innerHTML = str;
4234  }
4235  catch(e)
4236  {
4237  Debug.log("Ignoring missing outline element. Assuming header not shown.");
4238  return;
4239  }
4240 
4242  // localHandleCcOutline
4243  function localHandleCcOutline()
4244  {
4245  if(startCi && i < startCi)
4246  return undefined; //reject if within last outlined thing
4247 
4248  starti = i-1; //text.indexOf("::",starti)+2
4249 
4250  endi = -1;
4251  startCi = -1;
4252  endPi = -1;
4253  lastWhiteSpacei = -1;
4254 
4255  //do this:
4256  // endi = elTextObj.text.indexOf('(',starti+3);
4257  // startCi = elTextObj.text.indexOf('{',endi+2);
4258  // endPi = elTextObj.text.lastIndexOf(')',startCi-1);
4259 
4260  for(j=i+2;j<elTextObj.text.length;++j)
4261  {
4262  if(inComment && elTextObj.text[j] == '\n')
4263  inComment = false; //reset
4264  else if(inBlockComment && j+1 < elTextObj.text.length &&
4265  elTextObj.text[j] == '*' &&
4266  elTextObj.text[j+1] == '/')
4267  inBlockComment = false;
4268  else if(inComment || inBlockComment) continue;
4269  else if(j+1 < elTextObj.text.length)
4270  {
4271  if(elTextObj.text[j] == '/' &&
4272  elTextObj.text[j+1] == '*')
4273  {
4274  inBlockComment = true;
4275  continue;
4276  }
4277  else if(elTextObj.text[j] == '/' &&
4278  elTextObj.text[j+1] == '/')
4279  {
4280  inComment = true;
4281  continue;
4282  }
4283  } //end comment handling
4284 
4285  if(elTextObj.text[j] == ';' || //any semi-colon is a deal killer
4286  elTextObj.text[j] == '+' || //or non-function name characters
4287  elTextObj.text[j] == '"' ||
4288  elTextObj.text[j] == "'" ||
4289  elTextObj.text[j] == "!" ||
4290  elTextObj.text[j] == "|")
4291  return undefined;
4292 
4293 
4294 
4295  if(endi < 0) //first find end of name
4296  {
4297  if(elTextObj.text[j] == '(')
4298  endi = j++; //found end of name, and skip ahead
4299  else
4300  {
4301  if(elTextObj.text[j] == ')')
4302  return undefined; //found wrong direction brace
4303 
4304  //if space, then moveup start index
4305  if(elTextObj.text[j] == ' ' ||
4306  elTextObj.text[j] == '\t' ||
4307  elTextObj.text[j] == '\n')
4308  lastWhiteSpacei = j;
4309  else if(lastWhiteSpacei != -1 &&
4310  elTextObj.text[j] == ':')
4311  {
4312  //then found a white space after indicator
4313  // then another indicator started, so give up
4314  return undefined;
4315  }
4316 
4317  }
4318  }
4319  else if(startCi < 0)
4320  {
4321  if(elTextObj.text[j] == '{')
4322  {
4323  startCi = j--; //found start of curly brackets, and exit loop
4324  break;
4325  }
4326  }
4327  }
4328 
4329  //have endi and startCi
4330 
4331  if(endi < 0 || startCi < 0)
4332  {
4333  return undefined;
4334  }
4335 
4336  //find endPi
4337 
4338  for(j;j>endi;--j)
4339  {
4340  if(elTextObj.text[j] == ')')
4341  {
4342  endPi = j; //found end of parameters
4343  break;
4344  }
4345  }
4346 
4347  if(endPi < 0)
4348  {
4349  return undefined;
4350  }
4351 
4352  //found key moments with no ';', done!
4353 
4354  return elTextObj.text.substr(starti+2,endi-starti-2).replace(/\s+/g,'');
4355 
4356  } //end localHandleCcOutline()
4357 
4359  // localHandleJsOutline
4360  function localHandleJsOutline()
4361  {
4362  if(elTextObj.text[i + 1] == '(')
4363  {
4364  //console.log("=style",text.substr(i-30,100));
4365 
4366  found = false; //init
4367 
4368  //look backward for =, and only accept \t or space
4369  for(j=i-1-("function").length;j>=0;--j)
4370  {
4371  if(elTextObj.text[j] == '=')
4372  {
4373  //found next phase
4374  found = true;
4375  k = j; //save = pos
4376  }
4377  else if(!(elTextObj.text[j] == ' ' || elTextObj.text[j] == '\t' ||
4378  (elTextObj.text[j] == '=' && !found)))
4379  break; //give up on this function if not white space or =
4380  }
4381 
4382  if(found)
4383  {
4384  //found = sign so now find last character
4385  for(j;j>=0;--j)
4386  {
4387  if(elTextObj.text[j] == ' ' || elTextObj.text[j] == '\t' ||
4388  elTextObj.text[j] == '\n')
4389  {
4390  //found white space on other side, so done!
4391  return elTextObj.text.substr(j+1,k-j-1).trim();
4392  }
4393  }
4394  }
4395  } //end handling for backward = style js function
4396  else
4397  {
4398  //console.log("fwd style",text.substr(i,30));
4399 
4400  //look forward until new line or (
4401  for(j=i+2;j<elTextObj.text.length;++j)
4402  {
4403  if(elTextObj.text[j] == '\n')
4404  break;
4405  else if(elTextObj.text[j] == '(')
4406  {
4407  //found end
4408  return elTextObj.text.substr(i+2,j-(i+2)).trim();
4409  }
4410  }
4411  } //end handling for forward tyle js function
4412 
4413  return undefined; //if no function found
4414  } //end localHandleJsOutline()
4415 
4416  } //end updateOutline()
4417 
4418  //=====================================================================================
4419  //handleOutlineSelect ~~
4420  this.handleOutlineSelect = function(forPrimary)
4421  {
4422  forPrimary = forPrimary?1:0;
4423 
4424  Debug.log("handleOutlineSelect() " + forPrimary);
4425 
4426  var val = document.getElementById("textEditorOutlineSelect" + forPrimary).value | 0;
4427  if(val < 1) val = 1;
4428  console.log("line val",val);
4429 
4430  CodeEditor.editor.gotoLine(forPrimary,val,
4431  undefined /*selectionCursor*/,
4432  true /*topOfView*/);
4433 
4434  } //end handleOutlineSelect()
4435 
4436  //=====================================================================================
4437  //keyDownHandler ~~
4438  var TABKEY = 9;
4439  var SPACEKEY = 32;
4440  this.keyDownHandler = function(e,forPrimary,shortcutsOnly)
4441  {
4442  forPrimary = forPrimary?1:0;
4443 
4444  var keyCode = e.keyCode;
4445 
4446  CodeEditor.editor.stopUpdateHandling();
4447 
4448  //if just pressing shiftKey, ignore
4449  if(keyCode == 16 /*shift*/)
4450  return;
4451 
4452  //if command key pressed, ignore
4453  if(_commandKeyDown)
4454  return;
4455 
4456  var c = e.key;
4457  Debug.log("keydown c=" + keyCode + " " + c + " shift=" + e.shiftKey +
4458  " ctrl=" + e.ctrlKey + " command=" + _commandKeyDown);
4459 
4460  //set timeout for decoration update
4461  CodeEditor.editor.startUpdateHandling(forPrimary);
4462 
4463  var el = _eel[forPrimary];
4464  var cursor;
4465  var cursorSelection = false;
4466 
4467 
4468  //handle preempt keys
4469  if(!shortcutsOnly)
4470  {
4471  cursor = CodeEditor.editor.getCursor(el);
4472 
4473  cursorSelection = (cursor.startNodeIndex !== undefined &&
4474  (cursor.startNodeIndex != cursor.endNodeIndex ||
4475  cursor.startPos != cursor.endPos));
4476 
4477  if(!cursorSelection)
4478  _lastPageUpDownLine = -1;
4479 
4481  function localInsertCharacter(c)
4482  {
4483  Debug.log("Inserting character... " + c);
4484 
4485  var node,val;
4486  var found;
4487 
4488  //steps:
4489  // delete all text in selection (to be replaced by newline)
4490  // reverse find previous new line
4491  // capture previous line tabbing/whitespace
4492  // use previous line tabbing/whitespace to insert white space after newline
4493  // if { then give extra tab
4494 
4495  //delete all nodes between endNode and startNode
4496  if(cursor.endNodeIndex > cursor.startNodeIndex)
4497  {
4498  //handle end node first, which is a subset effect
4499  val = el.childNodes[cursor.endNodeIndex].textContent;
4500  val = val.substr(cursor.endPos);
4501  el.childNodes[cursor.endNodeIndex].textContent = val;
4502  --cursor.endNodeIndex;
4503  while(cursor.endNodeIndex > cursor.startNodeIndex)
4504  {
4505  //delete node
4506  el.removeChild(el.childNodes[cursor.endNodeIndex]);
4507  --cursor.endNodeIndex;
4508  }
4509  //place end pos to delete the remainder of current node
4510  cursor.endPos = el.childNodes[cursor.startNodeIndex].textContent.length;
4511  }
4512 
4513  var whiteSpaceString = "";
4514  var postWhiteSpaceString = "";
4515  var text = el.childNodes[cursor.startNodeIndex].textContent;
4516  var preCharString = text.substr(0,cursor.startPos);
4517  var cursorPosDelta = 0;
4518 
4519  if(c == '\n')
4520  {
4521  //for newline case, determine whitespace before cursor to add
4522  // or if previous character is a {, then close brackets
4523 
4524  var firstChar = ''; //init to empty char
4525 
4526  //reverse-find new line
4527  found = false;
4528  for(n=cursor.startNodeIndex;n>=0; --n)
4529  {
4530  node = el.childNodes[n];
4531  val = node.textContent;
4532 
4533  for(i=(n==cursor.startNodeIndex?cursor.startPos-1:
4534  val.length-1); i>=0; --i)
4535  {
4536  if(val[i] == '\n')
4537  {
4538  //found start of line
4539  found = true;
4540  break;
4541  }
4542  else if(firstChar == '' &&
4543  val[i] != '\t' && val[i] != ' ')
4544  firstChar = val[i]; //found first encountered character
4545  }
4546  if(found) break;
4547  } //end reverse find new line loop
4548 
4549  //assume at new line point (or start of file)
4550  console.log("at leading newline - n",n,"i",i,"firstChar",firstChar);
4551  if(n < 0) n = 0;
4552  if(i < 0) i = 0;
4553  else ++i; //skip past new line
4554 
4555  //now return to cursor and aggregate white space
4556  found = false;
4557  for(n; n<el.childNodes.length; ++n)
4558  {
4559  node = el.childNodes[n];
4560  val = node.textContent;
4561 
4562  for(i;i<val.length;++i)
4563  {
4564  //exit loop when not white space found
4565  if((val[i] != '\t' && val[i] != ' ') ||
4566  (n == cursor.startNodeIndex &&
4567  i >= cursor.startPos))
4568  {
4569  found = true;
4570  break;
4571  }
4572 
4573  whiteSpaceString += val[i];
4574  }
4575 
4576  if(found || n == cursor.startNodeIndex) break;
4577 
4578  i = 0; //reset i for next loop
4579  } //end white non-white space loop
4580 
4581  if(firstChar == '{')
4582  {
4583  postWhiteSpaceString += "\n" + whiteSpaceString + "}";
4584  whiteSpaceString += '\t';
4585  postWhiteSpaceString += text.substr(cursor.endPos);
4586  }
4587  else //cut off leading white-space to pull text to cursor position on new line
4588  {
4589  val = text.substr(cursor.endPos);
4590  i = val.indexOf('\n');
4591  if(i >= 0)
4592  {
4593  //if there is a newline, stop removing whitespace there
4594  postWhiteSpaceString += val.substr(0,
4595  i).trimLeft();
4596  postWhiteSpaceString += val.substr(i);
4597  }
4598  else //else remove all leading white space
4599  postWhiteSpaceString += val.trimLeft();
4600  }
4601 
4602  } //end special newline handling
4603  else if(c == '}') //start special closing bracket handling
4604  {
4605  //determine the white space before previous open bracket
4606  // and match it
4607 
4608  //reverse find matching bracket
4609  var openCount = 1; //init to 1, when 0 done
4610  var foundFirstNewLine = false;
4611 
4612  //reverse-find new line
4613  found = false;
4614  for(n=cursor.startNodeIndex;n>=0; --n)
4615  {
4616  node = el.childNodes[n];
4617  val = node.textContent;
4618 
4619  for(i=(n==cursor.startNodeIndex?cursor.startPos-1:
4620  val.length-1); i>=0; --i)
4621  {
4622  if(val[i] == '{')
4623  {
4624  //close bracket
4625  --openCount;
4626 
4627  if(openCount == 0)
4628  {
4629  Debug.log("Found matching bracket n=" + n +
4630  " i=" + i);
4631  found = true;
4632  break;
4633  }
4634  //else keep looking for closing bracket
4635  }
4636  else if(val[i] == '}')
4637  ++openCount;
4638  else if(!foundFirstNewLine &&
4639  val[i] == '\n')
4640  {
4641  foundFirstNewLine = true;
4642 
4643  Debug.log("pre-deleted white space preCharString=" +
4644  preCharString.length + " " + preCharString);
4645 
4646  //delete all white space forward until start position
4647  var nn = n;
4648  var ii = i+1;
4649  for(nn;nn<el.childNodes.length;++nn)
4650  {
4651  if(nn < cursor.startNodeIndex)
4652  {
4653  //completely delete text
4654  el.childNodes[nn].textContent = "";
4655  }
4656  else if(nn == cursor.startNodeIndex)
4657  {
4658  //partially delete text by clearing preCharString
4659  preCharString = el.childNodes[nn].textContent.substr(
4660  0,ii);
4661  break; //done
4662  }
4663  ii = 0;
4664  } //end of delete white space to newline loop
4665 
4666  Debug.log("deleted white space preCharString=" +
4667  preCharString.length + " " + preCharString);
4668  }
4669  else if(!foundFirstNewLine && val[i] != ' ' &&
4670  val[i] != '\t')
4671  {
4672  Debug.log("Found character between } and new line, so doing nothing.");
4673  return false;
4674  }
4675  }
4676  if(found) break;
4677  } //end reverse find matching bracket loop
4678 
4679 
4680  //assume at matching bracket (or start of file)
4681  console.log("at closing bracket - n",n,"i",i);
4682 
4683  if(n < 0 || i < 0) //at beginning, so kill leading white space
4684  preCharString = preCharString.trimRight();
4685  else
4686  {
4687  //find previous new line to determine white space to match
4688  var matchingWhiteSpace = "";
4689  found = false;
4690  var firstTime = true;
4691 
4692  for(n;n>=0; --n)
4693  {
4694  node = el.childNodes[n];
4695  val = node.textContent;
4696 
4697  for(i=(firstTime?i:
4698  val.length-1); i>=0; --i)
4699  {
4700  if(val[i] == '\n')
4701  {
4702  //fully defined matching white space
4703  found = true;
4704  break;
4705  }
4706  else if(val[i] == ' ' ||
4707  val[i] == '\t')
4708  matchingWhiteSpace += val[i];
4709  else //clear white space if not white space encountered
4710  matchingWhiteSpace = "";
4711  }
4712  if(found) break;
4713 
4714  firstTime = false;
4715  } //end reverse find new line loop
4716  }
4717 
4718 
4719  preCharString += matchingWhiteSpace;
4720  Debug.log("matching white space preCharString=" +
4721  preCharString.length + " " + preCharString);
4722 
4723  postWhiteSpaceString += text.substr(cursor.endPos);
4724 
4725  } //end special closing bracket handling
4726  else
4727  postWhiteSpaceString += text.substr(cursor.endPos);
4728 
4729  val = preCharString + c +
4730  whiteSpaceString +
4731  postWhiteSpaceString;
4732 
4733  el.childNodes[cursor.startNodeIndex].textContent = val;
4734 
4735 
4736  console.log("cursorPosDelta",cursorPosDelta);
4737 
4738  cursor.startPos = c.length + preCharString.length + whiteSpaceString.length;
4739  cursor.endNodeIndex = cursor.startNodeIndex;
4740  cursor.endPos = cursor.startPos;
4741 
4742  console.log("cursor after newline",cursor);
4743 
4744  CodeEditor.editor.setCursor(el,cursor,true /*scrollIntoView*/);
4745 
4746  _fileWasModified[forPrimary] = true;
4747 
4748  return true; //character was inserted
4749  } //end localInsertCharacter()
4750 
4751  if(keyCode == 13) // ENTER -- should trigger updateDecorations immediately
4752  {
4753  //to avoid DIVs, ENTER should trigger updateDecorations immediately
4754 
4755 
4756  //document.execCommand('insertHTML', false, '&#010;&#013;');
4757  //document.execCommand('insertText', false, '\n');
4758  //CodeEditor.editor.updateDecorations(forPrimary, true /*insertNewLine*/,
4759  // keyCode == 46 /*delete highlight*/);
4760 
4761  localInsertCharacter('\n');
4762  e.preventDefault();
4763  //CodeEditor.editor.stopUpdateHandling(e);
4764 
4765  return;
4766  }
4767  else if(keyCode == 36) // HOME
4768  {
4769  //to position the cursor at text, rather than line start
4770  e.preventDefault();
4771 
4772  //Steps:
4773  // get cursor
4774  // reverse find new line
4775  // track last non-whitespace
4776  // set cursor
4777 
4778  var i,n,node,val;
4779  var found = false;
4780 
4781  var lastNonWhitespacePos = cursor.startPos;
4782  var lastNonWhitespaceNodeIndex = cursor.startNodeIndex;
4783  var lastPos = cursor.startPos;
4784  var lastNodeIndex = cursor.startNodeIndex;
4785 
4786  //reverse find new line
4787  for(n=cursor.startNodeIndex; n>=0; --n)
4788  {
4789  node = el.childNodes[n];
4790  val = node.textContent;
4791 
4792  for(i=(n==cursor.startNodeIndex?
4793  cursor.startPos-1:val.length-1);i>=0;--i)
4794  {
4795  if(val[i] == '\n')
4796  {
4797  found = true;
4798  break;
4799  }
4800  else if(!(val[i] == ' ' ||
4801  val[i] == '\t'))
4802  {
4803  lastNonWhitespacePos = i;
4804  lastNonWhitespaceNodeIndex = n;
4805  }
4806 
4807  lastPos = i;
4808  lastNodeIndex = n;
4809  }
4810  if(found) break;
4811  }
4812  console.log("lastNonWhitespacePos",lastNonWhitespacePos);
4813  console.log("lastNonWhitespaceNodeIndex",lastNonWhitespaceNodeIndex);
4814 
4815  if(lastNonWhitespacePos == cursor.startPos &&
4816  lastNonWhitespaceNodeIndex == cursor.startNodeIndex)
4817  {
4818  //if already at non-whitespace character, go to the new line
4819  lastNonWhitespacePos = lastPos;
4820  lastNonWhitespaceNodeIndex = lastNodeIndex;
4821  }
4822 
4823  //if to edge, force view to go all the way left
4824  if(lastNonWhitespacePos == lastPos &&
4825  lastNonWhitespaceNodeIndex == lastNodeIndex)
4826  document.getElementById("textEditorBody" + forPrimary).scrollLeft = 0;
4827 
4828  cursor.startNodeIndex = lastNonWhitespaceNodeIndex
4829  cursor.startPos = lastNonWhitespacePos;
4830 
4831  if(!e.shiftKey)
4832  {
4833  cursor.endNodeIndex = cursor.startNodeIndex;
4834  cursor.endPos = cursor.startPos;
4835  }
4836  //else leave end position for highlight effect
4837 
4838  CodeEditor.editor.setCursor(el,cursor,true /*scrollIntoView*/);
4839 
4840  return;
4841  }
4842  else if(keyCode == 35) // END
4843  {
4844  //to position the cursor at end of line, rather than end of file
4845 
4846  e.preventDefault();
4847 
4848 
4849  //Steps:
4850  // get cursor
4851  // forward find new line
4852  // track last non-whitespace
4853  // set cursor
4854 
4855  var i,n,node,val;
4856  var found = false;
4857 
4858  var wantNext = false;
4859  var lastNonWhitespacePos = cursor.startPos;
4860  var lastNonWhitespaceNodeIndex = cursor.startNodeIndex;
4861 
4862  //reverse find new line
4863  for(n=cursor.startNodeIndex; n<el.childNodes.length; ++n)
4864  {
4865  node = el.childNodes[n];
4866  val = node.textContent;
4867 
4868  for(i=(n==cursor.startNodeIndex?
4869  cursor.startPos:0);i<val.length;++i)
4870  {
4871  if(wantNext)
4872  {
4873  lastNonWhitespacePos = i;
4874  lastNonWhitespaceNodeIndex = n;
4875  }
4876 
4877  if(val[i] == '\n')
4878  {
4879  found = true;
4880  break;
4881  }
4882  else if(!(val[i] == ' ' ||
4883  val[i] == '\t'))
4884  wantNext = true;
4885  else
4886  wantNext = false;
4887  }
4888  if(found) break;
4889  }
4890  console.log("lastNonWhitespacePos",lastNonWhitespacePos);
4891  console.log("lastNonWhitespaceNodeIndex",lastNonWhitespaceNodeIndex);
4892 
4893  if(lastNonWhitespacePos == cursor.startPos &&
4894  lastNonWhitespaceNodeIndex == cursor.startNodeIndex)
4895  {
4896  //if already at non-whitespace character, go to the new line
4897  lastNonWhitespacePos = i;
4898  lastNonWhitespaceNodeIndex = n;
4899  }
4900 
4901  cursor.endNodeIndex = lastNonWhitespaceNodeIndex
4902  cursor.endPos = lastNonWhitespacePos;
4903 
4904  if(!e.shiftKey)
4905  {
4906  cursor.startNodeIndex = cursor.endNodeIndex;
4907  cursor.startPos = cursor.endPos;
4908  }
4909  //else leave end position for highlight effect
4910 
4911  CodeEditor.editor.setCursor(el,cursor,true /*scrollIntoView*/);
4912  return;
4913  }
4914  } //end non-shortcuts early handling
4915 
4916  //handle page-up and down for shortcut or not shortcut
4917  // because it can cause body to become selected
4918  if(keyCode == 33) // PAGE-UP
4919  {
4920  //to position the cursor at text, rather than only moving scroll bar
4921  e.preventDefault();
4922  e.stopPropagation();
4923 
4924  //Steps:
4925  // get cursor line
4926  // goto line-N
4927 
4928 
4929  var N = 50; //number of lines for page up
4930 
4931  var gotoLineCursor = {};
4932 
4933  //manage start and last line
4934  if(_lastPageUpDownLine == -1)
4935  {
4936  var cursorWithLine = CodeEditor.editor.getLine(forPrimary);
4937 
4938  _startPageUpDownNodeIndex = cursorWithLine.startNodeIndex;
4939  _startPageUpDownPos = cursorWithLine.startPos;
4940 
4941  _startPageUpDownLine = cursorWithLine.line;
4942  _lastPageUpDownLine = _startPageUpDownLine;
4943  }
4944 
4945  gotoLineCursor.startNodeIndex = _startPageUpDownNodeIndex;
4946  gotoLineCursor.startPos = _startPageUpDownPos;
4947 
4948 
4949  _lastPageUpDownLine -= N;
4950  gotoLineCursor.focusAtEnd = (_lastPageUpDownLine > _startPageUpDownLine);
4951 
4952  Debug.log("Page up to line " + _lastPageUpDownLine + " dir=" +
4953  gotoLineCursor.focusAtEnd);
4954 
4955  _lastPageUpDownLine = CodeEditor.editor.gotoLine(forPrimary,_lastPageUpDownLine,
4956  e.shiftKey?gotoLineCursor:undefined);
4957 
4958  return;
4959  }
4960  else if(keyCode == 34) // PAGE-DOWN
4961  {
4962  //to position the cursor at text, rather than only moving scroll bar
4963  e.preventDefault();
4964  e.stopPropagation();
4965 
4966  //Steps:
4967  // get cursor line
4968  // goto line-N
4969 
4970 
4971  var N = 50; //number of lines for page up
4972 
4973  var gotoLineCursor = {};
4974 
4975  //manage start and last line
4976  if(_lastPageUpDownLine == -1)
4977  {
4978  var cursorWithLine = CodeEditor.editor.getLine(forPrimary);
4979 
4980  _startPageUpDownNodeIndex = cursorWithLine.startNodeIndex;
4981  _startPageUpDownPos = cursorWithLine.startPos;
4982 
4983  _startPageUpDownLine = cursorWithLine.line;
4984  _lastPageUpDownLine = _startPageUpDownLine;
4985  }
4986 
4987  gotoLineCursor.startNodeIndex = _startPageUpDownNodeIndex;
4988  gotoLineCursor.startPos = _startPageUpDownPos;
4989 
4990 
4991  _lastPageUpDownLine += N;
4992  gotoLineCursor.focusAtEnd = (_lastPageUpDownLine > _startPageUpDownLine);
4993 
4994  Debug.log("Page down to line " + _lastPageUpDownLine + " dir=" +
4995  gotoLineCursor.focusAtEnd);
4996 
4997  _lastPageUpDownLine = CodeEditor.editor.gotoLine(forPrimary,_lastPageUpDownLine,
4998  e.shiftKey?gotoLineCursor:undefined);
4999 
5000  return;
5001  }
5002  else if(keyCode == 13) // ENTER
5003  {
5004  //ENTER may be hit when doing something in header
5005  // and we want to act.
5006  //e.g. Find and Replace
5007 
5008  if(CodeEditor.editor.findAndReplaceLastButton[forPrimary] > 0)
5009  {
5010  e.preventDefault();
5011  e.stopPropagation();
5012 
5013  Debug.log("Launch find and replace action " +
5014  CodeEditor.editor.findAndReplaceLastButton[forPrimary]);
5015  CodeEditor.editor.doFindAndReplaceAction(forPrimary,
5016  CodeEditor.editor.findAndReplaceLastButton[forPrimary]);
5017  return;
5018  }
5019  }
5020  else if(keyCode == 27) // ESCAPE
5021  {
5022  //ESCAPE may be hit when doing something in header
5023  // and we want to act.
5024  //e.g. Find and Replace
5025  //console.log(CodeEditor.editor.findAndReplaceLastButton,forPrimary);
5026  if(CodeEditor.editor.findAndReplaceLastButton[forPrimary] > 0)
5027  {
5028  e.preventDefault();
5029  e.stopPropagation();
5030 
5031  //close find and replace
5032  CodeEditor.editor.displayFileHeader(forPrimary);
5033  return;
5034  }
5035  }
5036 
5037  //end preempt key handling
5038 
5039 
5040  if(e.ctrlKey) //handle shortcuts
5041  {
5042  if(keyCode == 83) // S for file save
5043  {
5044  if (!_STAND_ALONE && !_READ_ONLY) {
5045  CodeEditor.editor.saveFile(forPrimary,true /*quiet*/);
5046  e.preventDefault();
5047  return;
5048  }
5049  }
5050  else if(keyCode == 68) // D for directory toggle
5051  {
5052  if (!_STAND_ALONE) {
5053  CodeEditor.editor.toggleDirectoryNav(forPrimary);
5054  e.preventDefault();
5055  return;
5056  }
5057  }
5058  else if(keyCode == 66) // B for incremental build
5059  {
5060  if (!_STAND_ALONE && !_READ_ONLY) {
5061  CodeEditor.editor.build();
5062  e.preventDefault();
5063  return;
5064  }
5065  }
5066  else if(keyCode == 70) // F for Find and Replace
5067  {
5068  CodeEditor.editor.showFindAndReplace(forPrimary);
5069  e.preventDefault();
5070  return;
5071  }
5072  else if(keyCode == 73) // I for auto indent
5073  {
5074  CodeEditor.editor.autoIndent(forPrimary, cursor);
5075  e.preventDefault();
5076  return;
5077  }
5078  else if(keyCode == 78) // N for clean build
5079  {
5080  if (!_STAND_ALONE && !_READ_ONLY) {
5081  CodeEditor.editor.build(true /*clean*/);
5082  e.preventDefault();
5083  return;
5084  }
5085  }
5086  else if(keyCode == 222 || // ' or
5087  keyCode == 48) // 0 for refresh file
5088  {
5089  CodeEditor.editor.openFile(forPrimary,
5090  _filePath[forPrimary],
5091  _fileExtension[forPrimary],
5092  true /*doConfirm*/);
5093  e.preventDefault();
5094  return;
5095  }
5096  else if(keyCode == 50) // 2 for view toggle
5097  {
5098  CodeEditor.editor.toggleView();
5099  e.preventDefault();
5100  return;
5101  }
5102  else if(keyCode == 85) // U for undo
5103  {
5104  CodeEditor.editor.undo(forPrimary, e.shiftKey /*redo*/);
5105  e.preventDefault();
5106  return;
5107  }
5108  else if(keyCode == 76 || // L or
5109  keyCode == 71) // G for go to line number
5110  {
5111  DesktopContent.popUpVerification(
5112  /*prompt*/ "Goto line number: ",
5113  /*func*/
5114  function(line)
5115  {
5116  Debug.log("Going to line... " + line);
5117  CodeEditor.editor.gotoLine(forPrimary,line);
5118  }, /*val*/ undefined,
5119  /*bgColor*/ undefined,
5120  /*textColor*/ undefined,
5121  /*borderColor*/ undefined,
5122  /*getUserInput*/ true,
5123  /*dialogWidth*/ undefined,
5124  /*cancelFunc*/ undefined,
5125  /*yesButtonText*/ "Go");
5126 
5127  e.preventDefault();
5128  return;
5129  }
5130  else if(keyCode == 186 || // ; or
5131  keyCode == 49) // 1 for switch to related file
5132  {
5133  CodeEditor.editor.openRelatedFile(forPrimary);
5134  e.preventDefault();
5135  return;
5136  }
5137 
5138  }//end shortcut cases
5139  if(shortcutsOnly)
5140  return; //if only doing short-cuts, dont handle text
5141 
5142 
5143 
5144  var rectangularTAB = false;
5145  var blockCOMMENT = false;
5146 
5147  // if(!e.shiftKey && e.ctrlKey &&
5148  // keyCode == 191) // ctrl+/ for block comment
5149  // blockCOMMENT = true;
5150  if(e.ctrlKey)//else if(e.ctrlKey)
5151  {
5152 
5153  if(keyCode == 84 ||
5154  keyCode == 89) // T or Y for rectangular TAB
5155  {
5156  rectangularTAB = true;
5157  e.preventDefault();
5158  //continue to tab handling below
5159  }
5160  else if(keyCode == 191) // ctrl+/ for block comment
5161  {
5162  blockCOMMENT = true;
5163  e.preventDefault();
5164  //continue to tab handling below
5165  }
5166  else
5167  return;
5168  } //end ctrl key editor handling
5169 
5170 
5171 
5172 
5173  if(keyCode == TABKEY ||
5174  (cursorSelection && keyCode == SPACEKEY) ||
5175  rectangularTAB ||
5176  blockCOMMENT)
5177  {
5178  _fileWasModified[forPrimary] = true;
5179  CodeEditor.editor.updateLastSave(forPrimary);
5180  e.preventDefault();
5181 
5182  //manage tabs
5183  // if selection, then tab selected lines
5184  // else insert tab character
5185 
5186  var i,j,k;
5187 
5188  if(cursorSelection)
5189  {
5190  //handle tabbing selected lines
5191  Debug.log("special key selected lines " + cursor.startNodeIndex + " - " +
5192  cursor.endNodeIndex);
5193 
5194 
5196  //start rectangular tab handling
5197  if(rectangularTAB)
5198  {
5199  Debug.log("Rectangular TAB");
5200 
5201  //steps:
5202  // determine x coordinate by
5203  // going backwards from start until a new line is found
5204  // and then going forward and counting spots back to cursor
5205  // then for each line in selection
5206  // add a tab at that x coordinate (offset from newline)
5207 
5208 
5209  var node,val;
5210  var found = false;
5211  var x = 0;
5212 
5213  //reverse-find new line
5214  for(n=cursor.startNodeIndex;n>=0; --n)
5215  {
5216  node = el.childNodes[n];
5217  val = node.textContent; //.nodeValue; //.wholeText
5218 
5219  for(i=(n==cursor.startNodeIndex?cursor.startPos-1:
5220  val.length-1); i>=0; --i)
5221  {
5222  if(val[i] == '\n')
5223  {
5224  //found start of line
5225  found = true;
5226  break;
5227  }
5228  }
5229  if(found) break;
5230  }
5231  //assume at new line point (or start of file)
5232  console.log("at leading newline - n",n,"i",i);
5233 
5234  //now return to cursor and count spots
5235  found = false;
5236  for(n; n<el.childNodes.length; ++n)
5237  {
5238  node = el.childNodes[n];
5239  val = node.textContent;
5240 
5241  for(i;i<val.length;++i)
5242  {
5243  //exit loop when back to cursor start position
5244  if(n == cursor.startNodeIndex &&
5245  i == cursor.startPos)
5246  {
5247  found = true;
5248 
5249  //insert tab at first position
5250  var prelength = val.length;
5251 
5252  if(e.shiftKey) //delete leading tab
5253  {
5254  if(i-1 >= 0 && val[i-1] == '\t')
5255  node.textContent = val.substr(0,i-1) + val.substr(i);
5256  }
5257  else //add leading tab
5258  {
5259  node.textContent = val.substr(0,i) + "\t" + val.substr(i);
5260 
5261  }
5262 
5263  //adjust selection to follow rectangular tabbing
5264  // so future rectangular tabbing works as expected
5265  cursor.startPos += node.textContent.length - prelength;
5266  break;
5267  }
5268 
5269  //console.log(xcnt," vs ",x,val[i]);
5270  if(val[i] == '\t')
5271  x += _TAB_SIZE - (x+_TAB_SIZE)%_TAB_SIZE; //jump to next multiple of _TAB_SIZE
5272  else
5273  ++x; //add single character spot
5274  }
5275 
5276  if(found) break;
5277 
5278  i = 0; //reset i for next loop
5279  }
5280 
5281  console.log("x",x);
5282 
5283 
5284  //fast-forward to endPos through each line and handle tab at x coord
5285 
5286  var xcnt = -1;
5287  for(n=cursor.startNodeIndex; n<el.childNodes.length; ++n)
5288  {
5289  node = el.childNodes[n];
5290  val = node.textContent; //.nodeValue; //.wholeText
5291 
5292  for(i=(n==cursor.startNodeIndex?cursor.startPos:
5293  0);
5294  i<
5295  (n==cursor.endNodeIndex?cursor.endPos:
5296  val.length);
5297  ++i)
5298  {
5299  //console.log(xcnt," vs ",x,val[i]);
5300  if(val[i] == '\n')
5301  {
5302  //reset x coord count
5303  xcnt = 0;
5304  }
5305  else if(xcnt == x)
5306  {
5307  //console.log("x match at ",xcnt,val.substr(0,i),"TTT",val.substr(i));
5308  xcnt = -1;
5309 
5310  if(e.shiftKey) //delete leading tab
5311  {
5312  if(i-1 < val.length && val[i-1] == '\t')
5313  {
5314  val = val.substr(0,i-1) + val.substr(i);
5315  node.textContent = val;
5316  if(n == cursor.endNodeIndex) --cursor.endPos;
5317  }
5318  }
5319  else //add leading tab
5320  {
5321  val = val.substr(0,i) + "\t" + val.substr(i);
5322  node.textContent = val;
5323  if(n == cursor.endNodeIndex) ++cursor.endPos;
5324  }
5325 
5326  }
5327  else if(xcnt != -1) //if counting, increase
5328  {
5329  if(val[i] == '\t')
5330  xcnt += _TAB_SIZE - (xcnt+_TAB_SIZE)%_TAB_SIZE; //jump to next multiple of _TAB_SIZE
5331  else
5332  ++xcnt; //add single character spot
5333  }
5334  } //end node text character loop
5335 
5336  if(n == cursor.endNodeIndex)
5337  break; //reached end of selection
5338  } //end node loop
5339 
5340 
5341 
5342  //need to set cursor
5343  CodeEditor.editor.setCursor(el,cursor,true /*scrollIntoView*/);
5344 
5345  return;
5346  } //end rectangular TAB handling
5347 
5348 
5349 
5350 
5351 
5353  // normal block TAB handling
5354  //steps:
5355  // go backwards from start until a new line is found
5356  // then add tab after each new line until end of selection reached
5357 
5358  //reverse-find new line
5359  var node,val;
5360  var found = false;
5361  var specialStr = keyCode == SPACEKEY?' ':'\t';
5362  if(blockCOMMENT)
5363  {
5364  if(_fileExtension[forPrimary][0] == 'c' ||
5365  _fileExtension[forPrimary][0] == 'C' ||
5366  _fileExtension[forPrimary][0] == 'h' ||
5367  _fileExtension[forPrimary][0] == 'H' ||
5368  _fileExtension[forPrimary][0] == 'j')
5369  specialStr = "//"; //comment string
5370  else
5371  specialStr = "#"; //comment string
5372  }
5373 
5374  for(n=cursor.startNodeIndex; n>=0; --n)
5375  {
5376  node = el.childNodes[n];
5377  val = node.textContent;
5378 
5379  for(i=(n==cursor.startNodeIndex?cursor.startPos-1:
5380  val.length-1); i>=0; --i)
5381  {
5382  if(val[i] == '\n')
5383  {
5384  //found new line, now continue forward to end
5385  found = true;
5386  break;
5387  }
5388 
5389  } //end node text character loop
5390 
5391  if(found) break; //exit outer loop
5392  } //end node loop
5393 
5394  //fast-forward to endPos and insert tab after each new line encountered
5395  found = false;
5396  var prevCharIsNewLine = false;
5397  var lookForNewLineIndex; //index into string
5398 
5399  for(; n<el.childNodes.length &&
5400  n <= cursor.endNodeIndex; ++n)
5401  {
5402  node = el.childNodes[n];
5403  val = node.textContent;
5404 
5405  lookForNewLineIndex = 0;
5406  for(;i<val.length;++i)
5407  {
5408  if(n == cursor.endNodeIndex && i >= cursor.endPos)
5409  {
5410  //reached end of selection
5411  found = true;
5412  break;
5413  }
5414 
5415  if(val[i] == '\n' ||
5416  (i == 0 && prevCharIsNewLine))
5417  {
5418  if(i == 0 && prevCharIsNewLine) --i; //so that tab goes in the right place
5419 
5420  if(e.shiftKey) //delete leading special string
5421  {
5422  var didDelete = false;
5423  if(i + specialStr.length < val.length &&
5424  (
5425  //if special string is found, or
5426  //only white space between new line and special string
5427  (j=val.indexOf(specialStr,i+1)) == i+1 ||
5428  (
5429  (
5430  (k=val.indexOf('\n',i+1)) < 0 ||
5431  k > j
5432  ) &&
5433  (
5434  j >= 0 &&
5435  val.substr(i+1,j-(i+1)).trim().length == 0
5436  )
5437  )
5438  )
5439  )
5440  {
5441  val = val.substr(0,j) +
5442  val.substr(j+specialStr.length);
5443  node.textContent = val;
5444  didDelete = true;
5445  lookForNewLineIndex = j+specialStr.length;
5446  }
5447  else if(specialStr == '\t')
5448  {
5449  //for tab case also get rid of 4-3-2-1 spaces after new line
5450  if((specialStr = " ") && //4 spaces
5451  i + specialStr.length < val.length &&
5452  val.indexOf(specialStr,i+1) == i+1)
5453  {
5454  val = val.substr(0,i+1) +
5455  val.substr(i+1+specialStr.length);
5456  node.textContent = val;
5457  didDelete = true;
5458  }
5459  else if((specialStr = " ") && //3 spaces
5460  i + specialStr.length < val.length &&
5461  val.indexOf(specialStr,i+1) == i+1)
5462  {
5463  val = val.substr(0,i+1) +
5464  val.substr(i+1+specialStr.length);
5465  node.textContent = val;
5466  didDelete = true;
5467  }
5468  else if((specialStr = " ") && //2 spaces
5469  i + specialStr.length < val.length &&
5470  val.indexOf(specialStr,i+1) == i+1)
5471  {
5472  val = val.substr(0,i+1) +
5473  val.substr(i+1+specialStr.length);
5474  node.textContent = val;
5475  didDelete = true;
5476  }
5477  else if((specialStr = " ") && //1 spaces
5478  i + specialStr.length < val.length &&
5479  val.indexOf(specialStr,i+1) == i+1)
5480  {
5481  val = val.substr(0,i+1) +
5482  val.substr(i+1+specialStr.length);
5483  node.textContent = val;
5484  didDelete = true;
5485  }
5486 
5487  specialStr = '\t'; //return special string value
5488  }
5489 
5490  //fix cursor if deleted special string
5491  if(didDelete)
5492  {
5493  //update position after text was removed
5494  if(n == cursor.startNodeIndex &&
5495  i < cursor.startPos)
5496  {
5497  cursor.startPos -= specialStr.length;
5498  }
5499  if(n == cursor.endNodeIndex &&
5500  i < cursor.endPos)
5501  {
5502  cursor.endPos -= specialStr.length;
5503  }
5504 
5505  //if running out of string to keep line selected.. jump to next node
5506  // with selection
5507  if(n == cursor.endNodeIndex &&
5508  cursor.endPos >= val.length)
5509  {
5510  ++cursor.endNodeIndex;
5511  cursor.endPos = 0;
5512  }
5513 
5514  // if(n < cursor.startNodeIndex)
5515  // {
5516  // cursor.startNodeIndex = n;
5517  // cursor.startPos = val.length-1;
5518  // }
5519  // else if(n == cursor.startNodeIndex &&
5520  // i < cursor.startPos)
5521  // {
5522  // cursor.startPos = i+1;
5523  // }
5524  }
5525 
5526  } //end delete leading tab
5527  else //add leading tab
5528  {
5529  if(specialStr == ' ') //handle white-space appending special string
5530  {
5531  //add safter white space
5532  var ii = i+1;
5533  while(ii < val.length &&
5534  (val[ii] == ' ' || val[ii] == '\t' || val[ii] == '\n'))
5535  ++ii;
5536  val = val.substr(0,ii) + specialStr + val.substr(ii);
5537  prevCharIsNewLine = (val[ii] == '\n');
5538  i = ii+1;
5539  }
5540  else //handle line-leading special string
5541  val = val.substr(0,i+1) + specialStr + val.substr(i+1);
5542  node.textContent = val;
5543  }
5544 
5545  if(i == -1 && prevCharIsNewLine) ++i; //so that loop continues properly
5546  }
5547  } //end node text character loop
5548  i = 0; //reset i for next character loop
5549 
5550  if(found) break; //exit outer loop
5551 
5552  if(e.shiftKey)
5553  { //for reverse special string, except prev newline
5554  //if white space after
5555  j = val.lastIndexOf('\n');
5556  if(j >= lookForNewLineIndex &&
5557  (
5558  j == val[val.length-1] ||
5559  val.substr(j+1).trim().length == 0
5560  ))
5561  prevCharIsNewLine = true;
5562  else if(j < 0 && prevCharIsNewLine &&
5563  val.trim().length == 0)
5564  prevCharIsNewLine = true; //last new line persists
5565  else
5566  prevCharIsNewLine = false;
5567  }
5568  else
5569  prevCharIsNewLine = (val.length && //handle last char newline case
5570  val[val.length-1] == '\n');
5571  } //end node loop
5572 
5573  //need to set cursor
5574  CodeEditor.editor.setCursor(el,cursor,true /*scrollIntoView*/);
5575  }
5576  else if(!blockCOMMENT) //not tabbing a selection, just add or delete single tab in place
5577  {
5578  if(e.shiftKey)
5579  {
5580  if(cursor.startNodeIndex !== undefined)
5581  {
5582  try
5583  {
5584  var node,val,i;
5585  i = cursor.startPos;
5586  node = el.childNodes[cursor.startNodeIndex];
5587  val = node.textContent; //.nodeValue; //.wholeText
5588 
5589  //console.log(node,val,val[i-1]);
5590 
5591  if(val[i-1] == '\t')
5592  {
5593  node.textContent = val.substr(0,i-1) + val.substr(i);
5594 
5595  //need to set cursor
5596  var range = document.createRange();
5597  range.setStart(node,i-1);
5598  range.setEnd(node,i-1);
5599 
5600  var selection = window.getSelection();
5601  selection.removeAllRanges();
5602  selection.addRange(range);
5603  }
5604  }
5605  catch(err)
5606  {
5607  console.log(err);
5608  return;
5609  }
5610  }
5611  else
5612  Debug.log("No cursor for reverse tab.");
5613  }
5614  else
5615  document.execCommand('insertHTML', false, '&#009');
5616  }
5617 
5618  return;
5619 
5620  } //end handle tab key
5621  else if(cursorSelection)
5622  {
5623  Debug.log("cursorSelection handling for speed-up");
5624  //Note: the default browser behavior really struggles
5625  // editing many elements in a selection when there are
5626  // a lot of elements (e.g. when the file is large)
5627  // so... let's be smarter.
5628 
5629 
5630  //Note: looks like the browser gives character in a e.key
5631  // but then there is also "Backspace" and "Delete"
5632 
5633  console.log("cursorSelection char",keyCode,c);
5634 
5635  if(e.key.length > 1)
5636  {
5637  if( keyCode != 46 && //delete
5638  keyCode != 8) //backspace
5639  return; //do default handling for other special characters
5640  c = ''; //default weird characters to blanks
5641  }
5642 
5643  e.preventDefault();
5644  e.stopPropagation();
5645  localInsertCharacter(c);
5646  }
5647  else //special single key handling
5648  {
5649  if(c == '}')
5650  {
5651  if(localInsertCharacter(c))
5652  {
5653  //if true, then character was added, otherwise do default
5654  e.preventDefault();
5655  e.stopPropagation();
5656  }
5657  }
5658  }
5659 
5660  } //end keyDownHandler()
5661 
5662  //=====================================================================================
5663  //updateLastSave ~~
5664  // update display based on lastSave and wasModified member variables
5665  this.updateLastSave = function(forPrimary)
5666  {
5667  forPrimary = forPrimary?1:0;
5668 
5669  var el = document.getElementById("textEditorLastSave" + forPrimary);
5670  if(!el) return; //if not displayed, quick exit
5671 
5672  Debug.log("updateLastSave() forPrimary=" + forPrimary);
5673  var str = "";
5674  if(_fileWasModified[forPrimary])
5675  str += "<label style='color:red'>Unsaved changes!</label> ";
5676  else
5677  str += "Unmodified. ";
5678 
5679  if(_fileLastSave[forPrimary])
5680  {
5681  var now = new Date();
5682  var d = new Date(_fileLastSave[forPrimary]);
5683  var tstr = d.toLocaleTimeString();
5684  tstr = tstr.substring(0,tstr.lastIndexOf(' ')) + //convert AM/PM to am/pm with no space
5685  (tstr[tstr.length-2]=='A'?"am":"pm");
5686 
5687  var diff = ((now.getTime() - d.getTime())/1000)|0; //in seconds
5688  var diffStr = "";
5689 
5690  if(diff < 5)
5691  diffStr = "(just now) ";
5692  else if(diff < 10)
5693  diffStr = "(5 seconds ago) ";
5694  else if(diff < 20)
5695  diffStr = "(15 seconds ago) ";
5696  else if(diff < 40)
5697  diffStr = "(30 seconds ago) ";
5698  else if(diff < 50)
5699  diffStr = "(45 seconds ago) ";
5700  else if(diff < 120)
5701  diffStr = "(one minute ago) ";
5702  else if(diff < 15*60)
5703  diffStr = "(" + ((diff/60)|0) + " minutes ago) ";
5704  else if(diff < 20*60) //about 15 minutes
5705  diffStr = "(15 minutes ago) ";
5706  else if(diff < 40*60) //about 30 minutes
5707  diffStr = "(30 minutes ago) ";
5708  else if(diff < 50*60) //about 45 minutes
5709  diffStr = "(45 minutes ago) ";
5710  else if(diff < 90*60) //about an hour
5711  diffStr = "(an hour ago) ";
5712  else //hours
5713  diffStr = "(" + (Math.round(diff/60/60)) + " hours ago) ";
5714 
5715 
5716  str += "Last save was " + diffStr + tstr;
5717  }
5718  el.innerHTML = str;
5719  } //end updateLastSave()
5720 
5721  //=====================================================================================
5722  //handleFileNameMouseMove ~~
5723  this.handleFileNameMouseMove = function(forPrimary,doNotStartTimer)
5724  {
5725  forPrimary = forPrimary?1:0;
5726 
5727  //console.log("handleFileNameMouseMove " + forPrimary + " - " + doNotStartTimer);
5728 
5729  if(_fileNameEditing[forPrimary]) return;
5730 
5731  var el = document.getElementById("fileButtonContainerShowHide" + forPrimary);
5732  el.style.display = "block";
5733 
5734  window.clearTimeout(_fileNameMouseMoveTimerHandle);
5735 
5736  if(doNotStartTimer) return;
5737 
5738  _fileNameMouseMoveTimerHandle = window.setTimeout(
5739  function()
5740  {
5741  el.style.display = "none";
5742  } //end mouse move timeout handler
5743  ,1000);
5744 
5745  } //end handleFileNameMouseMove()
5746 
5747  //=====================================================================================
5748  //startEditFileName ~~
5749  this.startEditFileName = function(forPrimary)
5750  {
5751  forPrimary = forPrimary?1:0;
5752 
5753  if(_fileNameEditing[forPrimary]) return;
5754  _fileNameEditing[forPrimary] = true;
5755 
5756  //hide edit button
5757  document.getElementById("fileButtonContainerShowHide" + forPrimary).style.display = "none";
5758 
5759 
5760  console.log("startEditFileName " + forPrimary);
5761 
5762  var el = document.getElementById("fileNameDiv" + forPrimary);
5763 
5764  var keys = Object.keys(_fileHistoryStack);
5765  var initVal = keys[document.getElementById("fileNameHistorySelect" +
5766  forPrimary).value|0].trim();//el.textContent.trim();
5767 
5768  var _OK_CANCEL_DIALOG_STR = "";
5769 
5770  _OK_CANCEL_DIALOG_STR += "<div title='' style='padding:5px;background-color:#eeeeee;border:1px solid #555555;position:relative;z-index:2000;" + //node the expander nodes in tree-view are z-index:1000
5771  "width:105px;height:20px;margin: 4px -122px -32px -120px; font-size: 16px; white-space:nowrap; text-align:center;'>";
5772  _OK_CANCEL_DIALOG_STR += "<a class='popUpOkCancel' onclick='" +
5773  "CodeEditor.editor.editCellOK(" + forPrimary +
5774  "); event.stopPropagation();' onmouseup='event.stopPropagation();' title='Accept Changes' style='color:green'>" +
5775  "<b style='color:green;font-size: 16px;'>OK</b></a> | " +
5776  "<a class='popUpOkCancel' onclick='" +
5777  "CodeEditor.editor.editCellCancel(" + forPrimary +
5778  "); event.stopPropagation();' onmouseup='event.stopPropagation();' title='Discard Changes' style='color:red'>" +
5779  "<b style='color:red;font-size: 16px;'>Cancel</b></a>";
5780  _OK_CANCEL_DIALOG_STR += "</div>";
5781 
5782  //create input box and ok | cancel
5783  var str = "";
5784  str += htmlOpen("input",
5785  {
5786  "type":"text",
5787  "style":"text-align:center;margin:-4px -2px -4px -1px;width:90%;" +
5788  " height:" + (el.offsetHeight>20?el.offsetHeight:20) + "px",
5789  "value": initVal,
5790  "onclick":"event.stopPropagation();",
5791  },0 /*innerHTML*/, true /*doCloseTag*/);
5792 
5793 
5794  // "<input type='text' style='text-align:center;margin:-4px -2px -4px -1px;width:90%;" +
5795  // " height:" + (el.offsetHeight>20?el.offsetHeight:20) + "px' value='";
5796  // str += initVal;
5797  // str += "' >";
5798 
5799  str += _OK_CANCEL_DIALOG_STR;
5800 
5801  el.innerHTML = str;
5802 
5803  //select text in new input
5804  el = el.getElementsByTagName("input")[0];
5805  var startPos = initVal.lastIndexOf('/')+1;
5806  var endPos = initVal.lastIndexOf('.');
5807  if(endPos < 0) endPos = initVal.length;
5808  el.setSelectionRange(startPos, endPos);
5809  el.focus();
5810 
5811  } //end startEditFileName()
5812 
5813  //=====================================================================================
5814  //editCellOK ~~
5815  this.editCellOK = function(forPrimary)
5816  {
5817  forPrimary = forPrimary?1:0;
5818 
5819  var val = document.getElementById("fileNameDiv" + forPrimary).getElementsByTagName("input")[0].value;
5820  console.log("editCellOK " + forPrimary + " = " + val);
5821  _fileNameEditing[forPrimary] = false;
5822 
5823  var extPos = val.lastIndexOf('.');
5824 
5825  //set path and extension
5826 
5827  _filePath[forPrimary] = val.substr(0,extPos);
5828  _fileExtension[forPrimary] = extPos > 0?val.substr(extPos+1):"";
5829 
5830 
5831  // var el = document.getElementById("fileNameDiv" + forPrimary);
5832  //
5833  // var str = "";
5834  // str += "<a onclick='CodeEditor.editor.openFile(" + forPrimary +
5835  // ",\"" + _filePath[forPrimary] + "\",\"" + _fileExtension[forPrimary] + "\",true /*doConfirm*/);'>" +
5836  // _filePath[forPrimary] + "." + _fileExtension[forPrimary] + "</a>";
5837  //
5838  // el.innerHTML = str;
5839 
5840 
5841 
5842  //indicate file was not saved
5843  _fileWasModified[forPrimary] = true;
5844  _fileLastSave[forPrimary] = 0; //reset
5845  CodeEditor.editor.updateLastSave(forPrimary);
5846 
5847 
5848 
5849 
5850  // update file history stack to be displayed
5851  // in dropdown at filename position
5852  // place them in by time, so they are in time order
5853  // and in case we want to remove old ones
5854 
5855 
5856  _fileHistoryStack[_filePath[forPrimary] + "." +
5857  _fileExtension[forPrimary]] = [
5858  _eel[forPrimary].textContent,
5859  Date.now(),
5860  _fileWasModified[forPrimary],
5861  _fileLastSave[forPrimary]];
5862  console.log("_fileHistoryStack",_fileHistoryStack);
5863 
5864  CodeEditor.editor.updateFileHistoryDropdowns(); //both
5865 
5866  } //end editCellOK()
5867 
5868  //=====================================================================================
5869  //editCellCancel ~~
5870  this.editCellCancel = function(forPrimary)
5871  {
5872  forPrimary = forPrimary?1:0;
5873 
5874  Debug.log("editCellCancel " + forPrimary);
5875  _fileNameEditing[forPrimary] = false;
5876 
5877  //revert to same path and extension
5878  CodeEditor.editor.updateFileHistoryDropdowns(forPrimary);
5879 
5880  } //end editCellCancel()
5881 
5882 
5883 
5884  //=====================================================================================
5885  //updateFileHistoryDropdowns ~~
5886  // if forPrimarySelect is undefined, do both
5887  this.updateFileHistoryDropdowns = function(forPrimarySelect)
5888  {
5889  Debug.log("updateFileHistoryDropdowns forPrimarySelect=" + forPrimarySelect);
5890 
5891  var el;
5892  var str = "";
5893  var i;
5894 
5895  //_fileHistoryStack is map from filename to [content,time]
5896  var keys = Object.keys(_fileHistoryStack);
5897 
5898  var currentFile;
5899  for(var forPrimary=0;forPrimary<2;++forPrimary) //for primary and secondary
5900  {
5901  if(forPrimarySelect !== undefined && //target forPrimarySelect unless undefined
5902  forPrimarySelect != forPrimary) continue;
5903 
5904  currentFile = _filePath[forPrimary] + "." + _fileExtension[forPrimary];
5905  str = "";
5906  str += htmlOpen("select",
5907  {
5908  "class":"fileNameHistorySelect",
5909  "id":"fileNameHistorySelect" + forPrimary,
5910  "style":"width:100%;" +
5911  "text-align-last: center;",
5912  "title":"The current file is\n" + currentFile,
5913  "onchange":
5914  "CodeEditor.editor.handleFileNameHistorySelect(" +
5915  forPrimary + ");",
5916  "onclick":"CodeEditor.editor.stopUpdateHandling(event);",
5917  //"onfocus":"CodeEditor.editor.lastFileNameHistorySelectIndex = this.value;" +
5918  //"this.value = -1;", //force action even if same selected
5919  //"onblur":"this.value = CodeEditor.editor.lastFileNameHistorySelectIndex;",
5920 
5921  },0 /*innerHTML*/, false /*doCloseTag*/);
5922 
5923  //insert filename options
5924  for(i=0;i<keys.length;++i)
5925  {
5926  //Debug.log("key " + keys[i]);
5927 
5928  str += "<option value='" + i + "' ";
5929  if(currentFile == keys[i])
5930  str += "selected";
5931  str += ">";
5932  if(_fileHistoryStack[keys[i]][2])
5933  str += "*MODIFIED* ";
5934  str += keys[i];
5935  str += "</option>";
5936  } //end filaname option loop
5937 
5938  str += "</select>";
5939 
5940  try
5941  {
5942  el = document.getElementById("fileNameDiv" + forPrimary);
5943  el.innerHTML = str;
5944  }
5945  catch(e)
5946  {
5947  Debug.log("Ignoring error since file forPrimary=" +
5948  forPrimary + " is probably not opened:[" + DesktopContent.getExceptionLineNumber(e) + "]: " +
5949  e);
5950  }
5951  } // end primary and secondary loop
5952 
5953  } //end updateFileHistoryDropdowns()
5954 
5955 
5956  //=====================================================================================
5957  //handleFileNameHistorySelect ~~
5958  this.handleFileNameHistorySelect = function(forPrimary)
5959  {
5960  forPrimary = forPrimary?1:0;
5961 
5962  var selectedFileIndex = document.getElementById("fileNameHistorySelect" + forPrimary).value | 0;
5963  Debug.log("updateFileHistoryDropdowns " + forPrimary +
5964  "selected=" + selectedFileIndex);
5965 
5966  var keys = Object.keys(_fileHistoryStack);
5967  var selectedFileName = keys[selectedFileIndex];
5968 
5969  Debug.log("selectedFileName " + selectedFileName);
5970 
5971 
5972  //do not open file, just cut to the existing content in stack
5973 
5974  var fileObj = {};
5975  var fileArr = selectedFileName.split('.');
5976 
5977  //if same file, ask if user wants to reload
5978  if(fileArr[0] == _filePath[forPrimary] &&
5979  fileArr[1] == _fileExtension[forPrimary])
5980  {
5981  CodeEditor.editor.openFile(forPrimary,
5982  _filePath[forPrimary],
5983  _fileExtension[forPrimary],
5984  true /*doConfirm*/);
5985  return;
5986  }
5987 
5988  fileObj.path = fileArr[0];
5989  fileObj.extension = fileArr[1];
5990  fileObj.text = _fileHistoryStack[selectedFileName][0];
5991  fileObj.fileWasModified = _fileHistoryStack[selectedFileName][2];
5992  fileObj.fileLastSave = _fileHistoryStack[selectedFileName][3];
5993 
5994  console.log("fileObj",fileObj);
5995 
5996  CodeEditor.editor.handleFileContent(forPrimary,0,fileObj);
5997 
5998  } //end handleFileNameHistorySelect()
5999 
6000 
6001  //=====================================================================================
6002  //showFindAndReplace ~~
6003  this.showFindAndReplace = function(forPrimary)
6004  {
6005  forPrimary = forPrimary?1:0;
6006  _activePaneIsPrimary = forPrimary;
6007 
6008  Debug.log("showFindAndReplace forPrimary=" + forPrimary + " activePane=" + _activePaneIsPrimary);
6009 
6010  CodeEditor.editor.findAndReplaceLastButton[forPrimary] = 1;//default action is find
6011 
6012  //get cursor selection to use as starting point for action
6013  var el = _eel[forPrimary];
6014  var cursor = _findAndReplaceCursorInContent[forPrimary] =
6015  CodeEditor.editor.getCursor(el);
6016 
6017  //replace header with find and replace dialog
6018  el = document.getElementById("textEditorHeader" + forPrimary);
6019  var str = "";
6020 
6021  str += "<center>";
6022 
6023  str += "<table style='margin-top: 2px;'>";
6024 
6025 
6026  //row 1 -- Find
6027  str += "<tr><td style='text-align:right'>"; //col 1
6028  str += "Find:";
6029  str += "</td><td>"; //col 2
6030  str += htmlOpen("input",
6031  {
6032  "type":"text",
6033  "id":"findAndReplaceFind" + forPrimary,
6034  "style":"text-align:left; width:90%;" +
6035  " height:" + (20) + "px",
6036  "value": CodeEditor.editor.findAndReplaceFind[forPrimary],
6037  "onclick":"event.stopPropagation();",
6038  "onchange":"CodeEditor.editor.findAndReplaceFind[" +
6039  forPrimary + "] = this.value;" +
6040  "CodeEditor.editor.showFindAndReplaceSelection(" +
6041  forPrimary + ");",
6042  },0 /*innerHTML*/, true /*doCloseTag*/);
6043 
6044  str += "</td><td>"; //col 3
6045 
6046  //Scope options
6047  str += htmlOpen("select",
6048  {
6049  "id":"findAndReplaceScope" + forPrimary,
6050  "style":"width:100%;" +
6051  "text-align-last: center;",
6052  "title":"Choose the scope for Replace All",
6053  "onclick":"event.stopPropagation();" ,
6054  "onchange":"CodeEditor.editor.findAndReplaceScope[" +
6055  forPrimary + "] = this.value;" +
6056  "CodeEditor.editor.showFindAndReplaceSelection(" +
6057  forPrimary + ");",
6058 
6059  },0 /*innerHTML*/, false /*doCloseTag*/);
6060  str += "<option value='0'>All Lines</option>";
6061  str += "<option value='1' " + (CodeEditor.editor.findAndReplaceScope[forPrimary] ==
6062  1?"selected":"") + ">Selected Lines</option>";
6063  str += "</select>";
6064 
6065  str += "</td><td>"; //col 4
6066 
6067  //Option case-sensitive
6068  str += htmlOpen("input",
6069  {
6070  "type":"checkbox",
6071  "id":"findAndReplaceCaseSensitive" + forPrimary,
6072  "title":"Toggle case sensitive search",
6073  "onclick":"event.stopPropagation();",
6074  "style":"margin-left:10px;",
6075  "onchange":"CodeEditor.editor.findAndReplaceCaseSensitive[" +
6076  forPrimary + "] = this.checked;" +
6077  "CodeEditor.editor.showFindAndReplaceSelection(" +
6078  forPrimary + ");",
6079 
6080  },
6081  htmlOpen("a",
6082  {
6083  "title":"Toggle case sensitive search",
6084  "style":"margin-left:5px;",
6085  "onclick":"event.stopPropagation();" +
6086  "var el = document.getElementById(\"findAndReplaceCaseSensitive" +
6087  forPrimary + "\"); el.checked = !el.checked;" +
6088  "CodeEditor.editor.findAndReplaceCaseSensitive[" +
6089  forPrimary + "] = el.checked;" +
6090  "CodeEditor.editor.showFindAndReplaceSelection(" +
6091  forPrimary + ");",
6092 
6093  },
6094  "Case sensitive" /*innerHTML*/, true /*doCloseTag*/
6095  )/*innerHTML*/, true /*doCloseTag*/);
6096 
6097  str += "</td></tr>";
6098 
6099  //row 2 -- Replace
6100  str += "<tr><td style='text-align:right'>"; //col 1
6101  str += "Replace with:";
6102  str += "</td><td>"; //col 2
6103  str += htmlOpen("input",
6104  {
6105  "type":"text",
6106  "id":"findAndReplaceReplace" + forPrimary,
6107  "style":"text-align:left; width:90%;" +
6108  " height:" + (20) + "px",
6109  "value": CodeEditor.editor.findAndReplaceReplace[forPrimary],
6110  "onclick":"event.stopPropagation();",
6111  "onchange":"CodeEditor.editor.findAndReplaceReplace[" +
6112  forPrimary + "] = this.value; " +
6113  "CodeEditor.editor.showFindAndReplaceSelection(" +
6114  forPrimary + ");",
6115  },0 /*innerHTML*/, true /*doCloseTag*/);
6116 
6117  str += "</td><td>"; //col 3
6118 
6119  //Direction options
6120  str += htmlOpen("select",
6121  {
6122  "id":"findAndReplaceDirection" + forPrimary,
6123  "style":"width:100%;" +
6124  "text-align-last: center;",
6125  "title":"Choose the search direction for the Find & Replace",
6126  "onclick":"event.stopPropagation();",
6127  "onchange":"CodeEditor.editor.findAndReplaceDirection[" +
6128  forPrimary + "] = this.value;" +
6129  "CodeEditor.editor.showFindAndReplaceSelection(" +
6130  forPrimary + ");",
6131 
6132  },0 /*innerHTML*/, false /*doCloseTag*/);
6133  str += "<option value='0'>Search Forward</option>";
6134  str += "<option value='1' " + (CodeEditor.editor.findAndReplaceDirection[forPrimary] ==
6135  1?"selected":"") +
6136  ">Search Backward</option>";
6137  str += "</select>";
6138 
6139  str += "</td><td>"; //col 4
6140 
6141  //Option whole word
6142  str += htmlOpen("input",
6143  {
6144  "type":"checkbox",
6145  "id":"findAndReplaceWholeWord" + forPrimary,
6146  "title":"Toggle whole word search",
6147  "onclick":"event.stopPropagation();",
6148  "style":"margin-left:10px;",
6149  "onchange":"CodeEditor.editor.findAndReplaceWholeWord[" +
6150  forPrimary + "] = this.checked;" +
6151  "CodeEditor.editor.showFindAndReplaceSelection(" +
6152  forPrimary + ");",
6153 
6154  },
6155  htmlOpen("a",
6156  {
6157  "style":"margin-left:5px;",
6158  "title":"Toggle whole word search",
6159  "onclick":"event.stopPropagation();" +
6160  "var el = document.getElementById(\"findAndReplaceWholeWord" +
6161  forPrimary + "\"); el.checked = !el.checked;" +
6162  "CodeEditor.editor.findAndReplaceWholeWord[" +
6163  forPrimary + "] = el.checked;" +
6164  "CodeEditor.editor.showFindAndReplaceSelection(" +
6165  forPrimary + ");",
6166  },
6167  "Whole word" /*innerHTML*/, true /*doCloseTag*/
6168  )/*innerHTML*/, true /*doCloseTag*/);
6169 
6170  str += "</td></tr>";
6171 
6172 
6173  //Buttons row
6174  str += "<tr><td colspan='4' style='text-align:center'>";
6175  str += htmlOpen("div",
6176  {
6177  "id": "findAndReplaceWrapped" + forPrimary,
6178  "style": "text-align:right; margin: 4px; width:115px;" +
6179  "color: red; float: left;",
6180  },0 /*innerHTML*/, true /*doCloseTag*/);
6181  str += "<div style='float:left;'>";
6182  str += htmlOpen("input",
6183  {
6184  "type": "button",
6185  "value": "Find",
6186 
6187  "style": "text-align:center; margin: 4px;" ,
6188  "onclick": "event.stopPropagation();" +
6189  "CodeEditor.editor.doFindAndReplaceAction(" + forPrimary + ",1)",
6190  },0 /*innerHTML*/, true /*doCloseTag*/);
6191 
6192  str += htmlOpen("input",
6193  {
6194  "type": "button",
6195  "value": "Replace",
6196 
6197  "style": "text-align:center; margin: 4px;" ,
6198  "onclick": "event.stopPropagation();" +
6199  "CodeEditor.editor.doFindAndReplaceAction(" + forPrimary + ",2)",
6200  },0 /*innerHTML*/, true /*doCloseTag*/);
6201 
6202  str += htmlOpen("input",
6203  {
6204  "type": "button",
6205  "value": "Replace & Find",
6206 
6207  "style": "text-align:center; margin: 4px;" ,
6208  "onclick": "event.stopPropagation();" +
6209  "CodeEditor.editor.doFindAndReplaceAction(" + forPrimary + ",3)",
6210  },0 /*innerHTML*/, true /*doCloseTag*/);
6211 
6212  str += htmlOpen("input",
6213  {
6214  "type": "button",
6215  "value": "Replace All",
6216 
6217  "style": "text-align:center; margin: 4px;" ,
6218  "onclick": "event.stopPropagation();" +
6219  "CodeEditor.editor.doFindAndReplaceAction(" + forPrimary + ",4)",
6220  },0 /*innerHTML*/, true /*doCloseTag*/);
6221 
6222  str += htmlOpen("input",
6223  {
6224  "type": "button",
6225  "value": "Cancel",
6226  "title": "Close find and replace controls.",
6227  "style": "text-align:center; margin: 4px;" ,
6228 
6229  "onclick": "event.stopPropagation();" +
6230  "CodeEditor.editor.displayFileHeader(" + forPrimary + ")",
6231  },0 /*innerHTML*/, true /*doCloseTag*/);
6232 
6233  str += "</div>";
6234  str += "</td></tr>";
6235 
6236  str += "</table>";
6237  str += "</center>";
6238 
6239  el.innerHTML = str;
6240 
6241  el = document.getElementById("findAndReplaceFind" + forPrimary);
6242  el.setSelectionRange(0, el.value.length);
6243  el.focus();
6244 
6245  el = document.getElementById("findAndReplaceCaseSensitive" + forPrimary);
6246  el.checked = CodeEditor.editor.findAndReplaceCaseSensitive[forPrimary];
6247 
6248  el = document.getElementById("findAndReplaceWholeWord" + forPrimary);
6249  el.checked = CodeEditor.editor.findAndReplaceWholeWord[forPrimary];
6250 
6251  } //end showFindAndReplace()
6252 
6253 
6254  //=====================================================================================
6255  //showFindAndReplaceSelection ~~
6256  this.showFindAndReplaceSelection = function(forPrimary)
6257  {
6258  forPrimary = forPrimary?1:0;
6259  Debug.log("showFindAndReplaceSelection forPrimary=" + forPrimary);
6260 
6261  var el = _eel[forPrimary];
6262  var cursor = CodeEditor.editor.getCursor(el);
6263 
6264  if(cursor.startPosInContent !== undefined)
6265  CodeEditor.editor.setCursor(el,
6266  cursor,
6267  true /*scrollIntoView*/);
6268  else if( //if find is open, then go to find cursor
6269  CodeEditor.editor.findAndReplaceLastButton[forPrimary] > 0 &&
6270  _findAndReplaceCursorInContent[forPrimary] !== undefined)
6271  CodeEditor.editor.setCursor(el,
6272  _findAndReplaceCursorInContent[forPrimary],
6273  true /*scrollIntoView*/);
6274 
6275 
6276  } //end showFindAndReplaceSelection()
6277 
6278  //=====================================================================================
6279  //doFindAndReplaceAction ~~
6280  // actions:
6281  // 1 := Find
6282  // 2 := Replace
6283  // 3 := Replace & Find
6284  // 4 := Replace All
6285  this.doFindAndReplaceAction = function(forPrimary,action)
6286  {
6287  forPrimary = forPrimary?1:0;
6288  action = action | 0; //force integer
6289 
6290  CodeEditor.editor.findAndReplaceLastButton[forPrimary] = action; //record last action
6291 
6292  var find = document.getElementById("findAndReplaceFind" + forPrimary).value;//CodeEditor.editor.findAndReplaceFind[forPrimary];
6293  var originalFind = find;
6294  if(!find || find == "")
6295  {
6296  Debug.log("Illegal empty string to find.", Debug.HIGH_PRIORITY);
6297  return;
6298  }
6299  var replace = CodeEditor.editor.findAndReplaceReplace[forPrimary];
6300  var scope = CodeEditor.editor.findAndReplaceScope[forPrimary]|0;
6301  var direction = CodeEditor.editor.findAndReplaceDirection[forPrimary]|0;
6302  if(action == 4) //always go forward for Replace All
6303  direction = 0;
6304  var caseSensitive = CodeEditor.editor.findAndReplaceCaseSensitive[forPrimary]?1:0;
6305  var wholeWord = CodeEditor.editor.findAndReplaceWholeWord[forPrimary]?1:0;
6306 
6307  Debug.log("doFindAndReplaceAction forPrimary=" + forPrimary +
6308  " action=" + action +
6309  " find=" + find +
6310  " replace=" + replace +
6311  " scope=" + scope +
6312  " direction=" + direction +
6313  " caseSensitive=" + caseSensitive +
6314  " wholeWord=" + wholeWord);
6315 
6316  //Steps:
6317  // loop
6318  // if 2, 3, 4
6319  // replace current found word
6320  // if 1, 3, 4
6321  // find a word based on criteria
6322  // if 4 and found a word
6323  // continue loop, else done!
6324 
6325  var el = _eel[forPrimary];
6326  var originalText = el.textContent;
6327 
6328  if(caseSensitive)
6329  text = originalText;
6330  else //case insensitive, so force lower case
6331  {
6332  text = originalText.toLowerCase();
6333  find = find.toLowerCase();
6334  }
6335 
6336  var i = direction?text.length:-1; //init to 1 off the end
6337  var j = text.length-1;
6338  //,n,node,el,val;
6339  var cursor = CodeEditor.editor.getCursor(el);
6340 
6341  //if there is a cursor and havent wrapped around, use the cursor
6342  if(cursor.startPosInContent !== undefined &&
6343  (action == 4 ||
6344  document.getElementById("findAndReplaceWrapped" + forPrimary).textContent == ""))
6345  i = cursor.startPosInContent;
6346  else if(_findAndReplaceCursorInContent[forPrimary] !== undefined &&
6347  _findAndReplaceCursorInContent[forPrimary].startPosInContent !== undefined &&
6348  _findAndReplaceCursorInContent[forPrimary].startPosInContent >= 0 &&
6349  (action == 4 ||
6350  document.getElementById("findAndReplaceWrapped" + forPrimary).textContent == ""))
6351  {
6352  i = _findAndReplaceCursorInContent[forPrimary].startPosInContent;
6353 
6354  if(action == 4)
6355  CodeEditor.editor.setCursor(el,
6356  _findAndReplaceCursorInContent[forPrimary],
6357  true /*scrollIntoView*/);
6358  }
6359 
6360  //clear wrapped
6361  document.getElementById("findAndReplaceWrapped" + forPrimary).innerHTML = "";
6362 
6363  Debug.log("Starting position: " + i);
6364 
6365  if(scope == 1) //only selected lines
6366  {
6367  if(cursor.endPosInContent !== undefined)
6368  j = cursor.endPosInContent;
6369  else if(_findAndReplaceCursorInContent[forPrimary] !== undefined &&
6370  _findAndReplaceCursorInContent[forPrimary].endPosInContent != undefined &&
6371  _findAndReplaceCursorInContent[forPrimary].endPosInContent >= 0)
6372  j = _findAndReplaceCursorInContent[forPrimary].endPosInContent;
6373 
6374  Debug.log("Ending position: " + j);
6375  }
6376  else if(action == 4) //if all lines, replace all, then start i at beginning
6377  i = -1;
6378 
6379  var replaceCount = 0;
6380  var done;
6381  var found = false;
6382  do
6383  {
6384  done = true; //init to one time through
6385 
6387  //replace current found word
6388  switch(action)
6389  {
6390  case 2: //Replace
6391  case 3: //Replace & Find
6392  if(i > 0 && i + find.length <= text.length)
6393  found = true; //replace first time through
6394  case 4: //Replace All
6395 
6396  if(found)
6397  {
6398  Debug.log("Replacing");
6399  ++replaceCount;
6400 
6401  //do replace
6402  originalText =
6403  originalText.substr(0,i) +
6404  replace +
6405  originalText.substr(i+find.length);
6406 
6407  //update text, so indices still matchup
6408  if(caseSensitive)
6409  text = originalText;
6410  else //case insensitive, so force lower case
6411  text = originalText.toLowerCase();
6412  }
6413 
6414  break;
6415  case 1: //Find
6416  break; //do nothing
6417  default:
6418  Debug.log("Unrecognized action! " + action, Debug.HIGH_PRIORITY);
6419  return;
6420  } //end replace word
6421 
6422 
6424  //find a word based on criteria
6425  switch(action)
6426  {
6427  case 1: //Find
6428  case 3: //Replace & Find
6429  case 4: //Replace All
6430 
6431  if(direction == 0) //forward
6432  i = text.indexOf(find,i+1);
6433  else if(direction == 1) //reverse
6434  i = text.lastIndexOf(find,i-1);
6435 
6436  if(wholeWord)
6437  {
6438  //confirm non-alpha-numeric before and after
6439  if(i>0 && (
6440  (text[i-1] >= 'a' && text[i-1] <= 'z') ||
6441  (text[i-1] >= 'A' && text[i-1] <= 'Z') ||
6442  (text[i-1] >= '0' && text[i-1] <= '9') ||
6443  text[i-1] == '_'
6444  )) //if leading character is alpha-numeric
6445  {
6446  //invalidate find!
6447  done = false; //look for next
6448  }
6449  else if(i>0 && i+find.length<text.length && (
6450  (text[i+find.length] >= 'a' && text[i+find.length] <= 'z') ||
6451  (text[i+find.length] >= 'A' && text[i+find.length] <= 'Z') ||
6452  (text[i+find.length] >= '0' && text[i+find.length] <= '9') ||
6453  text[i+find.length] == '_'
6454  )) //if trailing character is alpha-numeric
6455  {
6456  //invalidate find!
6457  done = false; //look for next
6458  }
6459  }
6460 
6461  console.log(i);//,text.substr(i,find.length));
6462 
6463  if(done) //handle end game, done overloaded to handle wholeWord functionality
6464  {
6465  if(i >= 0) //found something
6466  {
6467  found = true;
6468 
6469  //if Replace All, then keep going
6470  if(action == 4)
6471  {
6472  //keep going if within selection
6473  if(i + find.length < j)
6474  done = false;
6475  //else outside of selected lines
6476  }
6477  }
6478  else //found nothing
6479  {
6480  found = false;
6481  document.getElementById("findAndReplaceWrapped" + forPrimary).innerHTML = "Reached end";
6482  }
6483  } //end end game handling
6484  else
6485  found = false;
6486 
6487  break;
6488  case 2: //Replace
6489  break; //do nothing
6490  default:
6491  Debug.log("Unrecognized action! " + action, Debug.HIGH_PRIORITY);
6492  return;
6493  } //end find word
6494 
6495  //Debug.log("done " + done);
6496  } while(!done); //end main replace & find loop
6497 
6498 
6500  //wrap it up
6501  switch(action)
6502  {
6503  case 2: //Replace
6504  case 3: //Replace & Find
6505  case 4: //Replace All
6506 
6507  //set to modified original text
6508  // then re-decorate
6509  el.textContent = originalText;
6510  CodeEditor.editor.updateDecorations(forPrimary);
6511 
6512  //select the find
6513  if(action == 3)
6514  {
6515  _findAndReplaceCursorInContent[forPrimary] =
6516  CodeEditor.editor.createCursorFromContentPosition(el,
6517  i, i + find.length);
6518  CodeEditor.editor.setCursor(
6519  el,
6520  _findAndReplaceCursorInContent[forPrimary],
6521  true /*scrollIntoView*/);
6522  }
6523 
6524 
6525  break;
6526  case 1: //Find
6527 
6528  //select the find
6529  _findAndReplaceCursorInContent[forPrimary] =
6530  CodeEditor.editor.createCursorFromContentPosition(el,
6531  i, i + find.length)
6532  CodeEditor.editor.setCursor(
6533  el,
6534  _findAndReplaceCursorInContent[forPrimary],
6535  true /*scrollIntoView*/);
6536 
6537  break;
6538  default:
6539  Debug.log("Unrecognized action! " + action, Debug.HIGH_PRIORITY);
6540  return;
6541  } //end wrap it up
6542 
6543 
6544 
6545  //display replace count for Replace All
6546  if(action == 4)
6547  document.getElementById("findAndReplaceWrapped" + forPrimary).innerHTML =
6548  replaceCount + " Replaced";
6549 
6550 
6551  } //end doFindAndReplaceAction()
6552 
6553  //=====================================================================================
6554  //displayFileHeader ~~
6555  this.displayFileHeader = function(forPrimary)
6556  {
6557  forPrimary = forPrimary?1:0;
6558 
6559  var forceDisplayComplete = false;
6560  if(CodeEditor.editor.findAndReplaceLastButton[forPrimary] != -1)
6561  {
6562  CodeEditor.editor.findAndReplaceLastButton[forPrimary] = -1; //clear default find and replace action
6563  forceDisplayComplete = true;
6564  }
6565 
6566  Debug.log("displayFileHeader forPrimary=" + forPrimary);
6567 
6568  //set path and extension and last save to header
6569  var el = document.getElementById("textEditorHeader" + forPrimary);
6570 
6571 
6572  var path = _filePath[forPrimary];
6573  var extension = _fileExtension[forPrimary];
6574  // var fileWasModified = _fileWasModified[forPrimary];
6575  // var fileLastSave = _fileLastSave[forPrimary];
6576 
6577  var str = "";
6578 
6579  //add file name div
6580  str += htmlOpen("div",
6581  {
6582  "onmousemove" :
6583  "CodeEditor.editor.handleFileNameMouseMove(" + forPrimary + ");",
6584  },0 /*innerHTML*/, false /*doCloseTag*/);
6585  str += "<center>";
6586 
6587 
6588  str += htmlOpen("div", //this is place holder, that keeps height spacing
6589  {
6590 
6591  "style": "width: 172px;", //_READ_ONLY make different width
6592  "class": "fileButtonContainer",
6593  "id": "fileButtonContainer" + forPrimary,
6594 
6595  }, 0 /*innerHTML*/, false /*doCloseTag*/);
6596 
6597  str += htmlOpen("div", //this is el that gets hide/show toggle
6598  {
6599  "class":"fileButtonContainerShowHide",
6600  "id":"fileButtonContainerShowHide" + forPrimary,
6601  "onmousemove":
6602  "event.stopPropagation(); " +
6603  "CodeEditor.editor.handleFileNameMouseMove(" + forPrimary +
6604  ",1 /*doNotStartTimer*/);",
6605 
6606  },0 /*innerHTML*/, false /*doCloseTag*/);
6607 
6608  //add rename button
6609  if (!_READ_ONLY)
6610  {
6611  str += htmlOpen("div",
6612  {
6613  "class":"fileButton",
6614  "id":"fileRenameButton" + forPrimary,
6615  "title": "Change the filename\n" + path + "." + extension,
6616  "onclick":
6617  "event.stopPropagation(); " +
6618  "CodeEditor.editor.startEditFileName(" + forPrimary + ");",
6619  },0 /*innerHTML*/, true /*doCloseTag*/);
6620  }
6621  str += htmlOpen("div",
6622  {
6623  "class":"fileButton",
6624  "id":"fileDownloadButton" + forPrimary,
6625  "title": "Download the file content from\n" + path + "." + extension,
6626  "onclick":
6627  "event.stopPropagation(); " +
6628  "CodeEditor.editor.download(" + forPrimary + ");",
6629  },
6630  //make download arrow
6631  "<div class='fileDownloadButtonBgChild' style='display: ; margin-left: 0px; margin-top: 1px; height:7px; width: 6px; background-color: rgb(202, 204, 210);'></div>" +
6632  "<div class='fileDownloadButtonBorderChild' style='display: block; width: 0; height: 0; border-left: 6px solid transparent; border-right: 6px solid transparent; border-top: 8px solid rgb(202, 204, 210);'></div>" +
6633  "<div class='fileDownloadButtonBgChild' style='position: relative; top: 2px; width: 12px; height: 2px; display: block; background-color: rgb(202, 204, 210);'></div>"
6634  /*innerHTML*/, true /*doCloseTag*/);
6635  if (!_READ_ONLY) {
6636  str += htmlOpen("div",
6637  {
6638  "class":"fileButton",
6639  "id":"fileUploadButton" + forPrimary,
6640  "title": "Upload file content to\n" + path + "." + extension,
6641  "onclick":
6642  "event.stopPropagation(); " +
6643  "CodeEditor.editor.upload(" + forPrimary + ");",
6644  },
6645 
6646  //make upload arrow
6647  "<div class='fileDownloadButtonBorderChild' style='display: block; width: 0; height: 0; border-left: 6px solid transparent; border-right: 6px solid transparent; border-bottom: 8px solid rgb(202, 204, 210);'></div>" +
6648  "<div class='fileDownloadButtonBgChild' style='display: block; margin-left: 0px; height:7px; width: 6px; background-color: rgb(202, 204, 210);'></div>" +
6649  "<div class='fileDownloadButtonBgChild' style='position: relative; top: 3px; width: 12px; height: 2px; display: block; background-color: rgb(202, 204, 210);'></div>"
6650  /*innerHTML*/, true /*doCloseTag*/);
6651  }
6652  if (!_READ_ONLY) {
6653  str += htmlOpen("div",
6654  {
6655  "class":"fileButton fileUndoButton",
6656  "id":"fileUndoButton" + forPrimary,
6657  "title": "Undo to rewind to last recorded checkpoint for\n" + path + "." + extension,
6658  "style": "color: rgb(202, 204, 210);" +
6659  "padding: 0 5px 0;" +
6660  "font-size: 17px;" +
6661  "font-weight: bold;",
6662  "onclick":
6663  "event.stopPropagation(); " +
6664  "CodeEditor.editor.undo(" + forPrimary + ");",
6665  },
6666  //make undo arrow
6667  "&#8617;"
6668  /*innerHTML*/, true /*doCloseTag*/);
6669 
6670  str += htmlOpen("div",
6671  {
6672  "class":"fileButton fileUndoButton",
6673  "id":"fileRedoButton" + forPrimary,
6674  "title": "Redo to fast-forward to last recorded checkpoint for\n" + path + "." + extension,
6675  "style": "color: rgb(202, 204, 210);" +
6676  "padding: 0 5px 0;" +
6677  "font-size: 17px;" +
6678  "font-weight: bold;",
6679  "onclick":
6680  "event.stopPropagation(); " +
6681  "CodeEditor.editor.undo(" + forPrimary + ",1 /*redo*/);",
6682  },
6683  //make redo arrow
6684  "&#8618;"
6685  /*innerHTML*/, true /*doCloseTag*/);
6686  }
6687 
6688  str += htmlOpen("div",
6689  {
6690  "class":"fileButton openRelatedFileButton",
6691  "id":"openRelatedFileButton" + forPrimary,
6692  "title": "Open related file in other pane for\n" + path + "." + extension,
6693  "style": "color: rgb(202, 204, 210);" +
6694  "padding: 0 5px 0;" +
6695  "font-size: 17px;" +
6696  "font-weight: bold;",
6697  "onclick":
6698  "event.stopPropagation(); " +
6699  "CodeEditor.editor.openRelatedFile(" + forPrimary +
6700  ", true /*inOtherPane*/);",
6701  },
6702  //make redo arrow
6703  ":"
6704  /*innerHTML*/, true /*doCloseTag*/);
6705 
6706  str += htmlOpen("div",
6707  {
6708  "class":"fileButton refreshFileButton",
6709  "id":"refreshFileButton" + forPrimary,
6710  "title": "Refresh file \n" + path + "." + extension,
6711  "style": "color: rgb(202, 204, 210);" +
6712  "padding: 0 5px 0;" +
6713  (Debug.BROWSER_TYPE == Debug.BROWSER_TYPE_FIREFOX? //firefox shows circle-arrow character smaller
6714  "font-size: 28px;margin-top:-6px;":"font-size: 17px;font-weight:bold;"),
6715  "onclick":
6716  "event.stopPropagation(); " +
6717  "CodeEditor.editor.openFile(" + forPrimary +
6718  ",\"" + path + "\",\"" +
6719  extension + "\", true /*doConfirm*/);",
6720  },
6721  //make refresh circle arrow
6722  "&#8635;"
6723  /*innerHTML*/, true /*doCloseTag*/);
6724 
6725 
6726 
6727  str += "</div>"; //end fileButtonContainerShowHide
6728  str += "</div>"; //end fileButtonContainer
6729 
6730  str += htmlClearDiv();
6731 
6732  var nameArr = path.split('/');
6733 
6734  //table for open icons and filename select
6735  str += "<table><tr><td>";
6736  //open in new window
6737  str += htmlOpen("a",
6738  {
6739  "title":"Open file in a new browser tab: \n" +
6740  "srcs" + path + "." + extension,
6741  "onclick":"DesktopContent.openNewBrowserTab(" +
6742  "\"" + nameArr[nameArr.length-1] + "." + extension + "\",\"\"," +
6743  "\"/WebPath/html/CodeEditor.html?urn=" +
6744  DesktopContent._localUrnLid + "&" +
6745  "startFilePrimary=" +
6746  path + "." + extension + "\",0 /*unique*/);' ", //end onclick
6747  },
6748  "<img class='dirNavFileNewWindowImgNewWindow' " +
6749  "src='/WebPath/images/windowContentImages/CodeEditor-openInNewWindow.png'>"
6750  /*innerHTML*/, true /*doCloseTag*/);
6751 
6752  //open in other pane
6753  str += htmlOpen("a",
6754  {
6755  "title":"Open file in the other editor pane of the split-view: \n" +
6756  "srcs" + path + "." + extension,
6757  "onclick":"CodeEditor.editor.openFile(" +
6758  (!forPrimary) + ",\"" +
6759  path + "\", \"" +
6760  extension + "\"" + //extension
6761  ");", //end onclick
6762  },
6763  "<img class='dirNavFileNewWindowImgNewPane' " +
6764  "src='/WebPath/images/windowContentImages/CodeEditor-openInOtherPane.png'>"
6765  /*innerHTML*/, true /*doCloseTag*/);
6766  str += "</td><td>";
6767 
6768  //add path div
6769  str += htmlOpen("div",
6770  {
6771  "class":"fileNameDiv",
6772  "id":"fileNameDiv" + forPrimary,
6773  "style":"margin: 0 5px 0 5px",
6774  },0 /*innerHTML*/, false /*doCloseTag*/);
6775  str += "<a onclick='CodeEditor.editor.openFile(" + forPrimary +
6776  ",\"" + path + "\",\"" + extension + "\",true /*doConfirm*/);' " +
6777  "title='Click to reload \n" + path + "." + extension + "' " +
6778  ">" +
6779  path + "." + extension + "</a>";
6780  str += "</div>"; //end fileNameDiv
6781 
6782  str += "</td></tr></table>";
6783  str += "</center>";
6784  str += "</div>"; //end file name div
6785 
6786  str += htmlClearDiv();
6787 
6788  //last modified div
6789  str += "<div class='textEditorLastSave' id='textEditorLastSave" +
6790  forPrimary + "'>Unmodified</div>";
6791 
6792  str += htmlClearDiv();
6793  //outline div
6794  str += "<div class='textEditorOutline' id='textEditorOutline" +
6795  forPrimary + "'>Outline:</div>";
6796 
6797  el.innerHTML = str;
6798 
6799  CodeEditor.editor.updateDecorations(forPrimary,forceDisplayComplete);
6800  CodeEditor.editor.updateFileHistoryDropdowns();
6801 
6802  } //end displayFileHeader()
6803 
6804  //=====================================================================================
6805  //updateFileSnapshot ~~
6806  // handle undo stack and file history stack management
6807  // if new, then place in stacks
6808  //
6809  // Note: pass text as object to avoid copy of giant string
6810  this.updateFileSnapshot = function(forPrimary,textObj /*{text,time}*/, ignoreTimeDelta)
6811  {
6812  forPrimary = forPrimary?1:0;
6813 
6814  Debug.log("updateFileSnapshot forPrimary=" + forPrimary);
6815 
6816 
6817  var addSnapshot = false;
6818  //var now = textObj.time;//Date.now() /*milliseconds*/;
6819  if(_undoStackLatestIndex[forPrimary] != -1)
6820  {
6821  //compare with last to see if different
6822  // and that it has been 2 seconds
6823  if((ignoreTimeDelta ||
6824  2*1000 < textObj.time - _undoStack[forPrimary][_undoStackLatestIndex[forPrimary]][1]) &&
6825  _undoStack[forPrimary][_undoStackLatestIndex[forPrimary]][0] != textObj.text)
6826  addSnapshot = true;
6827  }
6828  else //else first, so add to stack
6829  addSnapshot = true;
6830 
6831 
6832  if(addSnapshot)
6833  { //add to stack
6834  ++_undoStackLatestIndex[forPrimary];
6835  if(_undoStackLatestIndex[forPrimary] >= _undoStack_MAX_SIZE)
6836  _undoStackLatestIndex[forPrimary] = 0; //wrap around
6837 
6838  _undoStack[forPrimary][_undoStackLatestIndex[forPrimary]] =
6839  [textObj.text,
6840  textObj.time];
6841 
6842  console.log("snapshot added to stack",_undoStack[forPrimary]);
6843 
6844 
6845  // update file history stack to be displayed
6846  // in dropdown at filename position
6847  // place them in by time, so they are in time order
6848  // and in case we want to remove old ones
6849 
6850  _fileHistoryStack[_filePath[forPrimary] + "." +
6851  _fileExtension[forPrimary]] = [
6852  textObj.text,
6853  textObj.time,
6854  _fileWasModified[forPrimary],
6855  _fileLastSave[forPrimary]];
6856  console.log("_fileHistoryStack",_fileHistoryStack);
6857 
6858  CodeEditor.editor.updateFileHistoryDropdowns();
6859  }
6860 
6861  return addSnapshot;
6862 
6863  } //end updateFileSnapshot()
6864 
6865  //=====================================================================================
6866  //startUpdateHandling ~~
6867  // unify update handling
6868  this.startUpdateHandling = function(forPrimary)
6869  {
6870  _updateHandlerTargetPane[forPrimary] = true; //mark need to update
6871 
6872  window.clearTimeout(_updateTimerHandle);
6873  _updateTimerHandle = window.setTimeout(
6874  CodeEditor.editor.updateTimeoutHandler,
6875  _UPDATE_DECOR_TIMEOUT /*ms*/);
6876 
6877  } //end startUpdateHandling()
6878 
6879  //=====================================================================================
6880  //stopUpdateHandling ~~
6881  // unify update handling
6882  this.stopUpdateHandling = function(event)
6883  {
6884  if(event) event.stopPropagation();
6885  window.clearTimeout(_updateTimerHandle);
6886  } //end stopUpdateHandling()
6887 
6888  //=====================================================================================
6889  //updateTimeoutHandler ~~
6890  // unify update handling
6891  this.updateTimeoutHandler = function()
6892  {
6893  if(_updateHandlerTargetPane[0])
6894  {
6895  CodeEditor.editor.updateDecorations(0 /*forPrimary*/);
6896  _updateHandlerTargetPane[0] = false;
6897  }
6898  if(_updateHandlerTargetPane[1])
6899  {
6900  CodeEditor.editor.updateDecorations(1 /*forPrimary*/);
6901  _updateHandlerTargetPane[1] = false;
6902  }
6903  } //end updateTimeoutHandler()
6904 
6905 
6906  //=====================================================================================
6907  //doubleClickHandler ~~
6908  this.doubleClickHandler = function(forPrimary)
6909  {
6910  forPrimary = forPrimary?1:0;
6911 
6912  Debug.log("doubleClickHandler forPrimary=" + forPrimary);
6913 
6914  //get character before cursor
6915  // if { or }
6916  // then highlight entire section
6917 
6918  var el = _eel[forPrimary];
6919  var cursor = CodeEditor.editor.getCursor(el);
6920 
6921  if(!cursor || cursor.startNodeIndex === undefined)
6922  return;
6923 
6924  var n = cursor.startNodeIndex;
6925  var c = el.childNodes[n].textContent[
6926  cursor.startPos];
6927 
6928  var openCount = 0; //init
6929 
6930  if(c != '{' && c != '}')
6931  {
6932  if(cursor.startPos == 0)
6933  {
6934  //find character in previous node
6935  do
6936  {
6937  --n;
6938  } while(n >= 0 && el.childNodes[n].textContent.length);
6939 
6940  if(n < 0) return;
6941  c = el.childNodes[n].textContent[
6942  el.childNodes[n].textContent.length-1];
6943  }
6944  else
6945  c = el.childNodes[n].textContent[
6946  cursor.startPos-1];
6947 
6948  if(c == '{')
6949  openCount = 1; //already counted the 1
6950  }
6951 
6952  Debug.log("character before cursor " + c);
6953 
6954  if(c != '{' && c != '}') return;
6955 
6956  var i;
6957  var found = false;
6958  var foundDoubleQuote = false;
6959  var foundSingleQuote = false;
6960  var foundComment = false;
6961 
6962  if(c == '}')
6963  {
6964  //go backwards looking for matching bracket
6965  cursor.endNodeIndex = -1;
6966  cursor.endPos = -1;
6967 
6968 
6969  //if a comment is found, restore start of line
6970  // openCount value
6971 
6972  var openCountSave = openCount; //init
6973  var prelimFound = false;
6974 
6975  for(n;n>=0; --n)
6976  {
6977  node = el.childNodes[n];
6978  val = node.textContent;
6979  for(i=(n==cursor.startNodeIndex?cursor.startPos-1:
6980  val.length-1); i>=0; --i)
6981  {
6982  if(cursor.endNodeIndex == -1) //first time take first position
6983  {
6984  cursor.endNodeIndex = n;
6985  cursor.endPos = i;
6986  }
6987 
6988  if(val[i] == '\n')
6989  {
6990  foundSingleQuote = false;
6991  foundDoubleQuote = false;
6992  openCountSave = openCount; //save count at start of new line
6993 
6994  if(prelimFound) //if found end
6995  {
6996  Debug.log("confirmed found open count match ni " + n + " " + i);
6997  found = true;
6998  break;
6999  }
7000  }
7001 
7002  if(!foundSingleQuote && val[i] == '"')
7003  foundDoubleQuote = !foundDoubleQuote;
7004  else if(!foundDoubleQuote && val[i] == "'")
7005  foundSingleQuote = !foundSingleQuote;
7006  else if(val[i]== '/' && i-1 >= 0 &&
7007  val[i-1] == '/')
7008  {
7009  //found comment! invalidate findings in comment
7010  openCount = openCountSave; //restore count
7011  if(openCount > 0)
7012  prelimFound = false; //invalidate end reached
7013 
7014  --i; //skip previous / part of comment indicator
7015  continue; //do not proceed with command tabbing if in comment
7016  }
7017 
7018  if(foundDoubleQuote || foundSingleQuote ||
7019  prelimFound)
7020  continue; //skip if in quote, or if we think we are done
7021 
7022  if(val[i] == '}')
7023  ++openCount;
7024  else if(val[i] == '{')
7025  {
7026  --openCount;
7027  if(openCount <= 0)
7028  {
7029  prelimFound = true;
7030  Debug.log("found open count match ni " + n + " " + i);
7031 
7032  //do NOT break right away, in case we are in a comment, to be
7033  //break;
7034  }
7035  }
7036  //take second to last position as start
7037  cursor.startNodeIndex = n;
7038  cursor.startPos = i;
7039  }
7040 
7041  if(found) break;
7042  } // end matching bracket search loop
7043 
7044  } //done handling } case
7045  else // c == '{'
7046  {
7047  //go forwards looking for matching bracket
7048 
7049  i = cursor.startPos;
7050  for(n=cursor.startNodeIndex;n<el.childNodes.length; ++n)
7051  {
7052  node = el.childNodes[n];
7053  val = node.textContent;
7054 
7055  for(; i<val.length; ++i)
7056  {
7057  if(val[i] == '\n')
7058  {
7059  foundSingleQuote = false;
7060  foundDoubleQuote = false;
7061  foundComment = false;
7062  }
7063 
7064  if(foundComment)
7065  continue;
7066 
7067  if(!foundSingleQuote && val[i] == '"')
7068  foundDoubleQuote = !foundDoubleQuote;
7069  else if(!foundDoubleQuote && val[i] == "'")
7070  foundSingleQuote = !foundSingleQuote;
7071  else if(val[i]== '/' && i+1 < val.length &&
7072  val[i+1] == '/')
7073  {
7074  foundComment = true;
7075  continue; //do not proceed with command tabbing if in comment
7076  }
7077 
7078  if(foundDoubleQuote || foundSingleQuote)
7079  continue; //skip if in quote
7080 
7081  if(val[i] == '{')
7082  ++openCount;
7083  else if(val[i] == '}')
7084  {
7085  --openCount;
7086  if(openCount <= 0)
7087  {
7088  found = true;
7089  Debug.log("found open count match ni " + n + " " + i);
7090  break;
7091  }
7092  }
7093  //take second to last position as end
7094  cursor.endNodeIndex = n;
7095  cursor.endPos = i;
7096  }
7097 
7098  if(found) break;
7099 
7100  i = 0; //reset
7101 
7102  } // end matching bracket search loop
7103  } //done handling { case
7104 
7105  //set cursor selection
7106  CodeEditor.editor.setCursor(el,cursor);
7107 
7108  } //end doubleClickHandler()
7109 
7110 
7111  //=====================================================================================
7112  //download ~~
7113  this.download = function(forPrimary)
7114  {
7115  forPrimary = forPrimary?1:0;
7116 
7117  Debug.log("download forPrimary=" + forPrimary);
7118 
7119  var dataStr = "data:text/plain;charset=utf-8," +
7120  encodeURIComponent(_eel[forPrimary].textContent);
7121 
7122  var filename = _filePath[forPrimary];
7123  var i = filename.lastIndexOf('/');
7124  if(i >= 0)
7125  filename = filename.substr(i+1); //only keep file name, discard path
7126  filename += "." + _fileExtension[forPrimary];
7127 
7128  Debug.log("Downloading to filename " + filename);
7129 
7130  var link = document.createElement("a");
7131  link.setAttribute("href", dataStr); //double encode, so encoding remains in CSV
7132  link.setAttribute("style", "display:none");
7133  link.setAttribute("download", filename);
7134  document.body.appendChild(link); // Required for FF
7135 
7136  link.click(); // This will download the data file named "my_data.csv"
7137 
7138  link.parentNode.removeChild(link);
7139 
7140  } //end download()
7141 
7142  //=====================================================================================
7143  //upload ~~
7144  this.upload = function(forPrimary)
7145  {
7146  forPrimary = forPrimary?1:0;
7147 
7148  Debug.log("upload forPrimary=" + forPrimary);
7149 
7150  _fileUploadString = ""; //clear upload string
7151 
7152  var str = "";
7153 
7154  var el = document.getElementById("popUpDialog");
7155  if(!el)
7156  {
7157  el = document.createElement("div");
7158  el.setAttribute("id", "popUpDialog");
7159  }
7160  el.style.display = "none";
7161 
7162  //set position and size
7163  var w = 400;
7164  var h = 205;
7165  DesktopContent.setPopUpPosition(el,w /*w*/,h /*h*/);
7166 
7167  var str = "<a id='" +
7168  "popUpDialog" + //clear upload string on cancel!
7169  "-header' onclick='var el = document.getElementById(" +
7170  "\"popUpDialog\"); if(el) el.parentNode.removeChild(el); return false;'>Cancel</a><br><br>";
7171 
7172  str += "<div id='popUpDialog-div'>";
7173 
7174  str += "Please choose the file to upload which has the text content to place in the open source file:<br><br>" +
7175  _filePath[forPrimary] + "." + _fileExtension[forPrimary] +
7176  "<br><br>";
7177 
7178  str += "<center>";
7179 
7180  str += "<input type='file' id='popUpDialog-fileUpload' " +
7181  "accept='";
7182  for(var i=0;i<_ALLOWED_FILE_EXTENSIONS.length;++i)
7183  str += (i?", ":"") + "." + _ALLOWED_FILE_EXTENSIONS[i];
7184  str += "' enctype='multipart/form-data' />";
7185  str += "<br><br>";
7186 
7187  var onmouseupJS = "";
7188  onmouseupJS += "document.getElementById(\"popUpDialog-submitButton\").disabled = true;";
7189  onmouseupJS += "CodeEditor.editor.uploadTextFromFile(" + forPrimary + ");";
7190 
7191  str += "<input id='popUpDialog-submitButton' disabled type='button' onmouseup='" +
7192  onmouseupJS + "' " +
7193  "value='Upload File' title='" +
7194  "Upload the chosen file text content to\n" +
7195  _filePath[forPrimary] + "." + _fileExtension[forPrimary] +
7196  "'/>";
7197 
7198  el.innerHTML = str;
7199  document.body.appendChild(el); //add element to display div
7200  el.style.display = "block";
7201 
7202  document.getElementById('popUpDialog-fileUpload').addEventListener(
7203  'change', function(evt) {
7204  var files = evt.target.files;
7205  var file = files[0];
7206  var reader = new FileReader();
7207  reader.onload = function() {
7208  //store uploaded file and enable button
7209  _fileUploadString = this.result;
7210  Debug.log("_fileUploadString = " + _fileUploadString);
7211  document.getElementById('popUpDialog-submitButton').disabled = false;
7212 
7213  if(_STAND_ALONE)
7214  {
7215  var i=file.name.lastIndexOf('.');
7216  _filePath[forPrimary] = file.name.substr(0,i);
7217  _fileExtension[forPrimary] = file.name.substr(i+1);
7218  }
7219  }
7220  reader.readAsText(file);
7221  }, false);
7222  } //end upload()
7223 
7224  //=====================================================================================
7225  //uploadTextFromFile ~~
7226  this.uploadTextFromFile = function(forPrimary)
7227  {
7228  forPrimary = forPrimary?1:0;
7229 
7230  Debug.log("uploadTextFromFile forPrimary=" + forPrimary);
7231 
7232  //enable button so can retry if failure
7233  document.getElementById('popUpDialog-submitButton').disabled = false;
7234 
7235  Debug.log("uploadTextFromFile _fileUploadString = " + _fileUploadString);
7236 
7237 
7238  //do decor in timeout to show loading
7239  DesktopContent.showLoading(function()
7240  {
7241  try
7242  {
7243  //replace weird space characters (e.g. from emacs tab character two &#160's)
7244  // with spaces
7245  _fileUploadString = _fileUploadString.replace(new RegExp(
7246  String.fromCharCode(160),'g'),' ');
7247 
7248  var el = _eel[forPrimary];
7249  el.textContent = _fileUploadString;
7250  CodeEditor.editor.displayFileHeader(forPrimary);
7251  }
7252  catch(e)
7253  {
7254  Debug.log("There was an error uploading the text:[" + DesktopContent.getExceptionLineNumber(e) + "]: " + e,
7255  Debug.HIGH_PRIORITY);
7256  return;
7257  }
7258  Debug.log("Source upload complete! (You can use undo to go back) ",
7259  Debug.INFO_PRIORITY);
7260 
7261  //on succes remove popup
7262  var el = document.getElementById("popUpDialog");
7263  if(el) el.parentNode.removeChild(el);
7264 
7265  }); //end show loading
7266 
7267  } //end uploadTextFromFile()
7268 
7269 } //end create() CodeEditor instance
7270 
7271 
7272 
7273 
7274 
7275 
7276 
7277 
7278 
7279 
7280