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