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

From: Stefan Kangas <[email-masked]>
Date: Sun, 23 Feb 2025 01:33:46 +0000

Perry Smith <[email-masked]> writes:

>> 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

Sorry, but could you please resend this as formatted by

    git format-patch -1

?