Skip to content

Latest commit

 

History

History
217 lines (185 loc) · 10 KB

20230116.org

File metadata and controls

217 lines (185 loc) · 10 KB

Accessing offline API documentation from Emacs

Prominent Python developer and blogger Hynek Schlawack wrote a blog post about the offline API browser Dash. Dash is a MacOS application that lets you download the API documentation of multiple software packages and search it offline. You start it with a press of a button, enter your search terms and it searches the documentation you downloaded. At the time of writing this, the Dash website states that it has the documentation of over 200+ software packages, for example C++, Pandas, Python and Qt.

The blog post mentions there is a package for Emacs that allows you to download this documentation and search it via Helm, namely helm-dash. I decided to try it out…

Nice but…

At the moment I’ve only installed the documentation for Python 3. helm-dash allows me to have a browser window at the right location in its official docs with only a few keystrokes. I’ve always used Google for that, but hardly ever does it have the official documentation at the top of its search results. For now, helm-dash is here to stay.

I am a bit concerned about its performance once I start adding more documentation. Entering the search term in Helm is already sluggish, sometimes (but not always) the UI locks up for a second. This might find its cause in the Windows laptop I used to evaluate ~helm-dash~[fn:1].

Unfortunately, before I was able to use helm-dash, I encountered a lot of issues that I had to work around. Most of these issues are Windows-specific and made me wonder whether I’m its only Windows user ;).

Issue 1: No function helm-dash-install-docset

After the installation of helm-dash, you have to install the documentation for a specific API (or subject), a so-called docset. To install a docset, you use function helm-dash-install-docset. Unfortunately, that function isn’t available yet as it isn’t autoloaded. As a workaround, if you execute helm-dash and immediately abort that function, helm-dash-install-docset is loaded and available.

Issue 2: Docset installation stops after creation of ~~/.docsets~

By default, helm-dash installs your docsets in ~~/.docsets~. If that directory doesn’t exist, helm-dash-install-docset asks you whether it should create that directory. If you answer “yes”, it creates that directory and then stops without downloading the docset. Fortunately, if you call it again, it does download the docset.

Issue 3: Failure to extract docset tarball

So I executed helm-dash-install-docset again and for about a minute, nothing seems to happen. Then it stops and the *Messages* buffer contains the following lines:

Contacting host: api.github.com:443
Contacting host: raw.github.com:443
Wrote c:/Users/<username>/AppData/Local/Temp/Python_3-feed.xml
Contacting host: sanfrancisco.kapeli.com:80
Wrote c:/Users/<username>/AppData/Local/Temp/Python_3-docset.tgz
helm-M-x-execute-command: Failed to extract c:/Users/<username>/AppData/Local/Temp/Python_3-docset.tgz to c:/Users/<username>/.docsets. Error: 1

Browsing through the stack of ELisp functions called, I found the following function in dash-docs.el:

