├── ftdetect └── agda.vim ├── syntax ├── lagdamd.vim └── lagda.vim ├── lua └── agda │ ├── loc.lua │ └── init.lua ├── ftplugin └── agda.vim ├── README.md └── agda-input.vim /ftdetect/agda.vim: -------------------------------------------------------------------------------- 1 | au BufNewFile,BufRead *.lagda.md :set filetype=agda syntax=lagdamd 2 | au BufNewFile,BufRead *.agda setf agda 3 | au BufNewFile,BufRead *.lagda :set filetype=agda syntax=lagda 4 | 5 | 6 | -------------------------------------------------------------------------------- /syntax/lagdamd.vim: -------------------------------------------------------------------------------- 1 | 2 | " Set the entire syntax to markdown and ensure that we 3 | " do not colorise ```-regions, as agda mode would do this 4 | " for us. 5 | set syntax=markdown 6 | 7 | 8 | syn match lagdaBeginCode /\s*```(agda)?/ contained 9 | syn match lagdaEndCode /\s*```(agda)?/ contained 10 | 11 | syn region lagdaCode start=/```(agda)?/ end=/```(agda)?/ keepend 12 | 13 | hi default link lagdaBeginCode Statement 14 | hi default link lagdaEndCode Statement 15 | hi default link lagdaCode Normal 16 | -------------------------------------------------------------------------------- /lua/agda/loc.lua: -------------------------------------------------------------------------------- 1 | -- Loc class 2 | Loc = {} 3 | Loc.__index = Loc 4 | 5 | function Loc:new(l,c) 6 | local loc = {} 7 | setmetatable(loc,Loc) 8 | loc.line = l 9 | loc.col = c 10 | return loc 11 | end 12 | 13 | Loc.__le = function (l1, l2) 14 | if l1.line < l2.line then return true 15 | elseif l1.line == l2.line then return l1.col <= l2.col end 16 | return false 17 | end 18 | 19 | Loc.__lt = function (l1, l2) 20 | if l1.line < l2.line then return true 21 | elseif l1.line == l2.line then return l1.col < l2.col end 22 | return false 23 | end 24 | 25 | return Loc 26 | -------------------------------------------------------------------------------- /syntax/lagda.vim: -------------------------------------------------------------------------------- 1 | 2 | " For now we set the entire syntax to tex, but later we can 3 | " actually describe that we apply this only to code regions. 4 | "set syntax=tex 5 | 6 | if exists("b:current_syntax") 7 | finish 8 | endif 9 | 10 | if !exists('main_syntax') 11 | let main_syntax = 'lagda' 12 | endif 13 | 14 | runtime! syntax/tex.vim 15 | unlet! b:current_syntax 16 | 17 | " Here we want to match Agda code block and disable 18 | " any hilighting inside. Otherwise, not only we have 19 | " conflicting hilighting of codeblocks, but also we 20 | " run into issues such as broken math environment when 21 | " using `$`s in Agda code. 22 | " 23 | " The rule below says: 24 | " * matchgroup: colorise begin/end as tex statements 25 | " * start/end: boundaries of the code block 26 | " * containedin: (crucially important!) ensure that the 27 | " rule is applied in all matchgroups 28 | syn region lagdaCode 29 | \ matchgroup=texStatement 30 | \ start=+\\begin{code}+ end=+\\end{code}+ 31 | \ containedin=ALL 32 | 33 | hi default link lagdaCode Normal 34 | 35 | let b:current_syntax = "lagda" 36 | if main_syntax ==# 'lagda' 37 | unlet main_syntax 38 | endif 39 | 40 | -------------------------------------------------------------------------------- /ftplugin/agda.vim: -------------------------------------------------------------------------------- 1 | 2 | " Kill the cache of previously loaded "agda" module. 3 | " Lua does this automatically, but we want a newly loaded 4 | " module per every buffer. 5 | lua package.loaded.agda = nil 6 | 7 | " The name of this variable should not be changed, as it is 8 | " hardcoded in agda/init.lua when creating bindings for prompt windows. 9 | let b:AgdaMod = luaeval("require'agda'") 10 | 11 | hi Todo ctermbg=DarkGray ctermfg=Black gui=NONE guibg=#353535 guifg=#efefef 12 | " hi NonTerminating ctermbg=Red 13 | hi NonTerminating ctermbg=LightRed ctermfg=Black gui=NONE guibg=#994444 14 | hi NoDefinition ctermbg=Brown gui=NONE guibg=#354657 15 | "ctermbg=LightBlue ctermfg=Black 16 | 17 | if !exists("g:nvim_agda_settings") 18 | let g:nvim_agda_settings = {} 19 | endif 20 | 21 | " The setting is a dictionary, so far the only key in the 22 | " dictionary is the location of the Agda binary, e.g. 23 | " { "agda": "/usr/local/bin/agda" } 24 | " Any key in the dictionary can be omitted, in which case, 25 | " we are going to use hard-coded defaults. 26 | call b:AgdaMod.setup(g:nvim_agda_settings) 27 | 28 | 29 | " Vim commands 30 | command! AgdaStart :call b:AgdaMod.agda_start() 31 | command! AgdaLoad :call b:AgdaMod.agda_load(expand("%:p")) 32 | command! AgdaContext :call b:AgdaMod.agda_context(expand("%:p")) 33 | command! AgdaTypeContext :call b:AgdaMod.agda_type_context(expand("%:p")) 34 | command! AgdaTypeContextNorm :call b:AgdaMod.agda_type_context(expand("%:p"),"Normalised") 35 | command! AgdaTypeContextInfer :call b:AgdaMod.agda_type_context_infer(expand("%:p")) 36 | command! AgdaInfer :call b:AgdaMod.agda_infer(expand("%:p")) 37 | command! AgdaCompute :call b:AgdaMod.agda_compute(expand("%:p")) 38 | command! AgdaRefine :call b:AgdaMod.agda_refine(expand("%:p")) 39 | command! AgdaAuto :call b:AgdaMod.agda_auto(expand("%:p")) 40 | command! AgdaSolve :call b:AgdaMod.agda_solve(expand("%:p")) 41 | command! AgdaMakeCase :call b:AgdaMod.agda_make_case(expand("%:p")) 42 | command! AgdaHelperFun :call b:AgdaMod.agda_helper_fun(expand("%:p")) 43 | command! AgdaModuleContents :call b:AgdaMod.agda_module_contents(expand("%:p")) 44 | command! AgdaWhyInscope :call b:AgdaMod.agda_why_inscope(expand("%:p")) 45 | command! AgdaShowConstraints :call b:AgdaMod.agda_show_constraints(expand("%:p")) 46 | 47 | command! ShowImplicit :call b:AgdaMod.show_implicit(expand("%:p")) 48 | command! HideImplicit :call b:AgdaMod.hide_implicit(expand("%:p")) 49 | 50 | command! MkPrompt :call b:AgdaMod.edit_goal(expand("%:p")) 51 | command! PrintGoals :call b:AgdaMod.agda_show_goals(expand("%:p")) 52 | command! GoalNext :call b:AgdaMod.agda_goal_next(expand("%:p")) 53 | command! GoalPrev :call b:AgdaMod.agda_goal_prev(expand("%:p")) 54 | 55 | command! AgdaCloseMsg :call b:AgdaMod.close_msg_win() 56 | command! GoalContent :call b:AgdaMod.gc() 57 | command! GetEvbuf :call b:AgdaMod.getevbuf() 58 | 59 | 60 | " Key mappings 61 | nm l :AgdaLoad 62 | nm q :AgdaCloseMsg 63 | nm , :AgdaTypeContext 64 | nm . :AgdaTypeContextInfer 65 | nm u, :AgdaTypeContextNorm 66 | nm d :AgdaInfer 67 | nm r :AgdaRefine 68 | nm c :AgdaMakeCase 69 | nm n :AgdaCompute 70 | nm a :AgdaAuto 71 | nm s :AgdaSolve 72 | nm h :AgdaHelperFun 73 | nm o :AgdaModuleContents 74 | nm w :AgdaWhyInscope 75 | nm e :MkPrompt 76 | nm ? :PrintGoals 77 | nm f :GoalNext 78 | nm b :GoalPrev 79 | 80 | 81 | " mappings 82 | runtime agda-input.vim 83 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Agda for neovim 2 | 3 | [![asciicast](https://asciinema.org/a/TvNvhve83WWqJsK2TiN9aAeyp.svg)](https://asciinema.org/a/TvNvhve83WWqJsK2TiN9aAeyp) 4 | 5 | # Installation 6 | 7 | This plugin requires [Neovim (0.5)](https://github.com/neovim/neovim/releases/tag/nightly) 8 | to work. One could probably force earlier versions to work as well, but 9 | this is non-trivial. 10 | 11 | 1. Install [lua-utf8](https://github.com/starwing/luautf8) library on your system. 12 | 13 | 2. Use a plugin manager such as [paq](https://github.com/savq/paq-nvim) 14 | and pass the name of this repository: 15 | ```lua 16 | paq 'ashinkarov/nvim-agda' 17 | ``` 18 | Alternatively, you can install the plugin manually as follows: 19 | ```sh 20 | $ mkdir -p ~/.local/share/nvim/site/pack/git-plugins/start 21 | $ git clone https://github.com/ashinkarov/nvim-agda.git --depth=1 ~/.local/share/nvim/site/pack/git-plugins/start/nvim-agda 22 | ``` 23 | 24 | ## Configuration 25 | 26 | The plugin can be configured by defining a global variable named 27 | `g:nvim_agda_settings` of type dictionary. So far the following options 28 | are supported (I use lua syntax, but you can define this in vimscript too). 29 | ```lua 30 | { 31 | agda : "/usr/local/bin/agda", -- Location of Agda binary 32 | agda_args : { "--arg1", "--arg2" }, -- Default arguments to Agda binary 33 | debug_p : true -- Turn debug prints on or off 34 | } 35 | ``` 36 | All the fields of the dictionary may be omitted, as well as the 37 | definition of `g:nvim_agda_settings`. In this case hard-coded 38 | defaults would be used. 39 | 40 | # Details 41 | 42 | [NeoVim](https://neovim.io/) comes with built-in [Lua](https://www.lua.org/) support 43 | which dramatically simplifies development of complex plugins. This plugin is written 44 | almost entirely in Lua, which handles asynchronous interaction and editor state 45 | manipulation. 46 | 47 | ## Design principles 48 | * Use asynchronous communication so that we can use the editor when Agda is working. 49 | * Avoid goal commands when the file has not been typechecked. 50 | * Use popup windows to show location-specific information. 51 | * Don't treat Agda as a black box; change the way it interacts in case it makses sense. 52 | * Try to be efficient. 53 | 54 | ## Status 55 | 56 | Minimal functionality including communication with Agda process, basic utf8 57 | input, and basic commands are implemented. Goal specific information such as 58 | context, goal type, etc. is shown in a popup window appearing at the goal location. 59 | The goal content is edited in a separate window, so that it does not alter the 60 | state of the file. Goal actions on a modified file reload the file and synchronise 61 | the goal list. Both `?` and `{! !}` goals are supported, however the latter 62 | is discouraged, as every edit would modify the file and trigger file reload. 63 | 64 | ### Implemented commands and shortcuts. 65 | Mostly we implement the same commands as agda-mode in emacs. Several new 66 | commands have to do with management of the popups and goal editing. 67 | 68 | | `` Key | Agda mode Command | 69 | |:--------:| -------------- | 70 | | l | Load file | 71 | | , | Show type and context | 72 | | d | Infer type | 73 | | r | Refine the goal | 74 | | c | Case split on a variable(s) | 75 | | n | Compute the goal content or toplevel expression | 76 | | a | Automatically solve a goal (or all goals in the file) | 77 | | s | Automatically solve constraints in a goal (or in all goals in the file) | 78 | | h | Helper function for the goal | 79 | | o | Show the content of the module | 80 | | w | Explain why the name (under cursor) is in scope | 81 | | ? | Show Goals | 82 | | f | Go to the next goal | 83 | | b | Go to the previous goal | 84 | 85 | 86 | | `` Key | Agda mode Command | 87 | |:--------:| -------------- | 88 | | q | Close the message box | 89 | | e | Edit the goal content | 90 | 91 | Within the goal-edit window, `q` is mapped to close the window. 92 | The regular `:q` command would work as well. 93 | 94 | 95 | ### Todo 96 | * Implement remaining commands from the original `agda-mode` 97 | * Goal actions on a modified buffer reload the file and postpone the 98 | action via continuation. Ensure that this is "thread safe" (does not 99 | have race conditions). 100 | * Asynchronous communication quirks: 101 | - Highlighting information uses offsets (number of characters from the 102 | beginning of file), whereas nvim interface expects line and byte-offset 103 | in that line to set the hilighting. This is important as this conversion 104 | is expensive and it 105 | pulls `lua-utf8` as a dependency. 106 | - Errors are printed on `stdout` (not `stderr`) 107 | * If highlighting information arrives, and the file is modified --- should we 108 | attempt to colorise all but modified pieces? This is a typical case whe 109 | happening big files. Agda is very slow, and it is tempting to edit a few 110 | things before the colors arrive. 111 | * Utf-8 input is happening via defining insert mode shortcuts, so there 112 | is no visual response when one is typing things like `\alpha`. Should 113 | we implement a [which-key](https://github.com/liuchengxu/vim-which-key) 114 | for the insert mode? 115 | 116 | ### Done 117 | * Support working with multiple files. Currently we assume a single 118 | file per vim instance. See issue [#1](https://github.com/ashinkarov/nvim-agda/issues/1) for more details. 119 | 120 | 121 | # Credit 122 | * https://github.com/coot/vim-agda-integration 123 | * https://github.com/derekelkins/agda-vim 124 | -------------------------------------------------------------------------------- /agda-input.vim: -------------------------------------------------------------------------------- 1 | " Mapping unicode symbols. 2 | noremap! to → 3 | noremap! - → 4 | noremap! <- ← 5 | "noremap! and ∧ 6 | "noremap! or ∨ 7 | noremap! qed ∎ 8 | noremap! o ∘ 9 | noremap! comp ∘ 10 | "noremap! < ⟨ 11 | "noremap! > ⟩ 12 | "noremap! == ≡ 13 | "noremap! ==n ≢ 14 | noremap! :: ∷ 15 | noremap! all ∀ 16 | noremap! ex ∃ 17 | "noremap! le ≤ 18 | "noremap! ge ≥ 19 | noremap! ' ′ 20 | noremap! top ⊤ 21 | noremap! bot ⊥ 22 | noremap! ?= ≟ 23 | "noremap! o* ⊛ 24 | noremap! x × 25 | "noremap! o+ ⊕ 26 | noremap! .- ∸ 27 | "noremap! {{ ⦃ 28 | "noremap! }} ⦄ 29 | 30 | "noremap! bN ℕ 31 | noremap! => ⇒ 32 | noremap! nin ∉ 33 | noremap! in ∈ 34 | noremap! ni ∋ 35 | noremap! neg ¬ 36 | noremap! ... ⋯ 37 | 38 | "noremap! u+ ⊎ 39 | "noremap! [[ ⟦ 40 | "noremap! ]] ⟧ 41 | noremap! ell ℓ 42 | 43 | 44 | " We follow 45 | " https://people.inf.elte.hu/divip/AgdaTutorial/Symbols.html 46 | 47 | " Equality and similar symbols 48 | noremap! =n ≠ 49 | noremap! ~ ∼ 50 | noremap! ~n ≁ 51 | noremap! ~~ ≈ 52 | noremap! ~~n ≉ 53 | noremap! ~ ≋ 54 | noremap! :~ ∻ 55 | noremap! ~- ≃ 56 | noremap! ~-n ≄ 57 | noremap! -~ ≂ 58 | noremap! ~= ≅ 59 | noremap! ~=n ≇ 60 | noremap! ~~- ≊ 61 | noremap! == ≡ 62 | noremap! ==n ≢ 63 | noremap! === ≣ 64 | noremap! .= ≐ 65 | noremap! .=. ≑ 66 | noremap! := ≔ 67 | noremap! =: ≕ 68 | noremap! =o ≗ 69 | noremap! (= ≘ 70 | noremap! and= ≙ 71 | noremap! or= ≚ 72 | noremap! *= ≛ 73 | noremap! t= ≜ 74 | noremap! def= ≝ 75 | noremap! m= ≞ 76 | noremap! ?= ≟ 77 | 78 | " Inequality and similar symbols 79 | noremap! <= ≤ 80 | noremap! >= ≥ 81 | noremap! <=n ≰ 82 | noremap! >=n ≱ 83 | noremap! len ≰ 84 | noremap! gen ≱ 85 | noremap! >n ≯ 87 | noremap! <~ ≲ 88 | noremap! >~ ≳ 89 | noremap! <~n ⋦ 90 | noremap! >~n ⋧ 91 | noremap! <~nn ≴ 92 | noremap! >~nn ≵ 93 | noremap! sub ⊂ 94 | noremap! sup ⊃ 95 | noremap! subn ⊄ 96 | noremap! supn ⊅ 97 | noremap! sub= ⊆ 98 | noremap! sup= ⊇ 99 | noremap! sub=n ⊈ 100 | noremap! sup=n ⊉ 101 | noremap! squb ⊏ 102 | noremap! squp ⊐ 103 | noremap! squb= ⊑ 104 | noremap! squp= ⊒ 105 | noremap! squb=n ⋢ 106 | noremap! squp=n ⋣ 107 | 108 | 109 | noremap! cul ⌜ 110 | noremap! cuL ⌈ 111 | noremap! cur ⌝ 112 | noremap! cuR ⌉ 113 | noremap! cll ⌞ 114 | noremap! clL ⌊ 115 | noremap! clr ⌟ 116 | noremap! clR ⌋ 117 | 118 | "ci ●○◎◌◯◍◐◑◒◓◔◕◖◗◠◡◴◵◶◷⚆⚇⚈⚉ 119 | noremap! cib ● 120 | noremap! ciw ○ 121 | noremap! ci. ◎ 122 | noremap! ci.. ◌ 123 | noremap! ciO ◯ 124 | 125 | noremap! . ∙ 126 | noremap! * ⋆ 127 | noremap! .+ ∔ 128 | noremap! .- ∸ 129 | noremap! : ∶ 130 | noremap! :: ∷ 131 | noremap! ::- ∺ 132 | 133 | 134 | "noremap! tr ▹ 135 | "noremap! t> ▹ 136 | 137 | 138 | noremap! tlb ◂ 139 | noremap! tlw ◃ 140 | noremap! tLb ◄ 141 | noremap! tLw ◅ 142 | noremap! trb ▸ 143 | noremap! trw ▹ 144 | noremap! tRb ► 145 | noremap! tRw ▻ 146 | noremap! tub ▴ 147 | noremap! tuw ▵ 148 | noremap! tdb ▾ 149 | noremap! tdw ▿ 150 | " FIXME add codes for these ◢◿◣◺◤◸◥◹ 151 | noremap! Tlb ◀ 152 | noremap! Tlw ◁ 153 | noremap! Trb ▶ 154 | noremap! Trw ▷ 155 | noremap! Tub ▲ 156 | noremap! Tuw △ 157 | noremap! Tdb ▼ 158 | noremap! Tdw ▽ 159 | noremap! T\.uw ◬ 160 | " FIXME add codes for these ◭◮ 161 | 162 | noremap! [[ ⟦ 163 | noremap! ]] ⟧ 164 | noremap! < ⟨ 165 | noremap! > ⟩ 166 | noremap! << ⟪ 167 | noremap! >> ⟫ 168 | noremap! {{ ⦃ 169 | noremap! }} ⦄ 170 | noremap! (b ⟅ 171 | noremap! )b ⟆ 172 | noremap! lbag ⟅ 173 | noremap! rbag ⟆ 174 | 175 | 176 | 177 | noremap! \|- ⊢ 178 | noremap! \|-n ⊬ 179 | noremap! -\| ⊣ 180 | noremap! \|= ⊨ 181 | noremap! \|=n ⊭ 182 | noremap! \|\|- ⊩ 183 | noremap! \|\|-n ⊮ 184 | noremap! \|\|= ⊫ 185 | noremap! \|\|=n ⊯ 186 | noremap! \|\|\|- ⊪ 187 | 188 | noremap! and ∧ 189 | noremap! or ∨ 190 | noremap! And ⋀ 191 | noremap! Or ⋁ 192 | noremap! i ∩ 193 | noremap! un ∪ 194 | noremap! u+ ⊎ 195 | noremap! u. ⊍ 196 | noremap! I ⋂ 197 | noremap! Un ⋃ 198 | noremap! U+ ⨄ 199 | noremap! U. ⨃ 200 | noremap! glb ⊓ 201 | noremap! lub ⊔ 202 | noremap! Glb ⨅ 203 | noremap! Lub ⨆ 204 | 205 | noremap! o+ ⊕ 206 | noremap! o-- ⊖ 207 | noremap! ox ⊗ 208 | noremap! o/ ⊘ 209 | noremap! o. ⊙ 210 | noremap! oo ⊚ 211 | noremap! o* ⊛ 212 | noremap! o= ⊜ 213 | noremap! o- ⊝ 214 | noremap! O+ ⨁ 215 | noremap! Ox ⨂ 216 | noremap! O. ⨀ 217 | noremap! O* ⍟ 218 | 219 | noremap! b+ ⊞ 220 | noremap! b- ⊟ 221 | noremap! bx ⊠ 222 | noremap! b. ⊡ 223 | 224 | " Blackboard bold letters 225 | noremap! bn ℕ 226 | noremap! bz ℤ 227 | noremap! bq ℚ 228 | noremap! br ℝ 229 | noremap! bc ℂ 230 | noremap! bp ℙ 231 | noremap! bb 𝔹 232 | noremap! bsum ⅀ 233 | 234 | 235 | " This comes from https://handwiki.org/wiki/Blackboard_bold 236 | noremap! bA 𝔸 237 | noremap! ba 𝕒 238 | noremap! bB 𝔹 239 | noremap! bb 𝕓 240 | noremap! bC ℂ 241 | noremap! bc 𝕔 242 | noremap! bD 𝔻 243 | noremap! bd 𝕕 244 | noremap! bE 𝔼 245 | noremap! be 𝕖 246 | noremap! bF 𝔽 247 | noremap! bf 𝕗 248 | noremap! bG 𝔾 249 | noremap! bg 𝕘 250 | noremap! bH ℍ 251 | noremap! bh 𝕙 252 | noremap! bI 𝕀 253 | noremap! bi 𝕚 254 | noremap! bJ 𝕁 255 | noremap! bj 𝕛 256 | noremap! bK 𝕂 257 | noremap! bk 𝕜 258 | noremap! bL 𝕃 259 | noremap! bl 𝕝 260 | noremap! bM 𝕄 261 | noremap! bm 𝕞 262 | noremap! bN ℕ 263 | noremap! bn 𝕟 264 | noremap! bO 𝕆 265 | noremap! bo 𝕠 266 | noremap! bP ℙ 267 | noremap! bp 𝕡 268 | noremap! bQ ℚ 269 | noremap! bq 𝕢 270 | noremap! bR ℝ 271 | noremap! br 𝕣 272 | noremap! bS 𝕊 273 | noremap! bs 𝕤 274 | noremap! bT 𝕋 275 | noremap! bt 𝕥 276 | noremap! bU 𝕌 277 | noremap! bu 𝕦 278 | noremap! bV 𝕍 279 | noremap! bv 𝕧 280 | noremap! bW 𝕎 281 | noremap! bw 𝕨 282 | noremap! bX 𝕏 283 | noremap! bx 𝕩 284 | noremap! bY 𝕐 285 | noremap! by 𝕪 286 | noremap! bZ ℤ 287 | noremap! bz 𝕫 288 | 289 | 290 | 291 | " Blackboard bold numbers 292 | noremap! b0 𝟘 293 | noremap! b1 𝟙 294 | noremap! b2 𝟚 295 | noremap! b3 𝟛 296 | noremap! b4 𝟜 297 | noremap! b5 𝟝 298 | noremap! b6 𝟞 299 | noremap! b7 𝟟 300 | noremap! b8 𝟠 301 | noremap! b9 𝟡 302 | 303 | 304 | " Lower numbers. Note, no '_' in the mapping. 305 | noremap! 0 ₀ 306 | noremap! 1 ₁ 307 | noremap! 2 ₂ 308 | noremap! 3 ₃ 309 | noremap! 4 ₄ 310 | noremap! 5 ₅ 311 | noremap! 6 ₆ 312 | noremap! 7 ₇ 313 | noremap! 8 ₈ 314 | noremap! 9 ₉ 315 | " Lower letters 316 | noremap! t ₜ 317 | 318 | " Greek Letters. 319 | noremap! ga α 320 | noremap! gb β 321 | noremap! gg γ 322 | noremap! gd δ 323 | noremap! ge ε 324 | noremap! gz ζ 325 | noremap! gh η 326 | noremap! gth θ 327 | noremap! gi ι 328 | noremap! gk κ 329 | noremap! gl λ 330 | noremap! gl- ƛ 331 | noremap! gm μ 332 | noremap! gn ν 333 | noremap! gx ξ 334 | noremap! gr ρ 335 | noremap! gs σ 336 | noremap! gt τ 337 | noremap! gu υ 338 | noremap! gf φ 339 | noremap! gc χ 340 | noremap! gp ψ 341 | noremap! go ω 342 | noremap! gA Α 343 | noremap! gB Β 344 | noremap! gG Γ 345 | noremap! gD Δ 346 | noremap! gE Ε 347 | noremap! gZ Ζ 348 | noremap! gH Η 349 | noremap! gTH Θ 350 | noremap! gI Ι 351 | noremap! gK Κ 352 | noremap! gL Λ 353 | noremap! gM Μ 354 | noremap! gN Ν 355 | noremap! gX Ξ 356 | noremap! gR Ρ 357 | noremap! gS Σ 358 | noremap! gT Τ 359 | noremap! gU Υ 360 | noremap! gF Φ 361 | noremap! gC Χ 362 | noremap! gP Ψ 363 | noremap! gO Ω 364 | noremap! omicron ο 365 | noremap! pi π 366 | noremap! Omicron Ο 367 | noremap! Pi Π 368 | 369 | " Arrows 370 | " l ←⇐⇚⇇⇆↤⇦↞↼↽⇠⇺↜⇽⟵⟸↚⇍⇷ ↹ ↢↩↫⇋⇜⇤⟻⟽⤆↶↺⟲ 371 | " r →⇒⇛⇉⇄↦⇨↠⇀⇁⇢⇻↝⇾⟶⟹↛⇏⇸⇶ ↴ ↣↪↬⇌⇝⇥⟼⟾⤇↷↻⟳⇰⇴⟴⟿ ➵➸➙➔➛➜➝➞➟➠➡➢➣➤➧➨➩➪➫➬➭➮➯➱➲➳➺➻➼➽➾⊸ 372 | " u ↑⇑⟰⇈⇅↥⇧↟↿↾⇡⇞ ↰↱➦ ⇪⇫⇬⇭⇮⇯ 373 | " d ↓⇓⟱⇊⇵↧⇩↡⇃⇂⇣⇟ ↵↲↳➥ ↯ 374 | " ud ↕⇕ ↨⇳ 375 | " lr ↔⇔ ⇼↭⇿⟷⟺↮⇎⇹ 376 | " ul ↖⇖ ⇱↸ 377 | " ur ↗⇗ ➶➹➚ 378 | " dr ↘⇘ ⇲ ➴➷➘ 379 | " dl ↙⇙ 380 | noremap! l- ← 381 | noremap! <- ← 382 | noremap! l= ⇐ 383 | noremap! r- → 384 | noremap! -> → 385 | noremap! r= ⇒ 386 | noremap! => ⇒ 387 | noremap! u- ↑ 388 | noremap! u= ⇑ 389 | noremap! d- ↓ 390 | noremap! d= ⇓ 391 | noremap! ud- ↕ 392 | noremap! ud= ⇕ 393 | noremap! lr- ↔ 394 | noremap! <-> ↔ 395 | noremap! lr= ⇔ 396 | noremap! <=> ⇔ 397 | noremap! ul- ↖ 398 | noremap! ul= ⇖ 399 | noremap! ur- ↗ 400 | noremap! ur= ⇗ 401 | noremap! dr- ↘ 402 | noremap! dr= ⇘ 403 | noremap! dl- ↙ 404 | noremap! dl= ⇙ 405 | noremap! l== ⇚ 406 | noremap! l-2 ⇇ 407 | noremap! l-r- ⇆ 408 | noremap! r== ⇛ 409 | noremap! r-2 ⇉ 410 | noremap! r-3 ⇶ 411 | noremap! r-l- ⇄ 412 | noremap! u== ⟰ 413 | noremap! u-2 ⇈ 414 | noremap! u-d- ⇅ 415 | noremap! d== ⟱ 416 | noremap! d-2 ⇊ 417 | noremap! d-u- ⇵ 418 | noremap! l-- ⟵ 419 | noremap! <-- ⟵ 420 | "noremap! l~ ↜⇜ 421 | noremap! r-- ⟶ 422 | noremap! --> ⟶ 423 | "noremap! r~ ↝⇝⟿ 424 | noremap! lr-- ⟷ 425 | noremap! <--> ⟷ 426 | noremap! lr~ ↭ 427 | noremap! l-n ↚ 428 | noremap! <-n ↚ 429 | noremap! l=n ⇍ 430 | noremap! r-n ↛ 431 | noremap! ->n ↛ 432 | noremap! r=n ⇏ 433 | noremap! =>n ⇏ 434 | noremap! lr-n ↮ 435 | noremap! <->n ↮ 436 | noremap! lr=n ⇎ 437 | noremap! <=>n ⇎ 438 | noremap! l-\| ↤ 439 | noremap! ll- ↞ 440 | noremap! r-\| ↦ 441 | noremap! rr- ↠ 442 | noremap! u-\| ↥ 443 | noremap! uu- ↟ 444 | noremap! d-\| ↧ 445 | noremap! dd- ↡ 446 | noremap! ud-\| ↨ 447 | noremap! l-> ↢ 448 | noremap! r-> ↣ 449 | noremap! r-o ⊸ 450 | noremap! -o ⊸ 451 | noremap! dz ↯ 452 | 453 | noremap! _a ₐ 454 | noremap! _e ₑ 455 | noremap! _h ₕ 456 | noremap! _i ᵢ 457 | noremap! _j ⱼ 458 | noremap! _k ₖ 459 | noremap! _l ₗ 460 | noremap! _m ₘ 461 | noremap! _n ₙ 462 | noremap! _o ₒ 463 | noremap! _p ₚ 464 | noremap! _r ᵣ 465 | noremap! _s ₛ 466 | noremap! _t ₜ 467 | noremap! _u ᵤ 468 | noremap! _v ᵥ 469 | noremap! _x ₓ 470 | 471 | noremap! ^a ᵃ 472 | noremap! ^b ᵇ 473 | noremap! ^c ᶜ 474 | noremap! ^d ᵈ 475 | noremap! ^e ᵉ 476 | noremap! ^f ᶠ 477 | noremap! ^g ᵍ 478 | noremap! ^h ʰ 479 | noremap! ^i ⁱ 480 | noremap! ^j ʲ 481 | noremap! ^k ᵏ 482 | noremap! ^l ˡ 483 | noremap! ^m ᵐ 484 | noremap! ^n ⁿ 485 | noremap! ^o ᵒ 486 | noremap! ^p ᵖ 487 | noremap! ^r ʳ 488 | noremap! ^s ˢ 489 | noremap! ^t ᵗ 490 | noremap! ^u ᵘ 491 | noremap! ^v ᵛ 492 | noremap! ^w ʷ 493 | noremap! ^x ˣ 494 | noremap! ^y ʸ 495 | noremap! ^z ᶻ 496 | 497 | 498 | 499 | noremap! fl ♭ 500 | noremap! sh ♯ 501 | -------------------------------------------------------------------------------- /lua/agda/init.lua: -------------------------------------------------------------------------------- 1 | local utf8 = require('lua-utf8') 2 | local Loc = require('agda.loc') 3 | 4 | local M = {} 5 | 6 | ------ State Variable ------ 7 | ---------------------------- 8 | 9 | -- Agda Process Handle. Created by `agda_start`. 10 | local agda_bin = "agda" 11 | local agda_args = {} 12 | 13 | local agda_job = nil 14 | 15 | -- Agda version 16 | local agda_major = 0 17 | local agda_minor = 0 18 | local agda_patch = 0 19 | 20 | -- The list of goals in the format {id = {start = {l,c}, end = {l,c}}} 21 | -- l and c are 1-based line and column as it is given by agda. 22 | local goals = {} 23 | 24 | -- Handles for the (vim)window and (vim)buffer that we use in 25 | -- `mk_window` to display popups. 26 | local msg_buf = nil 27 | local msg_win = nil 28 | local msg_min_width = 25 29 | 30 | -- Buffer for the bytes coming from the agda process. 31 | -- See `on_event` for the details on the usage. 32 | local evbuf = "" 33 | 34 | -- Variables to set continuation to be run by handle_interpoints 35 | -- after updating the list of goals. 36 | local sfunc = nil 37 | local sargs = nil 38 | 39 | local error_count = 0 40 | 41 | local showImplicitArgs = false 42 | 43 | -- Main window handles 44 | -- XXX should we set them up at Agda load? 45 | local main_win = vim.fn.win_getid() 46 | local main_buf = vim.api.nvim_win_get_buf(main_win) 47 | 48 | -- Content and line-offset structure for handling 49 | -- highlighting. 50 | local main_content = "" 51 | local main_lines = {} 52 | 53 | -- Prompt window and buffer 54 | local pwin = nil 55 | local pbuf = nil 56 | local pmin_width = 25 57 | 58 | -- Whether to print debug information 59 | local debug_p = false 60 | 61 | -- Highlighting namespace. 62 | local hl_ns = vim.api.nvim_create_namespace("agda-hl-ns") 63 | 64 | 65 | ------ Helper functions ------ 66 | ------------------------------ 67 | 68 | local function error(msg) 69 | vim.api.nvim_err_writeln(msg) 70 | end 71 | 72 | local function warning(msg) 73 | print("Warning: " .. msg) 74 | end 75 | 76 | local function prefix(p, s) 77 | return p == string.sub(s, 1, #p) 78 | end 79 | 80 | local function len(t) 81 | local l = 0 82 | for _,_ in pairs(t) do 83 | l = l + 1 84 | end 85 | return l 86 | end 87 | 88 | local function debug(x) 89 | if debug_p then 90 | print(vim.inspect(x)) 91 | end 92 | end 93 | 94 | local function dprint(...) 95 | if debug_p then 96 | print(...) 97 | end 98 | end 99 | 100 | -- Either a list of lines (key=nil), or the list of objects 101 | -- and we are interested in the field "key". 102 | local function max_width(lines,key) 103 | local max = 0 104 | for _,v in pairs(lines) do 105 | if key ~= nil then 106 | v = v[key] 107 | end 108 | max = math.max(max, utf8.len(v)) 109 | end 110 | return max 111 | end 112 | 113 | local function mk_delim(w) 114 | local s = "" 115 | for _ = 1,w do 116 | s = s .. "─" 117 | end 118 | return s 119 | end 120 | 121 | local function split_lines(s) 122 | return vim.split(s, "\n") 123 | -- local ls = {} 124 | -- for l in string.gmatch(s, "[^\n]+") do 125 | -- table.insert(ls, l) 126 | -- end 127 | -- return ls 128 | end 129 | 130 | -- This one reiles on the presence of main_buf variable. 131 | -- TODO we also need to consider the case when the file 132 | -- has been changed externally. Otherwise locations 133 | -- and definitions wouldn't match-up. 134 | local function main_buf_changed() 135 | for _,v in pairs(vim.fn.getbufinfo()) do 136 | if v.bufnr == main_buf then 137 | return v.changed 138 | end 139 | end 140 | return nil 141 | end 142 | 143 | -- Get the first visible line number in the main window 144 | local function main_win_visl() 145 | local w = vim.fn.win_getid() 146 | local visl = nil 147 | if w ~= main_win then 148 | vim.fn.win_gotoid(main_win) 149 | visl = vim.fn.line("w0") 150 | vim.fn.win_gotoid(w) 151 | else 152 | visl = vim.fn.line("w0") 153 | end 154 | return visl 155 | end 156 | 157 | local function get_current_loc() 158 | return Loc:new(vim.fn.line("."), vim.fn.virtcol(".")) 159 | end 160 | 161 | ------ Agda interaction-related functions ------ 162 | ------------------------------------------------ 163 | 164 | function M.setup(config) 165 | if not config then 166 | return 167 | end 168 | if config.agda then agda_bin = config.agda end 169 | if config.agda_args then agda_args = config.agda_args end 170 | if config.debug_p then debug_p = config.debug_p end 171 | end 172 | 173 | function M.close_prompt_win() 174 | if pwin ~= nil then 175 | vim.api.nvim_win_close(pwin, true) 176 | pwin = nil 177 | end 178 | end 179 | 180 | -- goalid is a pair 181 | local function mk_prompt_window(file, goal, loff) 182 | if pbuf == nil then 183 | pbuf = vim.api.nvim_create_buf(false, true) 184 | -- load unicode mappings. 185 | vim.api.nvim_buf_call(pbuf, function () vim.cmd("runtime agda-input.vim") end) 186 | end 187 | 188 | local status = {silent=true, nowait=true, noremap=true} 189 | local function mk_mapping(key, fun, args) 190 | vim.api.nvim_buf_set_keymap( 191 | pbuf, 'n', key, 192 | ":call getbufvar(" .. main_buf .. ", 'AgdaMod')." .. fun .. "(" .. args .. ")", 193 | status) 194 | end 195 | mk_mapping(",", "agda_type_context", string.format("'%s', 'Simplified', %d", file, goal[1])) 196 | mk_mapping(".", "agda_type_context_infer", string.format("'%s', 'Simplified', %d", file, goal[1])) 197 | mk_mapping("d", "agda_infer", string.format("'%s', %d", file, goal[1])) 198 | mk_mapping("c", "agda_make_case", string.format("'%s', %d", file, goal[1])) 199 | mk_mapping("r", "agda_refine", string.format("'%s', %d", file, goal[1])) 200 | mk_mapping("n", "agda_compute", string.format("'%s', %d", file, goal[1])) 201 | mk_mapping("a", "agda_auto", string.format("'%s', %d", file, goal[1])) 202 | mk_mapping("s", "agda_solve", string.format("'%s', %d", file, goal[1])) 203 | mk_mapping("h", "agda_helper_fun", string.format("'%s', %d", file, goal[1])) 204 | mk_mapping("q", "close_msg_win", "") 205 | mk_mapping("q", "close_prompt_win", "") 206 | 207 | local visl = loff or 1 208 | -- We need to add this because things like numbers may 209 | -- take some additional space (and it changes when the overall 210 | -- number of lines increases). 211 | local offset = vim.fn.getwininfo(main_win)[1].textoff 212 | pwin = vim.api.nvim_open_win( 213 | pbuf, true, 214 | { 215 | relative='win', 216 | win=main_win, 217 | row=goal[2].start.line - visl + 1, 218 | col=goal[2].start.col + offset - 1, 219 | width=pmin_width, 220 | height=1, 221 | style="minimal", 222 | anchor="SW" 223 | }) 224 | 225 | end 226 | 227 | 228 | -- Show message window. `loc:Loc` is the location of the goal 229 | -- in the file. If `loc` is omitted, the window is shown at 230 | -- the current position of the cursor. 231 | local function mk_window(lines,loc) 232 | if msg_buf == nil then 233 | msg_buf = vim.api.nvim_create_buf(false, true) 234 | end 235 | 236 | local max = max_width(lines) 237 | vim.api.nvim_buf_set_lines(msg_buf, 0, -1, true, lines) 238 | 239 | if msg_win ~= nil then 240 | vim.api.nvim_win_close(msg_win, true) 241 | end 242 | 243 | local state = { 244 | width=math.max(max,msg_min_width), 245 | height=#lines, 246 | style="minimal" 247 | } 248 | if loc == nil then 249 | state.relative='cursor' 250 | state.row=1 251 | state.col=1 252 | else 253 | state.relative = 'win' 254 | state.win = main_win 255 | -- We know that goal defines a position that has to 256 | -- exist in the buffer, therefore we can use it to 257 | -- position the window. 258 | state.bufpos = {loc.line-1, loc.col-1} 259 | end 260 | 261 | -- We need to register autocommand that sets `msg_win` to nil 262 | -- when leaving window with :q. 263 | vim.api.nvim_buf_call(msg_buf, function () 264 | vim.cmd("au WinClosed call getbufvar(" .. main_buf .. ", 'AgdaMod').close_msg_win()") 265 | 266 | end) 267 | 268 | local status = {silent=true, nowait=true, noremap=true} 269 | local function mk_mappingx(key, fun, args) 270 | vim.api.nvim_buf_set_keymap( 271 | msg_buf, 'n', key, 272 | ":call getbufvar(" .. main_buf .. ", 'AgdaMod')." .. fun .. "(" .. args .. ")", 273 | status) 274 | end 275 | mk_mappingx("q", "close_msg_win", "") 276 | mk_mappingx("q", "close_msg_win", "") 277 | 278 | --print("mk_win: l="..(l or "nil")..", ".."c="..(c or "nil").."; "..vim.inspect(state)) 279 | msg_win = vim.api.nvim_open_win(msg_buf, false, state) 280 | 281 | --FIXME figure out a good way to give a message window a better color. 282 | --vim.cmd('hi Active ctermbg=DarkYellow ctermfg=Black') 283 | --vim.api.nvim_win_set_option(msg_win, 'winhl', "Normal:Active") 284 | end 285 | 286 | function M.close_msg_win() 287 | if msg_win ~= nil then 288 | vim.api.nvim_win_close(msg_win, true) 289 | msg_win = nil 290 | end 291 | end 292 | 293 | 294 | local function handle_hl(msg) 295 | -- Convert an offset into a line/lineoffset format 296 | -- that is suitable for the vim highlighter. 297 | -- XXX we can use binsearch here if we want to 298 | local function find_line(offset, lines, content) 299 | for i,v in pairs(lines) do 300 | if (offset >= v[1] and offset <= v[2]) then 301 | return {i-1, utf8.offset(content[i], offset-v[1]+1)-1} 302 | end 303 | end 304 | end 305 | 306 | -- Convert the names of the agda hl groups into the 307 | -- Vim ones. TODO replace with table. 308 | local function translate_hl_group(name) 309 | if (name == "keyword") then return "Keyword" 310 | elseif (name == "symbol") then return "Normal" 311 | elseif (name == "datatype") then return "Type" 312 | -- XXX we can give a distinct color to primitive types if we want to 313 | elseif (name == "primitivetype") then return "Type" 314 | elseif (name == "function") then return "Operator" 315 | elseif (name == "bound") then return "Identifier" 316 | elseif (name == "inductiveconstructor") then return "Constant" 317 | elseif (name == "number") then return "Number" 318 | elseif (name == "comment") then return "Comment" 319 | elseif (name == "hole") then return "Todo" 320 | elseif (name == "unsolvedmeta") then return "Todo" 321 | elseif (name == "string") then return "String" 322 | elseif (name == "catchallclause") then return "Folded" 323 | -- XXX I am not sure what's the purpose of highlighting 324 | -- areas that have been typechecked with a different color... 325 | elseif (name == "typechecks") then return "Normal" 326 | elseif (name == "module") then return "Structure" 327 | elseif (name == "postulate") then return "PreProc" 328 | elseif (name == "primitive") then return "PreProc" 329 | elseif (name == "error") then return "Error" 330 | elseif (name == "terminationproblem") then return "NonTerminating" 331 | elseif (name == "missingdefinition") then return "NoDefinition" 332 | -- TODO add more! 333 | else 334 | --name = name or "nil, WEIRD!!!" 335 | --dprint("Don't know hl-group for " .. name) 336 | return "Normal" 337 | end 338 | end 339 | 340 | 341 | -- FIXME just seen the case when `info` field is nil! Weird... 342 | if msg.info then 343 | for _, v in pairs(msg.info.payload) do 344 | local r = v.range 345 | -- FIXME s sometimes can be nil... Why? 346 | local s = find_line(r[1], main_lines, main_content) 347 | local e = find_line(r[2], main_lines, main_content) 348 | 349 | if s == nil or e == nil then 350 | dprint("handle_hl: crazy s=nil state " .. vim.inspect(v)) 351 | elseif #v.atoms > 0 then 352 | local g = translate_hl_group(v.atoms[1]) 353 | -- We are using `highlight.range` instead of `nvim_buf_add_highlight` 354 | -- as we can have multiline comments. 355 | if g ~= "Normal" then 356 | vim.highlight.range(main_buf,hl_ns,g,s,e) 357 | end 358 | end 359 | --print("hl: [" .. vim.inspect(s) .. ", " .. vim.inspect(e) .. "] ", v["atoms"][1]) 360 | end 361 | end 362 | end 363 | 364 | 365 | local function handle_displayinfo(msg) 366 | -- Adds the header and delimiter(s) to the list of lines. 367 | -- If `lines` is a singleton, and `sep` is not nil, 368 | -- we prepend `header` to the line, and only the top delimiter: 369 | -- ------------------------ 370 | -- `header` `sep` `line[1]` 371 | -- 372 | -- Otherwise, we add two delimiters around `header` like this: 373 | -- ------------------------ 374 | -- `header` 375 | -- ------------------------ 376 | -- `lines` 377 | -- 378 | local function add_sep(lines,header,sep) 379 | -- FIXME we also need to consider `max_width`, as we can only have the window 380 | -- as large as the buffer/window width. 381 | -- 382 | -- Single-line output. 383 | if #lines == 1 and sep ~= nil then 384 | lines[1] = header .. sep .. lines[1] 385 | table.insert(lines, 1, mk_delim(math.max(msg_min_width, utf8.len(lines[1])))) 386 | return lines 387 | end 388 | 389 | -- Multiline ouput with header. 390 | local m = max_width(lines) 391 | local l = mk_delim(math.max(msg_min_width, m)) 392 | table.insert(lines,1,header) 393 | table.insert(lines,2,l) 394 | table.insert(lines,1,l) 395 | return lines 396 | end 397 | 398 | local function mk_decl(name,lines,sep) 399 | local p = {} 400 | local spc = string.rep(" ", utf8.len(sep)) 401 | for k,v in ipairs(lines) do 402 | if k == 1 then 403 | table.insert(p, name .. sep .. v) 404 | name = string.rep(" ", utf8.len(name)) 405 | else 406 | table.insert(p, name .. spc .. v) 407 | end 408 | end 409 | return p 410 | end 411 | 412 | local function table_append(p,q) 413 | for _,v in ipairs(q) do 414 | table.insert(p,v) 415 | end 416 | end 417 | 418 | local inf = msg.info 419 | --debug(inf) 420 | if (inf.kind == "Error") then 421 | -- Latest versions of Agda changed the interface. 422 | local text = inf.message or inf["error"].message 423 | -- if the prompt window is open, the error message (most likely!) 424 | -- is coming from the incorrect info we passed. It should not 425 | -- indicate that the entire file needs loading. 426 | if pwin == nil then 427 | error_count = error_count + 1 428 | -- clear all the possible continuations 429 | sfunc = nil 430 | sargs = nil 431 | end 432 | error(text) 433 | elseif inf.kind == "Context" then 434 | local p = {} 435 | local max_name_len = max_width(inf.context, "originalName") 436 | for _,v in ipairs(inf.context) do 437 | -- FIXME we want reifiedName here, as it may differ. 438 | local name = v.originalName 439 | name = name .. string.rep(" ", max_name_len - utf8.len(name)) 440 | table_append(p, mk_decl(name, split_lines(v.binding), " : ")) 441 | end 442 | mk_window(add_sep(p,"Context")) 443 | elseif inf.kind == "InferredType" then 444 | mk_window(add_sep(split_lines(inf.expr), "Inferred Type", " : ")) 445 | elseif inf.kind == "NormalForm" then 446 | mk_window(add_sep(split_lines(inf.expr), "Normal Form", " : ")) 447 | elseif inf.kind == "Auto" then 448 | print(string.format("Auto: %s", inf.info)) 449 | elseif inf.kind == "ModuleContents" then 450 | local indent = " " 451 | local p = {} 452 | for _,v in ipairs(inf.contents) do 453 | table.insert(p, indent .. v.name) 454 | table_append(p, mk_decl(indent .. indent, split_lines(v.term), " : ")) 455 | end 456 | mk_window(add_sep(p, "Module Content")) 457 | elseif inf.kind == "WhyInScope" then 458 | mk_window(add_sep(split_lines(inf.message), 459 | "Why `" .. inf.thing .. "' is in scope")) 460 | elseif inf.kind == "AllGoalsWarnings" then 461 | local m = {} 462 | local indent = " " 463 | -- At some point the json API changed, and erros are put into the table instead of string. 464 | if type(inf.errors) == "string" and inf.errors ~= "" then 465 | table.insert(m, "Errors") 466 | for _,v in ipairs(vim.split(inf.errors, "\n")) do 467 | table.insert(m, indent .. v) 468 | end 469 | end 470 | if type(inf.errors) == "table" and #inf.errors > 0 then 471 | table.insert(m, "Errors") 472 | for _,v in ipairs(inf.errors) do 473 | for k,w in ipairs(vim.split(v.message, "\n")) do 474 | table.insert(m, string.format("%s%s", indent, w)) 475 | end 476 | end 477 | end 478 | 479 | -- Same as above, in 2.6.2 json API changed. 480 | if type(inf.warnings) == "string" and inf.warnings ~= "" then 481 | table.insert(m, "Warnings") 482 | for _,v in ipairs(vim.split(inf.warnings, "\n")) do 483 | table.insert(m, indent .. v) 484 | end 485 | end 486 | if type(inf.warnings) == "table" and #inf.warnings > 0 then 487 | table.insert(m, "Warnings") 488 | for _,v in ipairs(inf.warnings) do 489 | for k,w in ipairs(vim.split(v.message, "\n")) do 490 | table.insert(m, string.format("%s%s", indent, w)) 491 | end 492 | end 493 | end 494 | 495 | if #inf.invisibleGoals > 0 then 496 | table.insert(m, "Invisible Goals") 497 | for _,v in ipairs(inf.invisibleGoals) do 498 | local o = v.constraintObj 499 | if type(o) == "table" then o = o.name end 500 | if v.type ~= nil then 501 | table.insert(m, string.format("%s%s %s %s", indent, o, v.kind, v.type)) 502 | else 503 | table.insert(m, string.format("%s%s %s", indent, o, v.kind)) 504 | end 505 | end 506 | end 507 | if #inf.visibleGoals > 0 then 508 | table.insert(m, "Visible Goals") 509 | for _,v in ipairs(inf.visibleGoals) do 510 | local o = v.constraintObj 511 | if type(o) == "table" then o = o.id end 512 | table.insert(m, string.format("%s%s %s %s", indent, o, v.kind, v.type)) 513 | end 514 | end 515 | print(table.concat(m, "\n")) 516 | -- This is a way to see all the info at the same time, but this 517 | -- gets too noisy when we are in the middle of development. 518 | --vim.api.nvim_echo({ { table.concat(m, "\n") } }, true, {}) 519 | elseif inf.kind == "GoalSpecific" then 520 | local p = {} 521 | local g = inf.goalInfo 522 | local ip = inf.interactionPoint 523 | if g.kind == "InferredType" then 524 | p = add_sep(split_lines(g.expr), "Inferred Type", " : ") 525 | elseif g.kind == "NormalForm" then 526 | p = add_sep(split_lines(g.expr), "Normal Form", " : ") 527 | elseif g.kind == "GoalType" then 528 | -- FIXME(artem) This is a quick hack to display boundary 529 | -- when using cubical agda. Adjust the overall width 530 | -- considering boundaries as well as type/context info. 531 | local max_b_len = 0 532 | if #g["boundary"] > 0 then 533 | table_append(p, { "Boundary: " }) 534 | for _,v in pairs(g.boundary) do 535 | local vs = split_lines(v) 536 | max_b_len = math.max(max_b_len, max_width(vs)) 537 | table_append(p, vs) 538 | end 539 | table_append(p, { mk_delim(max_b_len) }) 540 | end 541 | local max_name_len = max_width(g.entries, "reifiedName") 542 | 543 | for _,v in ipairs(g.entries) do 544 | local name = v.reifiedName 545 | name = name .. string.rep(" ", max_name_len - utf8.len(name)) 546 | table_append(p, mk_decl(name, split_lines(v.binding), " : ")) 547 | end 548 | local ty = split_lines(g.type) 549 | local gt = "Goal Type : " 550 | local ht = "Have Type : " 551 | 552 | -- If there is inferred type we want to show this as well 553 | local have_ty = {} 554 | if g.typeAux.kind == "GoalAndHave" then 555 | have_ty = split_lines(g.typeAux.expr) 556 | end 557 | 558 | -- XXX we can lift this into the add_sep, if this is a common case. 559 | for k,_ in ipairs(ty) do 560 | if k == 1 then 561 | ty[k] = gt .. ty[k] 562 | else 563 | ty[k] = string.rep(" ", utf8.len(gt)) .. ty[k] 564 | end 565 | end 566 | -- In case we have inferred type 567 | for k,_ in ipairs(have_ty) do 568 | if k == 1 then 569 | have_ty[k] = ht .. have_ty[k] 570 | else 571 | have_ty[k] = string.rep(" ", utf8.len(ht)) .. have_ty[k] 572 | end 573 | end 574 | 575 | local m = max_width(p) 576 | m = math.max(max_width(ty),m) 577 | m = math.max(max_width(have_ty),m) 578 | m = math.max(msg_min_width, m) 579 | local l = mk_delim(m) 580 | -- Insert line at the top of context 581 | table.insert(p,1,l) 582 | -- Insert line at the top of goal type 583 | table.insert(ty,1,l) 584 | -- Add inferred type to goal type (no delimiter) 585 | for _,v in ipairs(have_ty) do 586 | table.insert(ty,v) 587 | end 588 | -- Add context to `ty` 589 | for _,v in ipairs(p) do 590 | table.insert(ty,v) 591 | end 592 | p = ty 593 | --end 594 | elseif g.kind == "HelperFunction" then 595 | p = add_sep(split_lines(g.signature), 596 | "Helper Function (copied to the \" register)") 597 | vim.fn.setreg('"', g.signature) 598 | else 599 | for _,v in pairs(vim.split(vim.inspect(g), "\n")) do 600 | table.insert(p, v) 601 | end 602 | p = add_sep(p, "Don't know how to show " .. g.kind) 603 | end 604 | -- We assume that GoalSpecific things always have a location 605 | mk_window(p, Loc:new(ip.range[1].start.line, ip.range[1].start.col)) 606 | else 607 | dprint("Don't know how to handle DisplayInfo of kind: " 608 | .. inf.kind .. " :: " .. vim.inspect(msg)) 609 | end 610 | end 611 | 612 | 613 | -- Goals 614 | local function print_goals() 615 | for k,v in pairs(goals) do 616 | print (k .. " : " .. vim.inspect(v.start) .. vim.inspect(v["end"])) 617 | end 618 | end 619 | 620 | -- This function runs continuations that might have been set by 621 | -- goal commands in case the buffer was modified. 622 | local function handle_interpoints(msg) 623 | goals = {} 624 | for _,v in pairs(msg.interactionPoints) do 625 | -- FIXME sometimes there is no range in the output... wtf... 626 | if #v.range > 0 then 627 | local s = v.range[1].start 628 | local e = v.range[1]["end"] 629 | goals[v.id] = { 630 | ["start"] = Loc:new(s.line, s.col), 631 | ["end"] = Loc:new(e.line, e.col) 632 | } 633 | end 634 | end 635 | --print("hanle interpoints, got: " .. len(goals) .. " goals") 636 | if sfunc ~= nil then 637 | --print("resuming suspended function") 638 | -- Run continuation, reset sfunc and sargs. 639 | sfunc(unpack(sargs)) 640 | sfunc = nil 641 | sargs = nil 642 | end 643 | end 644 | 645 | -- Search `goals` for the goal at: 646 | -- * loc => in case it is set 647 | -- * at the current location of the cursor => otherwise 648 | local function get_current_goal(loc) 649 | loc = loc or get_current_loc() --Loc:new(vim.fn.line("."), vim.fn.virtcol(".")) 650 | for k,v in pairs(goals) do 651 | if loc >= v.start and loc < v["end"] then 652 | return {k,v} 653 | end 654 | end 655 | return nil 656 | end 657 | 658 | -- The argument is a goal id. 659 | local function get_goal_content(id) 660 | local n = goals[id] 661 | if n == nil then return nil end 662 | 663 | --debug(n) 664 | local sl = n.start.line 665 | local sc = n.start.col 666 | local el = n["end"].line 667 | local ec = n["end"].col 668 | 669 | local content = vim.api.nvim_buf_get_lines(main_buf, sl-1, el, true) 670 | 671 | -- If the goal is "?", then it has no content. 672 | --print("get_goal_content: " .. content[1] .. ", " ..utf8.sub(content[1], sc, sc)) 673 | if utf8.sub(content[1], sc, sc) == "?" then 674 | return "" 675 | end 676 | 677 | -- Now we assume that the goal is "{! ... !}". 678 | local l = #content 679 | --print("goal lines " .. l) 680 | if l == 1 then 681 | content[1] = utf8.sub(content[1], sc+2, ec-3) 682 | else 683 | content[1] = utf8.sub(content[1], sc+2) 684 | content[l] = utf8.sub(content[l], 1, ec-3) 685 | end 686 | return table.concat(content, "\n") 687 | end 688 | 689 | -- XXX deprecated function. 690 | function M.gc() 691 | local n = get_current_goal() 692 | if n ~= nil then 693 | print("'" .. get_goal_content(n[1]) .. "'") 694 | else 695 | print("") 696 | end 697 | end 698 | 699 | local function handle_solve(soln) 700 | -- debug(msg) 701 | local n = goals[soln.interactionPoint] 702 | local sl = n.start.line 703 | local sc = n.start.col 704 | local el = n["end"].line 705 | local ec = n["end"].col 706 | 707 | -- Fucking vim api is in bytes! 708 | local content = vim.api.nvim_buf_get_lines(main_buf, sl-1, sl, true) 709 | local r = soln.expression 710 | local o = utf8.offset(content[1], sc) 711 | if pwin ~= nil then 712 | M.close_prompt_win() 713 | vim.api.nvim_buf_set_lines(pbuf, 0, -1, true, {}) 714 | end 715 | -- 1-based lines, 0-based columns! 716 | vim.api.nvim_win_set_cursor(main_win, {sl, o-1}) 717 | -- if the goal is "?" 718 | if utf8.sub(content[1], sc, sc) == "?" then 719 | vim.cmd("normal cl(" .. r .. ")") 720 | else 721 | vim.cmd("normal ca{(" .. r .. ")") 722 | end 723 | end 724 | 725 | local function handle_give(msg) 726 | --debug(msg) 727 | local n = goals[msg.interactionPoint.id] 728 | local sl = n.start.line 729 | local sc = n.start.col 730 | local el = n["end"].line 731 | local ec = n["end"].col 732 | 733 | -- Fucking vim api is in bytes! 734 | local content = vim.api.nvim_buf_get_lines(main_buf, sl-1, sl, true) 735 | local r = msg.giveResult.str 736 | local o = utf8.offset(content[1], sc) 737 | if pwin ~= nil then 738 | M.close_prompt_win() 739 | vim.api.nvim_buf_set_lines(pbuf, 0, -1, true, {}) 740 | end 741 | -- 1-based lines, 0-based columns! 742 | vim.api.nvim_win_set_cursor(main_win, {sl, o-1}) 743 | 744 | -- FIXME lift actions below into a function. 745 | 746 | -- Here we are abusing the block paste feature 747 | -- of vim, that is very convenient to position 748 | -- the content of the goal. However, it takes 749 | -- a bit of scaffolding to ensure that we have 750 | -- space to paste the text correctly. 751 | 752 | -- If the goal is {! !}, replace it with ? 753 | if utf8.sub(content[1], sc, sc) == "{" then 754 | vim.cmd('normal "_c%?') 755 | end 756 | 757 | local rr = split_lines(r) 758 | -- construct #rr + 1 empty lines and store 759 | -- it in nn. 760 | local nn = {} 761 | for _,_ in pairs(rr) do 762 | table.insert(nn, "") 763 | end 764 | -- Insert nn as characters, so that the 765 | -- content of the line after `?` moves nn+1 766 | -- lines down. 767 | table.insert(nn, "") 768 | vim.api.nvim_put(nn, "c", true, true) 769 | -- Go back to the place where '?' was 770 | vim.api.nvim_win_set_cursor(main_win, {sl, o-1}) 771 | 772 | -- Insert rr as block (note we use P) as we 773 | -- deleted `?` 774 | vim.api.nvim_put(rr, "b", true, true) 775 | -- Join the next line (we inserted one extra line 776 | -- to accomodate for the block paste). 777 | vim.cmd("normal J") 778 | vim.api.nvim_win_set_cursor(main_win, {sl, o-1}) 779 | 780 | -- Remove the ? 781 | vim.cmd('normal "_x') 782 | --debug(rr) 783 | end 784 | 785 | local function handle_make_case(msg) 786 | local n = goals[msg.interactionPoint.id] 787 | local sl = n.start.line --[1] 788 | local el = n["end"].line --[1] 789 | vim.api.nvim_buf_set_lines(main_buf, sl-1, el, true, msg.clauses) 790 | 791 | if pwin ~= nil then 792 | --print("closing window, setting buffer to nothing") 793 | M.close_prompt_win() 794 | vim.api.nvim_buf_set_lines(pbuf, 0, -1, true, {}) 795 | end 796 | end 797 | 798 | local function handle_status(msg) 799 | local a = msg.status.showImplicitArgs 800 | if a ~= nil then 801 | showImplicitArgs = a 802 | end 803 | end 804 | -- 805 | -- Do the actual work depending on the returned messagess 806 | local function handle_msg(msg) 807 | --debug(msg) 808 | if (msg["kind"] == "HighlightingInfo") then 809 | handle_hl(msg) 810 | elseif (msg["kind"] == "DisplayInfo") then 811 | handle_displayinfo(msg) 812 | elseif msg["kind"] == "InteractionPoints" then 813 | handle_interpoints(msg) 814 | elseif msg["kind"] == "GiveAction" then 815 | handle_give(msg) 816 | elseif msg["kind"] == "SolveAll" then 817 | for _,soln in ipairs(msg.solutions) do 818 | handle_solve(soln) 819 | end 820 | elseif msg.kind == "MakeCase" then 821 | handle_make_case(msg) 822 | elseif msg.kind == "Status" then 823 | handle_status(msg) 824 | elseif msg.kind == "RunningInfo" then 825 | print(msg.message) 826 | elseif msg.kind == "ClearHighlighting" then 827 | vim.api.nvim_buf_clear_namespace(main_buf,hl_ns,0,-1) 828 | else 829 | dprint("Don't know how to handle " .. msg["kind"] 830 | .. " :: " .. vim.inspect(msg)) 831 | end 832 | end 833 | 834 | -- XXX deprecated function 835 | function M.getevbuf() 836 | print("evbuf length is: " .. evbuf) 837 | end 838 | 839 | local function on_event(job_id, data, event) 840 | if event == "stdout" then 841 | -- We assume here that we can never receive mroe than one 842 | -- json message in one go. We can receive less than one, 843 | -- but never more. If this assumption fails, we'd have to 844 | -- adjust the code below. 845 | for _ , vv in pairs(data) do 846 | local v = string.gsub(vv, "^JSON> ", "") 847 | --print("on_event: " .. k .. ", v = " .. v , "\n" .. 848 | -- "on_event: " .. k .. ", e = ", evbuf) 849 | if v ~= "" then 850 | -- By some fucked up reason Agda prints failures to parse 851 | -- commands into stdout not stderr! 852 | if string.match(v, "^cannot read:") or string.match(v, "^not consumed:") then 853 | error("Agda interaction error: " .. v) 854 | elseif evbuf == "" then 855 | local status, parsed_v = pcall(vim.fn.json_decode, v) 856 | if status then 857 | handle_msg (parsed_v) 858 | else 859 | evbuf = v 860 | end 861 | else 862 | local status, parsed_evbuf = pcall(vim.fn.json_decode, evbuf) 863 | if status then 864 | handle_msg (parsed_evbuf) 865 | evbuf = v 866 | else 867 | evbuf = evbuf .. v 868 | end 869 | end 870 | end 871 | end 872 | -- If we have a valid json message in the evbuf, let's 873 | -- execute it. If not, we leave it for the next time this 874 | -- function is called. 875 | if evbuf ~= "" then 876 | local status, parsed_evbuf = pcall(vim.fn.json_decode, evbuf) 877 | if status then 878 | handle_msg(parsed_evbuf) 879 | evbuf = "" 880 | end 881 | end 882 | elseif event == "stderr" then 883 | error("Agda interaction error: " .. table.concat(data, "\n")) 884 | end 885 | end 886 | 887 | -- TODO we need to define a function that terminates the process 888 | -- and call it at exit. 889 | function M.agda_start() 890 | local t = {agda_bin} 891 | -- Before we start we need to figure out which version 892 | -- agda is running, as there are some changes in command interfaces 893 | -- starting from version 2.7.0 894 | -- TODO: put this into a function? 895 | local p = io.popen(agda_bin .. " --version") 896 | if p == nil then 897 | error("Cannot obtain agda version, running `" .. agda_bin .. " --version` failed") 898 | -- XXX: should we exit here? 899 | end 900 | local v = "" 901 | for l in p:lines() do v = v .. l end 902 | p:close() 903 | local major, minor, patch = string.match(v, "Agda version (%d+)%.(%d+)%.(%d+)") 904 | if major == nil or minor == nil or patch == nil then 905 | error("Cannot parse Agda version `" .. v .. "`") 906 | -- XXX: should we exit here? 907 | else 908 | agda_major = tonumber(major) or 0 909 | agda_minor = tonumber(minor) or 0 910 | agda_patch = tonumber(patch) or 0 911 | --print("version: " .. agda_major .. "." .. agda_minor .. "." .. agda_patch) 912 | end 913 | 914 | 915 | for _,arg in ipairs(agda_args) do 916 | table.insert(t, arg) 917 | end 918 | table.insert(t, "--interaction-json") 919 | agda_job = vim.fn.jobstart( 920 | t, 921 | { 922 | on_stderr = on_event, 923 | on_stdout = on_event, 924 | on_exit = on_event, 925 | stdout_buffered = false, 926 | stderr_buffered = false, 927 | }) 928 | end 929 | 930 | local function agda_feed (file, cmd) 931 | if (agda_job == nil) then 932 | M.agda_start() 933 | end 934 | local msg = "IOTCM \"" .. file .. "\" Interactive Direct " .. cmd 935 | vim.fn.jobsend(agda_job, {msg, ""}) 936 | end 937 | 938 | function M.agda_load (file) 939 | -- XXX should we kill evbuf in case we are stuck with an 940 | -- annoying error, or should it be done by a separate command? 941 | error_count = 0 942 | -- if the main buffer changed, save it before issuing agda (re)load. 943 | if main_buf_changed() == 1 then 944 | vim.api.nvim_buf_call(main_buf, function () vim.cmd('w') end) 945 | end 946 | -- Update the variable holding the content of the buffer 947 | main_content = vim.api.nvim_buf_get_lines(main_buf, 0, -1, true) 948 | 949 | -- Precompute offset positions for the beginning and end of 950 | -- each line. This will be used in the highlighting procedures. 951 | main_lines = {} 952 | local total = 1 953 | for i = 1, #main_content do 954 | local len = utf8.len(main_content[i]) 955 | main_lines[i] = {total, total+len} 956 | total = total + len + 1 957 | end 958 | 959 | 960 | -- TODO(artem) this is not triggered when we run `:split`, 961 | -- and I don't know what event will trigger that. WinEnter is 962 | -- too aggressive because it will trigger entering floating 963 | -- windows as well. 964 | vim.api.nvim_create_autocmd("BufWinEnter", { 965 | buffer = main_buf, 966 | callback = function () 967 | local w = vim.fn.win_getid() 968 | main_win = w 969 | end 970 | }) 971 | 972 | agda_feed(file, "(Cmd_load \"" .. file .. "\" [])") 973 | end 974 | 975 | ------ Goal-specific helper functions ------ 976 | -------------------------------------------- 977 | 978 | -- Wrap a function to run after the goals are updated. 979 | local function wrap_goal_action(func, file) 980 | if main_buf_changed() == 1 or error_count > 0 then 981 | if sfunc ~= nil then 982 | warning("an operation on the goal in progress") 983 | return 984 | end 985 | --print("suspending agda_context till the update") 986 | sfunc = func 987 | sargs = {} 988 | M.agda_load(file) 989 | else 990 | func() 991 | end 992 | end 993 | 994 | local function id_or_current_goal(id,loc) 995 | if id == nil then 996 | local n = get_current_goal(loc) 997 | if n ~= nil then 998 | return n[1] 999 | end 1000 | return nil 1001 | end 1002 | return id 1003 | end 1004 | 1005 | -- Get the content of the goal from the prompt buffer, or from the 1006 | -- goal, in case the prompt is empty. 1007 | local function get_trimmed_content(id) 1008 | local content = "" 1009 | -- Try getting content from the prompt window 1010 | if pbuf ~= nil then 1011 | content = table.concat(vim.api.nvim_buf_get_lines(pbuf, 0, -1, true), "\n") 1012 | content = vim.trim(content) 1013 | end 1014 | 1015 | -- If prompt window doesn't exist or is empty, try 1016 | -- looking inside the goal. 1017 | if content == "" then 1018 | content = vim.trim(get_goal_content(id)) 1019 | end 1020 | return content 1021 | end 1022 | 1023 | function M.agda_context(file) 1024 | wrap_goal_action(function () 1025 | local n = get_current_goal() 1026 | if n ~= nil then 1027 | -- The goal content shold not matter 1028 | local cmd = "(Cmd_context HeadNormal " .. n[1] .. " noRange \"\")" 1029 | agda_feed(file, cmd) 1030 | else 1031 | warning("cannot infer goal type, the cursor is not in the goal") 1032 | end 1033 | end, file) 1034 | end 1035 | 1036 | 1037 | function M.agda_show_goals(file) 1038 | local g = "" 1039 | local c = 0 1040 | for _,v in pairs(goals) do 1041 | g = g .. string.format("#%02d line %03d: ?\n", c, v.start.line) 1042 | c = c + 1 1043 | end 1044 | if c > 0 then 1045 | print(string.format("%d goal(s) in %s\n", c, file) .. g) 1046 | else 1047 | print(string.format("no goals in %s", file)) 1048 | end 1049 | end 1050 | 1051 | function M.agda_goal_next(file) 1052 | local loc = get_current_loc() 1053 | if len(goals) == 0 then 1054 | print(string.format("no goals in %s", file)) 1055 | return 1056 | end 1057 | local pos = nil 1058 | for _,v in pairs(goals) do 1059 | if v.start > loc then 1060 | pos = v.start 1061 | break 1062 | end 1063 | end 1064 | -- if we didn't find the goal after the cursor 1065 | -- just return the first goal in the list 1066 | if pos == nil then 1067 | pos = goals[0].start 1068 | end 1069 | 1070 | local content = vim.api.nvim_buf_get_lines(main_buf, pos.line-1, pos.line, true) 1071 | local o = utf8.offset(content[1], pos.col) 1072 | -- 1-based lines, 0-based columns! 1073 | vim.api.nvim_win_set_cursor(main_win, {pos.line, o-1}) 1074 | end 1075 | 1076 | 1077 | function M.agda_goal_prev(file) 1078 | local loc = get_current_loc() 1079 | if len(goals) == 0 then 1080 | print(string.format("no goals in %s", file)) 1081 | return 1082 | end 1083 | 1084 | -- We have a 0-indexed table of goals which is not 1085 | -- canonical in lua. Therefore we have to take an extra 1086 | -- step to traverse it in reverse. 1087 | local ix = {} 1088 | for k,_ in pairs(goals) do 1089 | table.insert(ix,k) 1090 | end 1091 | -- Fuck you LUA, fucking piece of shit! 1092 | -- As 0 is a non-canonical index, it is inserted at the end 1093 | -- of the table. What a nonsense! 1094 | table.sort(ix) 1095 | 1096 | local pos = nil 1097 | for i = #ix,1,-1 do 1098 | if goals[ix[i]].start < loc then 1099 | pos = goals[ix[i]].start 1100 | break 1101 | end 1102 | end 1103 | -- if we didn't find the goal before the cursor, 1104 | -- return the last goal in the list 1105 | if pos == nil then 1106 | -- We do have at least one goal 1107 | pos = goals[ix[#ix]].start 1108 | end 1109 | 1110 | local content = vim.api.nvim_buf_get_lines(main_buf, pos.line-1, pos.line, true) 1111 | local o = utf8.offset(content[1], pos.col) 1112 | -- 1-based lines, 0-based columns! 1113 | vim.api.nvim_win_set_cursor(main_win, {pos.line, o-1}) 1114 | end 1115 | 1116 | -- XXX weird name of the function 1117 | local function toggle_implicit(file, b) 1118 | if b then 1119 | agda_feed(file, "(ShowImplicitArgs True)") 1120 | else 1121 | agda_feed(file, "(ShowImplicitArgs False)") 1122 | end 1123 | end 1124 | 1125 | function M.agda_type_context(file,prec,id) 1126 | -- Get the location of the cursor at the time we called `agda_type_context`. 1127 | local loc = get_current_loc() 1128 | wrap_goal_action(function () 1129 | local id = id_or_current_goal(id, loc) 1130 | if id == nil then 1131 | return warning("cannot obtain goal type and context, the cursor is not in the goal") 1132 | end 1133 | 1134 | if prec == nil then 1135 | prec = "Simplified" 1136 | end 1137 | 1138 | local cmd = "(Cmd_goal_type_context " .. prec .. " " .. id .. " noRange \"\")" 1139 | -- XXX not sure whether we can avoid this, it 1140 | -- seems that agda_load sets the this flag to false. 1141 | -- if not showImplicitArgs then 1142 | -- toggle_implicit(file,true) 1143 | -- agda_feed(file, cmd) 1144 | -- toggle_implicit(file,false) 1145 | -- else 1146 | -- agda_feed(file, cmd) 1147 | -- end 1148 | agda_feed(file, cmd) 1149 | end, file) 1150 | end 1151 | 1152 | function M.agda_type_context_infer(file,prec,id) 1153 | -- Get the location of the cursor at the time we called `agda_type_context`. 1154 | local loc = get_current_loc() 1155 | wrap_goal_action(function () 1156 | local id = id_or_current_goal(id, loc) 1157 | if id == nil then 1158 | return warning("cannot obtain goal type and context, the cursor is not in the goal") 1159 | end 1160 | 1161 | if prec == nil then 1162 | prec = "Simplified" 1163 | end 1164 | 1165 | local content = get_trimmed_content(id) 1166 | debug("Goal content: " .. content) 1167 | local g = vim.fn.json_encode(content) -- puts "" around, and escapes \n 1168 | 1169 | local cmd = "(Cmd_goal_type_context_infer " .. prec .. " " .. id .. " noRange " .. g .. ")" 1170 | agda_feed(file, cmd) 1171 | end, file) 1172 | end 1173 | 1174 | 1175 | local function agda_infer_toplevel(file, e) 1176 | local e = vim.fn.json_encode(e) 1177 | local cmd = "(Cmd_infer_toplevel Normalised " .. e .. ")" 1178 | agda_feed(file, cmd) 1179 | end 1180 | 1181 | function M.agda_infer(file,id) 1182 | local loc = get_current_loc() 1183 | wrap_goal_action(function () 1184 | local id = id_or_current_goal(id, loc) 1185 | if id == nil then 1186 | -- In case we are not in the goal, or the content of the goal 1187 | -- is empty, prompt the user for the expression. 1188 | local g = vim.fn.input("Expression: ") 1189 | return agda_infer_toplevel(file, g) 1190 | end 1191 | local content = get_trimmed_content(id) 1192 | if content == "" then 1193 | return warning("The goal is empty") 1194 | end 1195 | local g = vim.fn.json_encode(content) -- puts "" around, and escapes \n 1196 | local cmd = "(Cmd_infer Normalised " .. id .. " noRange " .. g .. ")" 1197 | agda_feed(file, cmd) 1198 | end, file) 1199 | end 1200 | 1201 | function M.agda_make_case(file,id) 1202 | local loc = get_current_loc() 1203 | wrap_goal_action(function () 1204 | local id = id_or_current_goal(id, loc) 1205 | if id == nil then 1206 | return warning("cannot make case, the cursor is not in the goal") 1207 | end 1208 | local content = get_trimmed_content(id) 1209 | if content == "" then 1210 | content = vim.fn.input("Variables to split on: ") 1211 | end 1212 | local g = vim.fn.json_encode(content) 1213 | local cmd = "(Cmd_make_case " .. id .. " noRange " .. g .. ")" 1214 | agda_feed(file, cmd) 1215 | end, file) 1216 | end 1217 | 1218 | 1219 | function M.agda_refine(file,id) 1220 | local loc = get_current_loc() 1221 | wrap_goal_action(function () 1222 | local id = id_or_current_goal(id, loc) 1223 | if id == nil then 1224 | return warning("cannot refine, the cursor is not in the goal") 1225 | end 1226 | 1227 | local content = get_trimmed_content(id) 1228 | -- TODO handle IntroConstructorUnknown DisplayInfo 1229 | --if content == "" then 1230 | -- return warning("cannot refine empty goal") 1231 | --end 1232 | local g = vim.fn.json_encode(content) 1233 | local cmd = "(Cmd_refine_or_intro True " .. id .. " noRange " .. g .. ")" 1234 | agda_feed(file, cmd) 1235 | end, file) 1236 | end 1237 | 1238 | -- TODO ensure that we can pass the argument to Cmd_compute. Right now 1239 | -- we are just hard-coding DefaultCompute, but there are other values one 1240 | -- can pass: 1241 | -- * DefaultCompute 1242 | -- * IgnoreAbstract 1243 | -- * UseShowInstance 1244 | -- * HeadCompute 1245 | -- 1246 | local function agda_compute_toplevel(file, e) 1247 | local e = vim.fn.json_encode(e) 1248 | local cmd = "(Cmd_compute_toplevel DefaultCompute " .. e .. ")" 1249 | agda_feed(file, cmd) 1250 | end 1251 | 1252 | function M.agda_compute(file,id) 1253 | local loc = get_current_loc() 1254 | wrap_goal_action(function () 1255 | local id = id_or_current_goal(id, loc) 1256 | if id == nil then 1257 | -- In case we are not in the goal, or the content of the goal 1258 | -- is empty, prompt the user for the expression. 1259 | local g = vim.fn.input("Expression: ") 1260 | return agda_compute_toplevel(file, g) 1261 | end 1262 | local content = get_trimmed_content(id) 1263 | if content == "" then 1264 | return warning("The goal is empty") 1265 | end 1266 | local g = vim.fn.json_encode(content) -- puts "" around, and escapes \n 1267 | local cmd = "(Cmd_compute DefaultCompute " .. id .. " noRange " .. g .. ")" 1268 | agda_feed(file, cmd) 1269 | end, file) 1270 | end 1271 | 1272 | function M.agda_helper_fun(file,id) 1273 | local loc = get_current_loc() 1274 | wrap_goal_action(function () 1275 | local id = id_or_current_goal(id, loc) 1276 | if id == nil then 1277 | return warning("cannot create helper function, the cursor is not in the goal") 1278 | end 1279 | local content = get_trimmed_content(id) 1280 | if content == "" then 1281 | content = vim.fn.input("Expression: ") 1282 | end 1283 | local g = vim.fn.json_encode(content) -- puts "" around, and escapes \n 1284 | local cmd = "(Cmd_helper_function AsIs " .. id .. " noRange " .. g .. ")" 1285 | agda_feed(file, cmd) 1286 | end, file) 1287 | end 1288 | 1289 | 1290 | local function agda_auto_toplevel(file) 1291 | local cmd = "(Cmd_autoAll)" 1292 | -- Agda changed interface after 2.7.0 1293 | -- TODO: introduce proper lexicographic version comparison 1294 | if agda_major >= 2 and agda_minor >= 7 then 1295 | cmd = "(Cmd_autoAll AsIs)" 1296 | end 1297 | agda_feed(file, cmd) 1298 | end 1299 | 1300 | local function agda_solve_toplevel(file) 1301 | local cmd = "(Cmd_solveAll Simplified)" 1302 | agda_feed(file, cmd) 1303 | end 1304 | 1305 | function M.agda_auto(file,id) 1306 | local loc = get_current_loc() 1307 | wrap_goal_action(function () 1308 | local id = id_or_current_goal(id, loc) 1309 | if id == nil then 1310 | return agda_auto_toplevel(file) 1311 | end 1312 | local content = get_trimmed_content(id) 1313 | local g = vim.fn.json_encode(content) -- puts "" around, and escapes \n 1314 | local cmd = "(Cmd_autoOne " .. id .. " noRange " .. g .. ")" 1315 | -- Agda changed interface after 2.7.0 1316 | if agda_major >= 2 and agda_minor >= 7 then 1317 | cmd = "(Cmd_autoOne AsIs " .. id .. " noRange " .. g .. ")" 1318 | end 1319 | agda_feed(file, cmd) 1320 | end, file) 1321 | end 1322 | 1323 | function M.agda_solve(file,id) 1324 | local loc = get_current_loc() 1325 | wrap_goal_action(function () 1326 | local id = id_or_current_goal(id, loc) 1327 | if id == nil then 1328 | return agda_solve_toplevel(file) 1329 | end 1330 | local content = get_trimmed_content(id) 1331 | local g = vim.fn.json_encode(content) -- puts "" around, and escapes \n 1332 | local cmd = "(Cmd_solveOne Simplified " .. id .. " noRange " .. g .. ")" 1333 | agda_feed(file, cmd) 1334 | end, file) 1335 | end 1336 | 1337 | function M.agda_module_contents(file) 1338 | wrap_goal_action(function () 1339 | local content = vim.fn.input("Module Name: ") 1340 | local e = vim.fn.json_encode(content) 1341 | local cmd = "(Cmd_show_module_contents_toplevel Simplified" .. e .. ")" 1342 | agda_feed(file,cmd) 1343 | end, file) 1344 | end 1345 | 1346 | function M.agda_why_inscope(file) 1347 | wrap_goal_action(function () 1348 | -- FIXME this is a bug when we have something like 1349 | -- (F.blah + ...) 1350 | -- ^ 1351 | -- | 1352 | -- cWORD is returning `(F.blah`, what a nonsense... 1353 | local content = vim.fn.expand("") 1354 | local e = vim.fn.json_encode(content) 1355 | local cmd = "(Cmd_why_in_scope_toplevel " .. e .. ")" 1356 | agda_feed(file,cmd) 1357 | end, file) 1358 | end 1359 | 1360 | -- TODO add show-constraints 1361 | -- NonInteractive Indirect ( Cmd_constraints ) function 1362 | -- Should be rather straight-forward 1363 | 1364 | function M.agda_show_constraints(file) 1365 | wrap_goal_action(function () 1366 | local cmd = "(Cmd_constraints )" 1367 | agda_feed(file,cmd) 1368 | end, file) 1369 | end 1370 | 1371 | function M.edit_goal(file) 1372 | wrap_goal_action(function () 1373 | local n = get_current_goal() 1374 | if n ~= nil then 1375 | local visl = vim.fn.line("w0") 1376 | mk_prompt_window(file, n, visl) 1377 | else 1378 | warning("cannot edit goal, the cursor is not in the goal") 1379 | end 1380 | end, file) 1381 | end 1382 | 1383 | function M.show_implicit(file) 1384 | agda_feed(file, "(ShowImplicitArgs True)") 1385 | end 1386 | 1387 | function M.hide_implicit(file) 1388 | agda_feed(file, "(ShowImplicitArgs False)") 1389 | end 1390 | 1391 | return M 1392 | --------------------------------------------------------------------------------