diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..dccd411 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1 @@ +* @sotte diff --git a/.github/ISSUE_TEMPLATE/Bug_report.md b/.github/ISSUE_TEMPLATE/Bug_report.md new file mode 100644 index 0000000..a2fdb5f --- /dev/null +++ b/.github/ISSUE_TEMPLATE/Bug_report.md @@ -0,0 +1,24 @@ +--- +name: Bug report +about: It helps making the plugin more stable. +--- + +## Description + + + +## Steps to reproduce + +1. I did the command `...` +2. Then I saw `...` +3. Error + +## Expected behavior + + + +## Environment + +- Neovim version: [e.g. 0.8.x / 0.9.x / Nightly] +- presinting.nvim version: [e.g. latest / 0.1.2 / dev] +- Plugin clash: [e.g. Telescope float window / lsp diagnostic] diff --git a/.github/ISSUE_TEMPLATE/Feature_request.md b/.github/ISSUE_TEMPLATE/Feature_request.md new file mode 100644 index 0000000..8407bfa --- /dev/null +++ b/.github/ISSUE_TEMPLATE/Feature_request.md @@ -0,0 +1,12 @@ +--- +name: Feature request +about: Suggest anything that would make your life easier, or the plugin better. +--- + +## Describe the problem + + + +## Describe the solution + + diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..b5d9f9b --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,12 @@ +## πŸ“ƒ Summary + + + +## πŸ“Έ Preview + + + +## πŸ“ Checklist + +- [ ] I ran `make all` locally and checked for errors. +- [ ] I have updated the `CHANGELOG.md` file. diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000..378edf4 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,102 @@ +name: main + +on: + push: + branches: [main] + pull_request: + types: [opened, synchronize] + +concurrency: + group: github.head_ref + cancel-in-progress: true + +jobs: + lint: + runs-on: ubuntu-latest + name: lint + steps: + - uses: actions/checkout@v3 + + - uses: JohnnyMorganz/stylua-action@v2 + with: + token: ${{ secrets.GITHUB_TOKEN }} + version: latest + args: --check . + + documentation: + runs-on: ubuntu-latest + name: documentation + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 2 + + - name: setup neovim + uses: rhysd/action-setup-vim@v1 + with: + neovim: true + version: v0.8.3 + + - name: generate documentation + run: make docs + + - name: check docs diff + run: exit $(git status --porcelain doc | wc -l | tr -d " ") + + + # tests: + # TODO: enable me again once there are tests + # needs: + # - lint + # - documentation + # runs-on: ubuntu-latest + # timeout-minutes: 2 + # strategy: + # matrix: + # neovim_version: ['v0.9.0', 'v0.9.1', 'nightly'] + # + # steps: + # - uses: actions/checkout@v3 + # + # - run: date +%F > todays-date + # + # - name: restore cache for today's nightly. + # uses: actions/cache@v3 + # with: + # path: _neovim + # key: ${{ runner.os }}-x64-${{ hashFiles('todays-date') }} + # + # - name: setup neovim + # uses: rhysd/action-setup-vim@v1 + # with: + # neovim: true + # version: ${{ matrix.neovim_version }} + # + # - name: run tests + # run: make test + + release: + name: release + if: ${{ github.ref == 'refs/heads/main' }} + # needs: + # - tests + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - uses: google-github-actions/release-please-action@v3 + id: release + with: + release-type: simple + package-name: presenting.nvim + + - name: tag stable versions + if: ${{ steps.release.outputs.release_created }} + run: | + git config user.name github-actions[bot] + git config user.email github-actions[bot]@users.noreply.github.com + git remote add gh-token "https://${{ secrets.GITHUB_TOKEN }}@github.com/google-github-actions/release-please-action.git" + git tag -d stable || true + git push origin :stable || true + git tag -a stable -m "Last Stable Release" + git push origin stable diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2178fe2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/deps/ +/doc/tags diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..c4d1313 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,21 @@ +repos: +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v2.3.0 + hooks: + - id: check-yaml +- repo: https://github.com/JohnnyMorganz/StyLua + rev: v0.19.1 + hooks: + - id: stylua-system +- repo: local + hooks: + - id: generate-docs + name: generate-docs + entry: make docs + language: system + pass_filenames: false + - id: update-readmes + name: update-readmes + entry: make update-readmes + language: system + pass_filenames: false diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..0371794 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,23 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + + +## [Unreleased] +### Added +### Changed +### Fixed +### Removed + + +## [0.1.0] - 2023-12-30 + +### Added +- Release first basic version + +### Changed +### Fixed +### Removed diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..74d900f --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..9f38a1c --- /dev/null +++ b/Makefile @@ -0,0 +1,62 @@ +################################################################################ +# DEFINES +.DEFAULT_GOAL:=help +SHELL:=/bin/bash + +PROJECT_NAME:=presenting.nvim +COMMIT:=$(shell git rev-parse --short HEAD) + +################################################################################ +##@ Maintenance +.PHONY: all +all: update-readmes lint docs pre-commit ## Runs all the targets + +.PHONY: docs +docs: deps ## generates the docs. + nvim --headless --noplugin -u ./scripts/minimal_init.lua -c "lua require('mini.doc').generate()" -c "qa!" + +.PHONY: lint +lint: deps ## performs a lint check and fixes issue if possible, following the config in `stylua.toml`. + stylua . + +.PHONY: update-readmes +update-readmes: README.org README.adoc ## Update READMEs from README.md + +.PHONY: pre-commit +pre-commit: ## Run pre-commit on all + pre-commit run --all + +.PHONY: test +test: deps ## Runs all tests + echo "TODO: implement tests" + # nvim --version | head -n 1 && echo '' + # nvim --headless --noplugin -u ./scripts/minimal_init.lua \ + # -c "lua require('mini.test').setup()" \ + # -c "lua MiniTest.run({ execute = { reporter = MiniTest.gen_reporter.stdout({ group_depth = 1 }) } })" + + +################################################################################ +deps: ## installs `mini.nvim`, used for both the tests and documentation. + @mkdir -p deps + git clone --depth 1 https://github.com/echasnovski/mini.nvim deps/mini.nvim + +README.org: README.md + pandoc -f markdown -t org -o $@ $^ + +README.adoc: README.md + pandoc -f markdown -t asciidoc -o $@ $^ + +################################################################################ +##@ Helpers +.PHONY: clean +clean: ## Cleanup the project folders + rm -rf deps + rm -rf docs + +.PHONY: help +help: ## Display this help + @echo "Welcome to $$(tput bold)${PROJECT_NAME}$$(tput sgr0) πŸ₯³πŸ“ˆπŸŽ‰" + @echo "" + @echo "To get started:" + @echo " >>> $$(tput bold)make all$$(tput sgr0)" + @awk 'BEGIN {FS = ":.*##"; printf "\033[36m\033[0m"} /^[a-zA-Z_-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST) diff --git a/README.adoc b/README.adoc new file mode 100644 index 0000000..d5b8cac --- /dev/null +++ b/README.adoc @@ -0,0 +1,99 @@ +== `presenting.nvim` + +`presenting.nvim` is a neovim plugin that turns your markup files into +slides (in neovim). + +It is rewrite of +https://github.com/sotte/presenting.vim/[`presenting.vim`] in lua. It +simplifies the code (and removes some features). `presenting.vim` is a +clone of https://github.com/pct/present.vim[`present.vim`] which is a +clone of https://github.com/sorah/presen.vim[`presen.vim`]. + +== Usage + +With `presenting.nvim` installed and configured (see section below) open +a markdown/org/adoc file, then start the presentation using global +`Presenting` lua object: + +.... +:lua Presenting.toggle() +.... + +or using the `Presenting` user command: + +.... +:Presenting +.... + +Then navigate the presentation with the keys: - _q_: quit presentation +mode - _n_: next slide - _p_: previous slide - _f_: first slide - _l_: +last slide + +=== Usage - I want to know more + +This `README` is intentionally short. For more information, see +`:help presenting.nvim` and the +https://github.com/sotte/presenting.nvim/doc/presenting.txt[documentation]. + +== Installation and Setup + +With `lazy.nvim`: + +[source,lua] +---- +return { + "sotte/presenting.nvim", + opts = { + -- fill in your options here + -- see :help Presenting.config + }, + cmd = { "Presenting" }, +} +---- + +== Tips + +😎 Directly start a presentation from the CLI: + +[source,bash] +---- +nvim -c Presenting README.md +nvim -c Presenting README.org +nvim -c Presenting README.adoc +---- + +πŸ”¬ Zoom in with your terminal emulator to make the slides *bigger*. + +== DevMode + +There is a dev mode that unloads+reloads the plugin which is handy if +you’re developing the plugin. + +.... +PresentingDevMode +.... + +== Demo of markup elements + +I use this README as a demo of the markup elements. The following +sections don’t have to do anything with the plugin itself. + +=== Markup and Lists + +*bolb* and _italic_ and _italic_ and `code` and [line-through]*strike* +and mark and underline + +* list +* list +** list +** list + +=== Code block + +[source,python] +---- +def fib(n: int) -> int: + if n < 2: + return n + return fib(n - 1) + fib(n - 2) +---- diff --git a/README.md b/README.md new file mode 100644 index 0000000..bbfc557 --- /dev/null +++ b/README.md @@ -0,0 +1,102 @@ +# `presenting.nvim` + +`presenting.nvim` is a neovim plugin that turns your markup +files into slides (in neovim). + +It is rewrite of [`presenting.vim`](https://github.com/sotte/presenting.vim/) in lua. +It simplifies the code (and removes some features). +`presenting.vim` is a clone of [`present.vim`](https://github.com/pct/present.vim) +which is a clone of [`presen.vim`](https://github.com/sorah/presen.vim). + +# Usage + +With `presenting.nvim` installed and configured (see section below) +open a markdown/org/adoc file, +then start the presentation using global `Presenting` lua object: +``` +:lua Presenting.toggle() +``` +or using the `Presenting` user command: +``` +:Presenting +``` + +Then navigate the presentation with the keys: +- *q*: quit presentation mode +- *n*: next slide +- *p*: previous slide +- *f*: first slide +- *l*: last slide + + +## Usage - I want to know more + +This `README` is intentionally short. +For more information, +see `:help presenting.nvim` +and the [documentation](https://github.com/sotte/presenting.nvim/doc/presenting.txt). + + +# Installation and Setup + +With `lazy.nvim`: +```lua +return { + "sotte/presenting.nvim", + opts = { + -- fill in your options here + -- see :help Presenting.config + }, + cmd = { "Presenting" }, +} +``` + + +# Tips + +😎 Directly start a presentation from the CLI: + +```bash +nvim -c Presenting README.md +nvim -c Presenting README.org +nvim -c Presenting README.adoc +``` + +πŸ”¬ Zoom in with your terminal emulator +to make the slides **bigger**. + + +# DevMode + +There is a dev mode that unloads+reloads the plugin +which is handy if you're developing the plugin. + +```` +PresentingDevMode +```` + + +# Demo of markup elements + +I use this README as a demo of the markup elements. +The following sections don't have to do anything with the plugin itself. + + +## Markup and Lists + +**bolb** and *italic* and _italic_ and `code` +and ~~strike~~ and mark and underline + +- list +- list + - list + - list + +## Code block + +```python +def fib(n: int) -> int: + if n < 2: + return n + return fib(n - 1) + fib(n - 2) +``` diff --git a/README.org b/README.org new file mode 100644 index 0000000..80f9d53 --- /dev/null +++ b/README.org @@ -0,0 +1,124 @@ +* =presenting.nvim= + :PROPERTIES: + :CUSTOM_ID: presenting.nvim + :END: + +=presenting.nvim= is a neovim plugin that turns your markup files into +slides (in neovim). + +It is rewrite of +[[https://github.com/sotte/presenting.vim/][=presenting.vim=]] in lua. +It simplifies the code (and removes some features). =presenting.vim= is +a clone of [[https://github.com/pct/present.vim][=present.vim=]] which +is a clone of [[https://github.com/sorah/presen.vim][=presen.vim=]]. + +* Usage + :PROPERTIES: + :CUSTOM_ID: usage + :END: + +With =presenting.nvim= installed and configured (see section below) open +a markdown/org/adoc file, then start the presentation using global +=Presenting= lua object: + +#+BEGIN_EXAMPLE + :lua Presenting.toggle() +#+END_EXAMPLE + +or using the =Presenting= user command: + +#+BEGIN_EXAMPLE + :Presenting +#+END_EXAMPLE + +Then navigate the presentation with the keys: - /q/: quit presentation +mode - /n/: next slide - /p/: previous slide - /f/: first slide - /l/: +last slide + +** Usage - I want to know more + :PROPERTIES: + :CUSTOM_ID: usage---i-want-to-know-more + :END: + +This =README= is intentionally short. For more information, see +=:help presenting.nvim= and the +[[https://github.com/sotte/presenting.nvim/doc/presenting.txt][documentation]]. + +* Installation and Setup + :PROPERTIES: + :CUSTOM_ID: installation-and-setup + :END: + +With =lazy.nvim=: + +#+BEGIN_EXAMPLE + return { + "sotte/presenting.nvim", + opts = { + -- fill in your options here + -- see :help Presenting.config + }, + cmd = { "Presenting" }, + } +#+END_EXAMPLE + +* Tips + :PROPERTIES: + :CUSTOM_ID: tips + :END: + +😎 Directly start a presentation from the CLI: + +#+BEGIN_SRC sh + nvim -c Presenting README.md + nvim -c Presenting README.org + nvim -c Presenting README.adoc +#+END_SRC + +πŸ”¬ Zoom in with your terminal emulator to make the slides *bigger*. + +* DevMode + :PROPERTIES: + :CUSTOM_ID: devmode + :END: + +There is a dev mode that unloads+reloads the plugin which is handy if +you're developing the plugin. + +#+BEGIN_EXAMPLE + PresentingDevMode +#+END_EXAMPLE + +* Demo of markup elements + :PROPERTIES: + :CUSTOM_ID: demo-of-markup-elements + :END: + +I use this README as a demo of the markup elements. The following +sections don't have to do anything with the plugin itself. + +** Markup and Lists + :PROPERTIES: + :CUSTOM_ID: markup-and-lists + :END: + +*bolb* and /italic/ and /italic/ and =code= and +strike+ and mark and +underline + +- list +- list + + - list + - list + +** Code block + :PROPERTIES: + :CUSTOM_ID: code-block + :END: + +#+BEGIN_SRC python + def fib(n: int) -> int: + if n < 2: + return n + return fib(n - 1) + fib(n - 2) +#+END_SRC diff --git a/doc/presenting.txt b/doc/presenting.txt new file mode 100644 index 0000000..8bcf85e --- /dev/null +++ b/doc/presenting.txt @@ -0,0 +1,174 @@ +============================================================================== +------------------------------------------------------------------------------ +*presenting.nvim* +*Presenting* + +MIT License Copyright (c) 2024 Stefan Otte + +============================================================================== + +Present your markdown, org-mode, or asciidoc files in a nice way, +i.e. directly in nvim. + +------------------------------------------------------------------------------ + *Presenting.setup()* + `Presenting.setup`({config}) +Module setup + +Parameters ~ +{config} `(table|nil)` +Usage ~ +`require('presenting').setup({})` + +------------------------------------------------------------------------------ + *Presenting.config* + `Presenting.config` +Module config + +Default values: +> + Presenting.config = { + options = { + width = 60, + }, + separator = { + -- Note: separators are lua patterns, not regexes. + markdown = "^#+ ", + org = "^*+ ", + adoc = "^==+ ", + asciidoctor = "^==+ ", + }, + keymaps = { + ["n"] = "next", + ["p"] = "prev", + ["q"] = "quit", + ["f"] = "first", + ["l"] = "last", + [""] = "next", + [""] = "prev", + }, + } +< +# Options ~ + +------------------------------------------------------------------------------ +============================================================================== +Core functionality + +------------------------------------------------------------------------------ + *Presenting.toggle()* + `Presenting.toggle`({separator}) +Toggle presenting mode on/off for the current buffer. +Parameters ~ +{separator} `(string|nil)` + +------------------------------------------------------------------------------ + *Presenting.start()* + `Presenting.start`({separator}) +Start presenting the current buffer. +Parameters ~ +{separator} `(string|nil)` + +------------------------------------------------------------------------------ + *Presenting.quit()* + `Presenting.quit`() +Quit the current presentation and go back to the normal buffer. +By default this is mapped to `q`. + +------------------------------------------------------------------------------ + *Presenting.next()* + `Presenting.next`() +Go to the next slide. +By default this is mapped to `` and `n`. + +------------------------------------------------------------------------------ + *Presenting.prev()* + `Presenting.prev`() +Go to the previous slide. +By default this is mapped to `` and `p`. + +------------------------------------------------------------------------------ + *Presenting.first()* + `Presenting.first`() +Go to the first slide. +By default this is mapped to `f`. + +------------------------------------------------------------------------------ + *Presenting.last()* + `Presenting.last`() +Go to the last slide. +By default this is mapped to `l`. + +------------------------------------------------------------------------------ + *Presenting.resize()* + `Presenting.resize`() +the slide window. + +------------------------------------------------------------------------------ + *H.default_config* + `H.default_config` +============================================================================== +Internal Helper +As end user you should not need to use these functions. + +------------------------------------------------------------------------------ + *H.setup_config()* + `H.setup_config`({config}) +Parameters ~ +{config} `(table|nil)` + +------------------------------------------------------------------------------ + *H.apply_config()* + `H.apply_config`({config}) +Parameters ~ +{config} `(table)` + +------------------------------------------------------------------------------ + *H.get_win_configs()* + `H.get_win_configs`() +Return ~ +`(table)` + +------------------------------------------------------------------------------ + *H.create_slide_view()* + `H.create_slide_view`({state}) +Parameters ~ +{state} `(table)` + +------------------------------------------------------------------------------ + *H.parse_slides()* + `H.parse_slides`({lines}, {separator}) +Parameters ~ +{lines} `(table)` +{separator} `(string)` +Return ~ +`(table)` + +------------------------------------------------------------------------------ + *H.set_slide_buffer_options()* + `H.set_slide_buffer_options`({buf}) +Parameters ~ +{buf} `(integer)` + +------------------------------------------------------------------------------ + *H.set_slide_content()* + `H.set_slide_content`({state}, {slide}) +Parameters ~ +{state} `(table)` +{slide} `(integer)` + +------------------------------------------------------------------------------ + *H.set_slide_keymaps()* + `H.set_slide_keymaps`({buf}, {mappings}) +Parameters ~ +{buf} `(integer)` +{mappings} `(table)` + +------------------------------------------------------------------------------ + *H.in_presenting_mode()* + `H.in_presenting_mode`() +Return ~ +`(boolean)` + + + vim:tw=78:ts=8:noet:ft=help:norl: \ No newline at end of file diff --git a/lua/presenting/init.lua b/lua/presenting/init.lua new file mode 100644 index 0000000..9bce17c --- /dev/null +++ b/lua/presenting/init.lua @@ -0,0 +1,344 @@ +--- *presenting.nvim* +--- *Presenting* +--- +--- MIT License Copyright (c) 2024 Stefan Otte +--- +--- ============================================================================== +--- +--- Present your markdown, org-mode, or asciidoc files in a nice way, +--- i.e. directly in nvim. + +-- Module definition ========================================================== +local Presenting = {} +local H = {} +Presenting._state = nil + +--- Module setup +--- +---@param config table|nil +---@usage `require('presenting').setup({})` +Presenting.setup = function(config) + _G.Presenting = Presenting + config = H.setup_config(config) + H.apply_config(config) + + vim.api.nvim_create_user_command("Presenting", Presenting.toggle, {}) + vim.api.nvim_create_user_command("PresentingDevMode", Presenting.dev_mode, {}) + + local presenting_autocmd_group_id = vim.api.nvim_create_augroup("PresentingAutoGroup", {}) + vim.api.nvim_create_autocmd("WinResized", { + group = presenting_autocmd_group_id, + callback = function() + Presenting.resize() + end, + }) +end + +--- Module config +--- +--- Default values: +---@eval return MiniDoc.afterlines_to_code(MiniDoc.current.eval_section) +---@text # Options ~ +Presenting.config = { + options = { + width = 60, + }, + separator = { + -- Note: separators are lua patterns, not regexes. + markdown = "^#+ ", + org = "^*+ ", + adoc = "^==+ ", + asciidoctor = "^==+ ", + }, + keymaps = { + ["n"] = "next", + ["p"] = "prev", + ["q"] = "quit", + ["f"] = "first", + ["l"] = "last", + [""] = "next", + [""] = "prev", + }, +} +--minidoc_afterlines_end + +--- ============================================================================== +--- Core functionality + +--- Toggle presenting mode on/off for the current buffer. +---@param separator string|nil +Presenting.toggle = function(separator) + if H.in_presenting_mode() then + Presenting.quit() + else + Presenting.start(separator) + end +end + +--- Start presenting the current buffer. +---@param separator string|nil +Presenting.start = function(separator) + if H.in_presenting_mode() then + vim.notify("Already presenting") + return + end + + if type(separator) == "table" then + -- FIXME: why is separator a table when I don't pass anything? + -- into nil. I don't know why I get a table here when I don't pass anything. + -- print(vim.inspect(separator)) + -- Workaround: turn not passed separator + separator = nil + end + + local filetype = vim.bo.filetype + separator = separator or Presenting.config.separator[filetype] + + if separator == nil then + vim.notify( + "presenting.nvim does not support filetype " + .. filetype + .. ". You can specify a separator manually: Presenting.start('---')" + ) + return + end + + Presenting._state = { + filetype = filetype, + slides = {}, + slide = 1, + n_slides = nil, + slide_buf = nil, + slide_win = nil, + background_buf = nil, + background_win = nil, + footer_buf = nil, + footer_win = nil, + view = nil, + } + + -- content of slides + local lines = vim.api.nvim_buf_get_lines(0, 0, -1, false) + Presenting._state.slides = H.parse_slides(lines, separator) + Presenting._state.n_slides = #Presenting._state.slides + + H.create_slide_view(Presenting._state) +end + +--- Quit the current presentation and go back to the normal buffer. +--- By default this is mapped to `q`. +Presenting.quit = function() + if not H.in_presenting_mode() then + vim.notify("Not in presenting mode") + return + end + + vim.api.nvim_buf_delete(Presenting._state.slide_buf, { force = true }) + -- vim.api.nvim_win_close(Presenting._state.slide_win, true) + + vim.api.nvim_buf_delete(Presenting._state.footer_buf, { force = true }) + -- vim.api.nvim_win_close(Presenting._state.footer_win, true) + + vim.api.nvim_buf_delete(Presenting._state.background_buf, { force = true }) + -- vim.api.nvim_win_close(Presenting._state.background_win, true) + + Presenting._state = nil +end + +--- Go to the next slide. +--- By default this is mapped to `` and `n`. +Presenting.next = function() + if not H.in_presenting_mode() then + vim.notify("Not presenting. Call `PresentingStart` first.") + return + end + H.set_slide_content(Presenting._state, math.min(Presenting._state.slide + 1, Presenting._state.n_slides)) +end + +--- Go to the previous slide. +--- By default this is mapped to `` and `p`. +Presenting.prev = function() + if not H.in_presenting_mode() then + vim.notify("Not presenting. Call `PresentingStart` first.") + return + end + H.set_slide_content(Presenting._state, math.max(Presenting._state.slide - 1, 1)) +end + +--- Go to the first slide. +--- By default this is mapped to `f`. +Presenting.first = function() + if not H.in_presenting_mode() then + vim.notify("Not presenting. Call `PresentingStart` first.") + return + end + H.set_slide_content(Presenting._state, 1) +end + +--- Go to the last slide. +--- By default this is mapped to `l`. +Presenting.last = function() + if not H.in_presenting_mode() then + vim.notify("Not presenting. Call `PresentingStart` first.") + return + end + H.set_slide_content(Presenting._state, Presenting._state.n_slides) +end + +---Resize the slide window. +Presenting.resize = function() + if not H.in_presenting_mode() then + return + end + if (Presenting._state.background_win == nil) or (Presenting._state.slide_win == nil) then + return + end + + local window_config = H.get_win_configs() + vim.api.nvim_win_set_config(Presenting._state.background_win, window_config.background) + vim.api.nvim_win_set_config(Presenting._state.slide_win, window_config.slide) +end + +Presenting.dev_mode = function() + package.loaded["presenting"] = nil + _G.Presenting = nil + require("presenting").start() +end + +--- ============================================================================== +--- Internal Helper +--- As end user you should not need to use these functions. +H.default_config = vim.deepcopy(Presenting.config) + +---@param config table|nil +H.setup_config = function(config) + vim.validate({ config = { config, "table", true } }) + -- TODO: validate some more + return vim.tbl_deep_extend("force", vim.deepcopy(H.default_config), config or {}) +end + +---@param config table +H.apply_config = function(config) + -- nothing to do right now + Presenting.config = config +end + +---@return table +H.get_win_configs = function() + local slide_width = Presenting.config.options.width + local width = vim.api.nvim_get_option("columns") + local height = vim.api.nvim_get_option("lines") + local offset = math.ceil((width - slide_width) / 2) + return { + background = { + style = "minimal", + relative = "editor", + focusable = false, + width = width, + height = height, + row = 0, + col = 0, + zindex = 1, + }, + slide = { + style = "minimal", + relative = "editor", + width = slide_width, + height = height - 5, + row = 0, + col = offset, + zindex = 10, + }, + footer = { + style = "minimal", + relative = "editor", + width = slide_width, + height = 1, + row = height - 1, + col = offset, + focusable = false, + zindex = 2, + }, + } +end + +---@param state table +H.create_slide_view = function(state) + local window_config = H.get_win_configs() + + state.background_buf = vim.api.nvim_create_buf(false, true) + state.background_win = vim.api.nvim_open_win(state.background_buf, false, window_config.background) + + -- TODO: maybe just use vims statusline instead of my custom footer :) + state.footer_buf = vim.api.nvim_create_buf(false, true) + vim.api.nvim_buf_set_lines(state.footer_buf, 0, -1, false, { "presenting.nvim" }) + state.footer_win = vim.api.nvim_open_win(state.footer_buf, false, window_config.footer) + + state.slide_buf = vim.api.nvim_create_buf(false, true) + state.slide_win = vim.api.nvim_open_win(state.slide_buf, true, window_config.slide) + H.set_slide_buffer_options(Presenting._state.slide_buf) + H.set_slide_keymaps(state.slide_buf, Presenting.config.keymaps) + + H.set_slide_content(state, 1) +end + +---@param lines table +---@param separator string +---@return table +H.parse_slides = function(lines, separator) + -- TODO: isn't there a split() in lua that keeps the separator? + local slides = {} + local slide = {} + for i, line in pairs(lines) do + if line:match(separator) then + if #slide > 0 then + table.insert(slides, table.concat(slide, "\n")) + end + slide = {} + end + table.insert(slide, line) + end + table.insert(slides, table.concat(slide, "\n")) + + return slides +end + +---@param buf integer +H.set_slide_buffer_options = function(buf) + -- TODO: make this configurable via config + vim.api.nvim_buf_set_option(buf, "buftype", "nofile") + vim.api.nvim_buf_set_option(buf, "filetype", Presenting._state.filetype) + vim.api.nvim_buf_set_option(buf, "bufhidden", "wipe") + vim.api.nvim_buf_set_option(buf, "modifiable", false) +end + +---@param state table +---@param slide integer +H.set_slide_content = function(state, slide) + local orig_modifiable = vim.api.nvim_buf_get_option(state.slide_buf, "modifiable") + vim.api.nvim_buf_set_option(state.slide_buf, "modifiable", true) + state.slide = slide + vim.api.nvim_buf_set_lines(state.slide_buf, 0, -1, false, vim.split(state.slides[state.slide], "\n")) + vim.api.nvim_buf_set_option(state.slide_buf, "modifiable", orig_modifiable) + + local footer_text = "presenting.nvim | " .. state.slide .. "/" .. state.n_slides + vim.api.nvim_buf_set_lines(state.footer_buf, 0, -1, false, { footer_text }) +end + +---@param buf integer +---@param mappings table +H.set_slide_keymaps = function(buf, mappings) + for k, v in pairs(mappings) do + -- TODO: use lua functions instead of string inperpolation + -- TODO: make it possible to disable mappings + local cmd = ":lua require('presenting')." .. v .. "()" + vim.api.nvim_buf_set_keymap(buf, "n", k, cmd, { noremap = true, silent = true }) + end +end + +---@return boolean +H.in_presenting_mode = function() + return Presenting._state ~= nil +end + +return Presenting diff --git a/scripts/minidoc.lua b/scripts/minidoc.lua new file mode 100644 index 0000000..da298d7 --- /dev/null +++ b/scripts/minidoc.lua @@ -0,0 +1,7 @@ +local minidoc = require("mini.doc") + +if _G.MiniDoc == nil then + minidoc.setup() +end + +MiniDoc.generate({ "lua/presenting.lua" }, "doc/presenting.txt", {}) diff --git a/scripts/minimal_init.lua b/scripts/minimal_init.lua new file mode 100644 index 0000000..995a037 --- /dev/null +++ b/scripts/minimal_init.lua @@ -0,0 +1,15 @@ +-- Add current directory to 'runtimepath' to be able to use 'lua' files +vim.cmd([[let &rtp.=','.getcwd()]]) + +-- Set up 'mini.test' and 'mini.doc' only when calling headless Neovim (like with `make test` or `make documentation`) +if #vim.api.nvim_list_uis() == 0 then + -- Add 'mini.nvim' to 'runtimepath' to be able to use 'mini.test' + -- Assumed that 'mini.nvim' is stored in 'deps/mini.nvim' + vim.cmd("set rtp+=deps/mini.nvim") + + -- Set up 'mini.test' + require("mini.test").setup() + + -- Set up 'mini.doc' + require("mini.doc").setup() +end diff --git a/scripts/minitest.lua b/scripts/minitest.lua new file mode 100644 index 0000000..24e1fa8 --- /dev/null +++ b/scripts/minitest.lua @@ -0,0 +1,6 @@ +local minitest = require("mini.test") + +if _G.MiniTest == nil then + minitest.setup() +end +minitest.run() diff --git a/stylua.toml b/stylua.toml new file mode 100644 index 0000000..fb3dcf8 --- /dev/null +++ b/stylua.toml @@ -0,0 +1,10 @@ +column_width = 120 +line_endings = "Unix" +indent_type = "Spaces" +indent_width = 2 +quote_style = "AutoPreferDouble" +call_parentheses = "Always" +collapse_simple_statement = "Never" + +[sort_requires] +enabled = false