ziglang / zig.vim

Vim configuration for Zig
MIT License
466 stars 59 forks source link

Add auto-format on save #8

Closed tiehuis closed 6 years ago

tiehuis commented 6 years ago

This is largely taken from the great fatih/go-vim vim plugin. This is a quick go at isolating the required functionality for zig fmt to work in place of gofmt.

You will need to specify the binary path of the zig compiler for this to work in your zig config. e.g. let g:zig_bin_path = '/usr/bin'.

Since this requires the stage 2 compiler, I build it and copy it into the same directory as the C++ compiler, renaming it to zig2. Then, I set the following vim config let g:zig_fmt_command = ['zig2', 'fmt'].

There are a few things to fix up, namely error message reporting is not really working at all as we need to specify the message format. But have a play around and see how you find this.

andrewrk commented 6 years ago

Nice! This is exciting. I'll want to get zig fmt to a higher level of quality before turning this on by default, but once we're at that point I think this makes sense to add to zig.vim.

andrewrk commented 6 years ago

I think zig fmt is to a level of quality where I would feel comfortable running it on every save.

Do you think that we should do these things first?

tiehuis commented 6 years ago

I don't think caching is really necessary here since the action is pretty quick.

The way the Go plugin originally worked was by running go fmt and outputting the formatted code to a temporary file and then performing a rename to the current. zig fmt does this itself, but if there was an option to write the output to a new file -o or --output argument then it should be as straight-forward as switching the command. There are a few things the plugin does in order to retain its buffer position and undo history and I'd need to investigate how an in-place write may affect this otherwise.

Ahh, I see what you are meaning with regards to caching. If writing the file in place then I think that behaviour would be nice. Doesn't matter to this plugin, though.

andrewrk commented 6 years ago

To be clear, the point of this would not be to save time - it's not caching anything. The point would be to avoid modifying the file system timestamps and causing a file system event to trigger when zig fmt did not actually change anything. For example if the editor responded to the file on disk updating by popping up a GUI dialog box, this would prevent that from happening if zig fmt did not change anything. It would also enable zig fmt to print to stdout only the files that changed, which might be useful in a git hook script.

I'm still suggesting an atomic rename - just that we would notice when renaming would be pointless and so then delete the temporary file and call it a day.

andrewrk commented 6 years ago

I did this in https://github.com/ziglang/zig/commit/2c96f19fd3ed312e5cb0ac8006b73e73abf4a98d. I didn't end up needing shasum, I just compared bytes directly. screenshot_2018-05-30_14-58-50

andrewrk commented 6 years ago

I'm having trouble using this branch, do you know how to use it without Plug?

tiehuis commented 6 years ago

If using Vim 8, you should be able to just clone into the appropriate directory. https://gist.github.com/manasthakur/ab4cf8d32a28ea38271ac0d07373bb53#managing-plugins-natively-using-vim-8-packages

andrewrk commented 6 years ago

I was able to get the package loaded with vim 8, but I wasn't able to get zig fmt integration working. I didn't see any error messages or anything when I saved, even though the "fail silently" option was off

tiehuis commented 6 years ago

Check that you have enabled auto format by default in your vimrc, that you specify a binary path (irrelevant we don't actually check this and you specify the format command with whatever your binary name happens to mine).

let g:zig_fmt_autosave = 0
let g:zig_bin_path = '/home/me/local/bin'
let g:zig_fmt_command = ['zig2', 'fmt']

The Go plugin formats to a temporary file first before renaming in the plugin itself which is different from the default zig behavior. You'll need something more like the following with some more tweaks still. Didn't get a chance to look into this tonight. I need to figure out the best way reload the buffer in place and update the cursor and all that correctly. Side-note: %:p just uses the full filename path and was to work around the dirname issue so isn't needed.

If you need to check the executed commands, add an echo(l:cmd) to zig#fmt#run.

diff --git a/autoload/zig/fmt.vim b/autoload/zig/fmt.vim
index 510568a..5d8ba7d 100644
--- a/autoload/zig/fmt.vim
+++ b/autoload/zig/fmt.vim
@@ -45,13 +45,13 @@ function! zig#fmt#Format() abort
   let bin_name = zig#config#FmtCommand()

   let current_col = col('.')
-  let [l:out, l:err] = zig#fmt#run(bin_name, l:tmpname, expand('%'))
+  let [l:out, l:err] = zig#fmt#run(bin_name, expand('%:p'))
   let diff_offset = len(readfile(l:tmpname)) - line('$')

   if l:err == 0
-    call zig#fmt#update_file(l:tmpname, expand('%'))
+    call zig#fmt#update_file(expand('%:p'))
   elseif !zig#config#FmtFailSilently()
-    let errors = s:parse_errors(expand('%'), out)
+    let errors = s:parse_errors(expand('%:p'), out)
     call s:show_errors(errors)
   endif

@@ -81,8 +81,9 @@ function! zig#fmt#Format() abort
   syntax sync fromstart
 endfunction

-" update_file updates the target file with the given formatted source
-function! zig#fmt#update_file(source, target)
+" update_file updates reloads the buffer to reflect the new formatted code
+" TODO: This doesn't work as we want and simply restores the old content.
+function! zig#fmt#update_file(target)
   " remove undo point caused via BufWritePre
   try | silent undojoin | catch | endtry

@@ -92,8 +93,6 @@ function! zig#fmt#update_file(source, target)
     let original_fperm = getfperm(a:target)
   endif

-  call rename(a:source, a:target)
-
   " restore file permissions
   if exists("*setfperm") && original_fperm != ''
     call setfperm(a:target , original_fperm)
@@ -126,12 +125,12 @@ function! zig#fmt#update_file(source, target)
   endif
 endfunction

-" run runs the gofmt/goimport command for the given source file and returns
-" the output of the executed command. Target is the real file to be formatted.
-function! zig#fmt#run(bin_name, source, target)
+" run runs the zig fmt command for the given source and returns the output.
+" Target is the real file to be formatted.
+function! zig#fmt#run(bin_name, target)
   let l:cmd = []
   call extend(cmd, a:bin_name)
-  call extend(cmd, [a:source, a:target])
+  call extend(cmd, [a:target])
   return zig#util#Exec(l:cmd)
 endfunction
tiehuis commented 6 years ago

Alright, I've fixed the problems I think you were encountering. In particular, zig fmt was not returning an error code on failure which was likely why you weren't seeing any errors. Also fixed the buffer not being saved prior to formatting so you'd never see any changes beyond the first save.

Auto-format is disabled by default. Here is the config I use for reference (zig2 is the self-hosted binary):

let g:zig_fmt_command = ['zig2', 'fmt', '--color', 'off']
let g:zig_fmt_autosave = 1

Let me know if there are any pending issues once you try it.

andrewrk commented 6 years ago

Got it working! thanks! This is really fun