(defun dash-docs-extract-and-get-folder (docset-temp-path)
  "Extract DOCSET-TEMP-PATH to DASH-DOCS-DOCSETS-PATH, and return the folder that was newly extracted."
  (with-temp-buffer
    (let* ((call-process-args (list "tar" nil t nil))
	   (process-args (list
			  "xfv" docset-temp-path
			  "-C" (dash-docs-docsets-path)))
	   ;; On Windows, several elements need to be removed from filenames, see
	   ;; https://docs.microsoft.com/en-us/windows/desktop/FileIO/naming-a-file#naming-conventions.
	   ;; We replace with underscores on windows. This might lead to broken links.
	   (windows-args (list "--force-local" "--transform" "s/[<>\":?*^|]/_/g"))
	   (result (apply #'call-process
			  (append call-process-args process-args (when (eq system-type 'windows-nt) windows-args)))))
      ;; ...

Note the additional options when you want to unpack the docset tarball on Windows. It turns out that Windows comes with a version of BSD tar pre-installed and this version doesn’t support these options.

(ews-harvester) C:\Users\<username>>which tar
/c/WINDOWS/system32/tar

(ews-harvester) C:\Users\<username>>tar --version
bsdtar 3.5.2 - libarchive 3.5.2 zlib/1.2.5.f-ipp

(ews-harvester) C:\Users\<username>>

On my system I also have GNU tar installed (using MSYS2), so I changed the occurrence of “tar” to use my GNU tar,

(let* ((call-process-args (list "c:/Users/<username>/msys64/usr/bin/tar.exe" nil t nil))
       ;; ...

and re-evaluated the function. I executed helm-dash-install-docset, again with the minute-long wait and this time the command was successful:

Contacting host: api.github.com:443
Contacting host: raw.github.com:443
Wrote c:/Users/<username>/AppData/Local/Temp/Python_3-feed.xml
Wrote c:/Users/<username>/AppData/Local/Temp/Python_3-docset.tgz
Docset installed. Add "Python 3" to dash-docs-common-docsets or dash-docs-docsets.

Issue 4: Browser window doesn’t open

So time to try out helm-dash! I entered the string “pathlib” without the string and after some seconds delay it showed me the options. I pressed return and… was greeted with the following message:

helm-M-x-execute-command: ShellExecute failed: The system cannot find the file specified.

Again I browsed through the stack of ELisp functions and ended at browse-url-default-windows-browser in browse-url.el. This function uses the following construct to open the URL:

(w32-shell-execute "open"
                   ;; w32-shell-execute passes file:// URLs
                   ;; to APIs that expect file names, so we
                   ;; need to unhex any %nn encoded
                   ;; characters in the URL.  We don't do
                   ;; that for other URLs; in particular,
                   ;; default Windows mail client barfs on
                   ;; quotes in the MAILTO URLs, so we prefer
                   ;; to leave the URL with its embedded %nn
                   ;; encoding intact.
                   (if (eq t (compare-strings url nil 7
                                              "file://" nil nil))
                       (url-unhex-string url)
                     url)))))

Some debugging showed me that function url-unhex-string changed the URL from

file:///c:/Users/<username>/.docsets/Python%203.docset/Contents/Resources/Documents/doc/library/os.path.html#//apple_ref/Function/os.path.join

to

file:///c:/Users/<username>/.docsets/Python 3.docset/Contents/Resources/Documents/doc/library/os.path.html#//apple_ref/Function/os.path.join

The replacement of Python%203 by a space splits the file path in two, hence the “cannot find the file specified” error message. So I modified a copy of the function to add spaces around the string

(if (eq t (compare-strings url nil 7
                           "file://" nil nil))
    (concat "\"" (url-unhex-string url) "\"")
  url)))))

re-evaluated the function and called helm-dash again. Yes! This time the default browser opened the documentation.

Issue 5: Microsoft Edge ignores specified anchor

Everything looked fine and dandy but the browser showed the top of the page: it didn’t scroll the page to the anchor #//apple_ref/Function/os.path.join. It turned out that when you pass a URL to Microsoft Edge, it strips the anchor from the URL. For example, when I execute the following command in a Windows Command Prompt:

explorer "file:///c:/Users/<username>/.docsets/Python 3.docset/Contents/Resources/Documents/doc/library/os.path.html\#//apple_ref/Function/os.path.join"

Edge shows me the top of the page. If I use that command with Firefox instead, Firefox shows me the page at the anchor.

I’m testing this on a locked-down Windows laptop provided by my client. On this laptop, Microsoft Edge is the default browser and I cannot change it. I didn’t want to spent too much time looking into this issue so I just configured browse-url to use Firefox[fn:2]. In the end it turned out to be a nice workaround as it means I have a dedicated window for my offline API documentation.

Loose ends

If you install a docset, its installation ends with a message like:

Docset installed. Add "Python 3" to dash-docs-common-docsets or dash-docs-docsets.

If you don’t add it to these variables, these variables are empty after a restart of Emacs. If you execute helm-dash after such a restart, it won’t find anything. You can add it via function helm-dash-activate-docset or use add-to-list in your Emacs init fle.

When I use helm-dash, the *dash-docs-error* buffer opens with messages like this:

cannot open: "''"

cannot open: "''"

cannot open: "''"

cannot open: "''"
----------------
 HEY! This is dash-docs (sqlite) error logging. If you want to disable it, set `dash-docs-enable-debugging` to nil
----------------

I’ve encountered this message before and no, setting dash-docs-enable-debugging to nil doesn’t count. If I recall correctly, it has to do with the way the ELisp code inits the SQLite database. I will look into this at a later time.

Footnotes

[fn:1] The laptop is provided to me by one of my clients. It’s locked-down with a company proxy and virus scanner, somewhat unstable and definately not a speed daemon.

[fn:2] To do so, use the Emacs Customize interface to set variable browse-url-browser-function to Firefox, or set it in code to 'browse-url-firerox.