summaryrefslogtreecommitdiffstats
path: root/autoload/dart.vim
blob: 28e55552c0234b9492b782683fb913dbc8ea2d40 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
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
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
if polyglot#init#is_disabled(expand('<sfile>:p'), 'dart', 'autoload/dart.vim')
  finish
endif


function! s:error(text) abort
  echohl Error
  echomsg printf('[dart-vim-plugin] %s', a:text)
  echohl None
endfunction

function! s:cexpr(errorformat, lines, reason) abort
  call setqflist([], ' ', {
      \ 'lines': a:lines,
      \ 'efm': a:errorformat,
      \ 'context': {'reason': a:reason},
      \})
  copen
endfunction

" If the quickfix list has a context matching [reason], clear and close it.
function! s:clearQfList(reason) abort
  let context = get(getqflist({'context': 1}), 'context', {})
  if type(context) == v:t_dict &&
      \ has_key(context, 'reason') &&
      \ context.reason == a:reason
    call setqflist([], 'r')
    cclose
  endif
endfunction

function! dart#fmt(...) abort
  let l:dartfmt = s:FindDartFmt()
  if empty(l:dartfmt) | return | endif
  let buffer_content = getline(1, '$')
  let l:cmd = extend(l:dartfmt, ['--stdin-name', shellescape(expand('%'))])
  if exists('g:dartfmt_options')
    call extend(l:cmd, g:dartfmt_options)
  endif
  call extend(l:cmd, a:000)
  let lines = systemlist(join(l:cmd), join(buffer_content, "\n"))
  " TODO(https://github.com/dart-lang/sdk/issues/38507) - Remove once the
  " tool no longer emits this line on SDK upgrades.
  if lines[-1] ==# 'Isolate creation failed'
    let lines = lines[:-2]
  endif
  if buffer_content == lines
    call s:clearQfList('dartfmt')
    return
  endif
  if 0 == v:shell_error
    let win_view = winsaveview()
    silent keepjumps call setline(1, lines)
    if line('$') > len(lines)
      silent keepjumps execute string(len(lines)+1).',$ delete'
    endif
    call winrestview(win_view)
    call s:clearQfList('dartfmt')
  else
    let errors = lines[2:]
    let error_format = '%Aline %l\, column %c of %f: %m,%C%.%#'
    call s:cexpr(error_format, errors, 'dartfmt')
  endif
endfunction

function! s:FindDartFmt() abort
  if executable('dart')
    let l:version_text = system('dart --version')
    let l:match = matchlist(l:version_text,
        \ '\vDart SDK version: (\d+)\.(\d+)\.\d+.*')
    if empty(l:match)
      call s:error('Unable to determine dart version')
      return []
    endif
    let l:major = l:match[1]
    let l:minor = l:match[2]
    if l:major > 2 || l:major == 2 && l:minor >= 14
      return ['dart', 'format']
    endif
  endif
  " Legacy fallback for Dart SDK pre 2.14
  if executable('dartfmt') | return ['dartfmt'] | endif
  if executable('flutter')
    let l:flutter_cmd = resolve(exepath('flutter'))
    let l:bin = fnamemodify(l:flutter_cmd, ':h')
    let l:dartfmt = l:bin.'/cache/dart-sdk/bin/dartfmt'
    if executable(l:dartfmt) | return [l:dartfmt] | endif
  endif
  call s:error('Cannot find a `dartfmt` command')
  return []
endfunction

" Finds the path to `uri`.
"
" If the file is a package: uri, looks for a .packages file to resolve the path.
" If the path cannot be resolved, or is not a package: uri, returns the
" original.
function! dart#resolveUri(uri) abort
  if a:uri !~# 'package:'
    return a:uri
  endif
  let package_name = substitute(a:uri, 'package:\(\w\+\)\/.*', '\1', '')
  let [found, package_map] = s:PackageMap()
  if !found
    call s:error('cannot find .packages file')
    return a:uri
  endif
  if !has_key(package_map, package_name)
    call s:error('no package mapping for '.package_name)
    return a:uri
  endif
  let package_lib = package_map[package_name]
  return substitute(a:uri,
      \ 'package:'.package_name,
      \ escape(package_map[package_name], '\'),
      \ '')
endfunction

" A map from package name to lib directory parse from a '.packages' file.
"
" Returns [found, package_map]
function! s:PackageMap() abort
  let [found, dot_packages] = s:DotPackagesFile()
  if !found
    return [v:false, {}]
  endif
  let dot_packages_dir = fnamemodify(dot_packages, ':p:h')
  let lines = readfile(dot_packages)
  let map = {}
  for line in lines
    if line =~# '\s*#'
      continue
    endif
    let package = substitute(line, ':.*$', '', '')
    let lib_dir = substitute(line, '^[^:]*:', '', '')
    if lib_dir =~# 'file:/'
      let lib_dir = substitute(lib_dir, 'file://', '', '')
      if lib_dir =~# '/[A-Z]:/'
        let lib_dir = lib_dir[1:]
      endif
    else
      let lib_dir = resolve(dot_packages_dir.'/'.lib_dir)
    endif
    if lib_dir =~# '/$'
      let lib_dir = lib_dir[:len(lib_dir) - 2]
    endif
    let map[package] = lib_dir
  endfor
  return [v:true, map]
endfunction

" Toggle whether dartfmt is run on save or not.
function! dart#ToggleFormatOnSave() abort
  if get(g:, 'dart_format_on_save', 0)
    let g:dart_format_on_save = 0
    return
  endif
  let g:dart_format_on_save = 1
endfunction

" Finds a file name '.packages' in the cwd, or in any directory above the open
" file.
"
" Returns [found, file].
function! s:DotPackagesFile() abort
  if filereadable('.packages')
    return [v:true, '.packages']
  endif
  let dir_path = expand('%:p:h')
  while v:true
    let file_path = dir_path.'/.packages'
    if filereadable(file_path)
      return [v:true, file_path]
    endif
    let parent = fnamemodify(dir_path, ':h')
    if dir_path == parent
      break
    endif
    let dir_path = parent
  endwhile
  return [v:false, '']
endfunction

" Prevent writes to files in the pub cache.
function! dart#setModifiable() abort
  let full_path = expand('%:p')
  if full_path =~# '.pub-cache' ||
      \ full_path =~# 'Pub\Cache'
    setlocal nomodifiable
  endif
endfunction