gui: Update how the Run menu works in the syntax window.
authorBen Pfaff <blp@cs.stanford.edu>
Mon, 29 May 2023 20:23:07 +0000 (13:23 -0700)
committerBen Pfaff <blp@cs.stanford.edu>
Mon, 29 May 2023 20:23:18 +0000 (13:23 -0700)
Until now, the Run menu had both "Selection", which ran the current
selection, and "Current line", which ran the current line.  This commit
replaces that by a single menu item "Selection", which runs the current
command if there is no selection and all the selected or partially selected
commands if there is a selection.  This behavior matches SPSS.

Requested by Matthias Faeth.

src/ui/gui/psppire-syntax-window.c
src/ui/gui/syntax-editor.ui

index 3da9bf58f7b57722a206e55164252008fb3932c4..4f244a82062ef7fcb58de16adbecf2633c555ce5 100644 (file)
@@ -22,6 +22,7 @@
 
 #include <gtksourceview/gtksource.h>
 
+#include "language/lexer/command-segmenter.h"
 #include "language/lexer/lexer.h"
 #include "libpspp/encoding-guesser.h"
 #include "libpspp/i18n.h"
@@ -457,70 +458,137 @@ on_run_all (PsppireSyntaxWindow *se)
   editor_execute_syntax (se, begin, end);
 }
 
-/* Parse and execute the currently selected text */
-static void
-on_run_selection (PsppireSyntaxWindow *se)
+static bool
+overlaps (int a[2], int b[2])
 {
-  GtkTextIter begin, end;
-
-  if (gtk_text_buffer_get_selection_bounds (GTK_TEXT_BUFFER (se->buffer), &begin, &end))
-    editor_execute_syntax (se, begin, end);
+  return (b[0] <= a[0] && a[0] < b[1]) || (a[0] <= b[0] && b[0] < a[1]);
 }
 
-
-/* Parse and execute the from the current line, to the end of the
-   buffer */
+/* Parse and execute the commands that overlap [START, END). */
 static void
-on_run_to_end (PsppireSyntaxWindow *se)
+run_commands (PsppireSyntaxWindow *se, GtkTextIter start, GtkTextIter end)
 {
-  GtkTextIter begin, end;
-  GtkTextIter here;
-  gint line;
+  GtkTextBuffer *buf = GTK_TEXT_BUFFER (se->buffer);
+
+  /* Convert the iterator range into a line number range.  Both ranges are
+     half-open (they exclude the end), but it's OK for them to be empty. */
+  int in_lines[2] = {
+    gtk_text_iter_get_line (&start),
+    gtk_text_iter_get_line (&end),
+  };
+  if (in_lines[0] == in_lines[1] || gtk_text_iter_get_line_index (&end) > 0)
+    in_lines[1]++;
+
+  /* These are the lines that we're going to run.  */
+  int run_lines[2] = { -1, -1 };
+
+  /* Iterate through all the text in the buffer until we find a command that
+     spans the line we're on. */
+  struct command_segmenter *cs = command_segmenter_create (se->syntax_mode);
+  GtkTextIter begin;
+  gtk_text_buffer_get_start_iter (buf, &begin);
+  while (!gtk_text_iter_is_end (&begin))
+    {
+      GtkTextIter next = begin;
+      gtk_text_iter_forward_line (&next);
 
-  /* Get the current line */
-  gtk_text_buffer_get_iter_at_mark (GTK_TEXT_BUFFER (se->buffer),
-                                   &here,
-                                   gtk_text_buffer_get_insert (GTK_TEXT_BUFFER (se->buffer))
-                               );
+      gchar *text = gtk_text_iter_get_text (&begin, &next);
+      command_segmenter_push (cs, text, strlen (text));
+      g_free (text);
 
-  line = gtk_text_iter_get_line (&here) ;
+      if (gtk_text_iter_is_end (&next))
+        command_segmenter_eof (cs);
 
-  /* Now set begin and end to the start of this line, and end of buffer
-     respectively */
-  gtk_text_buffer_get_iter_at_line (GTK_TEXT_BUFFER (se->buffer), &begin, line);
-  gtk_text_buffer_get_iter_at_line (GTK_TEXT_BUFFER (se->buffer), &end, -1);
+      int cmd_lines[2];
+      while (command_segmenter_get (cs, cmd_lines))
+        {
+          if (overlaps (cmd_lines, in_lines))
+            {
+              /* This command's lines overlap with the lines we want to run.
+                 If we don't have any lines yet, take this command's lines;
+                 otherwise, extend the lines we have with this command's
+                 lines. */
+              if (run_lines[0] == -1)
+                {
+                  run_lines[0] = cmd_lines[0];
+                  run_lines[1] = cmd_lines[1];
+                }
+              else
+                run_lines[1] = cmd_lines[1];
+            }
+          else if (cmd_lines[0] >= in_lines[1])
+            {
+              /* We're moved past the lines that could possibly overlap with
+                 those that we want to run.
+
+                 If we don't have anything to run, we need to make some guess.
+                 If we were just given a single position, then probably it
+                 makes sense to run the next command.  Otherwise, we were given
+                 a nonempty selection that didn't contain any commands, and it
+                 seems reasonable to not run any. */
+              if (run_lines[0] == -1 && gtk_text_iter_equal (&start, &end))
+                {
+                  run_lines[0] = cmd_lines[0];
+                  run_lines[1] = cmd_lines[1];
+                }
+              break;
+            }
+        }
 
-  editor_execute_syntax (se, begin, end);
-}
+      begin = next;
+    }
+  command_segmenter_destroy (cs);
+
+  if (run_lines[0] != -1)
+    {
+      GtkTextIter begin, end;
+      gtk_text_buffer_get_iter_at_line (buf, &begin, run_lines[0]);
+      gtk_text_buffer_get_iter_at_line (buf, &end, run_lines[1]);
 
+      editor_execute_syntax (se, begin, end);
+    }
+}
 
