diff --git a/pd/nw/pd_canvas.js b/pd/nw/pd_canvas.js
index a67240a1af63dc632464c50db75f2930480bbad4..7883faa3aea8930c1ad04f38c41bebd6aca84d38 100644
--- a/pd/nw/pd_canvas.js
+++ b/pd/nw/pd_canvas.js
@@ -53,6 +53,24 @@ var canvas_events = (function() {
         textbox = function () {
             return document.getElementById("new_object_textentry");
         },
+        add_events = function(context, events) {
+            // convenience routine for adding a bunch of events at once
+            var e;
+            for (e in events) {
+                if (events.hasOwnProperty(e)) {
+                    context["addEventListener"](e, events[e], false);
+                }
+            }
+        },
+        remove_events = function(context, events) {
+            // convenience routine for removing a bunch of events at once
+            var e;
+            for (e in events) {
+                if (events.hasOwnProperty(e)) {
+                    context["addEventListener"](e, events[e], false);
+                }
+            }
+        },
         find_scalar_draggable = function (elem) {
             var ret = elem;
             while (ret) {
@@ -306,7 +324,7 @@ var canvas_events = (function() {
                 /*if (evt.which == 2)
                 {
                     evt.stopPropagation();
-                    evt.preventDefault();                
+                    evt.preventDefault();
                 }*/
                 if (evt.type === "touchstart") {
                     if (target_is_popup(evt)) {
@@ -638,23 +656,24 @@ var canvas_events = (function() {
                 canvas_events[canvas_events.get_previous_state()]();
             },
             hscroll_mouseup: function(evt) {
-                document.getElementById("hscroll").style.setProperty("background-color", "rgba(0, 0, 0, 0.267)");
+                document.getElementById("hscroll").style
+                    .setProperty("background-color", "rgba(0, 0, 0, 0.267)");
                 document.getElementById("patchsvg").style.cursor = "default";
                 canvas_events[canvas_events.get_previous_state()]();
             },
             hscroll_mousemove: function(evt) {
                 if (evt.movementX != 0) {
                     //console.log("move: " + e.movementX);
-                    
+
                     var hscroll = document.getElementById("hscroll");
                     var svg_elem = document.getElementById("patchsvg");
-                    
+
                     var min_width = document.body.clientWidth + 3;
                     var width = svg_elem.getAttribute('width');
                     var xScrollSize;
-                    
+
                     xScrollSize = hscroll.offsetWidth;
-                  
+
                     var xTranslate = evt.movementX *
                         ((width - min_width)/(min_width - xScrollSize)) *
                         (evt.movementX > 0 ? 1 : 0.75);
@@ -669,23 +688,24 @@ var canvas_events = (function() {
                 }
             },
             vscroll_mouseup: function(evt) {
-                document.getElementById("vscroll").style.setProperty("background-color", "rgba(0, 0, 0, 0.267)");
+                document.getElementById("vscroll").style
+                    .setProperty("background-color", "rgba(0, 0, 0, 0.267)");
                 document.getElementById("patchsvg").style.cursor = "default";
                 canvas_events[canvas_events.get_previous_state()]();
             },
             vscroll_mousemove: function(evt) {
                 if (evt.movementY != 0) {
                     //console.log("move: " + e.movementY);
-                    
+
                     var vscroll = document.getElementById("vscroll");
                     var svg_elem = document.getElementById("patchsvg");
-                    
+
                     var min_height = document.body.clientHeight + 3;
                     var height = svg_elem.getAttribute('height');
                     var yScrollSize;
-                    
+
                     yScrollSize = vscroll.offsetHeight;
-                  
+
                     var yTranslate = evt.movementY *
                         ((height - min_height)/(min_height - yScrollSize)) *
                         (evt.movementY > 0 ? 2 : 1.5);
@@ -900,16 +920,17 @@ var canvas_events = (function() {
         },
         normal: function() {
             canvas_events.none();
-
-            document.addEventListener("mousemove", events.mousemove, false);
-            document.addEventListener("touchmove", events.mousemove, false);
-            document.addEventListener("keydown", events.keydown, false);
-            document.addEventListener("keypress", events.keypress, false);
-            document.addEventListener("keyup", events.keyup, false);
-            document.addEventListener("mousedown", events.mousedown, false);
-            document.addEventListener("touchstart", events.mousedown, false);
-            document.addEventListener("mouseup", events.mouseup, false);
-            document.addEventListener("touchend", events.mouseup, false);
+            add_events(document, {
+                "mousemove": events.mousemove,
+                "touchmove": events.mousemove,
+                "keydown": events.keydown,
+                "keypress": events.keypress,
+                "keyup": events.keyup,
+                "mousedown": events.mousedown,
+                "touchstart": events.mousedown,
+                "mouseup": events.mouseup,
+                "touchend": events.mouseup
+            });
             state = "normal";
             set_edit_menu_modals(true);
         },
@@ -919,10 +940,12 @@ var canvas_events = (function() {
             // the other "normal" events live, since behavior like editmode
             // selection still happens from the Pd engine.
             //this.none();
-            document.addEventListener("mousemove", events.scalar_draggable_mousemove, false);
-            document.addEventListener("touchmove", events.scalar_draggable_mousemove, false);
-            document.addEventListener("mouseup", events.scalar_draggable_mouseup, false);
-            document.addEventListener("touchend", events.scalar_draggable_mouseup, false);
+            add_events(document, {
+                "mousemove": events.scalar_draggable_mousemove,
+                "touchmove": events.scalar_draggable_mousemove,
+                "mouseup": events.scalar_draggable_mouseup,
+                "touchend": events.scalar_draggable_mouseup
+            });
         },
         iemgui_label_drag: function() {
             // This is a workaround for dragging iemgui labels. Resizing iemguis
@@ -932,77 +955,86 @@ var canvas_events = (function() {
             // rectangle extends past the bbox that it reports to Pd.
             // Unfortunately that means a lot of work to treat it separately.
             canvas_events.none();
-            document.addEventListener("mousemove",
-                events.iemgui_label_mousemove, false);
-            document.addEventListener("touchmove",
-                events.iemgui_label_mousemove, false);
-            document.addEventListener("mouseup",
-                events.iemgui_label_mouseup, false);
-            document.addEventListener("touchend",
-                events.iemgui_label_mouseup, false);
+            add_events(document, {
+                "mousemove": events.iemgui_label_mousemove,
+                "touchmove": events.iemgui_label_mousemove,
+                "mouseup": events.iemgui_label_mouseup,
+                "touchend": events.iemgui_label_mouseup
+            });
         },
         hscroll_drag: function() {
             canvas_events.none();
-            document.getElementById("hscroll").style.cssText += "background-color: rgba(0, 0, 0, 0.5) !important";
-            document.getElementById("patchsvg").style.cursor = "-webkit-grabbing";
-            document.addEventListener("mouseup", events.hscroll_mouseup, false);
-            document.addEventListener("mousemove", events.hscroll_mousemove, false);
+            document.getElementById("hscroll").style
+                .cssText += "background-color: rgba(0, 0, 0, 0.5) !important";
+            document.getElementById("patchsvg").style.cursor =
+                "-webkit-grabbing";
+            add_events(document, {
+                "mouseup": events.hscroll_mouseup,
+                "mousemove": events.hscroll_mousemove
+            });
         },
         vscroll_drag: function() {
             canvas_events.none();
-            document.getElementById("vscroll").style.cssText += "background-color: rgba(0, 0, 0, 0.5) !important";
-            document.getElementById("patchsvg").style.cursor = "-webkit-grabbing";
-            document.addEventListener("mouseup", events.vscroll_mouseup, false);
-            document.addEventListener("mousemove", events.vscroll_mousemove, false);
+            document.getElementById("vscroll").style
+                .cssText += "background-color: rgba(0, 0, 0, 0.5) !important";
+            document.getElementById("patchsvg").style.cursor =
+                "-webkit-grabbing";
+            add_events(document, {
+                "mouseup": events.vscroll_mouseup,
+                "mousemove": events.vscroll_mousemove
+            });
         },
         text: function() {
             canvas_events.none();
-
-            document.addEventListener("mousemove", events.text_mousemove, false);
-            document.addEventListener("keydown", events.text_keydown, false);
-            document.addEventListener("keypress", events.text_keypress, false);
-            document.addEventListener("keyup", events.text_keyup, false);
-            document.addEventListener("paste", events.text_paste, false);
-            document.addEventListener("mousedown", events.text_mousedown, false);
-            document.addEventListener("mouseup", events.text_mouseup, false);
+            add_events(document, {
+                "mousemove": events.text_mousemove,
+                "keydown": events.text_keydown,
+                "keypress": events.text_keypress,
+                "keyup": events.text_keyup,
+                "paste": events.text_paste,
+                "mousedown": events.text_mousedown,
+                "mouseup": events.text_mouseup
+            });
             state = "text";
             set_edit_menu_modals(false);
         },
         floating_text: function() {
             canvas_events.none();
             canvas_events.text();
-            document.removeEventListener("mousedown", events.text_mousedown, false);
-            document.removeEventListener("mouseup", events.text_mouseup, false);
-            document.removeEventListener("keypress", events.text_keypress, false);
-            document.removeEventListener("mousemove", events.text_mousemove, false);
-            document.addEventListener("click", events.floating_text_click, false);
-            document.addEventListener("keypress", events.floating_text_keypress, false);
-            document.addEventListener("mousemove", events.mousemove, false);
+            remove_events(document, {
+                "mousedown": events.text_mousedown,
+                "mouseup": events.text_mouseup,
+                "keypress": events.text_keypress,
+                "mousemove": events.text_mousemove
+            });
+            add_events(document, {
+                "click": events.floating_text_click,
+                "keypress": events.floating_text_keypress,
+                "mousemove": events.mousemove
+            });
             state = "floating_text";
             set_edit_menu_modals(false);
         },
         dropdown_menu: function() {
             canvas_events.none();
-          
-            document.addEventListener("mousedown", events.dropdown_menu_mousedown, false);
-            document.addEventListener("touchstart", events.dropdown_menu_mousedown, false);
-            
-            document.addEventListener("mouseup", events.dropdown_menu_mouseup, false);
-            document.addEventListener("touchend", events.dropdown_menu_mouseup, false);
-            
-            document.addEventListener("mousemove", events.dropdown_menu_mousemove, false);
-            document.addEventListener("touchmove", events.dropdown_menu_mousemove, false);
-            
-            document.addEventListener("keydown", events.dropdown_menu_keydown, false);
-            
-            document.addEventListener("keypress", events.dropdown_menu_keypress, false);
-            
+            add_events(document, {
+                "mousedown": events.dropdown_menu_mousedown,
+                "touchstart": events.dropdown_menu_mousedown,
+                "mouseup": events.dropdown_menu_mouseup,
+                "touchend": events.dropdown_menu_mouseup,
+                "mousemove": events.dropdown_menu_mousemove,
+                "touchmove": events.dropdown_menu_mousemove,
+                "keydown": events.dropdown_menu_keydown,
+                "keypress": events.dropdown_menu_keypress
+            });
             document.querySelector("#dropdown_list")
                 .addEventListener("wheel", events.dropdown_menu_wheel, false);
         },
         search: function() {
             canvas_events.none();
-            document.addEventListener("keydown", events.find_keydown, false);
+            add_events(document, {
+                "keydown": events.find_keydown
+            });
             state = "search";
         },
         update_scrollbars: function() {
@@ -1125,92 +1157,102 @@ var canvas_events = (function() {
                     console.log("tried to save something");
                 }, false
             );
-                       
+
             // add listener for the scrollbars
             document.getElementById("hscroll").
                 addEventListener("mousedown", canvas_events.hscroll_drag, false);
             document.getElementById("vscroll").
                 addEventListener("mousedown", canvas_events.vscroll_drag, false);
-            
-            // Whoa-- huge workaround! Right now we're getting
-            // the popup menu the way Pd Vanilla does it:
-            // 1) send a mouse(down) message to Pd
-            // 2) Pd checks whether it wants to send us a popup
-            // 3) Pd checks what popup menu items are available for obj/canvas
-            // 4) Pd sends GUI back a message with this info
-            // 5) GUI finally displays the popup
-            // 6) GUI keeps a _global_ _variable_ to remember the popup coords
-            // 7) User clicks an option in the popup
-            // 8) GUI sends a message back to Pd with the popup index and coords
-            // 9) Pd walks the linked list of objects to look up the object
-            // 10) Pd asks the object if it reacts to popups, and if it reacts
-            //     to the selected item in the popup
-            // 11) Pd sends a message to the relevant object for the item in
-            //     question
-            // nw.js has a nice little "contextmenu" event handler, but it's too
-            // difficult to use when passing between GUI and Pd (twice). In the
-            // future we should do all popup menu event handling in the GUI,
-            // and only pass a message to Pd when the user has clicked an item.
-            // For now, however, we just turn off its default behavior and
-            // control it with a bunch of complicated callbacks.
-            document.addEventListener("contextmenu", function(evt) {
-                console.log("got a context menu evt...");
-                evt.stopPropagation()
-                evt.preventDefault();
-            });
-
-            // Cut event
-            document.addEventListener("cut", function(evt) {
-                // This event doesn't currently get called because the
-                // nw menubar receives the event and doesn't propagate
-                // to the DOM. But if we add the ability to toggle menubar
-                // display, we might need to rely on this listener.
-                pdgui.pdsend(name, "cut");
-            });
-
-            // Copy event
-            document.addEventListener("copy", function(evt) {
-                // On OSX, this event gets triggered when we're editing
-                // inside an object/message box. So we only forward the
-                // copy message to Pd if we're in a "normal" canvas state
-                if (canvas_events.get_state() === "normal") {
-                    pdgui.pdsend(name, "copy");
-                }
-            });
 
-            // Listen to paste event
-            // XXXTODO: Not sure whether this is even needed any more, as the
-            // paste-from-clipboard functionality has been moved to its own menu
-            // option. So this code may possibly be removed in the future. -ag
-            document.addEventListener("paste", function(evt) {
-                if (canvas_events.get_state() !== "normal") {
-                    return;
-                }
-                // Send a canvas "paste" message to Pd
-                pdgui.pdsend(name, "paste");
-            });
-
-            // MouseWheel event for zooming
-            document.addEventListener("wheel", function(evt) {
-                var d = { deltaX: 0, deltaY: 0, deltaZ: 0 };
-                Object.keys(d).forEach(function(key) {
-                    if (evt[key] < 0) {
-                        d[key] = -1;
-                    } else if (evt[key] > 0) {
-                        d[key] = 1;
-                    } else {
-                        d[key] = 0;
+            add_events(document, {
+                "contextmenu": function(evt) {
+                    // Whoa-- huge workaround! Right now we're getting
+                    // the popup menu the way Pd Vanilla does it:
+                    // 1) send a mouse(down) message to Pd
+                    // 2) Pd checks whether it wants to send us a popup
+                    // 3) Pd checks what popup menu items are available for
+                    //    obj/canvas
+                    // 4) Pd sends GUI back a message with this info
+                    // 5) GUI finally displays the popup
+                    // 6) GUI keeps a _global_ _variable_ to remember the popup
+                    //    coords
+                    // 7) User clicks an option in the popup
+                    // 8) GUI sends a message back to Pd with the popup index
+                    //    and coords
+                    // 9) Pd walks the linked list of objects to look up the
+                    //    object
+                    // 10) Pd asks the object if it reacts to popups, and if it
+                    //     reacts to the selected item in the popup
+                    // 11) Pd sends a message to the relevant object for the
+                    //     item in question
+                    // nw.js has a nice little "contextmenu" event handler, but
+                    // it's too difficult to use when passing between GUI and
+                    // Pd (twice). In the future we should do all popup menu
+                    // event handling in the GUI, and only pass a message to Pd
+                    // when the user has clicked an item.
+                    // For now, however, we just turn off its default behavior
+                    // and control it with a bunch of complicated callbacks.
+
+                    console.log("got a context menu evt...");
+                    evt.stopPropagation()
+                    evt.preventDefault();
+                },
+                "scroll": function() {
+                    // map onscroll event
+                    pdgui.gui_update_scrollbars(name);
+                },
+                "cut": function(evt) {
+                    // Cut event
+                    // This event doesn't currently get called because the
+                    // nw menubar receives the event and doesn't propagate
+                    // to the DOM. But if we add the ability to toggle menubar
+                    // display, we might need to rely on this listener.
+                    pdgui.pdsend(name, "cut");
+                },
+                "copy": function(evt) {
+                    // Copy event
+                    // On OSX, this event gets triggered when we're editing
+                    // inside an object/message box. So we only forward the
+                    // copy message to Pd if we're in a "normal" canvas state
+                    if (canvas_events.get_state() === "normal") {
+                        pdgui.pdsend(name, "copy");
                     }
-                });
-                if (pdgui.cmd_or_ctrl_key(evt)) {
-                    // scroll up for zoom-in, down for zoom-out
-                    nw_window_zoom(name, -d.deltaY);
+                },
+                "paste": function(evt) {
+                    // Listen to paste event
+                    // XXXTODO: Not sure whether this is even needed any more,
+                    // as the paste-from-clipboard functionality has been moved
+                    // to its own menu option. So this code may possibly be
+                    // removed in the future. -ag
+                    if (canvas_events.get_state() !== "normal") {
+                        return;
+                    }
+                    // Send a canvas "paste" message to Pd
+                    pdgui.pdsend(name, "paste");
+                },
+                "wheel": function(evt) {
+                    // MouseWheel event for zooming
+                    var d = { deltaX: 0, deltaY: 0, deltaZ: 0 };
+                    Object.keys(d).forEach(function(key) {
+                        if (evt[key] < 0) {
+                            d[key] = -1;
+                        } else if (evt[key] > 0) {
+                            d[key] = 1;
+                        } else {
+                            d[key] = 0;
+                        }
+                    });
+                    if (pdgui.cmd_or_ctrl_key(evt)) {
+                        // scroll up for zoom-in, down for zoom-out
+                        nw_window_zoom(name, -d.deltaY);
+                    }
+                    // Send a message on to Pd for the [mousewheel] legacy
+                    // object (in the future we can refcount to prevent
+                    // forwarding these messages when there's no extant
+                    // receiver)
+                    pdgui.pdsend(name, "legacy_mousewheel",
+                        d.deltaX, d.deltaY, d.deltaZ);
                 }
-                // Send a message on to Pd for the [mousewheel] legacy object
-                // (in the future we can refcount to prevent forwarding
-                // these messages when there's no extant receiver)
-                pdgui.pdsend(name, "legacy_mousewheel",
-                    d.deltaX, d.deltaY, d.deltaZ);
             });
 
             // The following is commented out because we have to set the
@@ -1249,14 +1291,16 @@ var canvas_events = (function() {
             );
 
             // disable drag and drop for the time being
-            window.addEventListener("dragover", function (evt) {
-                evt.preventDefault();
-            }, false);
-            window.addEventListener("drop", function (evt) {
-                evt.preventDefault();
-            }, false);
+            add_events(window, {
+                "dragover": function (evt) {
+                    evt.preventDefault();
+                },
+                "drop": function (evt) {
+                    evt.preventDefault();
+                }
+            });
 
-            // Add placeholder text... this all needs to be collected into an 
+            // Add placeholder text... this all needs to be collected into an
             // add_events function similiar to the one in index.js
             document.querySelector("#canvas_find_text").placeholder =
                 l("canvas.find.placeholder");
@@ -1284,7 +1328,7 @@ var canvas_events = (function() {
             });
             gui.Window.get().on("focus", function() {
                 nw_window_focus_callback(name);
-            });            
+            });
             gui.Window.get().on("blur", function() {
                 nw_window_blur_callback(name);
             });
@@ -1293,12 +1337,7 @@ var canvas_events = (function() {
                 pdgui.pdsend(name, "setbounds", x, y,
                     x + w.width, y + w.height);
             });
-            
-            // map onscroll event
-            document.addEventListener("scroll", function() {
-                pdgui.gui_update_scrollbars(name);
-            });
-            
+
             // set minimum window size
             gui.Window.get().setMinimumSize(150, 100);
         }
@@ -1628,11 +1667,11 @@ function nw_create_patch_window_menus(gui, w, name) {
     minit(m.edit.paste_clipboard, {
         enabled: true,
         click: function () {
-	    var clipboard = nw.Clipboard.get();
-	    var text = clipboard.get('text');
-	    //pdgui.post("** paste from clipboard: "+text);
-	    canvas_events.paste_from_pd_file(name, text);
-	}
+            var clipboard = nw.Clipboard.get();
+            var text = clipboard.get('text');
+            //pdgui.post("** paste from clipboard: "+text);
+            canvas_events.paste_from_pd_file(name, text);
+        }
     });
     minit(m.edit.duplicate, {
         enabled: true,
@@ -2136,7 +2175,7 @@ function init_menu_font_size(size) {
         case 36:
             m.font.s36.checked = true;
             break;
-    } 
+    }
 }
 
 // ico@vt.edu 2020-08-24: this is called when the window is finally
diff --git a/pd/nw/pdgui.js b/pd/nw/pdgui.js
index ed52ab0a6d4f56bea3e3fd466530d613219a63d1..dd8d0bb349d523196f6e5797decdad137e47a87d 100644
--- a/pd/nw/pdgui.js
+++ b/pd/nw/pdgui.js
@@ -122,10 +122,10 @@ var index_manif = new Set();
 
 function index_entry_esc(s) {
     if (s) {
-	var t = s.replace(/\\/g, "\\\\").replace(/:/g, "\\:");
-	return t.replace(/(?:\r\n|\r|\n)/g, "\\n");
+        var t = s.replace(/\\/g, "\\\\").replace(/:/g, "\\:");
+        return t.replace(/(?:\r\n|\r|\n)/g, "\\n");
     } else {
-	return "";
+        return "";
     }
 }
 
@@ -151,7 +151,7 @@ function add_doc_to_index(filename, data) {
         title = title.slice(0, -5);
     }
     index_cache[index_cache.length] = [filename, title, keywords, desc]
-	.map(index_entry_esc).join(":");
+        .map(index_entry_esc).join(":");
     var d = path.dirname(filename);
     index_manif.add(d);
     // Also add the parent directory to catch additions of siblings.
@@ -200,32 +200,32 @@ function finish_index() {
     index_done = true;
     var have_cache = index_cache.length > 0;
     try {
-	// write the index cache if we have one
-	if (have_cache) {
-	    var a = new Array();
-	    index_manif.forEach(function(x) {
-		var st = fs.statSync(x);
-		a[a.length] = index_entry_esc(x) + ":" + st.mtimeMs;
-	    });
-	    a.sort();
-	    // Make sure that the target dir exists:
-	    try {
-		fs.mkdirSync(expand_tilde(path.dirname(cache_name)));
-	    } catch (err) {
-		//console.log(err);
-	    }
-	    fs.writeFileSync(expand_tilde(cache_name),
-			     index_cache.join("\n"), {mode: 0o644});
-	    // also write a manifest with the timestamps of all directories:
-	    fs.writeFileSync(expand_tilde(stamps_name),
-			     a.join("\n"), {mode: 0o644});
-	}
+        // write the index cache if we have one
+        if (have_cache) {
+            var a = new Array();
+            index_manif.forEach(function(x) {
+            var st = fs.statSync(x);
+                a[a.length] = index_entry_esc(x) + ":" + st.mtimeMs;
+            });
+            a.sort();
+            // Make sure that the target dir exists:
+            try {
+                fs.mkdirSync(expand_tilde(path.dirname(cache_name)));
+            } catch (err) {
+                //console.log(err);
+            }
+            fs.writeFileSync(expand_tilde(cache_name),
+                index_cache.join("\n"), {mode: 0o644});
+            // also write a manifest with the timestamps of all directories:
+            fs.writeFileSync(expand_tilde(stamps_name),
+                a.join("\n"), {mode: 0o644});
+        }
     } catch (err) {
-	console.log(err);
+        console.log(err);
     }
     var t = new Date().getTime() / 1000;
     post("finished " + (have_cache?"building":"loading") + " help index (" +
-	 (t-index_start_time).toFixed(2) + " secs)");
+        (t-index_start_time).toFixed(2) + " secs)");
 }
 
 // AG: pilfered from https://stackoverflow.com/questions/21077670
@@ -241,23 +241,23 @@ function check_timestamps(manif)
 {
     manif = manif.split('\n');
     for (var j = 0, l = manif.length; j < l; j++) {
-	if (manif[j]) {
-	    var e = manif[j].replace(/\\:/g, "\x1c").split(':')
-		.map(x => x
-		     .replace(/\x1c/g, ":")
-		     .replace(/\\n/g, "\n")
-		     .replace(/\\\\/g, "\\"));
-	    var dirname = e[0] ? e[0] : null;
-	    var stamp = e[1] ? parseFloat(e[1]) : 0.0;
-	    try {
-		var st = fs.statSync(dirname);
-		if (st.mtimeMs > stamp) {
-		    return false;
-		}
-	    } catch (err) {
-		return false;
-	    }
-	}
+        if (manif[j]) {
+            var e = manif[j].replace(/\\:/g, "\x1c").split(':')
+                .map(x => x
+                    .replace(/\x1c/g, ":")
+                    .replace(/\\n/g, "\n")
+                    .replace(/\\\\/g, "\\"));
+            var dirname = e[0] ? e[0] : null;
+            var stamp = e[1] ? parseFloat(e[1]) : 0.0;
+            try {
+                var st = fs.statSync(dirname);
+                if (st.mtimeMs > stamp) {
+                    return false;
+                }
+            } catch (err) {
+                return false;
+            }
+        }
     }
     return true;
 }
@@ -292,43 +292,43 @@ function make_index() {
     index_start_time = new Date().getTime() / 1000;
     var idx, manif;
     try {
-	// test for index cache and manifest
-	idx = fs.readFileSync
-	(expand_tilde(cache_name), 'utf8');
-	manif = fs.readFileSync
-	(expand_tilde(stamps_name), 'utf8');
+        // test for index cache and manifest
+        idx = fs.readFileSync
+        (expand_tilde(cache_name), 'utf8');
+        manif = fs.readFileSync
+        (expand_tilde(stamps_name), 'utf8');
     } catch (err) {
-	//console.log(err);
+        //console.log(err);
     }
     if (idx && manif && check_timestamps(manif)) {
-	// index cache is present and up-to-date, load it
-	post("loading cached help index from " + cache_name);
-	idx = idx.split('\n');
-	for (var j = 0, l = idx.length; j < l; j++) {
-	    if (idx[j]) {
-		var e = idx[j].replace(/\\:/g, "\x1c").split(':')
-		    .map(x => x
-			 .replace(/\x1c/g, ":")
-			 .replace(/\\n/g, "\n")
-			 .replace(/\\\\/g, "\\"));
-		var filename = e[0] ? e[0] : null;
-		var title = e[1] ? e[1] : null;
-		var keywords = e[2] ? e[2] : null;
-		var descr = e[3] ? e[3] : null;
-		index.addDoc({
-		    "id": filename,
-		    "title": title,
-		    "keywords": keywords,
-		    "description": descr
-		});
-	    }
-	}
+        // index cache is present and up-to-date, load it
+        post("loading cached help index from " + cache_name);
+        idx = idx.split('\n');
+        for (var j = 0, l = idx.length; j < l; j++) {
+            if (idx[j]) {
+                var e = idx[j].replace(/\\:/g, "\x1c").split(':')
+                    .map(x => x
+                        .replace(/\x1c/g, ":")
+                        .replace(/\\n/g, "\n")
+                        .replace(/\\\\/g, "\\"));
+                var filename = e[0] ? e[0] : null;
+                var title = e[1] ? e[1] : null;
+                var keywords = e[2] ? e[2] : null;
+                var descr = e[3] ? e[3] : null;
+                index.addDoc({
+                    "id": filename,
+                    "title": title,
+                    "keywords": keywords,
+                    "description": descr
+                });
+            }
+        }
         finish_index();
     } else {
-	// no index cache, or it is out of date, so (re)build it now, and
-	// save the new cache along the way
-	post("building help index in " + doc_path);
-	dive(doc_path, read_file, browser_path?make_index_cont:finish_index);
+        // no index cache, or it is out of date, so (re)build it now, and
+        // save the new cache along the way
+        post("building help index in " + doc_path);
+        dive(doc_path, read_file, browser_path?make_index_cont:finish_index);
     }
     pdsend("pd gui-busy 0");
 }
@@ -361,10 +361,10 @@ function rebuild_index()
     index = init_elasticlunr();
     index_started = index_done = false;
     try {
-	fs.unlink(expand_tilde(cache_name));
-	fs.unlink(expand_tilde(stamps_name));
+        fs.unlink(expand_tilde(cache_name));
+        fs.unlink(expand_tilde(stamps_name));
     } catch (err) {
-	//console.log(err);
+        //console.log(err);
     }
 }
 
@@ -375,15 +375,15 @@ function update_browser(doc_flag, path_flag)
     doc_flag = doc_flag?1:0;
     path_flag = path_flag?1:0;
     if (browser_doc !== doc_flag) {
-	browser_doc = doc_flag;
-	changed = true;
+        browser_doc = doc_flag;
+        changed = true;
     }
     if (browser_path !== path_flag) {
-	browser_path = path_flag;
-	changed = true;
+        browser_path = path_flag;
+        changed = true;
     }
     if (changed) {
-	rebuild_index();
+        rebuild_index();
     }
 }
 
@@ -1442,13 +1442,13 @@ function update_grid(grid) {
     // option in the gui prefs changes.
     var bg = grid != 0 ? gui_editmode_svg_background : "none";
     for (var cid in patchwin) {
-	gui(cid).get_elem("patchsvg", function(patchsvg, w) {
+        gui(cid).get_elem("patchsvg", function(patchsvg, w) {
             var editmode = patchsvg.classList.contains("editmode");
             if (editmode) {
                 patchwin[cid].window.document.body.style.setProperty
                 ("background-image", bg);
             }
-	});
+        });
     }
     // Also update the showgrid flags.
     set_showgrid(grid);
@@ -1725,7 +1725,7 @@ var scroll = {},
 
 var set_showgrid = function(grid) {
     for (var cid in showgrid) {
-	showgrid[cid] = grid;
+        showgrid[cid] = grid;
     }
 }
 
@@ -1828,7 +1828,7 @@ function canvas_sendkey(cid, state, evt, char_code, repeat) {
     var shift = evt.shiftKey ? 1 : 0,
         repeat_number = repeat ? 1 : 0;
     //post("canvas_sendkey state=" + state + " evt=" + evt +
-    //	" char_code=<" + char_code + "> repeat=" + repeat);
+    // " char_code=<" + char_code + "> repeat=" + repeat);
     pdsend(cid, "key", state, char_code, shift, 1, repeat_number);
 }
 
@@ -2576,29 +2576,32 @@ function message_border_points(width, height) {
 // called from pd_canvas.js text events to deal with 
 // the drawing of the msg box
 function gui_message_update_textarea_border(elem, init_width) {
-	if (elem.classList.contains("msg")) {
-		if (init_width) {
-			var i, ncols = 0,
-			    text = elem.innerHTML,
-			    textByLine = text.split(/\r*\n/);
-			for (i = 0; i < textByLine.length; i++) {
-				if (textByLine[i].length > ncols) {
-					ncols = textByLine[i].length;
-				}
-			}
-			configure_item(elem, {
-	            cols: ncols
-        	});
-        	gui_gobj_erase_io(elem.getAttribute("cid"), elem.getAttribute("tag"));
-		}
-
-		gui_message_redraw_border(
-			elem.getAttribute("cid"),
-			elem.getAttribute("tag"),
-			parseInt(elem.offsetWidth / elem.getAttribute("font_width")) * elem.getAttribute("font_width") + 4,
-			parseInt(elem.offsetHeight / elem.getAttribute("font_height")) * elem.getAttribute("font_height") + 4
-			);
-	}
+    if (elem.classList.contains("msg")) {
+        if (init_width) {
+            var i, ncols = 0,
+                text = elem.innerHTML,
+                textByLine = text.split(/\r*\n/);
+            for (i = 0; i < textByLine.length; i++) {
+                if (textByLine[i].length > ncols) {
+                    ncols = textByLine[i].length;
+                }
+            }
+            configure_item(elem, {
+                cols: ncols
+            });
+            gui_gobj_erase_io(elem.getAttribute("cid"),
+                elem.getAttribute("tag"));
+        }
+
+        gui_message_redraw_border(
+            elem.getAttribute("cid"),
+            elem.getAttribute("tag"),
+            parseInt(elem.offsetWidth / elem.getAttribute("font_width"))
+                * elem.getAttribute("font_width") + 4,
+            parseInt(elem.offsetHeight / elem.getAttribute("font_height"))
+                * elem.getAttribute("font_height") + 4
+        );
+    }
 }
 
 exports.gui_message_update_textarea_border = gui_message_update_textarea_border;
@@ -3331,8 +3334,8 @@ function gui_numbox_draw_text(cid,tag,text,font_size,color,xpos,ypos,basex,basey
     // below. But it works for most font sizes.
     gui(cid).get_gobj(tag)
     .append(function(frag, w) {
-    	//post("ypos=" + ypos + " int=" + Math.floor(ypos));
-    	//ypos = Math.floor(ypos);
+        //post("ypos=" + ypos + " int=" + Math.floor(ypos));
+        //ypos = Math.floor(ypos);
         var svg_text = create_item(cid, "text", {
             transform: "translate(" +
                         (xpos - basex) + "," +
@@ -5890,23 +5893,23 @@ function gui_font_dialog_change_size(did, font_size) {
 
 function gui_menu_font_change_size(canvas, newsize) {
     pdsend(canvas, "menufont", newsize);
-    	// ico@vt.edu 2020-08-24: changed to use submenu
-    	// this was the following
-        /*+document.querySelector('input[name="font_size"]:checked').value,
-        current_size,
-        100,
-        0 // "$noundo" from pd.tk-- not sure what it does*/
+    // ico@vt.edu 2020-08-24: changed to use submenu
+    // this was the following
+    //+document.querySelector('input[name="font_size"]:checked').value,
+    //current_size,
+    //100,
+    //0
 }
 
 exports.gui_menu_font_change_size = gui_menu_font_change_size;
 
 function gui_menu_font_set_initial_size(cid, size) {
-	//post("gui_menu_font_set_initial_size " + cid + " " + size);
-	gui(cid).get_nw_window(function(nw_win) {
-		if (cid !== "nobody") {
-    		nw_win.window.init_menu_font_size(size);
-    		//post("this should work");
-    	}
+    //post("gui_menu_font_set_initial_size " + cid + " " + size);
+    gui(cid).get_nw_window(function(nw_win) {
+        if (cid !== "nobody") {
+            nw_win.window.init_menu_font_size(size);
+            //post("this should work");
+        }
     });
 }
 
@@ -5926,7 +5929,7 @@ function gui_array_new(did, count) {
 }
 
 function gui_canvas_dialog(did, attr_arrays) {
-	//post("gui_canvas_dialog");
+    //post("gui_canvas_dialog");
     var i, j, inner_array, prop;
     // Convert array of arrays to an array of objects
     for (i = 0; i < attr_arrays.length; i++) {
@@ -5940,8 +5943,8 @@ function gui_canvas_dialog(did, attr_arrays) {
     var has_array = (attr_arrays.length > 1 ? 1 : 0);
     /*
     post("array.length=" + attr_arrays.length + " has_array=" + has_array +" width=" +
-    	(230 - (8 * has_array)) + " height=" +
-    	(attr_arrays.length > 1 ? 494-25+(attr_arrays.length > 2 ? 38 : 0) : 392-25));
+    (230 - (8 * has_array)) + " height=" +
+    (attr_arrays.length > 1 ? 494-25+(attr_arrays.length > 2 ? 38 : 0) : 392-25));
     */
     dialogwin[did] = create_window(did, "canvas",
         // ico@vt.edu: property dialog size is larger when one has
@@ -6308,7 +6311,7 @@ function textarea_font_size_to_index(font_size) {
 }
 
 function textarea_line_height_kludge(font_size, zoom) {
-	return textarea_font_height_array_kludge
+    return textarea_font_height_array_kludge
         [textarea_font_size_to_index(font_size)][zoom+7]+"%";
 }
 
@@ -6318,11 +6321,11 @@ function textarea_y_offset_kludge(font_size, zoom) {
 }
 
 function textarea_x_offset_kludge(font_size, zoom) {
-	if (font_size === 36) {
-		return -2;
-	} else {
-		return -0.5;
-	}
+    if (font_size === 36) {
+        return -2;
+    } else {
+        return -0.5;
+    }
 }
 
 function textarea_msg_y_offset_kludge(zoom) {
@@ -6365,17 +6368,17 @@ function gui_textarea(cid, tag, type, x, y, width_spec, height_spec, text,
         //     2) extend this to adjust patch cords as things are being edited, and
         //     3) extend this to all text objects.
         if (type === "msg") {
-        	// Message approach
-	        var i, nlets = patchwin[cid].window.document
-	        	.getElementById(tag+"gobj").querySelectorAll(".xlet_control");
-	        for (i = 0; i < nlets.length; i++) {
-	        	nlets[i].style.setProperty("visibility", "hidden");        	
-	        }
-	        gui(cid).get_gobj(tag).q(".box_text", { visibility: "hidden" });
-	    } else {
-	    	// Anything else but message
-			configure_item(gobj, { visibility: "hidden" });
-	    }
+            // Message approach
+            var i, nlets = patchwin[cid].window.document
+                .getElementById(tag+"gobj").querySelectorAll(".xlet_control");
+            for (i = 0; i < nlets.length; i++) {
+                nlets[i].style.setProperty("visibility", "hidden");
+            }
+            gui(cid).get_gobj(tag).q(".box_text", { visibility: "hidden" });
+        } else {
+        // Anything else but message
+            configure_item(gobj, { visibility: "hidden" });
+        }
 
         p = patchwin[cid].window.document.createElement("p");
         configure_item(p, {
@@ -6413,7 +6416,7 @@ function gui_textarea(cid, tag, type, x, y, width_spec, height_spec, text,
         p.style.setProperty("font-size",
             pd_fontsize_to_gui_fontsize(font_size) + "px");
         p.style.setProperty("line-height",
-        	textarea_line_height_kludge(font_size, zoom));
+            textarea_line_height_kludge(font_size, zoom));
             //pd_fontsize_to_gui_fontsize(font_size) + 1 + "px");
         p.style.setProperty("transform", "translate(0px, " + 
             (zoom > 0 ? 0.5 : 0) + "px)");
@@ -6450,7 +6453,7 @@ function gui_textarea(cid, tag, type, x, y, width_spec, height_spec, text,
             //shove_svg_background_data_into_css(patchwin[cid].window,
             //    parseInt(get_gobj(cid, tag).getBoundingClientRect().height /
             //        (parseInt(p.style.lineHeight) / 100 * font_size)));
-        	gui_message_update_textarea_border(p,1);
+            gui_message_update_textarea_border(p,1);
         }
         p.focus();
         select_text(cid, p, sel_start, sel_end);
@@ -6475,9 +6478,9 @@ function gui_textarea(cid, tag, type, x, y, width_spec, height_spec, text,
 
         // MSG approach
         var i, nlets = patchwin[cid].window.document
-        	.getElementById(tag+"gobj").querySelectorAll(".xlet_control");
+            .getElementById(tag+"gobj").querySelectorAll(".xlet_control");
         for (i = 0; i < nlets.length; i++) {
-        	nlets[i].style.setProperty("visibility", "visible");        	
+            nlets[i].style.setProperty("visibility", "visible");
         }
         gui(cid).get_gobj(tag).q(".box_text", { visibility: "visible" });