Subject: Re: bug#58626: Proposed changes to implement opening URLs on macOS

From: Perry Smith <[email-masked]>
Date: Sat, 12 Nov 2022 17:36:07 -0600

> On Nov 12, 2022, at 14:57, Stefan Kangas <[email-masked]> wrote:
> 
> Perry Smith <[email-masked]> writes:
> 
>> Attached is my patch file based upon the emacs-28.2 tar ball.  With
>> these changes, I can do:
>> 
>>    open emacs:///some/path/to/file.txt#25,40
> 
> Could you send the patch in uncompressed instead?  That will simplify
> reviewing it.

Please let me know if this doesn’t work...

Only in emacs-28.2-new: .DS_Store
Only in emacs-28.2-new: What-I-Did.txt
Only in emacs-28.2/admin/unidata: unidata-gen.elc
Only in emacs-28.2/admin/unidata: unidata.txt
Only in emacs-28.2/admin/unidata: uvs.elc
Only in emacs-28.2-new/lib: sys
Only in emacs-28.2-new/lib-src: ctags.dSYM
Only in emacs-28.2-new/lib-src: ebrowse.dSYM
Only in emacs-28.2-new/lib-src: emacsclient.dSYM
Only in emacs-28.2-new/lib-src: etags.dSYM
Only in emacs-28.2-new/lib-src: hexl.dSYM
Only in emacs-28.2-new/lib-src: make-docfile.dSYM
Only in emacs-28.2-new/lib-src: make-fingerprint.dSYM
Only in emacs-28.2-new/lib-src: movemail.dSYM
Only in emacs-28.2/lisp: loaddefs.el
Only in emacs-28.2-new/lisp: loaddefs.el~
diff -rc emacs-28.2/lisp/term/common-win.el emacs-28.2-new/lisp/term/common-win.el
*** emacs-28.2/lisp/term/common-win.el 2022-09-06 16:31:54.000000000 -0500
--- emacs-28.2-new/lisp/term/common-win.el 2022-10-16 07:53:34.000000000 -0500
***************
*** 73,78 ****
--- 73,79 ----
        (cons 12 'ns-new-frame)
        (cons 13 'ns-toggle-toolbar)
        (cons 14 'ns-show-prefs)
+       (cons 15 'ns-open-url)
        ))))
      (set-terminal-parameter frame 'x-setup-function-keys t)))
  Binary files emacs-28.2/lisp/term/common-win.elc and emacs-28.2-new/lisp/term/common-win.elc differ
diff -rc emacs-28.2/lisp/term/ns-win.el emacs-28.2-new/lisp/term/ns-win.el
*** emacs-28.2/lisp/term/ns-win.el 2022-09-06 16:31:54.000000000 -0500
--- emacs-28.2-new/lisp/term/ns-win.el 2022-10-18 11:36:44.000000000 -0500
***************
*** 182,187 ****
--- 182,188 ----
  (define-key global-map [ns-new-frame] 'make-frame)
  (define-key global-map [ns-toggle-toolbar] 'ns-toggle-toolbar)
  (define-key global-map [ns-show-prefs] 'customize)
+ (define-key global-map [ns-open-url] 'ns-open-url)
      ;; Set up a number of aliases and other layers to pretend we're using
***************
*** 530,535 ****
--- 531,578 ----
    (global-set-key [drag-n-drop] 'ns-drag-n-drop)
  + (defvar ns-input-url-scheme)            ; nsterm.m
+ (defvar ns-input-url-user)              ; nsterm.m
+ (defvar ns-input-url-password)          ; nsterm.m
+ (defvar ns-input-url-host)              ; nsterm.m
+ (defvar ns-input-url-port)              ; nsterm.m
+ (defvar ns-input-url-path)              ; nsterm.m
+ (defvar ns-input-url-query)             ; nsterm.m
+ (defvar ns-input-url-fragment)          ; nsterm.m
+ 
+ (defun ns-open-url ()
+   "Open a buffer as directed by the URL which has been broken down
+   into components:
+     `ns-input-url-scheme'   - the URL's scheme   (string)
+     `ns-input-url-user'     - the URL's user     (string)
+     `ns-input-url-password' - the URL's password (string)
+     `ns-input-url-host'     - the URL's host     (string)
+     `ns-input-url-port'     - the URL's port     (integer)
+     `ns-input-url-path'     - the URL's path     (string)
+     `ns-input-url-query'    - the URL's query    (string)
+     `ns-input-url-fragment' - the URL's fragment (string)
+ "
+   (interactive)
+   (cond
+    ((equal ns-input-url-scheme "file")
+     (progn (setq ns-input-file (append (list ns-input-url-path)))
+            (setq ns-input-line nil)     ; My testing on macOS 12.6 shows the fragment is never passed
+            (ns-open-file-select-line)))
+    ((equal ns-input-url-scheme "emacs")
+     (progn (setq ns-input-file (append (list ns-input-url-path)))
+            (setq ns-input-line
+                  (and ns-input-url-fragment
+                       (let ((seq (mapcar #'string-to-number (split-string ns-input-url-fragment "[-,:]" t))))
+                         (cond
+                          ((= (length seq) 1) (car seq))
+                          ((= (length seq) 2) (cons (car seq) (car (cdr seq))))))))
+            (ns-open-file-select-line)))
+    (t (message (format "scheme: %s; user: %s; password: %s; host: %s; port: %d; path: %s; query: %s; fragment: %s"
+                        ns-input-url-scheme ns-input-url-user
+                        ns-input-url-password ns-input-url-host
+                        ns-input-url-port ns-input-url-path
+                        ns-input-url-query ns-input-url-fragment)))))
+ 
  ;;;; Frame-related functions.
    ;; nsterm.m
Binary files emacs-28.2/lisp/term/ns-win.elc and emacs-28.2-new/lisp/term/ns-win.elc differ
Only in emacs-28.2-new/nextstep: .DS_Store
Only in emacs-28.2-new/nextstep/Cocoa/Emacs.base/Contents/Resources: English.lproj
diff -rc emacs-28.2/nextstep/templates/Info.plist.in emacs-28.2-new/nextstep/templates/Info.plist.in
*** emacs-28.2/nextstep/templates/Info.plist.in 2022-09-06 16:31:54.000000000 -0500
--- emacs-28.2-new/nextstep/templates/Info.plist.in 2022-10-15 11:31:41.000000000 -0500
***************
*** 672,677 ****
--- 672,687 ----
  <string>mailto</string>
  </array>
  </dict>
+ <dict>
+ <key>CFBundleTypeRole</key>
+ <string>Editor</string>
+ <key>CFBundleURLName</key>
+ <string>Emacs Local Address URL</string>
+ <key>CFBundleURLSchemes</key>
+ <array>
+ <string>emacs</string>
+ </array>
+ </dict>
  </array>
  <key>NSAppleScriptEnabled</key>
  <string>YES</string>
Only in emacs-28.2-new/nextstep/templates: Info.plist.in-orig
diff -rc emacs-28.2/src/nsterm.m emacs-28.2-new/src/nsterm.m
*** emacs-28.2/src/nsterm.m 2022-09-06 16:31:54.000000000 -0500
--- emacs-28.2-new/src/nsterm.m 2022-10-16 12:13:33.000000000 -0500
***************
*** 292,298 ****
  static struct input_event *q_event_ptr = NULL;
  static int n_emacs_events_pending = 0;
  static NSMutableArray *ns_pending_files, *ns_pending_service_names,
!   *ns_pending_service_args;
  static BOOL ns_do_open_file = NO;
  static BOOL ns_last_use_native_fullscreen;
  --- 292,298 ----
  static struct input_event *q_event_ptr = NULL;
  static int n_emacs_events_pending = 0;
  static NSMutableArray *ns_pending_files, *ns_pending_service_names,
!   *ns_pending_service_args, *ns_pending_urls;
  static BOOL ns_do_open_file = NO;
  static BOOL ns_last_use_native_fullscreen;
  ***************
*** 4361,4366 ****
--- 4361,4372 ----
            [ns_pending_service_names removeObjectAtIndex: 0];
            [ns_pending_service_args removeObjectAtIndex: 0];
          }
+       /* Process the open URL requests */
+       else if (ns_pending_urls && [ns_pending_urls count] != 0
+                && [(EmacsApp *) NSApp openURL: [ns_pending_urls objectAtIndex: 0]])
+         {
+             [ns_pending_urls removeObjectAtIndex: 0];
+         }
        else
          {
            /* Run and wait for events.  We must always send one NX_APPDEFINED event
***************
*** 5123,5128 ****
--- 5129,5135 ----
    ns_pending_files = [[NSMutableArray alloc] init];
    ns_pending_service_names = [[NSMutableArray alloc] init];
    ns_pending_service_args = [[NSMutableArray alloc] init];
+   ns_pending_urls = [[NSMutableArray alloc] init];
      /* Start app and create the main menu, window, view.
       Needs to be here because ns_initialize_display_info () uses AppKit classes.
***************
*** 5938,5943 ****
--- 5945,6004 ----
    /* ==========================================================================
  +     Open URL
+ 
+    ========================================================================== */
+ 
+ /* Open a URL after going into queue read by ns_read_socket.  */
+ - (BOOL) openURL: (NSURL *)url
+ {
+   NSTRACE ("[EmacsApp openURL:]");
+ 
+   struct frame *emacsframe = SELECTED_FRAME ();
+   NSEvent *theEvent = [NSApp currentEvent];
+ 
+   if (!emacs_event)
+     return NO;
+ 
+   emacs_event->kind = NS_NONKEY_EVENT;
+   emacs_event->code = KEY_NS_OPEN_URL;
+   ns_input_url_scheme   = [[url scheme]   lispString];
+   ns_input_url_user     = [[url user]     lispString];
+   ns_input_url_password = [[url password] lispString];
+   ns_input_url_host     = [[url host]     lispString];
+   ns_input_url_port     = make_int([[url port] intValue]);
+   ns_input_url_path     = [[url path]     lispString];
+   ns_input_url_query    = [[url query]    lispString];
+   ns_input_url_fragment = [[url fragment] lispString];
+   EV_TRAILER (theEvent);
+ 
+   return YES;
+ }
+ 
+ /* Notification from the Workspace to open a URL.  */
+ - (void)application: sender openURLs: (NSArray<NSURL *> *)urlList
+ {
+   NSEnumerator *urls = [urlList objectEnumerator];
+   NSURL *url;
+ 
+   NSTRACE ("[EmacsApp openURLs:]");
+   while ((url = [urls nextObject]) != nil)
+     if (ns_do_open_file || not_in_argv ([url path])) {
+       [ns_pending_urls addObject: url];
+     }
+ 
+   /* The documentation says to do this for openFiles but it is not
+    * mentioned in the openURLs doc so I'm going to leave it out for
+    * now.
+    
+   [self replyToOpenOrPrint: NSApplicationDelegateReplySuccess];
+    */
+ 
+ }
+ 
+ 
+ /* ==========================================================================
+ 
      Service provision
       ========================================================================== */
***************
*** 7004,7010 ****
    height = (int)NSHeight (frame);
      NSTRACE_SIZE ("New size", NSMakeSize (width, height));
!   NSTRACE_SIZE ("Original size", size);
      /* Reset the frame size to match the bounds of the superview (the
       NSWindow's contentView).  We need to do this as sometimes the
--- 7065,7071 ----
    height = (int)NSHeight (frame);
      NSTRACE_SIZE ("New size", NSMakeSize (width, height));
!   NSTRACE_SIZE ("Original size", oldSize);
      /* Reset the frame size to match the bounds of the superview (the
       NSWindow's contentView).  We need to do this as sometimes the
***************
*** 9845,9850 ****
--- 9906,9947 ----
                "The file specified in the last NS event.");
    ns_input_file =Qnil;
  +   /* -- URL components */
+ 
+   DEFVAR_LISP ("ns-input-url-scheme", ns_input_url_scheme,
+                "The scheme component of the URL specified in the last NS event.");
+   ns_input_url_scheme =Qnil;
+ 
+   DEFVAR_LISP ("ns-input-url-user", ns_input_url_user,
+                "The user component of the URL specified in the last NS event.");
+   ns_input_url_user =Qnil;
+ 
+   DEFVAR_LISP ("ns-input-url-password", ns_input_url_password,
+                "The password component of the URL specified in the last NS event.");
+   ns_input_url_password =Qnil;
+ 
+   DEFVAR_LISP ("ns-input-url-host", ns_input_url_host,
+                "The host component of the URL specified in the last NS event.");
+   ns_input_url_host =Qnil;
+ 
+   DEFVAR_LISP ("ns-input-url-port", ns_input_url_port,
+                "The port component of the URL specified in the last NS event.");
+   ns_input_url_port =Qnil;
+ 
+   DEFVAR_LISP ("ns-input-url-path", ns_input_url_path,
+                "The path component of the URL specified in the last NS event.");
+   ns_input_url_path =Qnil;
+ 
+   DEFVAR_LISP ("ns-input-url-query", ns_input_url_query,
+                "The query component of the URL specified in the last NS event.");
+   ns_input_url_query =Qnil;
+ 
+   DEFVAR_LISP ("ns-input-url-fragment", ns_input_url_fragment,
+                "The fragment component of the URL specified in the last NS event.");
+   ns_input_url_fragment =Qnil;
+ 
+   /* -- */
+ 
    DEFVAR_LISP ("ns-working-text", ns_working_text,
                "String for visualizing working composition sequence.");
    ns_working_text =Qnil;
Only in emacs-28.2-new: trace