├── lean-toolchain ├── .gitignore ├── lake-manifest.json ├── LeanSlides └── Init.lean ├── lakefile.lean ├── Demo.lean ├── README.md ├── LeanSlides.lean └── LICENSE /lean-toolchain: -------------------------------------------------------------------------------- 1 | leanprover/lean4:v4.27.0-rc1 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | /lake-packages/* 3 | /md 4 | /slides 5 | /.lake/* 6 | -------------------------------------------------------------------------------- /lake-manifest.json: -------------------------------------------------------------------------------- 1 | {"version": "1.1.0", 2 | "packagesDir": ".lake/packages", 3 | "packages": 4 | [{"url": "https://github.com/leanprover-community/ProofWidgets4", 5 | "type": "git", 6 | "subDir": null, 7 | "scope": "leanprover-community", 8 | "rev": "ef8377f31b5535430b6753a974d685b0019d0681", 9 | "name": "proofwidgets", 10 | "manifestFile": "lake-manifest.json", 11 | "inputRev": "v0.0.84", 12 | "inherited": false, 13 | "configFile": "lakefile.lean"}], 14 | "name": "«lean-slides»", 15 | "lakeDir": ".lake"} 16 | -------------------------------------------------------------------------------- /LeanSlides/Init.lean: -------------------------------------------------------------------------------- 1 | import Lean.Elab.Command 2 | 3 | open Lean 4 | 5 | namespace LeanSlides 6 | 7 | initialize pandocOptions : IO.Ref (Array String) ← IO.mkRef {} 8 | 9 | elab "#add_pandoc_option" s:str : command => 10 | pandocOptions.modify (·.push s.getString) 11 | 12 | elab "#set_pandoc_options" s:str* : command => 13 | pandocOptions.set <| s.map (·.getString) 14 | 15 | elab "#clear_pandoc_options" : command => 16 | pandocOptions.set #[] 17 | 18 | register_option leanSlides.cache_slides : Bool := { 19 | defValue := false, 20 | descr := "Cache generated slides to avoid unnecessary recomputation." 21 | } 22 | 23 | end LeanSlides 24 | -------------------------------------------------------------------------------- /lakefile.lean: -------------------------------------------------------------------------------- 1 | import Lake 2 | open Lake DSL 3 | 4 | package «lean-slides» { 5 | precompileModules := true 6 | } 7 | 8 | @[default_target] 9 | lean_lib LeanSlides where 10 | 11 | @[default_target] 12 | lean_exe «lean-slides» { 13 | root := `LeanSlides 14 | } 15 | 16 | require "leanprover-community" / "proofwidgets" @ git "v0.0.84" 17 | 18 | section Scripts 19 | 20 | def getPort : IO String := do 21 | let defaultPort := 3000 22 | match ← IO.getEnv "LEANSLIDES_PORT" with 23 | | some port => return port 24 | | none => do 25 | IO.println "Could not find `LEANSLIDES_PORT` variable in environment" 26 | IO.println s!"Using default port {defaultPort} instead ..." 27 | return toString defaultPort 28 | 29 | def slidesDir : IO System.FilePath := 30 | return (← IO.currentDir) / "slides" |>.normalize 31 | 32 | script «serve-slides» do 33 | IO.println "Starting HTTP server for `Lean Slides` ..." 34 | let slidesDir ← slidesDir 35 | unless (← slidesDir.pathExists) do 36 | IO.println s!"Creating slides directory at {slidesDir} ..." 37 | IO.FS.createDir slidesDir 38 | IO.println s!"Serving slides ..." 39 | if System.Platform.isWindows then 40 | IO.eprintln "This tool is not yet supported on Windows." 41 | return 1 42 | else 43 | let _stdioCfg ← IO.Process.spawn { 44 | cmd := "browser-sync", 45 | args := #["slides", "--port", ← getPort, 46 | "--watch", "--no-open"] 47 | } 48 | return 0 49 | 50 | end Scripts 51 | -------------------------------------------------------------------------------- /Demo.lean: -------------------------------------------------------------------------------- 1 | import LeanSlides 2 | 3 | #set_pandoc_options "-V" "revealjs-url=https://unpkg.com/reveal.js@5.2.0" 4 | 5 | #slides Introduction /-! 6 | % Lean Slides 7 | % https://github.com/0art0/lean-slides/ 8 | % Today 9 | 10 | # About 11 | 12 | `Lean Slides` is a tool to 13 | automatically generate `reveal.js` slides 14 | from Markdown comments in the Lean editor. 15 | 16 | # Use cases 17 | 18 | `Lean Slides` can be used in 19 | tutorials or demonstrations 20 | to avoid switching between 21 | the slides and the Lean editor. 22 | -/ 23 | 24 | -- Some intervening Lean code 25 | example : 1 = 1 := by 26 | rfl 27 | 28 | #slides Tool /-! 29 | # Dependencies 30 | 31 | `Lean Slides` works by combining together several tools: 32 | 33 | - [`reveal.js`](https://revealjs.com/) 34 | 35 | - [`pandoc`](https://pandoc.org/) 36 | 37 | - [`node.js`](https://nodejs.org/en) 38 | 39 | - [`http-server`](https://www.npmjs.com/package/http-server) 40 | 41 | # Usage 42 | 43 | To use `Lean Slides`, first install all the dependencies 44 | and start the HTTP server with the command 45 | ```lean 46 | lake run lean-slides/serve-slides 47 | ``` 48 | 49 | --- 50 | 51 | In any file that imports `Lean Slides`, type 52 | 53 | ```lean 54 | #slides +draft Test /-! 55 | 56 | -/ 57 | ``` 58 | 59 | # Features 60 | 61 | `Lean Slides` turns comments written in the above format 62 | into `reveal.js` slides which are rendered in the infoview 63 | as a [`Widget`](https://github.com/EdAyers/ProofWidgets4). 64 | 65 | The tool also features a code action to 66 | go in and out of draft mode. 67 | -/ 68 | 69 | -- Some more intervening Lean code 70 | #check Nat.succ 71 | 72 | #slides +draft Math /-! 73 | # Rendering math 74 | 75 | The generated `reveal.js` slides 76 | render mathematics by default 77 | using $\KaTeX$. 78 | 79 | -/ 80 | 81 | #set_pandoc_options "-V" "theme=white" 82 | 83 | #slides Options /-! 84 | # A test for pandoc options. 85 | 86 | This should use the white theme. 87 | -/ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Lean Slides 2 | 3 | `Lean Slides` is a tool to 4 | automatically generate `reveal.js` slides 5 | from Markdown comments in the Lean editor. 6 | 7 | ![LeanSlides](https://github.com/0art0/lean-slides/assets/18333981/29029c7b-f586-45a1-b203-ffdc66a41049) 8 | 9 | See `Demo.lean` for more details. 10 | 11 | # Dependencies 12 | 13 | `Lean Slides` works by combining together several tools: 14 | 15 | - [`reveal.js`](https://revealjs.com/) (no install required) 16 | 17 | - [`pandoc`](https://pandoc.org/) (commit >= `7c6dbd3`) 18 | 19 | - [`node.js`](https://nodejs.org/en) 20 | 21 | - [`browser-sync`](https://browsersync.io/) 22 | 23 | Note that `LeanSlides` may have issues with older versions of `pandoc`. 24 | A manual intervention using 25 | ```lean 26 | #set_pandoc_options "-V" "revealjs-url=\"https://unpkg.com/reveal.js@^4/\"" 27 | ``` 28 | may fix the issues in such cases. 29 | 30 | # Usage 31 | 32 | To use `Lean Slides`, first install all the dependencies listed above. 33 | 34 | `Lean Slides` can be added to an existing Lean repository 35 | by inserting the following lines in the `lakefile.toml` file: 36 | 37 | ```toml 38 | [[require]] 39 | name = "lean-slides" 40 | git = "https://github.com/0art0/lean-slides.git" 41 | ``` 42 | 43 | If the repository uses a `lakefile.lean` instead, try: 44 | 45 | ```lean 46 | require «lean-slides» from git "https://github.com/0art0/lean-slides"@"master" 47 | ``` 48 | 49 | --- 50 | 51 | In any file that imports `LeanSlides`, type 52 | 53 | ```lean 54 | #slides +draft Test /-! 55 | 56 | -/ 57 | ``` 58 | 59 | **Run `lake run lean-slides/serve-slides` from the command line 60 | to start the HTTP server for the slides.** 61 | 62 | Any slides that are not in draft mode should now be rendered. 63 | 64 | The port used by `Lean Slides` can be modified through 65 | an environment variable with the name `LEANSLIDES_PORT`. 66 | 67 | # Features 68 | 69 | `Lean Slides` turns comments written in the above format 70 | into `reveal.js` slides which are rendered in the infoview 71 | as a [`Widget`](https://github.com/EdAyers/ProofWidgets4). 72 | 73 | The tool also features a code action to 74 | go in and out of draft mode. 75 | 76 | The generated `reveal.js` slides 77 | render mathematics by default 78 | using `KaTeX`. 79 | 80 | ## Custom styling 81 | 82 | To enable custom CSS styling for the `reveal.js` presentation, insert a file named `style.css` in the `slides` folder (which is usually automatically generated in the home directory of your project by `Lean Slides`). A sample style sheet is shown below. 83 | 84 | ```css 85 | .reveal h1, 86 | .reveal h2, 87 | .reveal h3, 88 | .reveal h4, 89 | .reveal h5, 90 | .reveal h6 { 91 | text-transform: none; 92 | } 93 | 94 | .reveal code { 95 | background-color: #5b5b5b; 96 | padding: 0.1em 0.2em; 97 | border-radius: 6px; 98 | } 99 | 100 | .reveal pre code { 101 | background-color: #5b5b5b; 102 | display: block; 103 | padding: 1em; 104 | border-radius: 6px; 105 | overflow: auto; 106 | } 107 | ``` 108 | -------------------------------------------------------------------------------- /LeanSlides.lean: -------------------------------------------------------------------------------- 1 | import ProofWidgets.Component.HtmlDisplay 2 | import LeanSlides.Init 3 | 4 | open Lean ProofWidgets Elab Parser Command Server System 5 | 6 | section Utils 7 | 8 | def markdownDir : IO System.FilePath := return ((← IO.currentDir) / "md").normalize 9 | def slidesDir : IO System.FilePath := return ((← IO.currentDir) / "slides").normalize -- this must be in sync with the `slidesDir` in the `lakefile` 10 | 11 | def getServerPort : IO String := do 12 | match ← IO.getEnv "LEANSLIDES_PORT" with 13 | | some port => return port 14 | | none => return "3000" 15 | 16 | def getServerUrl : IO String := do 17 | let url := s!"http://localhost:{← getServerPort}/" 18 | try 19 | let out ← IO.Process.output { cmd := "curl", args := #[url] } 20 | if out.exitCode != 0 then 21 | IO.eprintln "The server for `Lean Slides` is not running." 22 | IO.eprintln "It can be started using the command `lake run lean-slides/serve-slides`." 23 | catch err => IO.eprintln err 24 | return url 25 | 26 | def System.FilePath.getRelativePath (filePath : FilePath) : String := 27 | if filePath.isRelative then 28 | filePath.normalize.toString.dropWhile (· ≠ FilePath.pathSeparator) |>.toString 29 | else 30 | panic! s!"The file path {filePath} is not a relative path." 31 | 32 | def extractModuleDocContent : TSyntax ``moduleDoc → String 33 | | ⟨.node _ _ #[_, .atom _ doc]⟩ => doc.dropEnd 2 |>.toString 34 | | _ => panic! "Ill-formed module docstring." 35 | 36 | def createMarkdownFile (title text : String) : IO FilePath := do 37 | let markdownDir ← markdownDir 38 | let mdFile := markdownDir / (title ++ ".md") 39 | unless ← markdownDir.pathExists do 40 | IO.FS.createDir markdownDir 41 | IO.FS.writeFile mdFile text 42 | return mdFile 43 | 44 | def runPandoc (mdFile : FilePath) : IO FilePath := do 45 | let markdownDir ← markdownDir 46 | let slidesDir ← slidesDir 47 | 48 | unless (← mdFile.pathExists) && mdFile.extension = some "md" do 49 | IO.throwServerError s!"The file {mdFile} is not a valid Markdown file." 50 | unless mdFile.parent = some markdownDir do 51 | IO.throwServerError s!"The file {mdFile} is not in the directory {markdownDir}." 52 | 53 | let htmlFile : FilePath := slidesDir / (mdFile.fileStem.get! ++ ".html") 54 | unless ← slidesDir.pathExists do 55 | IO.FS.createDir slidesDir 56 | let styleSheet ← do 57 | if ← (slidesDir / "style.css").pathExists then 58 | pure #["--css=" ++ (← getServerUrl) ++ "style.css"] 59 | else pure #[] 60 | let out ← IO.Process.run { 61 | cmd := "pandoc", 62 | args := #["-s", "--katex", 63 | "-t", "revealjs"] ++ 64 | (← LeanSlides.pandocOptions.get) ++ 65 | styleSheet ++ 66 | [ mdFile.toString, 67 | "-o", htmlFile.toString] 68 | } 69 | IO.println out 70 | return htmlFile 71 | 72 | open scoped ProofWidgets.Jsx in 73 | def iframeComponent (url : String) := 74 |