Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support an include option in .clang_complete #557

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,16 @@ the conceal feature.
```

- Compiler options can be configured in a `.clang_complete` file in each project
root. Example of `.clang_complete` file:
root. `.clang_complete` can also include other files in the same format to
share options between projects. Example of `.clang_complete` file:

```
-DDEBUG
-include ../config.h
-I../common
-I/usr/include/c++/4.5.3/
-I/usr/include/c++/4.5.3/x86_64-slackware-linux/
@../../.clang_complete
```

## Usage
Expand Down
8 changes: 8 additions & 0 deletions doc/clang_complete.txt
Original file line number Diff line number Diff line change
Expand Up @@ -79,12 +79,20 @@ of linker arguments in .clang_complete file. They will lead to completion
failure when using clang executable and will be completely ignored by
libclang.

A .clang_complete file can include other files with compiler options. This is
useful if multiple projects share the same set of common options. An include
option consists of an '@' sign followed by an absolute or relative path to
another options file. This option can appear multiple times. It is also
handled recursively so any depth of nesting is supported.

Example .clang_complete file: >
-DDEBUG
-include ../config.h
-I../common
-I/usr/include/c++/4.5.3/
-I/usr/include/c++/4.5.3/x86_64-slackware-linux/
@../../.clang_complete
@/home/user/universal_options
<
==============================================================================
5. Options *clang_complete-options*
Expand Down
114 changes: 86 additions & 28 deletions plugin/clang_complete.vim
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,10 @@ let s:flagInfo = {
\ '-include': {
\ 'pattern': '-include\s\+',
\ 'output': '-include '
\ },
\ '@': {
\ 'pattern': '@\s*',
\ 'output': '@'
\ }
\ }

Expand All @@ -276,56 +280,105 @@ for s:flag in values(s:flagInfo)
endfor
let s:flagPattern = '\%(' . join(s:flagPatterns, '\|') . '\)'

" Characters that need escaping inside shell double quotes.
let s:shellDquoteEscapes = ['$', '`', '\', '"']

" Unfortunately VIM does not have this.
function! s:shellunescape(path)
let l:escaped = a:path
let l:unescaped = ''
let l:in_quote = ''

while len(l:escaped)
let l:ch = strpart(l:escaped, 0, 1)
let l:escaped = strpart(l:escaped, 1)

function! s:processFilename(filename, root)
" Handle Unix absolute path
if matchstr(a:filename, '\C^[''"\\]\=/') != ''
let l:filename = a:filename
" Handle Windows absolute path
elseif s:isWindows()
\ && matchstr(a:filename, '\C^"\=[a-zA-Z]:[/\\]') != ''
let l:filename = a:filename
" Convert relative path to absolute path
else
" If a windows file, the filename may need to be quoted.
if s:isWindows()
let l:root = substitute(a:root, '\\', '/', 'g')
if matchstr(a:filename, '\C^".*"\s*$') == ''
let l:filename = substitute(a:filename, '\C^\(.\{-}\)\s*$'
\ , '"' . l:root . '\1"', 'g')
else
" Strip first double-quote and prepend the root.
let l:filename = substitute(a:filename, '\C^"\(.\{-}\)"\s*$'
\ , '"' . l:root . '\1"', 'g')
" Handle quotes.
if l:ch == '"'
if l:in_quote == '"'
if strpart(l:escaped, 0, 1) == '"'
" This is an escaped quote. Leave one.
let l:escaped = strpart(l:escaped, 1)
else
" End of a quoted region. Ignore and continue.
let l:in_quote = ''
continue
endif
else
" Start of a quoted region. Ignore and continue.
let l:in_quote = '"'
continue
endif
endif
let l:filename = substitute(l:filename, '/', '\\', 'g')
else
" For Unix, assume the filename is already escaped/quoted correctly
let l:filename = shellescape(a:root) . a:filename
" Handle quotes.
if l:ch == l:in_quote
" End of a quoted region. Ignore and continue.
let l:in_quote = ''
continue
elseif l:in_quote == '' && (l:ch == "'" || l:ch == '"')
" Start of a quoted region. Ignore and continue.
let l:in_quote = l:ch
continue
endif

" Handle escapes
if l:ch == '\'
let l:ech = strpart(l:escaped, 0, 1)
if l:in_quote == '"'
" Remove the backslash if a special char follows.
" Otherwise leave as is.
if index(s:shellDquoteEscapes, l:ech) != -1
let l:ch = l:ech
let l:escaped = strpart(l:escaped, 1)
endif
elseif l:in_quote == ''
" Any char becomes literal after backslash outside quotes.
let l:ch = l:ech
let l:escaped = strpart(l:escaped, 1)
endif
endif
endif

let l:unescaped .= l:ch
endwhile

return l:unescaped
endfunction

function! s:processFilename(filename, root)
" Handle absolute paths
if a:filename =~ '^/' || (s:isWindows() && a:filename =~ '\c^[a-z]:[/\\]')
return a:filename
endif

return l:filename
return a:root . a:filename
endfunction

function! s:parseConfig()
let l:local_conf = findfile('.clang_complete', getcwd() . ',.;')
if l:local_conf == '' || !filereadable(l:local_conf)
return
endif
call s:parseConfigRecurse(l:local_conf)
endfunction

function! s:parseConfigRecurse(local_conf)
let l:sep = '/'
if s:isWindows()
let l:sep = '\'
endif

let l:root = fnamemodify(l:local_conf, ':p:h') . l:sep
let l:root = fnamemodify(a:local_conf, ':p:h') . l:sep

let l:opts = readfile(l:local_conf)
let l:opts = readfile(a:local_conf)
for l:opt in l:opts
" Remove any escaping that might have been in the config file.
let l:opt = s:shellunescape(l:opt)

" Ensure passed filenames are absolute. Only performed on flags which
" require a filename/directory as an argument, as specified in s:flagInfo
if matchstr(l:opt, '\C^\s*' . s:flagPattern . '\s*') != ''
if l:opt =~ '\C^\s*' . s:flagPattern
let l:flag = substitute(l:opt, '\C^\s*\(' . s:flagPattern . '\).*'
\ , '\1', 'g')
let l:flag = substitute(l:flag, '^\(.\{-}\)\s*$', '\1', 'g')
Expand All @@ -336,7 +389,12 @@ function! s:parseConfig()
let l:opt = s:flagInfo[l:flag].output . l:filename
endif

let b:clang_user_options .= ' ' . l:opt
if l:opt =~ '^@'
" Parse an included config recursively
call s:parseConfigRecurse(strpart(l:opt, 1))
else
let b:clang_user_options .= ' ' . shellescape(l:opt)
endif
endfor
endfunction

Expand Down