kristijanhusak / vim-dadbod-ui

Simple UI for https://github.com/tpope/vim-dadbod
MIT License
1.36k stars 70 forks source link

Show Full Minutes and Seconds on Query Execute #241

Closed jtw023 closed 1 month ago

jtw023 commented 1 month ago

Another customization I've made is to show full minutes and seconds on query execute. This would be a completely new feature but it's something I use so I thought I'd put it into a pull request in case you'd like to merge it.

All it does is adjust the screen to show the full time of your query rather than only the seconds.

Examples: Less than 10 seconds into the query: ![LessThan10S](https://github.com/kristijanhusak/vim-dadbod-ui/assets/59935328/d336624c-1b6b-46fb-afdf-b56643836e9e) Less than 1 minute into the query: ![LessThan1M](https://github.com/kristijanhusak/vim-dadbod-ui/assets/59935328/a17c1cce-4eb1-4315-852e-c677041ce957) Less than 1 minute and 10 seconds into the query: ![LessThan1M10S](https://github.com/kristijanhusak/vim-dadbod-ui/assets/59935328/706ae9bf-4b77-4765-84d4-9a29db8a5506) Less than 2 minutes into the query: ![LessThan2M](https://github.com/kristijanhusak/vim-dadbod-ui/assets/59935328/7996902b-0b89-4679-bb60-330e1e2ba1e0) Less than 2 minutes and 10 seconds into the query: ![LessThan2M10S](https://github.com/kristijanhusak/vim-dadbod-ui/assets/59935328/ee61da81-2453-4248-bd33-f5efc17e5777) More than 2 minutes and 10 seconds into the query: ![MoreThan2M10S](https://github.com/kristijanhusak/vim-dadbod-ui/assets/59935328/ca303b02-0547-497f-95fe-d01b4185ef11)
kristijanhusak commented 1 month ago

Instead of adding this as an option, I'd rather leave it to the user to code their own progress bar in case they need it. For example, you can hook into *DBExecutePre and *DBExecutePost in your vimrc and write a logic for the progress bar however you want. This just doesn't seem like an option that a lot of people would use.

We can add an option to disable the built in progress bar to not interfere with a custom one, something like g:db_ui_disable_progress_bar = 0.

jtw023 commented 1 month ago

Set default of g:db_ui_disable_progress_bar to 0 and made code plus documentation changes to disable progress bar. However, I tried to source the following file in my vimrc which did not work. Do you know how I can overwrite the current autocommands without touching autoload/db_ui/dbout.vim?

Once I get this working I would like to update the documentation to show this process and how exactly a user can do this.

let g:db_ui_disable_progress_bar = 1
let g:db_ui_use_postgres_views = 0
let g:db_ui_force_echo_notifications = 1
let g:db_ui_use_nerd_fonts = 1
let g:db_ui_execute_on_save = 0
let g:db_ui_save_location = '/home/jordan/bitbucket_repos/jordanw/'
" let g:completion_matching_strategy_list = {'exact', 'substring'}
let g:completion_matching_ignore_case = 1

" NOTE: Progress timer
function! s:progress_tick(progress, timer) abort
  let a:progress.counter += 100
  if a:progress.icon_counter > 3
    let a:progress.icon_counter = 0
  endif
  let secs = a:progress.counter * 0.001
  let minutes = string(floor(secs / 60))
  let formattedminutes = substitute(minutes, '\.0$', '', '')
  let seconds = string(((fmod(secs / 60, 1) * 60) / 100) * 100)
  if formattedminutes > 0
    if formattedminutes < 2
      if seconds < 10
        let content = ' '.s:progress_icons[a:progress.icon_counter].' Execute query ---- '.formattedminutes.' minute '.seconds.' seconds '
      else
        let content = ' '.s:progress_icons[a:progress.icon_counter].' Execute query --- '.formattedminutes.' minute '.seconds.' seconds '
      endif
    else
      if seconds < 10
        let content = ' '.s:progress_icons[a:progress.icon_counter].' Execute query --- '.formattedminutes.' minutes '.seconds.' seconds '
      else
        let content = ' '.s:progress_icons[a:progress.icon_counter].' Execute query -- '.formattedminutes.' minutes '.seconds.' seconds '
      endif
    endif
  else
    if seconds < 10
      let content = ' '.s:progress_icons[a:progress.icon_counter].' Execute query ------------- '.seconds.' seconds'
    else
      let content = ' '.s:progress_icons[a:progress.icon_counter].' Execute query ------------ '.seconds.' seconds'
    endif
  endif
  if has('nvim')
    call nvim_buf_set_lines(a:progress.buf, 0, -1, v:false, [content])
  else
    call popup_settext(a:progress.win, content)
  endif
  let a:progress.icon_counter += 1
endfunction

function! s:progress_winpos(win)
  let pos = win_screenpos(a:win)
  return [
        \ pos[0] + (winheight(a:win) / 2),
        \ pos[1] + (winwidth(a:win) / 2) - (winwidth(a:win) / 5),
        \ ]
endfunction

function! s:progress_hide(...) abort
  let bufname = a:0 > 0 ? a:1 : bufname()
  let progress = get(s:progress_buffers, bufname, {})
  if empty(progress)
    return
  endif
  if has('nvim')
    silent! call nvim_win_close(progress.win, v:true)
  else
    silent! call popup_close(progress.win)
  endif
  silent! call timer_stop(progress.timer)
  unlet! s:progress_buffers[bufname]
  call s:progress_reset_positions()
endfunction

function! s:progress_show_neovim(path) abort
  let bufname =  !empty(a:path) ? a:path : bufname()
  let outwin = win_getid(bufwinnr(bufname))
  let progress = copy(s:progress)
  let progress.outwin = outwin
  let progress.buf = nvim_create_buf(v:false, v:true)
  call nvim_buf_set_lines(progress.buf, 0, -1, v:false, ['| Execute query --- 0 minutes 0 seconds'])
  let [row, col] = s:progress_winpos(outwin)
  let opts = {
        \ 'relative': 'editor',
        \ 'width': 43,
        \ 'height': 1,
        \ 'row': row - 2,
        \ 'col': col,
        \ 'focusable': v:false,
        \ 'style': 'minimal'
        \ }
  if has('nvim-0.5')
    let opts.border = 'rounded'
  endif
  let progress.win = nvim_open_win(progress.buf, v:false, opts)
  let progress.timer = timer_start(100, function('s:progress_tick', [progress]), { 'repeat': -1 })
  let s:progress_buffers[bufname] = progress
endfunction

augroup dbui_async_queries_dbout
  autocmd!
  autocmd User DBQueryPre call s:progress_show()
  autocmd User DBQueryPost call s:progress_hide()
  autocmd User *DBExecutePre call s:progress_show(expand('<amatch>:h'))
  autocmd User *DBExecutePost call s:progress_hide(expand('<amatch>:h'))
augroup END
kristijanhusak commented 1 month ago

Changes look good, thanks!

Regarding the example, feel free to create a wiki page, I opened it up so you should have permission to do it.

jtw023 commented 1 month ago

What does this expand('<amatch>:h') do in:

I think I'm running into a problem there. When I try to run a query the progress bar pops up as expected but my queries don't actually run against Redshift.

kristijanhusak commented 1 month ago

Every time the query is ran, it triggers an autocmd with name similar to this:

/tmp/nvim/CmkUz1/11.dbout/DBExecutePre

<amatch> returns the autocmd name, and with :h we parse the file name only:

/tmp/nvim/CmkUz1/11.dbout/DBExecutePre -> /tmp/nvim/CmkUz1/11.dbout

I'm not sure what you mean by queries not running against Redshift. Do they run if you do not use custom progress bar?

jtw023 commented 1 month ago

If I do not source the file where I define my custom progress bar the queries run against Redshift exactly as expected. This happens with the new db_ui_disable_progress_bar variable set to 1 or to 0. The issue is nothing that we just committed.

The only time I run into the issue is when I source the custom progress bar. See the video for an example. The timer runs but nothing is actually running. I checked Redshift as well and nothing from my user is running on that cluster. I also cannot cancel like I normally can.

https://github.com/kristijanhusak/vim-dadbod-ui/assets/59935328/271d0cf3-42d5-4935-87da-5d1ac3be8336

kristijanhusak commented 1 month ago

If everything works normally when you do not add your progress bar, then it's probably some issue in there. Does cancelling work when you just disable the built in progress bar but not add your own?

jtw023 commented 1 month ago

Does cancelling work when you just disable the built in progress bar but not add your own?

Yep! There is no issue at all unless I source my own progress bar.

I think the issue is related to me not calling db_ui#dbout#jump_to_foreign_table() in my init.vim. However, when I do that it tells me that b:db is an undefined variable.

plug-dadbod-progress.vim which is being sourced in init.lua ``` " NOTE: Progress timer call db_ui#dbout#jump_to_foreign_table() call db_ui#dbout#foldexpr(lnum) call db_ui#dbout#get_cell_value() call db_ui#dbout#toggle_layout() call db_ui#dbout#yank_header() " function! db_ui#dbout#jump_to_foreign_table() abort " let db_url = b:db.db_url " let parsed = db#url#parse(db_url) " let scheme = db_ui#schemas#get(parsed.scheme) " if empty(scheme) " return db_ui#notifications#error(parsed.scheme.' scheme not supported for foreign key jump.') " endif " let cell_line_number = s:get_cell_line_number(scheme) " let cell_range = s:get_cell_range(cell_line_number, getcurpos(), scheme) " let virtual_cell_range = get(cell_range, 'virtual', cell_range) " let field_name = trim(getline(cell_line_number - 1)[virtual_cell_range.from : virtual_cell_range.to]) " let field_value = trim(getline('.')[cell_range.from : cell_range.to]) " let foreign_key_query = substitute(scheme.foreign_key_query, '{col_name}', field_name, '') " let Parser = get(scheme, 'parse_virtual_results', scheme.parse_results) " let result = Parser(db_ui#schemas#query(db_url, scheme, foreign_key_query), 3) " if empty(result) " return db_ui#notifications#error('No valid foreign key found.') " endif " let [foreign_table_name, foreign_column_name,foreign_table_schema] = result[0] " let query = printf(scheme.select_foreign_key_query, foreign_table_schema, foreign_table_name, foreign_column_name, db_ui#utils#quote_query_value(field_value)) " exe 'DB '.query " endfunction " function! db_ui#dbout#foldexpr(lnum) abort " if getline(a:lnum) !~? '^[[:blank:]]*$' " " Mysql " if getline(a:lnum) =~? '^+---' && getline(a:lnum + 2) =~? '^+---' " return '>1' " endif " " Postgres & Sqlserver " if getline(a:lnum + 1) =~? '^----' " return '>1' " endif " return 1 " endif " "Postgres & Sqlserver " if getline(a:lnum) =~? '^[[:blank:]]*$' " if getline(a:lnum + 2) !~? '^----' " return 1 " endif " return 0 " endif " return -1 " endfunction " function! db_ui#dbout#get_cell_value() abort " let parsed = db#url#parse(db_ui#resolve(b:db)) " let scheme = db_ui#schemas#get(parsed.scheme) " if empty(scheme) " return db_ui#notifications#error('Yanking cell value not supported for '.parsed.scheme.' scheme.') " endif " let cell_line_number = s:get_cell_line_number(scheme) " let cell_range = s:get_cell_range(cell_line_number, getcurpos(), scheme) " let field_value = getline('.')[(cell_range.from):(cell_range.to)] " let start_spaces = len(matchstr(field_value, '^[[:blank:]]*')) " let end_spaces = len(matchstr(field_value, '[[:blank:]]*$')) " let old_selection = &selection " set selection=inclusive " let from = cell_range.from + start_spaces + 1 " let to = cell_range.to - end_spaces + 1 " call cursor(line('.'), from) " let motion = max([(to - from), 0]) " let cmd = 'normal!v' " if motion > 0 " let cmd .= motion.'l' " endif " exe cmd " let &selection = old_selection " endfunction " function! db_ui#dbout#toggle_layout() abort " let parsed = db#url#parse(db_ui#resolve(b:db)) " let scheme = db_ui#schemas#get(parsed.scheme) " if !has_key(scheme, 'layout_flag') " return db_ui#notifications#error('Toggling layout not supported for '.parsed.scheme.' scheme.') " endif " let content = join(readfile(b:db.input), "\n") " let expanded_layout = get(b:, 'db_ui_expanded_layout', 0) " if expanded_layout " let b:db_ui_expanded_layout = !expanded_layout " norm R " return " endif " let content = substitute(content, ';\?$', ' '.scheme.layout_flag, '') " let tmp = tempname() " call writefile(split(content, "\n"), tmp) " let old_db_input = b:db.input " let b:db.input = tmp " norm R " let b:db.input = old_db_input " let b:db_ui_expanded_layout = !expanded_layout " endfunction " function! db_ui#dbout#yank_header() abort " let parsed = db#url#parse(db_ui#resolve(b:db)) " let scheme = db_ui#schemas#get(parsed.scheme) " if empty(scheme) " return db_ui#notifications#error('Yanking headers not supported for '.parsed.scheme.' scheme.') " endif " let cell_line_number = s:get_cell_line_number(scheme) " let table_line = '-' " let column_line = getline(cell_line_number-1) " let underline = getline(cell_line_number) " let from = 0 " let to = 0 " let i = 0 " let columns=[] " let lastcol = strlen(underline) " while i <= lastcol " if underline[i] !=? table_line || i == lastcol " let to = i-1 " call add(columns, trim(column_line[from:to])) " let from = i+1 " endif " let i += 1 " endwhile " let csv_columns = join(columns, ', ') " call setreg(v:register, csv_columns) " endfunction function! s:get_cell_range(cell_line_number, curpos, scheme) abort if get(a:scheme, 'has_virtual_results', v:false) return s:get_virtual_cell_range(a:cell_line_number, a:curpos) endif let line = getline(a:cell_line_number) let table_line = '-' let col = a:curpos[2] - 1 let from = 0 while col >= 0 && line[col] ==? table_line let from = col let col -= 1 endwhile let col = a:curpos[2] - 1 let to = 0 while col <= len(line) && line[col] ==? table_line let to = col let col += 1 endwhile return {'from': from, 'to': to} endfunction function! s:get_virtual_cell_range(cell_line_number, curpos) abort let line = getline(a:cell_line_number) let position = a:curpos[1:] let table_line = '-' let col = position[-1] - 1 let virtual_from = 0 while col >= 0 && line[col] ==? table_line let virtual_from = col let col -= 1 endwhile let col = position[-1] - 1 let virtual_to = 0 while col <= len(line) && line[col] ==? table_line let virtual_to = col let col += 1 endwhile let position_above = insert(position[1:], position[0] - 1) call cursor(add(position_above[:-2], virtual_from)) norm! j let from = col('.') call cursor(add(position_above[:-2], virtual_to)) norm! j let to = col('.') call cursor(position) " NOTE: 'virtual' refers to a position in reference to virtcol(). " other fields reference col() return { \ 'from': max([from, 0]), \ 'to': max([to, 0]), \ 'virtual': {'from': max([virtual_from, 0]), 'to': max([virtual_to, 0])} \} endfunction function! s:get_cell_line_number(scheme) abort let line = line('.') while (line > a:scheme.cell_line_number) if getline(line) =~? a:scheme.cell_line_pattern return line endif let line -= 1 endwhile return a:scheme.cell_line_number endfunction let s:progress_icons = ['/', '—', '\', '|'] let s:progress_buffers = {} let s:progress = { \ 'win': -1, \ 'outwin': -1, \ 'buf': -1, \ 'timer': -1, \ 'counter': 0, \ 'icon_counter': 0, \ } function! s:progress_tick(progress, timer) abort let a:progress.counter += 100 if a:progress.icon_counter > 3 let a:progress.icon_counter = 0 endif let secs = a:progress.counter * 0.001 let minutes = string(floor(secs / 60)) let formattedminutes = substitute(minutes, '\.0$', '', '') let seconds = string(((fmod(secs / 60, 1) * 60) / 100) * 100) if formattedminutes > 0 if formattedminutes < 2 if seconds < 10 let content = ' '.s:progress_icons[a:progress.icon_counter].' Execute query ---- '.formattedminutes.' minute '.seconds.' seconds ' else let content = ' '.s:progress_icons[a:progress.icon_counter].' Execute query --- '.formattedminutes.' minute '.seconds.' seconds ' endif else if seconds < 10 let content = ' '.s:progress_icons[a:progress.icon_counter].' Execute query --- '.formattedminutes.' minutes '.seconds.' seconds ' else let content = ' '.s:progress_icons[a:progress.icon_counter].' Execute query -- '.formattedminutes.' minutes '.seconds.' seconds ' endif endif else if seconds < 10 let content = ' '.s:progress_icons[a:progress.icon_counter].' Execute query ------------- '.seconds.' seconds' else let content = ' '.s:progress_icons[a:progress.icon_counter].' Execute query ------------ '.seconds.' seconds' endif endif if has('nvim') call nvim_buf_set_lines(a:progress.buf, 0, -1, v:false, [content]) else call popup_settext(a:progress.win, content) endif let a:progress.icon_counter += 1 endfunction function! s:progress_winpos(win) let pos = win_screenpos(a:win) return [ \ pos[0] + (winheight(a:win) / 2), \ pos[1] + (winwidth(a:win) / 2) - (winwidth(a:win) / 5), \ ] endfunction function! s:progress_hide(...) abort let bufname = a:0 > 0 ? a:1 : bufname() let progress = get(s:progress_buffers, bufname, {}) if empty(progress) return endif if has('nvim') silent! call nvim_win_close(progress.win, v:true) else silent! call popup_close(progress.win) endif silent! call timer_stop(progress.timer) unlet! s:progress_buffers[bufname] call s:progress_reset_positions() endfunction function! s:progress_show_neovim(path) abort let bufname = !empty(a:path) ? a:path : bufname() let outwin = win_getid(bufwinnr(bufname)) let progress = copy(s:progress) let progress.outwin = outwin let progress.buf = nvim_create_buf(v:false, v:true) call nvim_buf_set_lines(progress.buf, 0, -1, v:false, ['| Execute query --- 0 minutes 0 seconds']) let [row, col] = s:progress_winpos(outwin) let opts = { \ 'relative': 'editor', \ 'width': 43, \ 'height': 1, \ 'row': row - 2, \ 'col': col, \ 'focusable': v:false, \ 'style': 'minimal' \ } if has('nvim-0.5') let opts.border = 'rounded' endif let progress.win = nvim_open_win(progress.buf, v:false, opts) let progress.timer = timer_start(100, function('s:progress_tick', [progress]), { 'repeat': -1 }) let s:progress_buffers[bufname] = progress endfunction function! s:progress_show(...) if has('nvim') call s:progress_show_neovim(get(a:, 1, '')) else call s:progress_show_vim(get(a:, 1, '')) endif call s:progress_reset_positions() endfunction augroup dbui_async_queries_dbout autocmd! autocmd User DBQueryPre call s:progress_show() autocmd User DBQueryPost call s:progress_hide() autocmd User *DBExecutePre call s:progress_show(expand(':h')) autocmd User *DBExecutePost call s:progress_hide(expand(':h')) augroup END ```
Error message ``` Error detected while processing /home/jordan/.config/Linux/nvim/init.lua: E5113: Error while calling lua chunk: vim/_editor.lua:0: /home/jordan/.config/Linux/nvim/init.lua..nvim_exec2() called at /home/jordan/. config/Linux/nvim/init.lua:0../home/jordan/.config/Linux/nvim/lua/config/plug-dadbod-progress.vim[3]..function db_ui#dbout#jump_to_forei gn_table, line 1: Vim(let):E121: Undefined variable: b:db stack traceback: [C]: in function 'nvim_exec2' vim/_editor.lua: in function 'cmd' /home/jordan/.config/Linux/nvim/init.lua:27: in main chunk ```
kristijanhusak commented 1 month ago

You don't need to call that function for the progress bar. Copy this chunk of built in code and adapt it to your needs https://github.com/kristijanhusak/vim-dadbod-ui/blob/0dc68d9225a70d42f8645049482e090c1a8dce25/autoload/db_ui/dbout.vim?plain=1#L216-L344

jtw023 commented 1 month ago

Solved it. Will look into creating a wiki page this week.

jtw023 commented 1 month ago

Wiki page created