├── .gitignore ├── Cask ├── LICENSE ├── Makefile ├── README.md ├── convert.sh ├── debug_files ├── README.md ├── constant.ARCH.html ├── enum.Option.html ├── option.html ├── primitive.i16.html ├── shared.rs.html └── trait.AsRef.html ├── demo.gif ├── filter.lua ├── rustdoc.el └── test ├── rustdoc-to-org-test.el └── test-helper.el /.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | dist-* 3 | cabal-dev 4 | *.o 5 | *.hi 6 | *.hie 7 | *.chi 8 | *.chs.h 9 | *.dyn_o 10 | *.dyn_hi 11 | .hpc 12 | .hsenv 13 | .cabal-sandbox/ 14 | cabal.sandbox.config 15 | *.prof 16 | *.aux 17 | *.hp 18 | *.eventlog 19 | .stack-work/ 20 | cabal.project.local 21 | cabal.project.local~ 22 | .HTF/ 23 | .ghc.environment.* 24 | /option.org 25 | /filterednative 26 | /AsRef.org 27 | /native 28 | /debug_files/shared.rs.org 29 | /debug_files/filterednative 30 | /debug_files/option.org 31 | /debug_files/AsRef.org 32 | /debug_files/constant.arch.org 33 | /debug_files/datagram.org 34 | /ert-profile 35 | /ert-profile~ 36 | /debug_files/primitive.i16.org 37 | -------------------------------------------------------------------------------- /Cask: -------------------------------------------------------------------------------- 1 | (source gnu) 2 | (source melpa) 3 | 4 | (package-file "rustdoc.el") 5 | (depends-on "f") 6 | (depends-on "helm-ag") 7 | (depends-on "url") 8 | (depends-on "lsp") 9 | 10 | (development 11 | (depends-on "ecukes") 12 | (depends-on "ert-runner") 13 | (depends-on "el-mock")) 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Sam Hedin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Used for development 2 | standard: 3 | cd debug_files ;\ 4 | pandoc enum.Option.html --lua-filter ../filter.lua -t native -o filterednative ;\ 5 | pandoc -f native filterednative -o option.org 6 | 7 | standard_unfiltered: 8 | cd debug_files ;\ 9 | pandoc enum.Option.html -t native -o filterednative ;\ 10 | pandoc -f native filterednative -o option.org 11 | 12 | 13 | primitive: 14 | cd debug_files ;\ 15 | pandoc primitive.i16.html --lua-filter ../filter.lua -t native -o filterednative ;\ 16 | pandoc -f native filterednative -o primitive.i16.org 17 | 18 | 19 | primitive_unfiltered: 20 | cd debug_files ;\ 21 | pandoc primitive.i16.html -t native -o filterednative ;\ 22 | pandoc -f native filterednative -o primitive.i16.org 23 | 24 | trait: 25 | cd debug_files ;\ 26 | pandoc trait.AsRef.html --lua-filter ../filter.lua -t native -o filterednative ;\ 27 | pandoc -f native filterednative -o AsRef.org 28 | 29 | code: 30 | cd debug_files ;\ 31 | pandoc shared.rs.html --lua-filter ../filter.lua -t native -o filterednative ;\ 32 | pandoc -f native filterednative -o shared.rs.org 33 | 34 | constant: 35 | cd debug_files ;\ 36 | pandoc constant.ARCH.html --lua-filter ../filter.lua -t native -o filterednative ;\ 37 | pandoc -f native filterednative -o constant.arch.org 38 | 39 | overwrite_filter: 40 | rm ~/.local/bin/rustdoc-filter.lua 41 | cp ./filter.lua ~/.local/bin/rustdoc-filter.lua 42 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Rustdoc has moved! 2 | Rustdoc now comes with [rustic](https://github.com/brotzeit/rustic), and future development will happen in that repo. Join us there! 3 | 4 | In rustic, this feature goes under the name *rustic-doc*: https://github.com/brotzeit/rustic#rust-docs-in-org-mode 5 | 6 | # Rustdoc to org 7 | A Pandoc filter that converts rust documentation to .org-files, and a minor mode to go with! 8 | ![Demo with helm ag](demo.gif) 9 | 10 | ## Installation 11 | 12 | Installation is somewhat annoying at the moment. However, most of these should not be too painful to setup. 13 | 14 | ### Prequisites 15 | 16 | * Install Pandoc https://pandoc.org/installing.html 17 | * Install cargo https://doc.rust-lang.org/cargo/getting-started/installation.html 18 | * Install ripgrep with `cargo install ripgrep` or one of the alternatives: https://github.com/BurntSushi/ripgrep#installation 19 | * Install cargo-makedocs by running `cargo install cargo-makedocs` https://github.com/Bunogi/cargo-makedocs 20 | * Install fd with `cargo install fd`, or your package manager 21 | 22 | ### The package 23 | 24 | Rustdoc is not on melpa, but you can install it from here with your package manager of choice or manually. Please make an issue if something stops working, as rustdoc is currently changing rapidly. 25 | * With [straight.el](https://github.com/raxod502/straight.el#the-recipe-format) 26 | * With quelpa: `(quelpa '(rustdoc :fetcher github :repo "samhedin/rustdoc-to-org"))` 27 | * Manually 28 | * Install helm-ag from MELPA with M-x package-install [RET] helm-ag [RET] https://github.com/bridgesense/emacs-helm-ag#installation 29 | * Copy `rustdoc.el` and load it with `(require rustdoc.el)` 30 | 31 | ## Usage 32 | 33 | * Enable `rustdoc-mode`. 34 | * Run `M-x rustdoc-setup` to download files that rustdoc needs to convert rust documentation and also convert `std`. 35 | * You can now convert package-specific documentation with `M-x rustdoc-convert-current-package` 36 | * Search the org files with `rustdoc-search` (bound to `C-#` by default) if you are in `Rust mode`, `Rustic mode` or `Org mode`. If you hover over a symbol when you invoke the command, `rustdoc-search` will insert a default value. 37 | * Add `universal argument` to only search for level 1 headers like struct or enum names. 38 | 39 | ## Notes 40 | * We are waiting for an update to Pandoc that will make the generated documents prettier, it should be available soon https://github.com/jgm/pandoc/issues/6554 41 | * You should re-run `rustdoc-setup` once in a while, to update the pandoc filter. 42 | * If rustdoc does not find the documentation for something, the first thing to do is check the project's `target/doc` folder for the corresponding `.html-file`. If there is no file there, there is nothing for rustdoc to convert. If there is a file there, please create an issue! 43 | 44 | ## TODO 45 | 46 | * Figure out a way to get links working. All links point to .html files, they should be updated to point to .org files. 47 | * Make code not awful and not slow. 48 | * Fill out this list. 49 | 50 | All suggestions, comments, bug reports, PRs etc are very welcomed! 51 | -------------------------------------------------------------------------------- /convert.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | LUA_FILTER="$HOME/.local/bin/rustdoc-filter.lua" 4 | 5 | function get_toolchain { 6 | rustup show | sed -nr 's/(.*) \(default\)/\1/p' | head -n 1 7 | } 8 | 9 | if [ "$1" = "" ] || [ "$1" = "--help" ]; then 10 | MY_NAME="$(basename "$0")" 11 | echo "Usage:" 12 | echo " $MY_NAME " 13 | echo " $MY_NAME " 14 | exit 0 15 | fi 16 | 17 | DOC_PATH="$1" 18 | DEST_DIR="$2" 19 | 20 | if [ "$DEST_DIR" = "" ]; then 21 | LIBRARY="$1" 22 | TARGET="$(get_toolchain)" 23 | 24 | ## Users can change the location of the rustup directory 25 | RUSTUP_HOME="${RUSTUP_HOME:-$HOME/.rustup}" 26 | ## Set 27 | DOC_PATH="$RUSTUP_HOME/toolchains/$TARGET/share/doc/rust/html/$LIBRARY" 28 | DEST_DIR="${XDG_DATA_HOME:-$HOME/.local/share}/emacs/rustdoc/$LIBRARY" 29 | 30 | echo "Generating org files in: $DEST_DIR" 31 | fi 32 | 33 | mkdir -p "$DEST_DIR" || exit 1 34 | cd "$DOC_PATH" || exit 1 35 | 36 | ## Copy directory structure 37 | fd . -td -x mkdir -p "$DEST_DIR/{}" 38 | 39 | ## Find redirect files (removes $DOC_PATH prefix) 40 | ignore_file="$(mktemp)" 41 | rg -l "

Redirecting to "$ignore_file" 49 | 50 | ## Convert files 51 | fd . \ 52 | -ehtml \ 53 | --ignore-file "$ignore_file" \ 54 | -j"$(nproc)" \ 55 | -x pandoc '{}' \ 56 | --lua-filter "$LUA_FILTER" \ 57 | -o "$DEST_DIR/{.}.org" 58 | -------------------------------------------------------------------------------- /debug_files/README.md: -------------------------------------------------------------------------------- 1 | These files are here for developing the filter. 2 | When I develop, I have three files open: `filter.lua`, whatever org-file I am trying to get right (like `debug_files/option.org`), and `debug_files/filterednative` so that I can inspect the 3 | document structure. I then use make to update `filterednative` and the output as I am working on the filter. -------------------------------------------------------------------------------- /debug_files/constant.ARCH.html: -------------------------------------------------------------------------------- 1 | std::env::consts::ARCH - Rust

1.0.0[][src]Constant std::env::consts::ARCH

pub const ARCH: &str;

A string describing the architecture of the CPU that is currently 2 | in use.

3 |

Some possible values:

4 |
    5 |
  • x86
  • 6 |
  • x86_64
  • 7 |
  • arm
  • 8 |
  • aarch64
  • 9 |
  • mips
  • 10 |
  • mips64
  • 11 |
  • powerpc
  • 12 |
  • powerpc64
  • 13 |
  • riscv64
  • 14 |
  • s390x
  • 15 |
  • sparc64
  • 16 |
17 |
-------------------------------------------------------------------------------- /debug_files/shared.rs.html: -------------------------------------------------------------------------------- 1 | shared.rs.html -- source
 1
 2 |  2
 3 |  3
 4 |  4
 5 |  5
 6 |  6
 7 |  7
 8 |  8
 9 |  9
10 | 10
11 | 11
12 | 12
13 | 13
14 | 14
15 | 15
16 | 16
17 | 17
18 | 18
19 | 
20 | use adler32::RollingAdler32;
21 | 
22 | #[doc(hidden)]
23 | pub const MZ_ADLER32_INIT: u32 = 1;
24 | 
25 | #[doc(hidden)]
26 | pub const MZ_DEFAULT_WINDOW_BITS: i32 = 15;
27 | 
28 | pub const HUFFMAN_LENGTH_ORDER: [u8; 19] = [
29 |     16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15,
30 | ];
31 | 
32 | #[doc(hidden)]
33 | pub fn update_adler32(adler: u32, data: &[u8]) -> u32 {
34 |     let mut hash = RollingAdler32::from_value(adler);
35 |     hash.update_buffer(data);
36 |     hash.hash()
37 | }
38 | 
39 |
-------------------------------------------------------------------------------- /debug_files/trait.AsRef.html: -------------------------------------------------------------------------------- 1 | std::convert::AsRef - Rust

1.0.0[][src]Trait std::convert::AsRef

pub trait AsRef<T> where
    T: ?Sized
{ 2 | fn as_ref(&self) -> &T; 3 | }

Used to do a cheap reference-to-reference conversion.

4 |

This trait is similar to AsMut which is used for converting between mutable references. 5 | If you need to do a costly conversion it is better to implement From with type 6 | &T or write a custom function.

7 |

AsRef has the same signature as Borrow, but Borrow is different in few aspects:

8 |
    9 |
  • Unlike AsRef, Borrow has a blanket impl for any T, and can be used to accept either 10 | a reference or a value.
  • 11 |
  • Borrow also requires that Hash, Eq and Ord for borrowed value are 12 | equivalent to those of the owned value. For this reason, if you want to 13 | borrow only a single field of a struct you can implement AsRef, but not Borrow.
  • 14 |
15 |

Note: This trait must not fail. If the conversion can fail, use a 16 | dedicated method which returns an Option<T> or a Result<T, E>.

17 |

Generic Implementations

18 |
    19 |
  • AsRef auto-dereferences if the inner type is a reference or a mutable 20 | reference (e.g.: foo.as_ref() will work the same if foo has type 21 | &mut Foo or &&mut Foo)
  • 22 |
23 |

Examples

24 |

By using trait bounds we can accept arguments of different types as long as they can be 25 | converted to the specified type T.

26 |

For example: By creating a generic function that takes an AsRef<str> we express that we 27 | want to accept all references that can be converted to &str as an argument. 28 | Since both String and &str implement AsRef<str> we can accept both as input argument.

29 | 30 |
31 | fn is_hello<T: AsRef<str>>(s: T) {
32 |    assert_eq!("hello", s.as_ref());
33 | }
34 | 
35 | let s = "hello";
36 | is_hello(s);
37 | 
38 | let s = "hello".to_string();
39 | is_hello(s);
Run
40 |
41 |

Required methods

fn as_ref(&self) -> &T

Performs the conversion.

42 |
Loading content... 43 |

Implementors

impl AsRef<[u8]> for str[src]

impl AsRef<[u8]> for String[src]

impl AsRef<str> for str[src]

impl AsRef<str> for String[src]

impl AsRef<CStr> for CStr[src]

impl AsRef<CStr> for CString[src]

impl AsRef<OsStr> for str[src]

impl AsRef<OsStr> for OsStr[src]

impl AsRef<OsStr> for OsString[src]

impl AsRef<OsStr> for Path[src]

impl AsRef<OsStr> for PathBuf[src]

impl AsRef<OsStr> for String[src]

impl AsRef<Path> for str[src]

impl AsRef<Path> for OsStr[src]

impl AsRef<Path> for OsString[src]

impl AsRef<Path> for Path[src]

impl AsRef<Path> for PathBuf[src]

impl AsRef<Path> for String[src]

impl<'_> AsRef<OsStr> for Component<'_>[src]

impl<'_> AsRef<OsStr> for Components<'_>[src]

impl<'_> AsRef<OsStr> for std::path::Iter<'_>[src]

impl<'_> AsRef<Path> for Cow<'_, OsStr>[src]

impl<'_> AsRef<Path> for Component<'_>[src]

impl<'_> AsRef<Path> for Components<'_>[src]

impl<'_> AsRef<Path> for std::path::Iter<'_>[src]

impl<'_, T> AsRef<[T]> for std::slice::Iter<'_, T>[src]

impl<'_, T> AsRef<T> for Cow<'_, T> where
    T: ToOwned + ?Sized
[src]

impl<'_, T, U> AsRef<U> for &'_ T where
    T: AsRef<U> + ?Sized,
    U: ?Sized
[src]

impl<'_, T, U> AsRef<U> for &'_ mut T where
    T: AsRef<U> + ?Sized,
    U: ?Sized
[src]

impl<T> AsRef<[T]> for [T][src]

impl<T> AsRef<[T]> for Vec<T>[src]

impl<T> AsRef<Vec<T>> for Vec<T>[src]

impl<T> AsRef<T> for Box<T> where
    T: ?Sized
[src]

impl<T> AsRef<T> for Rc<T> where
    T: ?Sized
[src]

impl<T> AsRef<T> for Arc<T> where
    T: ?Sized
[src]

impl<const N: usize, T> AsRef<[T]> for [T; N] where
    [T; N]: LengthAtMost32
[src]

Loading content...
-------------------------------------------------------------------------------- /demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/samhedin/rustdoc-to-org/22686dce5b2144dec7e49990ef259a370bcaf100/demo.gif -------------------------------------------------------------------------------- /filter.lua: -------------------------------------------------------------------------------- 1 | -- Filter to convert rustdoc to org mode 2 | function tablelength(T) 3 | local count = 0 4 | for _ in pairs(T) do count = count + 1 end 5 | return count 6 | end 7 | function table.shallow_copy(t) 8 | local t2 = {} 9 | for k,v in pairs(t) do 10 | t2[k] = v 11 | end 12 | return t2 13 | end 14 | function dump(o) 15 | if type(o) == 'table' then 16 | local s = '{ ' 17 | for k,v in pairs(o) do 18 | if type(k) ~= 'number' then k = '"'..k..'"' end 19 | s = s .. '['..k..'] = ' .. dump(v) .. ',' 20 | end 21 | return s .. '} ' 22 | else 23 | return tostring(o) 24 | end 25 | end 26 | 27 | 28 | Span = function(el) 29 | if el.classes:includes("since") or el.classes:includes("inner") or tablelength(el.content) == 1 then 30 | return pandoc.Null 31 | end 32 | end 33 | 34 | 35 | cleanblocks = { 36 | Str = function(el) 37 | if el.text == "[src]" then 38 | return pandoc.Str(" ") 39 | end 40 | end, 41 | Link = function(el) 42 | return pandoc.Span(el.content) 43 | end, 44 | 45 | Header = function(el) 46 | if el.classes:includes("section-header") then 47 | return pandoc.Null 48 | end 49 | if el.classes:includes("small-section-header") then 50 | return pandoc.Header(1, pandoc.List:new({el.content[1]})) 51 | end 52 | if el.classes:includes("impl") and el.content then 53 | return pandoc.Header(2, pandoc.List:new{el.content[1]}) 54 | end 55 | if el.classes:includes("fqn") and el.level == 1 and el.content and el.content[1].content then 56 | crate = "" 57 | for i,v in ipairs(el.content[1].content) do 58 | if v.content and v.content[1].text then 59 | crate = crate .. v.content[1].text .. "::" 60 | end 61 | end 62 | 63 | return pandoc.Header(1, el.content) 64 | end 65 | if el.classes:includes("hidden") then 66 | return pandoc.Plain(el.content) -- We hide the headlines from search results by making them plain. Maybe the solution can be nicer, need to think about it. 67 | -- return pandoc.Header(4, el.content) 68 | end 69 | 70 | 71 | if el.classes:includes("method") then 72 | local code = el.content[1] 73 | local methodname = "" 74 | local must_use_text = "" 75 | local contains_must_use = false 76 | local in_methodname = true 77 | local in_must_use_text = false 78 | 79 | if string.match(code.text, "must_use") then 80 | in_methodname = false 81 | contains_must_use = true 82 | end 83 | 84 | for i = 1, #code.text do 85 | local c = code.text:sub(i, i); 86 | 87 | if in_methodname then 88 | methodname = methodname .. c 89 | elseif in_must_use_text then 90 | must_use_text = must_use_text .. c 91 | end 92 | 93 | if c == "]" then 94 | in_methodname = true 95 | elseif c == "\"" then 96 | in_must_use_text = true 97 | end 98 | end 99 | 100 | if contains_must_use then 101 | return pandoc.List:new({pandoc.Header(3, pandoc.List:new({pandoc.Code(methodname)})), pandoc.Plain(must_use_text:sub(1, -3))}) 102 | end 103 | return pandoc.Header(3, pandoc.List:new({pandoc.Code(methodname)})) 104 | end 105 | 106 | return pandoc.Header(el.level - 1, el.content) 107 | end, 108 | 109 | Div = function(el) 110 | if el.classes:includes("shortcuts") or el.classes:includes("sidebar-elems") or el.classes:includes("theme-picker") or el.classes:includes("infos") or el.classes:includes("search-container") or el.classes:includes("sidebar-menu") or el.classes:includes("logo-container") or el.classes:includes("toggle-wrapper") then 111 | return pandoc.Null 112 | elseif el.classes:includes("variant") and el.classes:includes("small-section-header") and el.content[1] and tablelength(el.content[1].content) > 1 then 113 | return pandoc.List:new({pandoc.Header(2, pandoc.Code(el.content[1].content[2].text))}) 114 | else 115 | return pandoc.Div(el.content) 116 | end 117 | end, 118 | 119 | Plain = function(el) 120 | for i,v in ipairs(el.content) do 121 | if v.t == "Span" and v.content[1] and v.content[1].t == "Str" and v.content[1].text == "Run" then 122 | return pandoc.Null 123 | end 124 | 125 | if v.t == "Span" and (v.classes:includes("loading-content") or tablelength(v.content) == 0) and tablelength(el.content) == 1 then --bug here! 1 week later: Why did I not explain what the bug was? I have no idea now. 126 | return pandoc.Null 127 | end 128 | 129 | if v.t == "Span" and v.classes:includes("emoji") then 130 | table.remove(el.content, 1) 131 | return pandoc.Plain(el.content) 132 | end 133 | end 134 | end, 135 | 136 | CodeBlock = function(el) 137 | if el.classes:includes("line-numbers") then 138 | return pandoc.Null 139 | else 140 | return pandoc.Para(pandoc.Str("#+BEGIN_SRC rust \n" .. el.text .. "\n#+END_SRC")) 141 | end 142 | end, 143 | 144 | Para = function(el) 145 | if el.content[1] and el.content[1].t == "Span" and tablelength(el.content[1].content) == 0 then 146 | return pandoc.Null 147 | end 148 | end, 149 | 150 | } 151 | 152 | function Pandoc(el) 153 | return pandoc.Pandoc(pandoc.walk_block(pandoc.Div(el.blocks), cleanblocks), pandoc.Meta({})) 154 | end 155 | -------------------------------------------------------------------------------- /rustdoc.el: -------------------------------------------------------------------------------- 1 | ;;; rustdoc.el --- Browse rust documentation as .org files -*- lexical-binding: t -*- 2 | 3 | ;; Copyright (c) 2020 Sam Hedin 4 | 5 | ;; Author: Sam Hedin 6 | ;; Jonas Møller 7 | ;; URL: https://github.com/samhedin/rustdoc-to-org 8 | ;; Version: 0.5 9 | ;; Keywords: docs languages 10 | ;; Package-Requires: ((emacs "26.1") (helm-ag "0.62") (lsp-mode "7.0") (f "0.20.0")) 11 | 12 | ;; This file is NOT part of GNU Emacs. 13 | 14 | ;; MIT License 15 | 16 | ;; Copyright (c) 2020 Sam Hedin 17 | 18 | ;; Permission is hereby granted, free of charge, to any person obtaining a copy 19 | ;; of this software and associated documentation files (the "Software"), to deal 20 | ;; in the Software without restriction, including without limitation the rights 21 | ;; to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 22 | ;; copies of the Software, and to permit persons to whom the Software is 23 | ;; furnished to do so, subject to the following conditions: 24 | 25 | ;; The above copyright notice and this permission notice shall be included in all 26 | ;; copies or substantial portions of the Software. 27 | 28 | ;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 29 | ;; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 30 | ;; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 31 | ;; AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 32 | ;; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 33 | ;; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 34 | ;; SOFTWARE. 35 | 36 | ;;; Commentary: 37 | 38 | ;; This package lets you convert rustdoc html-files to org mode files, and lets you browse them with `rustdoc-search'. 39 | ;; Run `M-x rustdoc-setup' to download the required files and convert the rust standard library. 40 | ;; Run `M-x rustdoc-convert-current-package' to generate and convert docs for the package you are currently visiting. 41 | 42 | ;;; Code: 43 | 44 | (require 'helm-ag) 45 | (require 'url) 46 | (require 'lsp-mode) 47 | (require 'f) 48 | 49 | (if (< emacs-major-version 27) 50 | (defun rustdoc--xdg-data-home () 51 | (or (getenv "XDG_DATA_HOME") 52 | (concat (file-name-as-directory (getenv "HOME")) 53 | ".local/share"))) 54 | (progn 55 | (require 'xdg) 56 | (fset 'rustdoc--xdg-data-home 'xdg-data-home))) 57 | 58 | (defvar rustdoc-lua-filter (concat (file-name-as-directory (getenv "HOME")) 59 | ".local/bin/rustdoc-filter.lua") 60 | "Save location for the rustdoc lua filter.") 61 | 62 | (defvar rustdoc-convert-prog (concat (file-name-as-directory (getenv "HOME")) 63 | ".local/bin/rustdoc-convert.sh") 64 | "Save location for the rustdoc conversion script.") 65 | 66 | (defvar rustdoc-source-user "samhedin") 67 | 68 | (defvar rustdoc-source-repo (format "https://raw.githubusercontent.com/%s/rustdoc-to-org/master/" 69 | rustdoc-source-user)) 70 | 71 | (defvar rustdoc-current-project nil "Location to search for documentation. 72 | All projects and std by default, otherwise last open project and std.") 73 | 74 | (defvar rustdoc-save-loc (concat (rustdoc--xdg-data-home) 75 | "/emacs/rustdoc")) 76 | 77 | (defvar rustdoc-resources `((,rustdoc-convert-prog 78 | (:exec) 79 | ,(concat rustdoc-source-repo "convert.sh")) 80 | (,rustdoc-lua-filter 81 | () 82 | ,(concat rustdoc-source-repo "filter.lua")))) 83 | 84 | (defun rustdoc--install-resources () 85 | "Install or update the rustdoc resources." 86 | (dolist (resource rustdoc-resources) 87 | (pcase resource 88 | (`(,dst ,opts ,src) 89 | (condition-case nil 90 | (progn 91 | (url-copy-file src dst t) 92 | (when (memq :exec opts) 93 | (call-process (executable-find "chmod") 94 | nil 95 | nil 96 | nil 97 | "+x" 98 | dst))) 99 | (error (progn 100 | (if (file-exists-p dst) 101 | (message (format "Could not update %s, using existing one" 102 | dst)) 103 | (error (format "Could not retrieve %s" dst))))))) 104 | (x (error "Invalid resource spec: %s" x))))) 105 | 106 | ;;;###autoload 107 | (defun rustdoc-dumb-search (search-term) 108 | "Search all projects and std for SEARCH-TERM. 109 | Use this when `rustdoc-search' does not find what you're looking for. 110 | Add `universal-argument' to only search level 1 headers. 111 | See `rustdoc-search' for more information." 112 | (interactive (let ((short-name (alist-get 'short-name 113 | (rustdoc--thing-at-point)))) 114 | (list (read-string (format "search term, default %s: " short-name) 115 | nil 116 | nil 117 | short-name)))) 118 | (rustdoc-search search-term t)) 119 | 120 | 121 | ;;;###autoload 122 | (defun rustdoc-search (search-term &optional root) 123 | "Search the rust documentation for SEARCH-TERM. 124 | Only searches in headers (structs, functions, traits, enums, etc) 125 | to limit the number of results. 126 | To limit search results to only level 1 headers, add `universal-argument' 127 | Level 1 headers are things like struct or enum names. 128 | if ROOT is non-nil the search is performed from the root dir. 129 | This function tries to be smart and limits the search results 130 | as much as possible. If it ends up being so smart that 131 | it doesn't manage to find what you're looking for, try `rustdoc-dumb-search'." 132 | (interactive (let ((short-name (alist-get 'short-name 133 | (rustdoc--thing-at-point)))) 134 | (list (read-string (format "search term, default %s: " short-name) 135 | nil 136 | nil 137 | short-name)))) 138 | ;; These helm-ag settings are to make it work properly with ripgrep. 139 | (let* ((helm-ag-base-command (if rustdoc-current-project ; If the user has not visited a project the search will be done from the doc root, in which case we should not follow symlinks. 140 | "rg -L --smart-case --no-heading --color=never --line-number --pcre2" 141 | "rg --smart-case --no-heading --color=never --line-number --pcre2")) 142 | (helm-ag-fuzzy-match t) 143 | (helm-ag-success-exit-status '(0 2)) 144 | (thing-at-point (rustdoc--thing-at-point)) 145 | (short-name (alist-get 'short-name thing-at-point)) 146 | ;; If the user did not accept the default search suggestion, we should not search in that suggestion's directory. 147 | (search-dir 148 | (cond 149 | (root rustdoc-save-loc) 150 | ((string-equal short-name search-term) (alist-get 'search-dir thing-at-point)) 151 | (t (rustdoc--project-doc-dest)))) 152 | ;; If the prefix arg is provided, we only search for level 1 headers by making sure that there is only one * at the beginning of the line. 153 | (regex (if current-prefix-arg 154 | (progn 155 | ;; If current-prefix-arg is not set to nil, helm-ag will pick up the prefix arg too and do funny business. 156 | (setq current-prefix-arg nil) 157 | "^\\*") 158 | "^(?!.*impl)^\\*+")) ; Do not match if it's an impl 159 | ;; This seq-reduce turns `enum option' into (kind of) `enum.*option', which lets there be chars between the searched words 160 | (regexed-search-term (concat regex 161 | ; Regex explanation 162 | ; `-' => Do not match if a return type. A search for Option should not show is_some -> Option 163 | ; `(' => Do not match if it's an argument name. 164 | ; `<' => Do not match if it's a generic type arg 165 | (seq-reduce (lambda (acc s) 166 | (concat acc "[^-\*(<]*" s)) 167 | (split-string search-term " ") 168 | "")))) 169 | (rustdoc--update-current-project) 170 | (unless (file-directory-p rustdoc-save-loc) 171 | (rustdoc-setup) 172 | (message "Running first time setup. Please re-run your search once conversion has completed.") 173 | (sleep-for 3)) 174 | ;; If the user has not run `rustdoc-convert-current-package' in the current project, we create a default directory that only contains a symlink to std. 175 | (unless (file-directory-p (rustdoc--project-doc-dest)) 176 | (rustdoc-create-project-dir)) 177 | (condition-case nil 178 | (helm-ag search-dir regexed-search-term) 179 | ;; If the search didn't turn anything up we re-run the search in the top level searchdir. 180 | (error (helm-ag rustdoc-save-loc regexed-search-term))))) 181 | 182 | (defun rustdoc--update-current-project () 183 | "Update `rustdoc-current-project' if editing a rust file, otherwise leave it." 184 | (when (and lsp-mode 185 | (derived-mode-p 'rust-mode 'rustic-mode)) 186 | (setq rustdoc-current-project (lsp-workspace-root)))) 187 | 188 | (defun rustdoc--deepest-dir (path) 189 | "Find the deepest existing and non-empty arg-directory parent of PATH. 190 | We can sometimes infer the filepath from the crate name. 191 | E.g the enum std::option::Option is in the folder std/option. 192 | Some filepaths can not be inferred properly, seemingly because of 193 | URL `https://github.com/rust-lang/rust/issues/21934'. 194 | In these cases, the deepest dir will be the current project dir." 195 | (if (and (file-exists-p path) 196 | (file-directory-p path) 197 | (not (f-empty-p path))) 198 | path 199 | (rustdoc--deepest-dir (f-slash (f-dirname path))))) 200 | 201 | (defun rustdoc--project-doc-dest () 202 | "The location of the documentation for the current or last seen project. 203 | If the user has not visited a project, returns the main doc directory." 204 | (if rustdoc-current-project 205 | (f-join rustdoc-save-loc 206 | (f-filename rustdoc-current-project)) 207 | rustdoc-save-loc)) 208 | 209 | ;;;###autoload 210 | (defun rustdoc-create-project-dir () 211 | "Create a rustdoc arg-directory for the current project. Link with std." 212 | (let* ((link-tgt (concat (file-name-as-directory (rustdoc--xdg-data-home)) 213 | "emacs/rustdoc/std")) 214 | (link-name (concat (rustdoc--project-doc-dest) 215 | "/std")) 216 | (current-doc-dest (rustdoc--project-doc-dest))) 217 | (if current-doc-dest 218 | (progn 219 | (make-directory (rustdoc--project-doc-dest) 220 | t) 221 | (make-symbolic-link link-tgt link-name t)) 222 | (message "Couldn't create project doc directory.")))) 223 | 224 | 225 | ;;;###autoload 226 | (defun rustdoc-convert-current-package () 227 | "Convert the documentation for a project and its dependencies." 228 | (interactive) 229 | (unless (file-directory-p rustdoc-save-loc) 230 | (rustdoc-setup) 231 | (message "Running first time setup.") 232 | (sleep-for 3)) 233 | (if rustdoc-current-project 234 | (progn 235 | (message "Converting documentation for %s " 236 | rustdoc-current-project) 237 | (if (/= 0 (call-process "cargo" nil "*cargo-makedocs*" nil "makedocs")) 238 | (message "cargo makedocs could not generate docs for the current package. See buffer *cargo-makedocs* for more info") 239 | (let* ((docs-src (concat (file-name-as-directory rustdoc-current-project) 240 | "target/doc")) 241 | ;; FIXME: Many projects could share the same docs. 242 | ;; *However* that would have to be versioned, so 243 | ;; we'll have to figure out a way to coerce `-` 244 | ;; strings out of cargo, or just parse the Cargo.toml file, but 245 | ;; then we'd have to review different parsing solutions. 246 | (finish-func (lambda (_p) 247 | (message (format "Finished converting docs for %s" 248 | rustdoc-current-project))))) 249 | (rustdoc-create-project-dir) 250 | (async-start-process "rustdoc-convert" 251 | rustdoc-convert-prog 252 | finish-func 253 | docs-src 254 | (rustdoc--project-doc-dest))))) 255 | (message "Could not find project to convert. Visit a rust project first! (Or activate rustdoc-mode if you are in one)"))) 256 | 257 | ;;;###autoload 258 | (defun rustdoc-setup () 259 | "Setup or update rustdoc filter and convert script. Convert std." 260 | (interactive) 261 | (rustdoc--install-resources) 262 | (message "Setup is converting the standard library") 263 | (delete-directory (concat rustdoc-save-loc "/std") 264 | t) 265 | (async-start-process "*rustdoc-std-conversion*" 266 | rustdoc-convert-prog 267 | (lambda (_p) 268 | (message "Finished converting docs for std")) 269 | "std")) 270 | 271 | (defun rustdoc--thing-at-point () 272 | "Return info about `thing-at-point'. If `thing-at-point' is nil, return defaults." 273 | (if-let ((active lsp-mode) 274 | (lsp-content (-some->> (lsp--text-document-position-params) 275 | (lsp--make-request "textDocument/hover") 276 | (lsp--send-request) 277 | (lsp:hover-contents))) 278 | ;; `short-name' is the unqalified of a struct, function etc, like `Option' 279 | (short-name (thing-at-point 'symbol t)) 280 | ;; If symbol at point is a primitive, the `value' key is different than in most cases. 281 | ;; If it is a primitive, we concat the name with primitive for searching. 282 | (lsp-info (or (nth 1 283 | (split-string (gethash "value" lsp-content))) 284 | (setq short-name (concat "primitive " 285 | (gethash "value" lsp-content))))) 286 | ;; If short-name was `Option', long-name would be `std::option::Option' 287 | (long-name (concat (cond 288 | ((string-prefix-p "core" lsp-info) 289 | (concat "std" 290 | (seq-drop lsp-info 4))) 291 | ((string-prefix-p "alloc" lsp-info) 292 | (concat "std" 293 | (seq-drop lsp-info 5))) 294 | (t lsp-info)) 295 | "::" 296 | short-name)) 297 | (search-dir (rustdoc--deepest-dir (concat (rustdoc--project-doc-dest) 298 | "/" 299 | (seq-reduce (lambda (path p) 300 | (concat path "/" p)) 301 | (split-string long-name "::") ""))))) 302 | `((search-dir . ,search-dir) 303 | (short-name . ,short-name)) 304 | `((search-dir . ,(rustdoc--project-doc-dest)) 305 | (short-name . ,nil)))) 306 | 307 | ;;;###autoload 308 | (define-minor-mode rustdoc-mode 309 | "Convert rust html docs to .org, and browse the converted docs." 310 | :lighter " browse rust documentation" 311 | :keymap (let ((map (make-sparse-keymap))) 312 | (define-key map (kbd "C-#") 'rustdoc-search) 313 | map) 314 | (dolist (mode '(rust-mode-hook rustic-mode-hook org-mode-hook)) 315 | (add-hook mode 'rustdoc-mode)) 316 | (rustdoc--update-current-project)) 317 | 318 | (provide 'rustdoc) 319 | 320 | ;;; rustdoc.el ends here 321 | -------------------------------------------------------------------------------- /test/rustdoc-to-org-test.el: -------------------------------------------------------------------------------- 1 | ;;; rustdoc-to-org-test.el --- Tests for rustdoc-to-org -*- lexical-binding: t -*- 2 | 3 | ;; Copyright (c) 2020 Sam Hedin 4 | 5 | ;; Author: Sam Hedin 6 | ;; URL: https://github.com/samhedin/rustdoc 7 | ;; Version: 0.5 8 | ;; Keywords: docs languages 9 | ;; Package-Requires: ((emacs "25.1") (helm-ag "0.62")) 10 | 11 | ;; This file is NOT part of GNU Emacs. 12 | 13 | ;; MIT License 14 | 15 | ;; Copyright (c) 2020 Sam Hedin 16 | 17 | ;; Permission is hereby granted, free of charge, to any person obtaining a copy 18 | ;; of this software and associated documentation files (the "Software"), to deal 19 | ;; in the Software without restriction, including without limitation the rights 20 | ;; to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 21 | ;; copies of the Software, and to permit persons to whom the Software is 22 | ;; furnished to do so, subject to the following conditions: 23 | 24 | ;; The above copyright notice and this permission notice shall be included in all 25 | ;; copies or substantial portions of the Software. 26 | 27 | ;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 28 | ;; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 29 | ;; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 30 | ;; AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 31 | ;; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 32 | ;; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 33 | ;; SOFTWARE. 34 | 35 | ;;; Commentary: 36 | 37 | ;; Tests for rustdoc 38 | 39 | ;;; Code: 40 | 41 | (require 'rustdoc (file-truename "rustdoc.el")) 42 | 43 | 44 | ;;; rustdoc-to-org-test.el ends here 45 | -------------------------------------------------------------------------------- /test/test-helper.el: -------------------------------------------------------------------------------- 1 | ;;; test-helper.el --- Helpers for rustdoc-to-org-test.el 2 | 3 | ;;; test-helper.el ends here 4 | --------------------------------------------------------------------------------