Patchwork commands: possibly start a web browser during `hg serve` (BC)

login
register
mail settings
Submitter Gregory Szorc
Date July 16, 2016, 4:57 a.m.
Message ID <d9f80be1178350827224.1468645072@ubuntu-vm-main>
Download mbox | patch
Permalink /patch/15898/
State Changes Requested
Headers show

Comments

Gregory Szorc - July 16, 2016, 4:57 a.m.
# HG changeset patch
# User Gregory Szorc <gregory.szorc@gmail.com>
# Date 1468645039 25200
#      Fri Jul 15 21:57:19 2016 -0700
# Node ID d9f80be11783508272240020c29ffd36529de53e
# Parent  1c22400db72de4bbeb992fde534c0abbe1367b03
commands: possibly start a web browser during `hg serve` (BC)

When D. Richard Hipp visited the Mozilla office several months ago
and was showing me all the awesomeness in his Fossil VCS, one of the
features that stood out to me was `fossil ui` starting a fully-featured
HTTP/HTML server including the web browser. It was so... elegant
to just type a command and get a web browser pointed to a local
HTTP server where you could interact with your VCS.

From my own experience, when I run `hg serve` from an interactive
terminal I open a web browser 95% of the time. The other 5% of the
time I start the server so I can point another Mercurial client at it.
`hg serve` isn't currently optimized for my use case because I have
to run `hg serve` then copy or type a URL into my browser. What a waste
of time!

This patch changes the behavior of `hg serve` to automatically start
a web browser if a) the terminal is interactive and we're not running
a background daemon or server for specialized clients b) the --browser
argument explicitly requests we start a browser.

While this change is backwards incompatible, I think it strikes the
right balance of behavior: we only open browsers if it appears a user
is typing `hg serve`. And, the escape hatch is simple for people
wanting the previous behavior: `hg serve --browser=` or set
"web.browser" to an empty string or "none."

If nothing else, this change will help users discover the built-in
HTML interface when running `hg serve` for the first time. I still
encounter Mercurial users who don't realize that `hg serve` starts
a browseable server (as opposed to a headless HTTP/protocol server).
Launching the browser automatically will show them that the local
server is more than a "listening at <URL>" message in their terminal.
Mike Hommey - July 16, 2016, 5:18 a.m.
On Fri, Jul 15, 2016 at 09:57:52PM -0700, Gregory Szorc wrote:
> # HG changeset patch
> # User Gregory Szorc <gregory.szorc@gmail.com>
> # Date 1468645039 25200
> #      Fri Jul 15 21:57:19 2016 -0700
> # Node ID d9f80be11783508272240020c29ffd36529de53e
> # Parent  1c22400db72de4bbeb992fde534c0abbe1367b03
> commands: possibly start a web browser during `hg serve` (BC)
> 
> When D. Richard Hipp visited the Mozilla office several months ago
> and was showing me all the awesomeness in his Fossil VCS, one of the
> features that stood out to me was `fossil ui` starting a fully-featured
> HTTP/HTML server including the web browser. It was so... elegant
> to just type a command and get a web browser pointed to a local
> HTTP server where you could interact with your VCS.
> 
> From my own experience, when I run `hg serve` from an interactive
> terminal I open a web browser 95% of the time. The other 5% of the
> time I start the server so I can point another Mercurial client at it.
> `hg serve` isn't currently optimized for my use case because I have
> to run `hg serve` then copy or type a URL into my browser. What a waste
> of time!

Being at the opposite end of the spectrum (rarely needing a web browser
when running hg serve), it strikes me that if your terminal emulator
doesn't allow you to click a link and have it open in your default
browser windows, you should investigate changing your terminal emulator.