+static GtkTextIter
+get_iter_for_cursor (PsppireSyntaxWindow *se)
+{
+  GtkTextBuffer *buf = GTK_TEXT_BUFFER (se->buffer);
+  GtkTextIter iter;
+  gtk_text_buffer_get_iter_at_mark (
+    buf, &iter, gtk_text_buffer_get_insert (buf));
+  return iter;
+}
 
-/* Parse and execute the current line */
+/* Parse and execute the currently selected syntax, if there is any, and
+   otherwise the command that the cursor is in. */
 static void
-on_run_current_line (PsppireSyntaxWindow *se)
+on_run_selection (PsppireSyntaxWindow *se)
 {
-  GtkTextIter begin, end;
-  GtkTextIter here;
-  gint line;
+  GtkTextBuffer *buf = GTK_TEXT_BUFFER (se->buffer);
 
-  /* Get the current line */
-  gtk_text_buffer_get_iter_at_mark (GTK_TEXT_BUFFER (se->buffer),
-                                   &here,
-                                   gtk_text_buffer_get_insert (GTK_TEXT_BUFFER (se->buffer))
-                               );
+  GtkTextIter begin, end;
+  if (gtk_text_buffer_get_selection_bounds (buf, &begin, &end))
+    run_commands (se, begin, end);
+  else
+    {
+      GtkTextIter iter = get_iter_for_cursor (se);
+      run_commands (se, iter, iter);
+    }
+}
 
-  line = gtk_text_iter_get_line (&here) ;
 
-  /* Now set begin and end to the start of this line, and start of
-     following line respectively */
-  gtk_text_buffer_get_iter_at_line (GTK_TEXT_BUFFER (se->buffer), &begin, line);
-  gtk_text_buffer_get_iter_at_line (GTK_TEXT_BUFFER (se->buffer), &end, line + 1);
+/* Parse and execute the syntax from the current line, to the end of the
+   buffer. */
+static void
+on_run_to_end (PsppireSyntaxWindow *se)
+{
+  GtkTextBuffer *buf = GTK_TEXT_BUFFER (se->buffer);
+  GtkTextIter end;
+  gtk_text_buffer_get_end_iter (buf, &end);
 
-  editor_execute_syntax (se, begin, end);
+  run_commands (se, get_iter_for_cursor (se), end);
 }
 
-
-
 static void
 on_syntax (GAction *action, GVariant *param, PsppireSyntaxWindow *sw)
 {
@@ -944,29 +1012,20 @@ psppire_syntax_window_init (PsppireSyntaxWindow *window)
   }
 
   {
-    GSimpleAction *run_current_line = g_simple_action_new ("run-current-line", NULL);
+    GSimpleAction *run_selection = g_simple_action_new ("run-selection", NULL);
 
-    g_signal_connect_swapped (run_current_line, "activate",
-                             G_CALLBACK (on_run_current_line), window);
+    g_signal_connect_swapped (run_selection, "activate",
+                             G_CALLBACK (on_run_selection), window);
 
-    g_action_map_add_action (G_ACTION_MAP (window), G_ACTION (run_current_line));
+    g_action_map_add_action (G_ACTION_MAP (window), G_ACTION (run_selection));
 
     GtkApplication *app = GTK_APPLICATION (g_application_get_default ());
     const gchar *accels[2] = { "<Primary>R", NULL};
     gtk_application_set_accels_for_action (app,
-                                          "win.run-current-line",
+                                          "win.run-selection",
                                           accels);
   }
 
-  {
-    GSimpleAction *run_selection = g_simple_action_new ("run-selection", NULL);
-
-    g_signal_connect_swapped (run_selection, "activate",
-                             G_CALLBACK (on_run_selection), window);
-
-    g_action_map_add_action (G_ACTION_MAP (window), G_ACTION (run_selection));
-  }
-
   {
     GSimpleAction *run_to_end = g_simple_action_new ("run-to-end", NULL);
 
index 49780048d1690f45cb438f709ca7763b93ee5901..b659164ed2c7a811a0fd2ab47b0385f60c1b5ed7 100644 (file)
       <item>
        <attribute name="label" translatable="yes">_Selection</attribute>
        <attribute name="action">win.run-selection</attribute>
-      </item>
-      <item>
-       <attribute name="label" translatable="yes">_Current Line</attribute>
-       <attribute name="action">win.run-current-line</attribute>
        <attribute name="accel">&lt;Primary&gt;r</attribute>
       </item>
       <item>