From a8f2703091cb83530e3931829366f79027a1d207 Mon Sep 17 00:00:00 2001
From: Albert Graef <aggraef@gmail.com>
Date: Mon, 14 Nov 2016 18:19:54 +0100
Subject: [PATCH] Improve error handling of clipboard operations.

---
 pd/nw/pd_canvas.js | 14 +++++++-------
 pd/nw/pd_menus.js  |  2 ++
 pd/src/g_editor.c  | 48 +++++++++++++++++++++++++++++++++++++---------
 3 files changed, 48 insertions(+), 16 deletions(-)

diff --git a/pd/nw/pd_canvas.js b/pd/nw/pd_canvas.js
index 894a094bd..2f0b2b170 100644
--- a/pd/nw/pd_canvas.js
+++ b/pd/nw/pd_canvas.js
@@ -1039,14 +1039,15 @@ function canvas_paste_from_clipboard(name, clipboard_data)
         return;
     }
 
+    if (!might_be_a_pd_file(clipboard_data)) {
+	pdgui.post("paste error: clipboard doesn't appear to contain valid Pd code");
+        return;
+    }
+
     // Maybe we want a warning prompt here? Then uncomment the line below. I
     // disabled this for now, as the paste-from-clipboard command now has its
     // own menu option, so presumably the user knows what he's doing. -ag
-    if (!might_be_a_pd_file(clipboard_data)
-	//|| !permission_to_paste_from_external_clipboard()
-       ) {
-        return;
-    }
+    //if (!permission_to_paste_from_external_clipboard()) return;
 
     // clear the buffer
     pdgui.pdsend(name, "copyfromexternalbuffer");
@@ -1071,8 +1072,7 @@ function canvas_paste_from_clipboard(name, clipboard_data)
         }
     }
     // This signals to the engine that we're done filling the external buffer,
-    // so this might conceivably call some internal cleanup code. At present
-    // it doesn't do anything, though.
+    // that it can do necessary checks and call some internal cleanup code.
     pdgui.pdsend(name, "copyfromexternalbuffer");
     // Send a canvas "paste" message to Pd
     pdgui.pdsend(name, "paste");
diff --git a/pd/nw/pd_menus.js b/pd/nw/pd_menus.js
index d1fcf9118..2121a94d5 100644
--- a/pd/nw/pd_menus.js
+++ b/pd/nw/pd_menus.js
@@ -199,6 +199,8 @@ function create_menu(gui, type) {
     if (canvas_menu) {
         edit_menu.append(m.edit.paste_clipboard = new gui.MenuItem({
             label: l("menu.paste_clipboard"),
+            key: "v",
+            modifiers: cmd_or_ctrl + "+alt",
             tooltip: l("menu.paste_clipboard_tt")
         }));
         edit_menu.append(m.edit.duplicate = new gui.MenuItem({
diff --git a/pd/src/g_editor.c b/pd/src/g_editor.c
index 1d05bb26e..1a8f72b0f 100644
--- a/pd/src/g_editor.c
+++ b/pd/src/g_editor.c
@@ -5820,6 +5820,7 @@ int abort_when_pasting_from_external_buffer = 0;
 static void canvas_copyfromexternalbuffer(t_canvas *x, t_symbol *s,
     int ac, t_atom *av)
 {
+  static int level, line;
     if (!x->gl_editor)
         return;
 
@@ -5834,24 +5835,43 @@ static void canvas_copyfromexternalbuffer(t_canvas *x, t_symbol *s,
         copiedfont = 0;
         binbuf_free(copy_binbuf);
         copy_binbuf = binbuf_new();
+	line = level = 0;
     }
     else if (ac && copyfromexternalbuffer)
     {
+        int begin_patch = av[0].a_type == A_SYMBOL &&
+	  !strcmp(av[0].a_w.w_symbol->s_name, "#N");
+        int end_patch = av[0].a_type == A_SYMBOL &&
+	  !strcmp(av[0].a_w.w_symbol->s_name, "#X") &&
+	  av[1].a_type == A_SYMBOL &&
+	  !strcmp(av[1].a_w.w_symbol->s_name, "restore");
+	line++;
+	// Keep track of the nesting of (sub)patches. Improperly nested
+	// patches will make Pd crash and burn if we just paste them, so we
+	// rather report such conditions as errors instead.
+	if (end_patch && --level < 0) {
+	    post("paste error: "
+		 "unmatched end of subpatch at line %d",
+		 line);
+	    copyfromexternalbuffer = 0;
+	    binbuf_clear(copy_binbuf);
+	    return;
+	}
         //fprintf(stderr,"fill %d\n", ac);
-        if (av[0].a_type == A_SYMBOL &&
-            strcmp(av[0].a_w.w_symbol->s_name, "#N") ||
-            copyfromexternalbuffer != 1)
+        if (copyfromexternalbuffer != 1 || !begin_patch || ac != 7)
         {
+	    // not a patch header, just copy
+	    if (begin_patch) level++;
             binbuf_add(copy_binbuf, ac, av);
             binbuf_addsemi(copy_binbuf);
             copyfromexternalbuffer++;
         }
         else if (copyfromexternalbuffer == 1 &&
-                   av[0].a_type == A_SYMBOL &&
-                   !strcmp(av[0].a_w.w_symbol->s_name, "#N") && ac == 7)
+		 begin_patch && ac == 7)
         {
+	    // patch header, if the canvas is empty adjust window size and
+	    // position here...
             int check = 0;
-            //if the canvas is empty resize window size and position here...
             //fprintf(stderr,
             //    "copying canvas properties for copyfromexternalbuffer\n");
             if (av[2].a_type == A_FLOAT)
@@ -5881,9 +5901,11 @@ static void canvas_copyfromexternalbuffer(t_canvas *x, t_symbol *s,
             }
             if (check != 5)
             {
-                post("error copying: copyfromexternalbuffer: "
-                     "canvas info has invalid data\n");
+                post("paste error: "
+		     "canvas info has invalid data at line %d",
+		     line);
                 copyfromexternalbuffer = 0;
+		binbuf_clear(copy_binbuf);
             }
             else
             {
@@ -5894,7 +5916,15 @@ static void canvas_copyfromexternalbuffer(t_canvas *x, t_symbol *s,
     else if (!ac && copyfromexternalbuffer)
     {
         // here we can do things after the copying process has been completed.
-        // currently we don't need this.
+        // in particular, we use this to check whether there's an incomplete
+        // subpatch definition
+	if (level > 0) {
+	    post("paste error: "
+		 "unmatched beginning of subpatch at line %d",
+		 line);
+	    copyfromexternalbuffer = 0;
+	    binbuf_clear(copy_binbuf);
+	}
     }
 }
 
-- 
GitLab