├── .gitignore ├── LICENSE ├── README.md ├── autoload └── agda.vim ├── ftdetect └── agda.vim ├── ftplugin └── agda.vim └── syntax └── agda.vim /.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msuperdock/vim-agda/884cd77ade0170fed10c8179e4b2f5d0760d38a2/.gitignore -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2020 Matt Superdock 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software is furnished to do so, 8 | subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vim-agda 2 | 3 | A neovim plugin for Agda, featuring: 4 | 5 | - Asynchronous type-checking. 6 | - Interaction with Agda executable (see functions [here](#functions)). 7 | - Unicode character input (e.g. `\to` for `→`). 8 | - Simple & correct syntax highlighting. 9 | - Optional syntax highlighting & folding in interaction buffer via 10 | [vim-foldout](https://github.com/msuperdock/vim-foldout). 11 | - Optional unused code checking via 12 | [agda-unused](https://github.com/msuperdock/agda-unused). 13 | 14 | Supported Agda versions: `>= 2.6.2 && < 2.6.3`. 15 | 16 | ## Installation 17 | 18 | Use your preferred installation method. For example, with 19 | [vim-plug](https://github.com/junegunn/vim-plug), use: 20 | 21 | ``` 22 | Plug 'msuperdock/vim-agda' 23 | ``` 24 | 25 | Optionally, also install the following: 26 | 27 | - [vim-foldout](https://github.com/msuperdock/vim-foldout) (Vim plugin, required 28 | for syntax highlighting & folding in interaction buffer.) 29 | - [agda-unused](https://github.com/msuperdock/agda-unused) (Haskell application, 30 | required for `agda#unused()` function.) 31 | 32 | ## Functions 33 | 34 | vim-agda provides the functions in the table below; we also present the 35 | corresponding emacs commands for reference. You can bind a key to a function in 36 | your `init.vim` using, for example: 37 | 38 | ``` 39 | autocmd BufWinEnter *.agda noremap l :call agda#load() 40 | ``` 41 | 42 | This binds `l` to `agda#load()` for all `.agda` files. 43 | 44 | | function | emacs | description | 45 | | --- | --- | --- | 46 | | `agda#load()` | `C-c C-l` | Load or reload Agda. | 47 | | `agda#abort()` | `C-c C-x C-a` | Abort the current Agda command. | 48 | | `agda#next()` | `C-c C-f` | Move cursor to next hole. | 49 | | `agda#previous()` | `C-c C-b` | Move cursor to previous hole. | 50 | | `agda#infer()` | `C-c C-d` | Infer type at hole, or at top level if no hole. | 51 | | `agda#give()` | `C-c C-SPC` | Give expression for hole at cursor. | 52 | | `agda#refine()` | `C-c C-r` | Refine expression for hole at cursor. | 53 | | `agda#context()` | `C-c C-e` | Display context for hole at cursor. | 54 | | `agda#unused()` | n/a | Check the current file for unused code. | 55 | 56 | The `agda#unused()` function requires the 57 | [agda-unused](https://github.com/msuperdock/agda-unused) executable (version 58 | `>= 0.3.0`) to be installed. 59 | 60 | ## Options 61 | 62 | vim-agda provides the global options in the table below. You can set an option 63 | in your `init.vim` using, for example: 64 | 65 | ``` 66 | let g:agda_args = ['--local-interfaces'] 67 | ``` 68 | 69 | | variable | default | description | 70 | | --- | --- | --- | 71 | | `g:agda_args` | `[]` | Arguments for `agda` executable. | 72 | | `g:agda_unused_args` | `[]` | Arguments for `agda-unused` executable. | 73 | | `g:agda_debug` | `0` | Log interaction output to the messages buffer. | 74 | 75 | ## vim-foldout 76 | 77 | vim-agda provides optional syntax highlighting & folding in the interaction 78 | buffer via [vim-foldout](https://github.com/msuperdock/vim-foldout). For 79 | example, consider the following Agda file: 80 | 81 | ``` 82 | module Test where 83 | 84 | postulate 85 | 86 | x = ? 87 | ``` 88 | 89 | After calling `agda#load()`, the interaction buffer appears: 90 | 91 | ``` 92 | -- ## Goals 93 | 94 | ?0 95 | : _1 96 | _0 97 | : Sort 98 | _1 99 | : _0 100 | 101 | -- ## Warnings 102 | 103 | /data/code/agda-test/Test.agda:3,1-10 104 | Empty postulate block. 105 | 106 | ``` 107 | 108 | If [vim-foldout](https://github.com/msuperdock/vim-foldout) is installed & 109 | enabled, then: 110 | 111 | - The goals are syntax-highlighted as Agda code. 112 | - The headings are syntax-highlighted as headings. 113 | - The sections can be folded using 114 | [vim-foldout](https://github.com/msuperdock/vim-foldout) commands. 115 | 116 | If [vim-foldout](https://github.com/msuperdock/vim-foldout) is not enabled, then 117 | the interaction buffer is not syntax-highlighted at all. 118 | 119 | -------------------------------------------------------------------------------- /autoload/agda.vim: -------------------------------------------------------------------------------- 1 | " ## Commands 2 | 3 | " ### Load 4 | 5 | " Load current file with no command-line options. 6 | function agda#load() 7 | if s:setup() < 0 8 | return -1 9 | endif 10 | 11 | if exists('s:agda_loading') && s:agda_loading > 0 12 | echom 'Loading Agda (command ignored).' 13 | return 14 | endif 15 | 16 | if !exists('g:agda_job') || g:agda_job < 0 17 | try 18 | let g:agda_job = jobstart(['agda', '--interaction-json'] + g:agda_args 19 | \ , {'on_stdout': function('s:handle_event')}) 20 | catch /E475/ 21 | echom 'Agda executable not found.' 22 | return 23 | endtry 24 | endif 25 | 26 | if g:agda_job < 0 27 | echom 'Failed to load Agda.' 28 | return 29 | endif 30 | 31 | redraw 32 | echom 'Loading Agda.' 33 | 34 | let s:data = '' 35 | 36 | let l:command = 37 | \ [ 'Cmd_load' 38 | \ , s:quote(s:code_file) 39 | \ , '[]' 40 | \ ] 41 | 42 | call s:send(l:command) 43 | endfunction 44 | 45 | " Handle initial setup common to Agda & agda-unused, or return -1 on failure. 46 | function s:setup() 47 | silent update 48 | 49 | if exists('s:agda_loading') && s:agda_loading > 0 50 | echom 'Loading Agda (command ignored).' 51 | return -1 52 | endif 53 | 54 | if !exists('s:agda_buffer') 55 | let s:agda_buffer = -1 56 | endif 57 | 58 | let s:code_buffer = bufnr() 59 | let s:code_file = expand('%:p') 60 | call s:auto_delete() 61 | endfunction 62 | 63 | " ### Abort 64 | 65 | " Abort the current Agda process operation. 66 | function agda#abort() 67 | if s:status(2) < 0 68 | return 69 | endif 70 | 71 | if s:agda_loading == 0 72 | echom 'Nothing to abort.' 73 | return 74 | endif 75 | 76 | call s:send(['Cmd_abort'], 1) 77 | endfunction 78 | 79 | " ### Next 80 | 81 | " Move cursor to next hole. 82 | function agda#next() 83 | if s:status(1) < 0 84 | return 85 | endif 86 | 87 | let l:pos = [line('.'), col('.')] 88 | for l:point in s:points 89 | if s:compare(l:point.start, l:pos) > 0 90 | call cursor(l:point.start) 91 | echo '' 92 | return 93 | endif 94 | endfor 95 | 96 | echom 'No hole found.' 97 | endfunction 98 | 99 | " ### Previous 100 | 101 | " Move cursor to previous hole. 102 | function agda#previous() 103 | if s:status(1) < 0 104 | return 105 | endif 106 | 107 | let l:pos = [line('.'), col('.')] 108 | for l:point in reverse(s:points) 109 | if s:compare(l:point.end, l:pos) < 0 110 | call cursor(l:point.start) 111 | echo '' 112 | return 113 | endif 114 | endfor 115 | 116 | echom 'No hole found.' 117 | endfunction 118 | 119 | " ### Infer 120 | 121 | " Infer type of expression in hole at cursor. 122 | " If no hole is found, prompt for an expression. 123 | function agda#infer() 124 | if s:status() < 0 125 | return 126 | endif 127 | 128 | let l:id = s:lookup() 129 | 130 | let l:input = s:input('Infer', 1) 131 | if l:input ==# '.' 132 | return 133 | endif 134 | 135 | if l:id >= 0 136 | let l:command = 137 | \ [ 'Cmd_infer' 138 | \ , 'Simplified' 139 | \ , l:id 140 | \ , 'noRange' 141 | \ , s:quote(l:input) 142 | \ ] 143 | 144 | else 145 | let l:command = 146 | \ [ 'Cmd_infer_toplevel' 147 | \ , 'Simplified' 148 | \ , s:quote(l:input) 149 | \ ] 150 | 151 | endif 152 | 153 | call s:send(l:command) 154 | endfunction 155 | 156 | " ### Give 157 | 158 | " Give expression for hole at cursor. 159 | function agda#give() 160 | if s:status() < 0 161 | return 162 | endif 163 | 164 | let l:id = s:lookup(1) 165 | if l:id < 0 166 | return 167 | endif 168 | 169 | let l:input = s:input('Give', 1) 170 | if l:input ==# '.' 171 | return 172 | endif 173 | 174 | let l:command = 175 | \ [ 'Cmd_give' 176 | \ , 'WithoutForce' 177 | \ , l:id 178 | \ , 'noRange' 179 | \ , s:quote(l:input) 180 | \ ] 181 | 182 | call s:send(l:command) 183 | endfunction 184 | 185 | " ### Refine 186 | 187 | " Refine expression for hole at cursor. 188 | function agda#refine() 189 | if s:status() < 0 190 | return 191 | endif 192 | 193 | let l:id = s:lookup(1) 194 | if l:id < 0 195 | return 196 | endif 197 | 198 | let l:input = s:input('Refine') 199 | if l:input ==# '.' 200 | return 201 | endif 202 | 203 | let l:command = 204 | \ [ 'Cmd_refine_or_intro' 205 | \ , 'False' 206 | \ , l:id 207 | \ , 'noRange' 208 | \ , s:quote(l:input) 209 | \ ] 210 | 211 | call s:send(l:command) 212 | endfunction 213 | 214 | " ### Context 215 | 216 | " Display context for hole at cursor. 217 | function agda#context() 218 | if s:status() < 0 219 | return 220 | endif 221 | 222 | let l:id = s:lookup(1) 223 | if l:id < 0 224 | return 225 | endif 226 | 227 | let l:command = 228 | \ [ 'Cmd_goal_type_context' 229 | \ , 'Simplified' 230 | \ , l:id 231 | \ , 'noRange' 232 | \ , s:quote('') 233 | \ ] 234 | 235 | call s:send(l:command) 236 | endfunction 237 | 238 | " ### Unused 239 | 240 | " Check for unused code in the current module. 241 | function agda#unused() 242 | if s:setup() < 0 243 | return -1 244 | endif 245 | 246 | try 247 | let l:agda_unused_job = jobstart 248 | \ ( ['agda-unused', expand('%'), '--json'] + g:agda_unused_args 249 | \ , {'on_stdout': function('s:handle_unused')} 250 | \ ) 251 | catch /E475/ 252 | echom 'agda-unused executable not found.' 253 | return 254 | endtry 255 | 256 | if l:agda_unused_job < 0 257 | echom 'Failed to run agda-unused.' 258 | else 259 | call s:handle_loading(1) 260 | endif 261 | endfunction 262 | 263 | " ### Debug 264 | 265 | " Toggle value of `agda_debug`. 266 | function agda#toggle_debug() 267 | if g:agda_debug 268 | echom "Agda debugging off." 269 | let g:agda_debug = 0 270 | else 271 | echom "Agda debugging on." 272 | let g:agda_debug = 1 273 | endif 274 | endfunction 275 | 276 | " ## Handlers 277 | 278 | " ### Event 279 | 280 | " Callback function for the Agda job. 281 | function s:handle_event(id, data, event) 282 | for l:line in a:data 283 | if g:agda_debug 284 | echom l:line 285 | echo '' 286 | redraw 287 | endif 288 | 289 | call s:handle_line(l:line) 290 | endfor 291 | endfunction 292 | 293 | " Callback function for the agda-unused job. 294 | function s:handle_unused(id, data, event) 295 | " Check if output is non-empty; return if not. 296 | if len(a:data) == 0 297 | return 298 | endif 299 | 300 | " Decode JSON; return if unsuccessful. 301 | try 302 | let l:json = json_decode(a:data[0]) 303 | catch 304 | return 305 | endtry 306 | 307 | " Handle output. 308 | if l:json.type ==# 'none' 309 | call s:handle_clear(trim(l:json.message)) 310 | elseif l:json.type ==# 'unused' 311 | call s:handle_output('Unused', l:json.message) 312 | elseif l:json.type ==# 'error' 313 | call s:handle_output('Unused', l:json.message) 314 | endif 315 | endfunction 316 | 317 | " ### Line 318 | 319 | " Handle a line of data from Agda. 320 | function s:handle_line(line) 321 | " Ignore interaction prompt. 322 | if a:line ==# 'JSON> ' 323 | let s:data = '' 324 | return 325 | endif 326 | 327 | " Try decoding JSON; store line if decoding JSON fails. 328 | try 329 | let l:json = json_decode(s:data . a:line) 330 | catch 331 | let s:data .= a:line 332 | return 333 | endtry 334 | 335 | " Reset data if decoding JSON succeeds. 336 | let s:data = '' 337 | 338 | " Handle goals. 339 | if l:json.kind ==# 'DisplayInfo' 340 | \ && l:json.info.kind ==# 'AllGoalsWarnings' 341 | call s:handle_goals_all(l:json.info) 342 | 343 | " Handle errors. 344 | elseif l:json.kind ==# 'DisplayInfo' 345 | \ && l:json.info.kind ==# 'Error' 346 | call s:handle_error(l:json.info) 347 | 348 | " Handle inferred type at hole. 349 | elseif l:json.kind ==# 'DisplayInfo' 350 | \ && l:json.info.kind ==# 'GoalSpecific' 351 | \ && l:json.info.goalInfo.kind ==# 'InferredType' 352 | call s:handle_infer(l:json.info.goalInfo.expr) 353 | 354 | " Handle context. 355 | elseif l:json.kind ==# 'DisplayInfo' 356 | \ && l:json.info.kind ==# 'GoalSpecific' 357 | call s:handle_context(l:json.info.goalInfo) 358 | 359 | " Handle inferred type. 360 | elseif l:json.kind ==# 'DisplayInfo' 361 | \ && l:json.info.kind ==# 'InferredType' 362 | call s:handle_infer(l:json.info.expr) 363 | 364 | " Handle introduction not found error. 365 | elseif l:json.kind ==# 'DisplayInfo' 366 | \ && l:json.info.kind ==# 'IntroNotFound' 367 | call s:handle_loading(0) 368 | echom 'No introduction forms found.' 369 | 370 | " Handle abort. 371 | elseif l:json.kind ==# 'DoneAborting' 372 | call s:handle_loading(0) 373 | echom 'Aborted Agda command.' 374 | 375 | " Handle give. 376 | elseif l:json.kind ==# 'GiveAction' 377 | call s:handle_give(l:json.giveResult.str, l:json.interactionPoint.id) 378 | 379 | " Handle interaction points. 380 | elseif l:json.kind ==# 'InteractionPoints' 381 | call s:handle_points(l:json.interactionPoints) 382 | 383 | " Handle jump to error. 384 | elseif l:json.kind ==# 'JumpToError' 385 | call s:goto(l:json.position) 386 | 387 | " Handle status messages. 388 | elseif l:json.kind ==# 'RunningInfo' 389 | call s:handle_message(l:json.message) 390 | 391 | endif 392 | endfunction 393 | 394 | " ### Goals 395 | 396 | function s:handle_goals_all(info) 397 | let l:outputs = [] 398 | 399 | if a:info.visibleGoals != [] || a:info.invisibleGoals != [] 400 | let l:output 401 | \ = s:handle_goals(a:info.visibleGoals, 1) 402 | \ . s:handle_goals(a:info.invisibleGoals, 0) 403 | call s:append_output(l:outputs, 'Goals', l:output, 1) 404 | endif 405 | 406 | call s:append_messages(l:outputs, 'Warnings', a:info.warnings) 407 | call s:append_messages(l:outputs, 'Errors', a:info.errors) 408 | 409 | call s:handle_outputs(l:outputs) 410 | endfunction 411 | 412 | function s:handle_goals(goals, visible) 413 | let l:goals = map(copy(a:goals), {_, val -> s:handle_goal(val, a:visible)}) 414 | return join(l:goals, '') 415 | endfunction 416 | 417 | function s:handle_goal(goal, visible) 418 | if a:goal.kind ==# 'OfType' 419 | let l:name 420 | \ = a:visible 421 | \ ? '?' . a:goal.constraintObj.id 422 | \ : a:goal.constraintObj.name 423 | return s:signature(l:name, a:goal.type) 424 | elseif a:goal.kind ==# 'JustSort' 425 | return s:signature(a:goal.constraintObj.name, 'Sort') 426 | else 427 | echoerr 'Unrecognized goal.' 428 | endif 429 | endfunction 430 | 431 | " ### Points 432 | 433 | " Initialize script-local points list. 434 | function s:handle_points(points) 435 | " If any points are missing range, determine ranges by scanning file. 436 | if s:handle_points_valid(a:points) 437 | let s:points = map(copy(a:points), {_, val -> s:handle_point(val)}) 438 | else 439 | call s:handle_points_manual(a:points) 440 | endif 441 | endfunction 442 | 443 | function s:handle_point(point) 444 | return 445 | \ { 'id': a:point.id 446 | \ , 'start': s:handle_position(a:point.range[0].start, 0) 447 | \ , 'end': s:handle_position(a:point.range[0].end, -1) 448 | \ } 449 | endfunction 450 | 451 | function s:handle_position(pos, offset) 452 | return [a:pos.line, byteidxcomp(getline(a:pos.line), a:pos.col + a:offset)] 453 | endfunction 454 | 455 | " Determine whether the given points list has nonempty ranges. 456 | function s:handle_points_valid(points) 457 | for l:point in a:points 458 | if l:point.range == [] 459 | return 0 460 | endif 461 | endfor 462 | 463 | return 1 464 | endfunction 465 | 466 | function s:handle_points_manual(points) 467 | " Save initial position. 468 | let l:window = winnr() 469 | let l:line = line('.') 470 | let l:col = col('.') 471 | 472 | " Patterns for start & end of token. 473 | let l:start = '\(^\|[[:space:].;{}()@"]\)' 474 | let l:end = '\($\|[[:space:].;{}()@"]\)' 475 | 476 | " Go to beginning of code window. 477 | call s:to_code() 478 | call cursor(1, 1) 479 | 480 | " Initialize points list. 481 | let s:points = [] 482 | 483 | let l:index = 0 484 | while l:index < len(a:points) 485 | 486 | " Match single-line comments. 487 | let l:pos1 = searchpos('\m' . l:start . '--', 'nWz') 488 | 489 | " Match block comments. 490 | let l:pos2 = searchpos('\m{-', 'nWz') 491 | 492 | " Match strings. 493 | let l:pos3 = searchpos('\m"', 'nWz') 494 | 495 | " Match holes. 496 | let l:pos4 = searchpos('\m' . l:start . '\zs?' . l:end, 'nWz') 497 | 498 | " Match block holes. 499 | let l:pos5 = searchpos('\m{!', 'nWz') 500 | 501 | " If single-line comment is found first: 502 | if l:pos1[0] > 0 503 | \ && (l:pos2[0] == 0 || s:compare(l:pos1, l:pos2) < 0) 504 | \ && (l:pos3[0] == 0 || s:compare(l:pos1, l:pos3) < 0) 505 | \ && (l:pos4[0] == 0 || s:compare(l:pos1, l:pos4) < 0) 506 | \ && (l:pos5[0] == 0 || s:compare(l:pos1, l:pos5) < 0) 507 | 508 | call cursor(l:pos1) 509 | call cursor(l:pos1[0], col('$')) 510 | 511 | " If block comment is found first: 512 | elseif l:pos2[0] > 0 513 | \ && (l:pos3[0] == 0 || s:compare(l:pos2, l:pos3) < 0) 514 | \ && (l:pos4[0] == 0 || s:compare(l:pos2, l:pos4) < 0) 515 | \ && (l:pos5[0] == 0 || s:compare(l:pos2, l:pos5) < 0) 516 | 517 | call cursor(l:pos2) 518 | if searchpair('\m{-', '', '\m-}', 'Wz') <= 0 519 | break 520 | endif 521 | 522 | " If string is found first: 523 | elseif l:pos3[0] > 0 524 | \ && (l:pos4[0] == 0 || s:compare(l:pos3, l:pos4) < 0) 525 | \ && (l:pos5[0] == 0 || s:compare(l:pos3, l:pos5) < 0) 526 | 527 | call cursor(l:pos3) 528 | if search('\m"', 'Wz') <= 0 || s:next() == 0 529 | break 530 | endif 531 | 532 | " If hole is found first: 533 | elseif l:pos4[0] > 0 534 | \ && (l:pos5[0] == 0 || s:compare(l:pos4, l:pos5) < 0) 535 | 536 | let s:points += [ 537 | \ { 'id': a:points[l:index].id 538 | \ , 'start': l:pos4 539 | \ , 'end': l:pos4 540 | \ }] 541 | let l:index += 1 542 | 543 | call cursor(l:pos4) 544 | if s:next() == 0 545 | break 546 | endif 547 | 548 | " If block hole is found first: 549 | elseif l:pos5[0] > 0 550 | 551 | call cursor(l:pos5) 552 | let l:pos6 = searchpairpos('\m{!', '', '\m!\zs}', 'Wz') 553 | if l:pos6[0] == 0 554 | break 555 | endif 556 | 557 | let s:points += [ 558 | \ { 'id': a:points[l:index].id 559 | \ , 'start': l:pos5 560 | \ , 'end': l:pos6 561 | \ }] 562 | let l:index += 1 563 | 564 | " If no match is found: 565 | else 566 | 567 | break 568 | 569 | endif 570 | endwhile 571 | 572 | " Restore original position. 573 | call s:to_window(l:window) 574 | call cursor(l:line, l:col) 575 | endfunction 576 | 577 | " ### Give 578 | 579 | function s:handle_give(result, id) 580 | for l:point in s:points 581 | if l:point.id == a:id 582 | call s:replace(bufwinnr(s:code_buffer), l:point.start, l:point.end, a:result) 583 | silent update 584 | return 585 | endif 586 | endfor 587 | endfunction 588 | 589 | " ### Infer 590 | 591 | function s:handle_infer(result) 592 | call s:handle_clear('Inferred type: ' . a:result) 593 | endfunction 594 | 595 | " ### Context 596 | 597 | function s:handle_context(info) 598 | let l:outputs = [] 599 | 600 | call s:append_output(l:outputs, 'Goal', 601 | \ s:signature('Goal', a:info.type), 1) 602 | call s:append_output(l:outputs, 'Context', 603 | \ s:handle_entries(a:info.entries), 1) 604 | call s:handle_outputs(l:outputs) 605 | endfunction 606 | 607 | function s:handle_entries(entries) 608 | return join(map(copy(a:entries), {_, val -> s:handle_entry(val)}), '') 609 | endfunction 610 | 611 | function s:handle_entry(entry) 612 | let l:name = a:entry.reifiedName . (a:entry.inScope ? '' : ' (out of scope)') 613 | return s:signature(l:name, a:entry.binding) 614 | endfunction 615 | 616 | " ### Message 617 | 618 | function s:handle_message(message) 619 | echom trim(substitute(a:message, '\m (.*)', '', 'g')) 620 | endfunction 621 | 622 | function s:append_message(outputs, name, content) 623 | return s:append_output(a:outputs, a:name, a:content.message . "\n") 624 | endfunction 625 | 626 | function s:append_messages(outputs, name, contents) 627 | if a:contents == [] 628 | return a:outputs 629 | endif 630 | 631 | let l:messages = map(copy(a:contents), {_, val -> val['message'] . "\n"}) 632 | return s:append_output(a:outputs, a:name, join(l:messages, '')) 633 | endfunction 634 | 635 | " ### Error 636 | 637 | function s:handle_error(info) 638 | let l:outputs = [] 639 | 640 | call s:append_messages(l:outputs, 'Warnings', a:info.warnings) 641 | call s:append_message(l:outputs, 'Error', a:info.error) 642 | call s:handle_outputs(l:outputs) 643 | endfunction 644 | 645 | " ### Output 646 | 647 | " Print the given output in the Agda buffer. 648 | " The optional argument indicates whether to treat the output as code. 649 | function s:handle_output(name, content, ...) 650 | let l:code = get(a:, 1) 651 | 652 | " Clear echo area. 653 | echo '' 654 | 655 | " Indicate that Agda is no longer loading. 656 | let s:agda_loading = 0 657 | 658 | " Save initial window. 659 | let l:current = winnr() 660 | 661 | " Switch to Agda buffer. 662 | let l:name = 'Agda (' . a:name . ')' 663 | if s:to_agda() >= 0 664 | execute 'file ' . l:name 665 | else 666 | execute 'belowright 10split ' . l:name 667 | let &l:buftype = 'nofile' 668 | let &l:swapfile = 0 669 | let s:agda_buffer = bufnr() 670 | call s:auto_delete() 671 | endif 672 | 673 | " Write output. 674 | let &l:modifiable = 1 675 | silent %delete _ 676 | silent put =a:content 677 | execute 'normal! ggdd' 678 | let &l:modifiable = 0 679 | silent! %foldopen! 680 | 681 | " Enable foldout if loaded. 682 | if exists('g:foldout_loaded') 683 | let &l:filetype = l:code ? 'agda' : '' 684 | let b:foldout_heading_comment = 1 685 | let b:foldout_heading_ignore = '\(Errors\|Warnings\)' 686 | let b:foldout_heading_string = '-- %s' 687 | call foldout#enable() 688 | endif 689 | 690 | " Restore original window. 691 | call s:to_window(l:current) 692 | endfunction 693 | 694 | " Print the given outputs in the Agda buffer, under separate headings. 695 | " The input should be a list of objects with `name`, `content`, `code` fields: 696 | " - The `name` field is a string for the section heading. 697 | " - The `content` field is a string for the section contents. 698 | " - The `code` field is a flag indicating whether to treat the contents as code. 699 | function s:handle_outputs(outputs) 700 | if a:outputs == [] 701 | call s:handle_clear('All done.') 702 | return 703 | endif 704 | 705 | let l:names 706 | \ = map(copy(a:outputs), {_, val -> val['name']}) 707 | let l:contents 708 | \ = len(a:outputs) == 1 709 | \ ? map(copy(a:outputs), {_, val -> val['content'] . "\n"}) 710 | \ : map(copy(a:outputs), {_, val -> s:section(val['name'], val['content'])}) 711 | let l:code 712 | \ = len(a:outputs) != 1 || a:outputs[0].code 713 | 714 | call s:handle_output 715 | \ ( join(l:names, ', ') 716 | \ , join(l:contents, '') 717 | \ , l:code 718 | \ ) 719 | endfunction 720 | 721 | " Clear the Agda buffer, and echo the given message string. 722 | function s:handle_clear(message) 723 | let s:agda_loading = 0 724 | call s:handle_delete() 725 | echom a:message 726 | endfunction 727 | 728 | " Display loading status in Agda buffer name. 729 | function s:handle_loading(loading) 730 | " Update `s:agda_loading` variable. 731 | let s:agda_loading = a:loading 732 | 733 | " Save initial window. 734 | let l:current = winnr() 735 | 736 | " Go to Agda window. 737 | if s:to_agda() < 0 738 | return 739 | endif 740 | 741 | " Change Agda buffer name, if necessary. 742 | let l:file = expand('%') 743 | let l:match = match(l:file, '\m \[loading\]$') 744 | if a:loading == 0 && l:match >= 0 745 | execute 'file ' . l:file[: l:match - 1] 746 | elseif a:loading > 0 && l:match < 0 747 | execute 'file ' . l:file . ' [loading]' 748 | endif 749 | 750 | " Restore original window. 751 | call s:to_window(l:current) 752 | endfunction 753 | 754 | function s:append_output(outputs, name, content, ...) 755 | let l:code = get(a:, 1) 756 | 757 | return add(a:outputs, 758 | \ { 'name': a:name 759 | \ , 'content': a:content 760 | \ , 'code': l:code 761 | \ }) 762 | endfunction 763 | 764 | " ### Delete 765 | 766 | " Delete the Agda buffer. 767 | function s:handle_delete() 768 | if s:agda_buffer >= 0 769 | execute s:agda_buffer . 'bdelete' 770 | endif 771 | 772 | let s:agda_buffer = -1 773 | endfunction 774 | 775 | " Set up automatic call to `s:handle_delete()`. 776 | function s:auto_delete() 777 | augroup agda 778 | autocmd! * 779 | autocmd QuitPre call s:handle_delete() 780 | augroup end 781 | endfunction 782 | 783 | " ## Print 784 | 785 | " Format a section with heading & content. 786 | function s:section(name, content) 787 | return '-- ## ' 788 | \ . a:name 789 | \ . "\n\n" 790 | \ . a:content 791 | \ . "\n" 792 | endfunction 793 | 794 | " Format a type signature. 795 | function s:signature(name, type) 796 | return a:name 797 | \ . "\n" 798 | \ . ' : ' 799 | \ . join(split(a:type, "\n"), "\n ") 800 | \ . "\n" 801 | endfunction 802 | 803 | " ## Indent 804 | 805 | " Compute indent level for a line. 806 | function agda#indent(lnum) 807 | if a:lnum <= 1 808 | return -1 809 | endif 810 | 811 | let l:line = getline(a:lnum - 1) 812 | 813 | let l:directive 814 | \ = l:line =~# '\musing\($\|[[:space:].;{}()@"]\)' 815 | \ || l:line =~# '\mrenaming\($\|[[:space:].;{}()@"]\)' 816 | \ || l:line =~# '\mhiding\($\|[[:space:].;{}()@"]\)' 817 | if !l:directive 818 | return -1 819 | endif 820 | 821 | let l:directive_open 822 | \ = l:line =~# '\musing\s*$' 823 | \ || l:line =~# '\mrenaming\s*$' 824 | \ || l:line =~# '\mhiding\s*$' 825 | \ || count(l:line, '(') > count(l:line, ')') 826 | if !l:directive_open 827 | return -1 828 | endif 829 | 830 | return indent(a:lnum) + shiftwidth() 831 | endfunction 832 | 833 | " ## Utilities 834 | 835 | " Return -1 if point1 is before point2. 836 | " Return 1 if point1 is after point2. 837 | " Return 0 if point1 equals point2. 838 | function s:compare(point1, point2) 839 | let [l:line1, l:col1] = a:point1 840 | let [l:line2, l:col2] = a:point2 841 | 842 | if l:line1 < l:line2 843 | return -1 844 | elseif l:line1 > l:line2 845 | return 1 846 | elseif l:col1 < l:col2 847 | return -1 848 | elseif l:col1 > l:col2 849 | return 1 850 | else 851 | return 0 852 | endif 853 | endfunction 854 | 855 | " Go to the nth character in the buffer. 856 | function s:goto(n) 857 | execute 'goto ' . byteidxcomp(join(getline(1, '$'), "\n"), a:n) 858 | endfunction 859 | 860 | " Get input from the user. 861 | " The `prompt` string should not include a trailing colon or space. 862 | " The optional argument indicates whether to fail on all-whitespace input. 863 | " Return an escaped string, or '.' on failure. 864 | function s:input(prompt, ...) 865 | let l:strict = get(a:, 1) 866 | 867 | let l:input = input( 868 | \ { 'prompt': a:prompt . ': ' 869 | \ , 'cancelreturn': '.' 870 | \ }) 871 | 872 | if l:strict && l:input !~# '\m\S' 873 | redraw 874 | echom 'No expression given.' 875 | return '.' 876 | endif 877 | 878 | return escape(l:input, '\"') 879 | endfunction 880 | 881 | " Get id of interaction point at cursor, or return -1 on failure. 882 | " The optional argument indicates whether to print an error message on failure. 883 | function s:lookup(...) 884 | let l:print = get(a:, 1) 885 | 886 | let l:line = line('.') 887 | let l:col = col('.') 888 | for l:point in s:points 889 | if s:compare([l:line, l:col], l:point.start) >= 0 890 | \ && s:compare([l:line, l:col], l:point.end) <= 0 891 | return l:point.id 892 | endif 893 | endfor 894 | 895 | if l:print 896 | echom 'Cursor not on hole.' 897 | endif 898 | 899 | return -1 900 | endfunction 901 | 902 | " Go to next character; return 1 if successful, 0 if at end of file. 903 | function s:next() 904 | let l:line = line('.') 905 | let l:col = col('.') 906 | 907 | if l:col < col('$') - 1 908 | call cursor(l:line, l:col + 1) 909 | return 1 910 | elseif l:line < line('$') 911 | call cursor(l:line + 1, 1) 912 | return 1 913 | endif 914 | 915 | return 0 916 | endfunction 917 | 918 | " Wrap a string in parentheses. 919 | function s:parens(str) 920 | return '(' . a:str . ')' 921 | endfunction 922 | 923 | " Wrap a string in double quotes. 924 | function s:quote(str) 925 | return '"' . a:str . '"' 926 | endfunction 927 | 928 | " Replace text at the given location, preserving cursor position. 929 | " Assume `str` does not contain any newline characters. 930 | function s:replace(window, start, end, str) 931 | " Save window. 932 | let l:window = winnr() 933 | call s:to_window(a:window) 934 | 935 | " Save cursor position. 936 | let l:line = line('.') 937 | let l:col = col('.') 938 | 939 | " Perform deletion. 940 | call cursor(a:start) 941 | if a:end[0] == a:start[0] 942 | execute 'normal! ' . (a:end[1] - a:start[1] + 1) . 'x' 943 | else 944 | let l:command 945 | \ = a:end[0] > a:start[0] + 1 946 | \ ? (a:start[0] + 1) . ',' . (a:end[0] - 1) . 'd' 947 | \ : '' 948 | execute 'normal! d$' 949 | execute l:command 950 | call cursor(a:start[0] + 1, 1) 951 | execute 'normal! ' . a:end[1] . 'x' 952 | call cursor(a:start[0], 1) 953 | execute 'normal! gJ' 954 | endif 955 | 956 | " Perform insertion. 957 | call cursor(a:start[0], a:start[1] - 1) 958 | execute 'normal! a' . a:str 959 | 960 | " Restore cursor position. 961 | if s:compare([l:line, l:col], a:start) <= 0 962 | call cursor(l:line, l:col) 963 | elseif s:compare([l:line, l:col], a:end) <= 0 964 | call cursor(a:start) 965 | elseif l:line == a:start[0] && l:line == a:end[0] 966 | call cursor(a:start[0], l:col - (a:end[1] - a:start[1] + 1)) 967 | elseif l:line == a:end[0] 968 | call cursor(a:start[0], l:col - a:end[1]) 969 | elseif a:end[0] == a:start[0] 970 | call cursor(l:line, l:col) 971 | else 972 | call cursor(l:line - (a:end[0] - a:start[0] - 1), l:col) 973 | endif 974 | 975 | " Restore window. 976 | call s:to_window(l:window) 977 | endfunction 978 | 979 | " Send command, as a list of tokens, to the Agda job. 980 | " The optional argument indicates whether to send an indirect command. 981 | function s:send(command, ...) 982 | let l:indirect = get(a:, 1) 983 | 984 | if !l:indirect 985 | call s:handle_loading(1) 986 | endif 987 | 988 | let l:command = 989 | \ [ 'IOTCM' 990 | \ , s:quote(s:code_file) 991 | \ , 'None' 992 | \ , (l:indirect ? 'Indirect' : 'Direct') 993 | \ , s:parens(join(a:command)) 994 | \ ] 995 | 996 | call chansend(g:agda_job, join(l:command) . "\n") 997 | endfunction 998 | 999 | " Check whether Agda is loaded on the current file. 1000 | " With no optional argument, require that Agda is loaded and not busy. 1001 | " With optional argument of 1, require only that Agda is loaded. 1002 | " With optional argument of 2, require only that Agda has started loading. 1003 | function s:status(...) 1004 | let l:mode = get(a:, 1) 1005 | 1006 | let l:loaded 1007 | \ = exists('g:agda_job') 1008 | \ && (l:mode == 2 || exists('s:agda_loading')) 1009 | \ && (l:mode == 2 || exists('s:code_buffer')) 1010 | \ && (l:mode == 2 || exists('s:code_file')) 1011 | \ && (l:mode == 2 || exists('s:points')) 1012 | \ && g:agda_job >= 0 1013 | 1014 | if !l:loaded 1015 | echom 'Agda not loaded.' 1016 | return -1 1017 | elseif expand('%:p') !=# s:code_file 1018 | echom 'Agda loaded on different file.' 1019 | return -1 1020 | elseif l:mode == 0 && s:agda_loading > 0 1021 | echom 'Loading Agda (command ignored).' 1022 | return -1 1023 | endif 1024 | endfunction 1025 | 1026 | " Go to agda window; return -1 if none. 1027 | function s:to_agda() 1028 | let l:agda_window = bufwinnr(s:agda_buffer) 1029 | 1030 | if l:agda_window >= 0 1031 | call s:to_window(l:agda_window) 1032 | else 1033 | return -1 1034 | endif 1035 | endfunction 1036 | 1037 | " Go to code window. 1038 | function s:to_code() 1039 | call s:to_window(bufwinnr(s:code_buffer)) 1040 | endfunction 1041 | 1042 | " Go to given window number. 1043 | function s:to_window(window) 1044 | execute a:window . 'wincmd w' 1045 | endfunction 1046 | 1047 | -------------------------------------------------------------------------------- /ftdetect/agda.vim: -------------------------------------------------------------------------------- 1 | autocmd BufRead,BufNewFile *.agda,*.lagda setfiletype agda 2 | 3 | -------------------------------------------------------------------------------- /ftplugin/agda.vim: -------------------------------------------------------------------------------- 1 | " vim-agda - Agda mode for vim. 2 | " Maintainer: Matt Superdock 3 | " Version: 1.0 4 | " License: MIT 5 | 6 | if exists('b:agda_loaded') 7 | finish 8 | else 9 | let b:agda_loaded = 1 10 | endif 11 | 12 | " ## Options 13 | 14 | " A list of arguments for the agda executable. 15 | if !exists('g:agda_args') 16 | let g:agda_args = [] 17 | endif 18 | 19 | " A list of arguments for the agda-unused executable. 20 | if !exists('g:agda_unused_args') 21 | let g:agda_unused_args = [] 22 | endif 23 | 24 | " Whether to log output from the Agda executable to the messages buffer. 25 | if !exists('g:agda_debug') 26 | let g:agda_debug = 0 27 | endif 28 | 29 | " ## Comments 30 | 31 | let &l:comments = 's1fl:{-,mb:-,ex:-},:--' 32 | let &l:commentstring = '-- %s' 33 | 34 | " ## Indentation 35 | 36 | let &l:indentexpr = 'agda#indent(v:lnum)' 37 | 38 | " ## Match pairs 39 | 40 | let &l:matchpairs 41 | \ = '(:)' 42 | \ . ',{:}' 43 | \ . ',[:]' 44 | \ . ',<:>' 45 | \ . ',«:»' 46 | \ . ',‹:›' 47 | \ . ',⁅:⁆' 48 | \ . ',⁽:⁾' 49 | \ . ',₍:₎' 50 | \ . ',⌈:⌉' 51 | \ . ',⌊:⌋' 52 | \ . ',〈:〉' 53 | \ . ',⎛:⎞' 54 | \ . ',⎜:⎟' 55 | \ . ',⎝:⎠' 56 | \ . ',⎡:⎤' 57 | \ . ',⎢:⎥' 58 | \ . ',⎣:⎦' 59 | \ . ',⎧:⎫' 60 | \ . ',⎨:⎬' 61 | \ . ',⎩:⎭' 62 | \ . ',⎪:⎪' 63 | \ . ',⎴:⎵' 64 | \ . ',❨:❩' 65 | \ . ',❪:❫' 66 | \ . ',❬:❭' 67 | \ . ',❮:❯' 68 | \ . ',❰:❱' 69 | \ . ',❲:❳' 70 | \ . ',❴:❵' 71 | \ . ',⟅:⟆' 72 | \ . ',⟦:⟧' 73 | \ . ',⟨:⟩' 74 | \ . ',⟪:⟫' 75 | \ . ',⦃:⦄' 76 | \ . ',⦅:⦆' 77 | \ . ',⦇:⦈' 78 | \ . ',⦉:⦊' 79 | \ . ',⦋:⦌' 80 | \ . ',⦍:⦎' 81 | \ . ',⦏:⦐' 82 | \ . ',⦑:⦒' 83 | \ . ',⦓:⦔' 84 | \ . ',⦕:⦖' 85 | \ . ',⦗:⦘' 86 | \ . ',⸠:⸡' 87 | \ . ',⸢:⸣' 88 | \ . ',⸤:⸥' 89 | \ . ',⸦:⸧' 90 | \ . ',⸨:⸩' 91 | \ . ',〈:〉' 92 | \ . ',《:》' 93 | \ . ',「:」' 94 | \ . ',『:』' 95 | \ . ',【:】' 96 | \ . ',〔:〕' 97 | \ . ',〖:〗' 98 | \ . ',〘:〙' 99 | \ . ',〚:〛' 100 | \ . ',︗:︘' 101 | \ . ',︵:︶' 102 | \ . ',︷:︸' 103 | \ . ',︹:︺' 104 | \ . ',︻:︼' 105 | \ . ',︽:︾' 106 | \ . ',︿:﹀' 107 | \ . ',﹁:﹂' 108 | \ . ',﹃:﹄' 109 | \ . ',﹇:﹈' 110 | \ . ',﹙:﹚' 111 | \ . ',﹛:﹜' 112 | \ . ',﹝:﹞' 113 | \ . ',(:)' 114 | \ . ',<:>' 115 | \ . ',[:]' 116 | \ . ',{:}' 117 | \ . ',⦅:⦆' 118 | \ . ',「:」' 119 | 120 | " ## Glyphs 121 | 122 | " ### List 123 | 124 | let s:glyphs = {} 125 | 126 | " #### Combining marks 127 | 128 | call extend(s:glyphs, 129 | \ { 'over`': ' ̀' 130 | \ , "over'": ' ́' 131 | \ , 'over^': ' ̂' 132 | \ , 'overv': ' ̌' 133 | \ , 'over~': ' ̃' 134 | \ , 'over-': ' ̄' 135 | \ , 'over_': ' ̅' 136 | \ , 'over–': ' ̅' 137 | \ , 'over—': ' ̅' 138 | \ , 'overcup': ' ̆' 139 | \ , 'overcap': ' ̑' 140 | \ , 'over.': ' ̇' 141 | \ , 'over..': ' ̈' 142 | \ , 'over"': ' ̈' 143 | \ , 'over...': ' ⃛' 144 | \ , 'overright.': ' ͘' 145 | \ , 'overo': ' ̊' 146 | \ , 'over``': ' ̏' 147 | \ , "over''": ' ̋' 148 | \ , 'overvec': ' ⃑' 149 | \ , 'vec': ' ⃑' 150 | \ , 'overlvec': ' ⃐' 151 | \ , 'lvec': ' ⃐' 152 | \ , 'overarc': ' ⃕' 153 | \ , 'overlarc': ' ⃔' 154 | \ , 'overto': ' ⃗' 155 | \ , 'overfrom': ' ⃖' 156 | \ , 'overfromto': ' ⃡' 157 | \ , 'over*': ' ⃰' 158 | \ , 'under`': ' ̖' 159 | \ , "under'": ' ̗' 160 | \ , 'under,': ' ̗' 161 | \ , 'under.': ' ̣' 162 | \ , 'under..': ' ̤' 163 | \ , 'under"': ' ̤' 164 | \ , 'undero': ' ̥' 165 | \ , 'under-': ' ̱' 166 | \ , 'under_': ' ̲' 167 | \ , 'under–': ' ̲' 168 | \ , 'under—': ' ̲' 169 | \ , 'through~': ' ̴' 170 | \ , 'through-': ' ̵' 171 | \ , 'through_': ' ̶' 172 | \ , 'through–': ' ̶' 173 | \ , 'through—': ' ̶' 174 | \ , 'through/': ' ̷' 175 | \ , 'not': ' ̷' 176 | \ , 'through?': ' ̸' 177 | \ , 'Not': ' ̸' 178 | \ , 'through\|': ' ⃒' 179 | \ , 'throughshortmid': ' ⃓' 180 | \ , 'througho': ' ⃘' 181 | \ }) 182 | 183 | " #### Symbols 184 | 185 | call extend(s:glyphs, 186 | \ { '{{': '⦃' 187 | \ , '}}': '⦄' 188 | \ , ':': '∶' 189 | \ , '::': '∷' 190 | \ , ';': '﹔' 191 | \ , '..': '‥' 192 | \ , '=?': '≟' 193 | \ , 'all': '∀' 194 | \ , 'always': '□' 195 | \ , 'approx': '≈' 196 | \ , 'bot': '⊥' 197 | \ , 'box': '□' 198 | \ , 'boxdot': '⊡' 199 | \ , 'box.': '⊡' 200 | \ , 'boxminus': '⊟' 201 | \ , 'box-': '⊟' 202 | \ , 'boxplus': '⊞' 203 | \ , 'box+': '⊞' 204 | \ , 'boxtimes': '⊠' 205 | \ , 'box*': '⊠' 206 | \ , 'bul': '•' 207 | \ , 'C': 'ℂ' 208 | \ , 'cdot': '∙' 209 | \ , '.': '∙' 210 | \ , 'cdots': '⋯' 211 | \ , 'check': '✓' 212 | \ , 'yes': '✓' 213 | \ , 'Check': '✔' 214 | \ , 'Yes': '✔' 215 | \ , 'circ': '∘' 216 | \ , 'clock': '↻' 217 | \ , 'cclock': '↺' 218 | \ , 'comp': '∘' 219 | \ , 'contra': '↯' 220 | \ , 'deg': '°' 221 | \ , 'den': '⟦⟧' 222 | \ , 'diamond': '◇' 223 | \ , 'dots': '…' 224 | \ , 'down': '↓' 225 | \ , 'downtri': '▼' 226 | \ , 'Down': '⇓' 227 | \ , 'dunion': '⊎' 228 | \ , 'du': '⊎' 229 | \ , 'ell': 'ℓ' 230 | \ , 'empty': '∅' 231 | \ , 'equiv': '≡' 232 | \ , 'eq': '≡' 233 | \ , 'eventually': '◇' 234 | \ , 'exists': '∃' 235 | \ , 'flat': '♭' 236 | \ , 'fold': '⦇⦈' 237 | \ , '(\|': '⦇' 238 | \ , '\|)': '⦈' 239 | \ , 'forall': '∀' 240 | \ , 'from': '←' 241 | \ , '<-': '←' 242 | \ , 'From': '⇐' 243 | \ , 'fromto': '↔' 244 | \ , 'Fromto': '⇔' 245 | \ , 'ge': '≥' 246 | \ , 'glub': '⊓' 247 | \ , 'iff': '⇔' 248 | \ , 'implies': '⇒' 249 | \ , 'impliedby': '⇐' 250 | \ , 'in': '∈' 251 | \ , 'infty': '∞' 252 | \ , 'inf': '∞' 253 | \ , 'int': '∫' 254 | \ , 'intersect': '∩' 255 | \ , 'iso': '≅' 256 | \ , 'join': '⋈' 257 | \ , 'land': '∧' 258 | \ , 'langle': '⟨' 259 | \ , 'lbrac': '⟦' 260 | \ , '[[': '⟦' 261 | \ , 'ldots': '…' 262 | \ , 'ldown': '⇃' 263 | \ , 'leadsto': '⇝' 264 | \ , '~>': '⇝' 265 | \ , 'le': '≤' 266 | \ , 'lift': '⌊⌋' 267 | \ , 'floor': '⌊⌋' 268 | \ , 'llangle': '⟪' 269 | \ , 'longto': '⟶ ' 270 | \ , '--': '⟶ ' 271 | \ , '–': '⟶ ' 272 | \ , 'lor': '∨' 273 | \ , 'lower': '⌈⌉' 274 | \ , 'ceil': '⌈⌉' 275 | \ , 'lub': '⊔' 276 | \ , 'lup': '↿' 277 | \ , 'mapsto': '↦' 278 | \ , 'map': '↦' 279 | \ , 'mid': '∣' 280 | \ , 'models': '⊨' 281 | \ , '\|=': '⊨' 282 | \ , 'N': 'ℕ' 283 | \ , 'ne': '≠' 284 | \ , 'nearrow': '↗' 285 | \ , 'Nearrow': '⇗' 286 | \ , 'neg': '¬' 287 | \ , '/=': '≠' 288 | \ , 'nequiv': '≢' 289 | \ , 'neq': '≢' 290 | \ , 'nexist': '∄' 291 | \ , 'none': '∄' 292 | \ , 'ni': '∋' 293 | \ , 'nin': '∉' 294 | \ , 'niso': '≇' 295 | \ , 'notin': '∉' 296 | \ , 'nwarrow': '↖' 297 | \ , 'Nwarrow': '⇖' 298 | \ , 'oast': '⊛' 299 | \ , 'odot': '⊙' 300 | \ , 'o.': '⊙' 301 | \ , 'of': '∘' 302 | \ , 'o': '∘' 303 | \ , 'ominus': '⊖' 304 | \ , 'o-': '⊖' 305 | \ , 'oplus': '⊕' 306 | \ , 'o+': '⊕' 307 | \ , 'oslash': '⊘' 308 | \ , 'o/': '⊘' 309 | \ , 'otimes': '⊗' 310 | \ , 'o*': '⊗' 311 | \ , 'par': '∂' 312 | \ , 'pge': '≽' 313 | \ , 'pgt': '≻' 314 | \ , 'ple': '≼' 315 | \ , 'plt': '≺' 316 | \ , 'p≥': '≽' 317 | \ , 'p>': '≻' 318 | \ , 'p≤': '≼' 319 | \ , 'p<': '≺' 320 | \ , 'pm': '±' 321 | \ , 'prec': '≼' 322 | \ , 'prod': '∏' 323 | \ , 'proves': '⊢' 324 | \ , '\|-': '⊢' 325 | \ , 'provedby': '⊣' 326 | \ , 'Q': 'ℚ' 327 | \ , 'qed': '∎' 328 | \ , 'R': 'ℝ' 329 | \ , 'rangle': '⟩' 330 | \ , 'rbrac': '⟧' 331 | \ , ']]': '⟧' 332 | \ , 'rdown': '⇂' 333 | \ , 'righttri': '▸' 334 | \ , 'rrangle': '⟫' 335 | \ , 'rup': '↾' 336 | \ , 'searrow': '↘' 337 | \ , 'Searrow': '⇘' 338 | \ , 'sec': '§' 339 | \ , 'setminus': '∖' 340 | \ , 'sharp': '♯' 341 | \ , '#': '♯' 342 | \ , 'sim': '∼' 343 | \ , 'simeq': '≃' 344 | \ , 'some': '∃' 345 | \ , 'sqge': '⊒' 346 | \ , 'sqgt': '⊐' 347 | \ , 'sqle': '⊑' 348 | \ , 'sqlt': '⊏' 349 | \ , 's≥': '⊒' 350 | \ , 's>': '⊐' 351 | \ , 's≤': '⊑' 352 | \ , 's<': '⊏' 353 | \ , 'sqr': '²' 354 | \ , 'sqrt': '√' 355 | \ , 'star': '✭' 356 | \ , 'subset': '⊂' 357 | \ , 'sub': '⊂' 358 | \ , 'subseteq': '⊆' 359 | \ , 'subeq': '⊆' 360 | \ , 'subsetneq': '⊊' 361 | \ , 'subneq': '⊊' 362 | \ , 'sum': '∑' 363 | \ , 'supset': '⊃' 364 | \ , 'sup': '⊃' 365 | \ , 'supseteq': '⊇' 366 | \ , 'supeq': '⊇' 367 | \ , 'supsetneq': '⊋' 368 | \ , 'supneq': '⊋' 369 | \ , 'swarrow': '↙' 370 | \ , 'Swarrow': '⇙' 371 | \ , 'thus': '∴' 372 | \ , 'times': '×' 373 | \ , '*': '×' 374 | \ , 'to': '→' 375 | \ , '-': '→' 376 | \ , 'To': '⇒' 377 | \ , '=': '⇒' 378 | \ , 'top': '⊤' 379 | \ , 'tuple': '⟨⟩' 380 | \ , 'up': '↑' 381 | \ , 'updown': '↕' 382 | \ , 'ud': '↕' 383 | \ , 'unfold': '⦉⦊' 384 | \ , '<\|': '⦉' 385 | \ , '\|>': '⦊' 386 | \ , 'up;down': '⇅' 387 | \ , 'u;d': '⇅' 388 | \ , 'uptri': '▲' 389 | \ , 'Up': '⇑' 390 | \ , 'union': '∪' 391 | \ , 'vdots': '⋮' 392 | \ , 'voltage': '⚡' 393 | \ , 'xmark': '✗' 394 | \ , 'no': '✗' 395 | \ , 'Xmark': '✘' 396 | \ , 'No': '✘' 397 | \ , 'Z': 'ℤ' 398 | \ }) 399 | 400 | " #### Superscripts 401 | 402 | call extend(s:glyphs, 403 | \ { '^0': '⁰' 404 | \ , '^1': '¹' 405 | \ , '^2': '²' 406 | \ , '^3': '³' 407 | \ , '^4': '⁴' 408 | \ , '^5': '⁵' 409 | \ , '^6': '⁶' 410 | \ , '^7': '⁷' 411 | \ , '^8': '⁸' 412 | \ , '^9': '⁹' 413 | \ , '^n': 'ⁿ' 414 | \ , '^i': 'ⁱ' 415 | \ , '^+': '⁺' 416 | \ , '^-': '⁻' 417 | \ , "'": '′' 418 | \ , "''": '″' 419 | \ , "'''": '‴' 420 | \ , "''''": '⁗' 421 | \ , '"': '″' 422 | \ , '""': '⁗' 423 | \ , '`': '‵' 424 | \ , '``': '‶' 425 | \ , '```': '‷' 426 | \ }) 427 | 428 | " #### Subscripts 429 | 430 | call extend(s:glyphs, 431 | \ { '0': '₀' 432 | \ , '1': '₁' 433 | \ , '2': '₂' 434 | \ , '3': '₃' 435 | \ , '4': '₄' 436 | \ , '5': '₅' 437 | \ , '6': '₆' 438 | \ , '7': '₇' 439 | \ , '8': '₈' 440 | \ , '9': '₉' 441 | \ , '_i': 'ᵢ' 442 | \ , '_j': 'ⱼ' 443 | \ , '_+': '₊' 444 | \ , '_-': '₋' 445 | \ , 'p0': 'π₀' 446 | \ , 'p1': 'π₁' 447 | \ , 'p2': 'π₂' 448 | \ , 'p3': 'π₃' 449 | \ , 'p4': 'π₄' 450 | \ , 'p5': 'π₅' 451 | \ , 'p6': 'π₆' 452 | \ , 'p7': 'π₇' 453 | \ , 'p8': 'π₈' 454 | \ , 'p9': 'π₉' 455 | \ , 'i0': 'ι₀' 456 | \ , 'i1': 'ι₁' 457 | \ , 'i2': 'ι₂' 458 | \ , 'i3': 'ι₃' 459 | \ , 'i4': 'ι₄' 460 | \ , 'i5': 'ι₅' 461 | \ , 'i6': 'ι₆' 462 | \ , 'i7': 'ι₇' 463 | \ , 'i8': 'ι₈' 464 | \ , 'i9': 'ι₉' 465 | \ }) 466 | 467 | " #### Greek 468 | 469 | " ##### Lowercase 470 | 471 | call extend(s:glyphs, 472 | \ { 'alpha': 'α' 473 | \ , 'a': 'α' 474 | \ , 'beta': 'β' 475 | \ , 'b': 'β' 476 | \ , 'gamma': 'γ' 477 | \ , 'g': 'γ' 478 | \ , 'delta': 'δ' 479 | \ , 'd': 'δ' 480 | \ , 'epsilon': 'ε' 481 | \ , 'e': 'ε' 482 | \ , 'zeta': 'ζ' 483 | \ , 'z': 'ζ' 484 | \ , 'eta': 'η' 485 | \ , 'h': 'η' 486 | \ , 'theta': 'θ' 487 | \ , 'iota': 'ι' 488 | \ , 'i': 'ι' 489 | \ , 'kappa': 'κ' 490 | \ , 'k': 'κ' 491 | \ , 'lambda': 'λ' 492 | \ , 'l': 'λ' 493 | \ , 'mu': 'μ' 494 | \ , 'm': 'μ' 495 | \ , 'nu': 'ν' 496 | \ , 'n': 'ν' 497 | \ , 'xi': 'ξ' 498 | \ , 'omicron': 'ο' 499 | \ , 'pi': 'π' 500 | \ , 'p': 'π' 501 | \ , 'rho': 'ρ' 502 | \ , 'r': 'ρ' 503 | \ , 'sigma': 'σ' 504 | \ , 's': 'σ' 505 | \ , 'varsigma': 'ς' 506 | \ , 'vars': 'ς' 507 | \ , 'tau': 'τ' 508 | \ , 't': 'τ' 509 | \ , 'u': 'υ' 510 | \ , 'phi': 'φ' 511 | \ , 'f': 'φ' 512 | \ , 'chi': 'χ' 513 | \ , 'x': 'χ' 514 | \ , 'psi': 'ψ' 515 | \ , 'c': 'ψ' 516 | \ , 'omega': 'ω' 517 | \ , 'v': 'ω' 518 | \ }) 519 | 520 | " ##### Uppercase 521 | 522 | call extend(s:glyphs, 523 | \ { 'Alpha': 'Α' 524 | \ , 'Beta': 'Β' 525 | \ , 'Gamma': 'Γ' 526 | \ , 'G': 'Γ' 527 | \ , 'Delta': 'Δ' 528 | \ , 'D': 'Δ' 529 | \ , 'Epsilon': 'Ε' 530 | \ , 'Zeta': 'Ζ' 531 | \ , 'Eta': 'Η' 532 | \ , 'Theta': 'Θ' 533 | \ , 'Iota': 'Ι' 534 | \ , 'Kappa': 'Κ' 535 | \ , 'Lambda': 'Λ' 536 | \ , 'L': 'Λ' 537 | \ , 'Mu': 'Μ' 538 | \ , 'Nu': 'Ν' 539 | \ , 'Xi': 'Ξ' 540 | \ , 'Omicron': 'Ο' 541 | \ , 'Pi': 'Π' 542 | \ , 'P': 'Π' 543 | \ , 'Rho': 'Ρ' 544 | \ , 'Sigma': 'Σ' 545 | \ , 'S': 'Σ' 546 | \ , 'Tau': 'Τ' 547 | \ , 'Upsilon': 'Υ' 548 | \ , 'Phi': 'Φ' 549 | \ , 'F': 'Φ' 550 | \ , 'Chi': 'Χ' 551 | \ , 'Psi': 'Ψ' 552 | \ , 'Omega': 'Ω' 553 | \ , 'V': 'Ω' 554 | \ }) 555 | 556 | " ### Bindings 557 | 558 | for [s:sequence, s:symbol] in items(s:glyphs) 559 | execute 'noremap! ' . s:sequence . ' ' . s:symbol 560 | endfor 561 | 562 | -------------------------------------------------------------------------------- /syntax/agda.vim: -------------------------------------------------------------------------------- 1 | if exists('b:current_syntax') 2 | finish 3 | endif 4 | 5 | " ## Identifiers 6 | 7 | " Match identifiers not starting with capital letter. 8 | syntax match agdaIdentifier 9 | \ /[^[:space:].;{}()@"]\+\($\|[[:space:];{}()@"]\)\@=/ 10 | 11 | " Match identifiers not containing a letter. 12 | syntax match agdaOperator 13 | \ /[^[:alpha:][:space:].;{}()@"]\+\($\|[[:space:];{}()@"]\)\@=/ 14 | 15 | " Match identifiers starting with capital letter. 16 | syntax match agdaType 17 | \ /[_¬]\?[A-Z][^[:space:].;{}()@"]*\($\|[[:space:];{}()@"]\)\@=/ 18 | 19 | " Match identifiers followed by a dot. 20 | syntax match agdaType 21 | \ /[^[:space:].;{}()@"]\+\.\@=/ 22 | 23 | syntax match agdaAs /@/ 24 | syntax match agdaDot /\./ contained 25 | syntax match agdaEllipses /\.\.\.\($\|[[:space:].;{}()@"]\)\@=/ 26 | syntax match agdaUnderscore '_' contained 27 | 28 | " ## Keywords 29 | 30 | syntax match agdaKeyword /abstract\($\|[[:space:].;{}()@"]\)\@=/ 31 | syntax match agdaKeyword /constructor\($\|[[:space:].;{}()@"]\)\@=/ 32 | syntax match agdaKeyword /data\($\|[[:space:].;{}()@"]\)\@=/ 33 | syntax match agdaKeyword /do\($\|[[:space:].;{}()@"]\)\@=/ 34 | syntax match agdaKeyword /eta-equality\($\|[[:space:].;{}()@"]\)\@=/ 35 | syntax match agdaKeyword /field\($\|[[:space:].;{}()@"]\)\@=/ 36 | syntax match agdaKeyword /forall\($\|[[:space:].;{}()@"]\)\@=/ 37 | syntax match agdaKeyword /hiding\($\|[[:space:].;{}()@"]\)\@=/ 38 | syntax match agdaKeyword /in\($\|[[:space:].;{}()@"]\)\@=/ 39 | syntax match agdaKeyword /inductive\($\|[[:space:].;{}()@"]\)\@=/ 40 | syntax match agdaKeyword /infix\($\|[[:space:].;{}()@"]\)\@=/ 41 | syntax match agdaKeyword /infixl\($\|[[:space:].;{}()@"]\)\@=/ 42 | syntax match agdaKeyword /infixr\($\|[[:space:].;{}()@"]\)\@=/ 43 | syntax match agdaKeyword /instance\($\|[[:space:].;{}()@"]\)\@=/ 44 | syntax match agdaKeyword /let\($\|[[:space:].;{}()@"]\)\@=/ 45 | syntax match agdaKeyword /macro\($\|[[:space:].;{}()@"]\)\@=/ 46 | syntax match agdaKeyword /module\($\|[[:space:].;{}()@"]\)\@=/ 47 | syntax match agdaKeyword /mutual\($\|[[:space:].;{}()@"]\)\@=/ 48 | syntax match agdaKeyword /no-eta-equality\($\|[[:space:].;{}()@"]\)\@=/ 49 | syntax match agdaKeyword /open\($\|[[:space:].;{}()@"]\)\@=/ 50 | syntax match agdaKeyword /overlap\($\|[[:space:].;{}()@"]\)\@=/ 51 | syntax match agdaKeyword /pattern\($\|[[:space:].;{}()@"]\)\@=/ 52 | syntax match agdaKeyword /postulate\($\|[[:space:].;{}()@"]\)\@=/ 53 | syntax match agdaKeyword /primitive\($\|[[:space:].;{}()@"]\)\@=/ 54 | syntax match agdaKeyword /private\($\|[[:space:].;{}()@"]\)\@=/ 55 | syntax match agdaKeyword /public\($\|[[:space:].;{}()@"]\)\@=/ 56 | syntax match agdaKeyword /quote\($\|[[:space:].;{}()@"]\)\@=/ 57 | syntax match agdaKeyword /quoteContext\($\|[[:space:].;{}()@"]\)\@=/ 58 | syntax match agdaKeyword /quoteGoal\($\|[[:space:].;{}()@"]\)\@=/ 59 | syntax match agdaKeyword /quoteTerm\($\|[[:space:].;{}()@"]\)\@=/ 60 | syntax match agdaKeyword /record\($\|[[:space:].;{}()@"]\)\@=/ 61 | syntax match agdaKeyword /rewrite\($\|[[:space:].;{}()@"]\)\@=/ 62 | syntax match agdaKeyword /syntax\($\|[[:space:].;{}()@"]\)\@=/ 63 | syntax match agdaKeyword /tactic\($\|[[:space:].;{}()@"]\)\@=/ 64 | syntax match agdaKeyword /unquote\($\|[[:space:].;{}()@"]\)\@=/ 65 | syntax match agdaKeyword /unquoteDecl\($\|[[:space:].;{}()@"]\)\@=/ 66 | syntax match agdaKeyword /unquoteDef\($\|[[:space:].;{}()@"]\)\@=/ 67 | syntax match agdaKeyword /using\($\|[[:space:].;{}()@"]\)\@=/ 68 | syntax match agdaKeyword /variable\($\|[[:space:].;{}()@"]\)\@=/ 69 | syntax match agdaKeyword /where\($\|[[:space:].;{}()@"]\)\@=/ 70 | syntax match agdaKeyword /with\($\|[[:space:].;{}()@"]\)\@=/ 71 | 72 | syntax match agdaKeyword /import\($\|[[:space:].;{}()@"]\)\@=/ 73 | \ skipwhite 74 | \ nextgroup=agdaImport,agdaImportQualified 75 | syntax match agdaImport /[^[:space:].;{}()@"]\+\($\|[[:space:];{}()@"]\)\@=/ 76 | \ contained skipnl skipwhite 77 | \ nextgroup=agdaImportAs,agdaImportComment 78 | syntax match agdaImportQualified /[^[:space:].;{}()@"]\+\.\@=/ 79 | \ contained 80 | \ nextgroup=agdaImportDot 81 | syntax match agdaImportDot /\./ 82 | \ contained 83 | \ nextgroup=agdaImport,agdaImportQualified 84 | syntax match agdaImportAs /as\($\|[[:space:].;{}()@"]\)\@=/ contained 85 | 86 | syntax match agdaKeyword /renaming\($\|[[:space:].;{}()@"]\)\@=/ 87 | \ skipnl skipwhite 88 | \ nextgroup=agdaRenaming 89 | syntax region agdaRenaming contained start=/(/ end=/)/ 90 | \ contains=agdaComment,agdaIdentifier,agdaOperator,agdaTo,agdaType 91 | syntax match agdaTo /to\($\|[[:space:].;{}()@"]\)\@=/ contained 92 | 93 | " ## Comments 94 | 95 | syntax match agdaComment /--.*/ 96 | syntax region agdaComment start=/{-/ end=/-}/ 97 | \ contains=agdaBlockComment 98 | syntax region agdaPragma start=/{-#/ end=/#-}/ 99 | 100 | syntax match agdaImportComment /--.*/ contained skipnl skipwhite 101 | \ nextgroup=agdaImportAs 102 | 103 | " ## Literals 104 | 105 | syntax match agdaNumber /-\?[0-9]\+\($\|[[:space:];{}()@"]\)\@=/ 106 | syntax match agdaNumber /-\?0x[0-9A-Fa-f]\+\($\|[[:space:];{}()@"]\)\@=/ 107 | syntax match agdaNumber /-\?[0-9]\+\.[0-9]\+\([Ee]\([+-]\)\?[0-9]\+\)\?/ 108 | syntax match agdaNumber /-\?[0-9]\+[Ee]\([+-]\)\?[0-9]\+/ 109 | 110 | syntax match agdaChar /'[^'\\]'/ 111 | syntax match agdaChar /'\\[0-9]\+'/ 112 | syntax match agdaChar /'\\0x[0-9A-Fa-f]\+'/ 113 | syntax match agdaChar /'\\a'/ 114 | syntax match agdaChar /'\\b'/ 115 | syntax match agdaChar /'\\t'/ 116 | syntax match agdaChar /'\\n'/ 117 | syntax match agdaChar /'\\v'/ 118 | syntax match agdaChar /'\\f'/ 119 | syntax match agdaChar /'\\\\'/ 120 | syntax match agdaChar /'\\''/ 121 | syntax match agdaChar /'\\"'/ 122 | syntax match agdaChar /'\\NUL'/ 123 | syntax match agdaChar /'\\SOH'/ 124 | syntax match agdaChar /'\\STX'/ 125 | syntax match agdaChar /'\\ETX'/ 126 | syntax match agdaChar /'\\EOT'/ 127 | syntax match agdaChar /'\\ENQ'/ 128 | syntax match agdaChar /'\\ACK'/ 129 | syntax match agdaChar /'\\BEL'/ 130 | syntax match agdaChar /'\\BS'/ 131 | syntax match agdaChar /'\\HT'/ 132 | syntax match agdaChar /'\\LF'/ 133 | syntax match agdaChar /'\\VT'/ 134 | syntax match agdaChar /'\\FF'/ 135 | syntax match agdaChar /'\\CR'/ 136 | syntax match agdaChar /'\\SO'/ 137 | syntax match agdaChar /'\\SI'/ 138 | syntax match agdaChar /'\\DLE'/ 139 | syntax match agdaChar /'\\DC1'/ 140 | syntax match agdaChar /'\\DC2'/ 141 | syntax match agdaChar /'\\DC3'/ 142 | syntax match agdaChar /'\\DC4'/ 143 | syntax match agdaChar /'\\NAK'/ 144 | syntax match agdaChar /'\\SYN'/ 145 | syntax match agdaChar /'\\ETB'/ 146 | syntax match agdaChar /'\\CAN'/ 147 | syntax match agdaChar /'\\EM'/ 148 | syntax match agdaChar /'\\SUB'/ 149 | syntax match agdaChar /'\\ESC'/ 150 | syntax match agdaChar /'\\FS'/ 151 | syntax match agdaChar /'\\GS'/ 152 | syntax match agdaChar /'\\RS'/ 153 | syntax match agdaChar /'\\US'/ 154 | syntax match agdaChar /'\\SP'/ 155 | syntax match agdaChar /'\\DEL'/ 156 | 157 | syntax region agdaString start=/"/ skip=/\\"/ end=/"\|$/ 158 | 159 | " ## Holes 160 | 161 | syntax match agdaHole /?\($\|[[:space:].;{}()@"]\)\@=/ 162 | syntax region agdaHole start=/{!/ end=/!}/ 163 | \ contains=agdaHole 164 | 165 | syntax match agdaHoleIndexed /?\d\+\($\|[[:space:].;{}()@"]\)\@=/ 166 | syntax match agdaHoleIndexed /_\d\+\($\|[[:space:].;{}()@"]\)\@=/ 167 | 168 | " ## Highlights 169 | 170 | highlight default link agdaAs agdaOperator 171 | highlight default link agdaChar agdaString 172 | highlight default link agdaComment Comment 173 | highlight default link agdaEllipses agdaOperator 174 | highlight default link agdaHole WarningMsg 175 | highlight default link agdaImport agdaType 176 | highlight default link agdaImportAs agdaKeyword 177 | highlight default link agdaImportComment agdaComment 178 | highlight default link agdaImportDot agdaDot 179 | highlight default link agdaImportQualified agdaType 180 | highlight default link agdaKeyword Statement 181 | highlight default link agdaLine Comment 182 | highlight default link agdaNumber Number 183 | highlight default link agdaOperator Operator 184 | highlight default link agdaPragma SpecialComment 185 | highlight default link agdaString String 186 | highlight default link agdaTo agdaKeyword 187 | highlight default link agdaType Type 188 | highlight default link agdaUnderscore agdaOperator 189 | 190 | " ## Variable 191 | 192 | " Ensure no other syntax file is loaded. 193 | let b:current_syntax = 'agda' 194 | 195 | --------------------------------------------------------------------------------