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