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