Mike
Anton Shestakov - July 16, 2016, 6:04 a.m.
16.07.2016, 12:58, "Gregory Szorc" <gregory.szorc@gmail.com>:
> # HG changeset patch
> # User Gregory Szorc <gregory.szorc@gmail.com>
> # Date 1468645039 25200
> # Fri Jul 15 21:57:19 2016 -0700
> # Node ID d9f80be11783508272240020c29ffd36529de53e
> # Parent 1c22400db72de4bbeb992fde534c0abbe1367b03
> commands: possibly start a web browser during `hg serve` (BC)
>
> When D. Richard Hipp visited the Mozilla office several months ago
> and was showing me all the awesomeness in his Fossil VCS, one of the
> features that stood out to me was `fossil ui` starting a fully-featured
> HTTP/HTML server including the web browser. It was so... elegant
> to just type a command and get a web browser pointed to a local
> HTTP server where you could interact with your VCS.
>
> From my own experience, when I run `hg serve` from an interactive
> terminal I open a web browser 95% of the time. The other 5% of the
> time I start the server so I can point another Mercurial client at it.
> `hg serve` isn't currently optimized for my use case because I have
> to run `hg serve` then copy or type a URL into my browser. What a waste
> of time!
>
> This patch changes the behavior of `hg serve` to automatically start
> a web browser if a) the terminal is interactive and we're not running
> a background daemon or server for specialized clients b) the --browser
> argument explicitly requests we start a browser.
>
> While this change is backwards incompatible, I think it strikes the
> right balance of behavior: we only open browsers if it appears a user
> is typing `hg serve`. And, the escape hatch is simple for people
> wanting the previous behavior: `hg serve --browser=` or set
> "web.browser" to an empty string or "none."
>
> If nothing else, this change will help users discover the built-in
> HTML interface when running `hg serve` for the first time. I still
> encounter Mercurial users who don't realize that `hg serve` starts
> a browseable server (as opposed to a headless HTTP/protocol server).
> Launching the browser automatically will show them that the local
> server is more than a "listening at <URL>" message in their terminal.

I've been running 3 different browsers with different arguments and extensions on 3 different workspaces (Xfce) for a long time and found that to be great. Now, if Xfce could pick up my custom menu items or shortcuts (it can't) and make one of them the default browser, maaybe I could pick just one for every possible case of me running `hg serve` [1]. I assume it would still open the browser on the workspace where the terminal with `hg serve` is, and not on the workspace I want to keep that browser on. All of that would be fairly annoying. Thank you for reading my rant.

So I think that opening a browser on `hg serve` without arguments should be opt-in (require e.g. web.browser=default, or an argument to the command). How about alias.ui = serve --browser?

I also think that for hgweb to become more discoverable, we could just fix https://bz.mercurial-scm.org/show_bug.cgi?id=3976 and then write "WebUI available at http://127.0.0.1:8000/" or something more... inviting. Luckily, many terminals actually allow double-clicking or right-clicking the link and picking "Open in browser" in the context menu.

[1] My actual default browser is w3m (the text based), that opens in a 80x24 terminal, because that's what I think an application that opens a URL without a warning deserves. (To be fair, w3m is actually nice for browsing pastebins)
Yuya Nishihara - July 17, 2016, 10:22 a.m.
On Sat, 16 Jul 2016 14:04:46 +0800, Anton Shestakov wrote:
> 16.07.2016, 12:58, "Gregory Szorc" <gregory.szorc@gmail.com>:
> I've been running 3 different browsers with different arguments and extensions on 3 different workspaces (Xfce) for a long time and found that to be great. Now, if Xfce could pick up my custom menu items or shortcuts (it can't) and make one of them the default browser, maaybe I could pick just one for every possible case of me running `hg serve` [1]. I assume it would still open the browser on the workspace where the terminal with `hg serve` is, and not on the workspace I want to keep that browser on. All of that would be fairly annoying. Thank you for reading my rant.
> 
> So I think that opening a browser on `hg serve` without arguments should be opt-in (require e.g. web.browser=default, or an argument to the command). How about alias.ui = serve --browser?

Agree. I don't wanna open GUI unintentionally while working in console. So,

 -1 for default
 0 for opt-in feature

> I also think that for hgweb to become more discoverable, we could just fix https://bz.mercurial-scm.org/show_bug.cgi?id=3976 and then write "WebUI available at http://127.0.0.1:8000/" or something more... inviting. Luckily, many terminals actually allow double-clicking or right-clicking the link and picking "Open in browser" in the context menu.

I think it's okay to show "listening at" message even if --port is specified.
Gregory Szorc - July 17, 2016, 5:27 p.m.
On Sun, Jul 17, 2016 at 3:22 AM, Yuya Nishihara <yuya@tcha.org> wrote:

> On Sat, 16 Jul 2016 14:04:46 +0800, Anton Shestakov wrote:
> > 16.07.2016, 12:58, "Gregory Szorc" <gregory.szorc@gmail.com>:
> > I've been running 3 different browsers with different arguments and
> extensions on 3 different workspaces (Xfce) for a long time and found that
> to be great. Now, if Xfce could pick up my custom menu items or shortcuts
> (it can't) and make one of them the default browser, maaybe I could pick
> just one for every possible case of me running `hg serve` [1]. I assume it
> would still open the browser on the workspace where the terminal with `hg
> serve` is, and not on the workspace I want to keep that browser on. All of
> that would be fairly annoying. Thank you for reading my rant.
> >
> > So I think that opening a browser on `hg serve` without arguments should
> be opt-in (require e.g. web.browser=default, or an argument to the
> command). How about alias.ui = serve --browser?
>
> Agree. I don't wanna open GUI unintentionally while working in console. So,
>
>  -1 for default
>  0 for opt-in feature
>

Fair enough.


>
> > I also think that for hgweb to become more discoverable, we could just
> fix https://bz.mercurial-scm.org/show_bug.cgi?id=3976 and then write
> "WebUI available at http://127.0.0.1:8000/" or something more...
> inviting. Luckily, many terminals actually allow double-clicking or
> right-clicking the link and picking "Open in browser" in the context menu.
>
> I think it's okay to show "listening at" message even if --port is
> specified.
>

It occurred to me while writing this that "auto launch a browser UI" is
applicable to a number of commands, much like having command output go to a
pager is. I could envision various commands accepting a "--browser"
argument that would open a browser to show results instead of sending it to
the terminal. I think the built-in web server needs a lot of love before we
could seriously consider this, however.

Taken to its extreme, you could put a "terminal emulator" like terminal.js
or https://hyperterm.org/ into the browser and create Jupyter Notebook
style interactions where you type hg commands into your browser, send them
to a command server, and render HTML results inline.
Jun Wu - July 18, 2016, 4:01 p.m.
Excerpts from Gregory Szorc's message of 2016-07-17 10:27:58 -0700:
> It occurred to me while writing this that "auto launch a browser UI" is
> applicable to a number of commands, much like having command output go to a
> pager is. I could envision various commands accepting a "--browser"
> argument that would open a browser to show results instead of sending it to
> the terminal. I think the built-in web server needs a lot of love before we
> could seriously consider this, however.

Since we have "ui.interface". How about adding suboptions to it? For example:

  ui.interface.serve=browser:firefox
  ui.interface.diff=browser:luakit

It also makes it more flexible, for example, let commit -i use text, split
use ncurses by default.

It may be too flexible and can be abused as the pager though.

> Taken to its extreme, you could put a "terminal emulator" like terminal.js
> or https://hyperterm.org/  into the browser and create Jupyter Notebook
> style interactions where you type hg commands into your browser, send them
> to a command server, and render HTML results inline.
Pierre-Yves David - July 18, 2016, 11:59 p.m.
On 07/16/2016 06:57 AM, Gregory Szorc wrote:
> # HG changeset patch
> # User Gregory Szorc <gregory.szorc@gmail.com>
> # Date 1468645039 25200
> #      Fri Jul 15 21:57:19 2016 -0700
> # Node ID d9f80be11783508272240020c29ffd36529de53e
> # Parent  1c22400db72de4bbeb992fde534c0abbe1367b03
> commands: possibly start a web browser during `hg serve` (BC)
> 
> When D. Richard Hipp visited the Mozilla office several months ago
> and was showing me all the awesomeness in his Fossil VCS, one of the
> features that stood out to me was `fossil ui` starting a fully-featured
> HTTP/HTML server including the web browser. It was so... elegant
> to just type a command and get a web browser pointed to a local
> HTTP server where you could interact with your VCS.
> 
> From my own experience, when I run `hg serve` from an interactive
> terminal I open a web browser 95% of the time. The other 5% of the
> time I start the server so I can point another Mercurial client at it.
> `hg serve` isn't currently optimized for my use case because I have
> to run `hg serve` then copy or type a URL into my browser. What a waste
> of time!
> 
> This patch changes the behavior of `hg serve` to automatically start
> a web browser if a) the terminal is interactive and we're not running
> a background daemon or server for specialized clients b) the --browser
> argument explicitly requests we start a browser.
> 
> While this change is backwards incompatible, I think it strikes the
> right balance of behavior: we only open browsers if it appears a user
> is typing `hg serve`. And, the escape hatch is simple for people
> wanting the previous behavior: `hg serve --browser=` or set
> "web.browser" to an empty string or "none."

I'm not super enthousiastic about making this the default either. pydoc
have '-g' flag to pop up a "graphical interface" which is actually just
the browser, I think a short flag would be good. Alternativement we
could make `hg view` generic and have a browser option for it.
Sean Farley - July 19, 2016, 6:16 p.m.
Jun Wu <quark@fb.com> writes:

> Excerpts from Gregory Szorc's message of 2016-07-17 10:27:58 -0700:
>> It occurred to me while writing this that "auto launch a browser UI" is
>> applicable to a number of commands, much like having command output go to a
>> pager is. I could envision various commands accepting a "--browser"
>> argument that would open a browser to show results instead of sending it to
>> the terminal. I think the built-in web server needs a lot of love before we
>> could seriously consider this, however.
>
> Since we have "ui.interface". How about adding suboptions to it? For example:
>
>   ui.interface.serve=browser:firefox
>   ui.interface.diff=browser:luakit

I like this idea the most. One day, I hope to drag and drop commits to
reorder them, along with other nice, modern ui via a web browser.

Patch

diff --git a/mercurial/commands.py b/mercurial/commands.py
--- a/mercurial/commands.py
+++ b/mercurial/commands.py
@@ -6439,16 +6439,17 @@  def root(ui, repo):
 
     Returns 0 on success.
     """
     ui.write(repo.root + "\n")
 
 @command('^serve',
     [('A', 'accesslog', '', _('name of access log file to write to'),
      _('FILE')),
+    ('', 'browser', '', _('web browser to run')),
     ('d', 'daemon', None, _('run server in background')),
     ('', 'daemon-postexec', [], _('used internally by daemon mode')),
     ('E', 'errorlog', '', _('name of error log file to write to'), _('FILE')),
     # use string type, then we can check if something was passed
     ('p', 'port', '', _('port to listen on (default: 8000)'), _('PORT')),
     ('a', 'address', '', _('address to listen on (default: all interfaces)'),
      _('ADDR')),
     ('', 'prefix', '', _('prefix path to serve from (default: server root)'),
@@ -6471,16 +6472,24 @@  def root(ui, repo):
 def serve(ui, repo, **opts):
     """start stand-alone webserver
 
     Start a local HTTP repository browser and pull server. You can use
     this for ad-hoc sharing and browsing of repositories. It is
     recommended to use a real web server to serve a repository for
     longer periods of time.
 
+    A web browser will be opened at the started server if the ``--browser``
+    argument is defined to any value other than ``none`` or when starting
+    a server in the foreground (not using ``--daemon``), an interactive
+    terminal is detected, and we're not starting a server for remote clients.
+    By default, the system default browser will be opened. The browser to open
+    and the behavior of auto-opening a browser can be changed by setting the
+    ``web.browser`` config option.
+
     Please note that the server does not implement access control.
     This means that, by default, anybody can read from the server and
     nobody can write to it by default. Set the ``web.allow_push``
     option to ``*`` to allow everybody to push to the server. You
     should use a real web server if you need to authenticate users.
 
     By default, the server logs accesses to stdout and errors to
     stderr. Use the -A/--accesslog and -E/--errorlog options to log to
diff --git a/mercurial/help/config.txt b/mercurial/help/config.txt
--- a/mercurial/help/config.txt
+++ b/mercurial/help/config.txt
@@ -1921,16 +1921,28 @@  The full set of options is:
     Whether to recurse into subrepositories when archiving.
     (default: False)
 
 ``baseurl``
     Base URL to use when publishing URLs in other locations, so
     third-party tools like email notification hooks can construct
     URLs. Example: ``http://hgserver/repos/``.
 
+``browser``
+    Web browser to open when starting a HTTP server via ``hg serve``.
+
+    Value is one of the browser type names accepted by the Python
+    ``webbrowser`` module (run ``pydoc webbrowser`` to see choices).
+    Common values include ``firefox`` and ``chrome``.
+
+    By default, the system default browser will be used.
+
+    The value can be empty or ``none`` to disable the behavior of
+    automatically starting a browser.
+
 ``cacerts``
     Path to file containing a list of PEM encoded certificate
     authority certificates. Environment variables and ``~user``
     constructs are expanded in the filename. If specified on the
     client, then it will verify the identity of remote HTTPS servers
     with these certificates.
 
     To disable SSL verification temporarily, specify ``--insecure`` from
diff --git a/mercurial/hgweb/__init__.py b/mercurial/hgweb/__init__.py
--- a/mercurial/hgweb/__init__.py
+++ b/mercurial/hgweb/__init__.py
@@ -4,16 +4,17 @@ 
 # Copyright 2005 Matt Mackall <mpm@selenic.com>
 #
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
 from __future__ import absolute_import
 
 import os
+import webbrowser
 
 from ..i18n import _
 
 from .. import (
     error,
     util,
 )
 
@@ -73,24 +74,61 @@  class httpservice(object):
 
         fqaddr = self.httpd.fqaddr
         if ':' in fqaddr:
             fqaddr = '[%s]' % fqaddr
         if self.opts['port']:
             write = self.ui.status
         else:
             write = self.ui.write
-        write(_('listening at http://%s%s/%s (bound to %s:%d)\n') %
-              (fqaddr, port, prefix, bindaddr, self.httpd.port))
+        self.url = 'http://%s%s/%s' % (fqaddr, port, prefix)
+        write(_('listening at %s (bound to %s:%d)\n') %
+              (self.url, bindaddr, self.httpd.port))
         self.ui.flush()  # avoid buffering of status message
 
     def run(self):
+        self._maybestartbrowser()
         self.httpd.serve_forever()
 
+    def _maybestartbrowser(self):
+        # If browser option is present and it isn't a special value "none",
+        # start the browser.
+        browser = self.opts.get('browser')
+        if browser:
+            if browser == 'none':
+                return
+
+        # Otherwise, start a browser if in interactive mode, not starting
+        # a daemon, and the config option hasn't been set to stop this behavior.
+        else:
+            if not self.opts.get('interactive') or self.opts.get('daemon'):
+                return
+
+            browser = self.ui.config('web', 'browser', None)
+            if browser == 'none' or (not browser and browser is not None):
+                return
+
+        try:
+            browser = webbrowser.get(browser)
+            # Text-mode browsers block the calling process, which interferes
+            # with our server.
+            if (not isinstance(browser, webbrowser.BackgroundBrowser) and
+                not getattr(browser, 'background', None)):
+                self.ui.warn(_('(cannot start text-mode browsers; '
+                               'continuing to start server)\n'))
+                return
+            browser.open_new_tab(self.url)
+        except webbrowser.Error as e:
+            self.ui.warn(_('(unable to start web browser: %s)\n') % e.args[0])
+
 def createservice(ui, repo, opts):
+    # Stash away whether UI is interactive so we can query it later
+    # to determine whether to start a web browser.
+    opts['interactive'] = ui.interactive()
+
     # this way we can check if something was given in the command-line
     if opts.get('port'):
         opts['port'] = util.getport(opts.get('port'))
 
     alluis = set([ui])
     if repo:
         baseui = repo.baseui
         alluis.update([repo.baseui, repo.ui])
diff --git a/tests/test-completion.t b/tests/test-completion.t
--- a/tests/test-completion.t
+++ b/tests/test-completion.t
@@ -149,16 +149,17 @@  Show the global options
   -q
   -v
   -y
 
 Show the options for the "serve" command
   $ hg debugcomplete --options serve | sort
   --accesslog
   --address
+  --browser
   --certificate
   --cmdserver
   --config
   --cwd
   --daemon
   --daemon-postexec
   --debug
   --debugger
@@ -214,17 +215,17 @@  Show all commands + options
   export: output, switch-parent, rev, text, git, nodates
   forget: include, exclude
   init: ssh, remotecmd, insecure
   log: follow, follow-first, date, copies, keyword, rev, removed, only-merges, user, only-branch, branch, prune, patch, git, limit, no-merges, stat, graph, style, template, include, exclude
   merge: force, rev, preview, tool
   pull: update, force, rev, bookmark, branch, ssh, remotecmd, insecure
   push: force, rev, bookmark, branch, new-branch, ssh, remotecmd, insecure
   remove: after, force, subrepos, include, exclude
-  serve: accesslog, daemon, daemon-postexec, errorlog, port, address, prefix, name, web-conf, webdir-conf, pid-file, stdio, cmdserver, templates, style, ipv6, certificate
+  serve: accesslog, browser, daemon, daemon-postexec, errorlog, port, address, prefix, name, web-conf, webdir-conf, pid-file, stdio, cmdserver, templates, style, ipv6, certificate
   status: all, modified, added, removed, deleted, clean, unknown, ignored, no-status, copies, print0, rev, change, include, exclude, subrepos, template
   summary: remote
   update: clean, check, date, rev, tool
   addremove: similarity, subrepos, include, exclude, dry-run
   archive: no-decode, prefix, rev, type, subrepos, include, exclude
   backout: merge, commit, no-commit, parent, rev, edit, tool, include, exclude, message, logfile, date, user
   bisect: reset, good, bad, skip, extend, command, noupdate
   bookmarks: force, rev, delete, rename, inactive, template