Merge pull request #578 from Valloric/ycmd

YCM rewritten to have a client-server architecture
This commit is contained in:
Val Markovic 2013-10-24 12:43:02 -07:00
commit 88d2171a16
121 changed files with 6851 additions and 2614 deletions

24
.gitmodules vendored
View File

@ -1,6 +1,24 @@
[submodule "python/ycm/completers/python/jedi"]
path = python/ycm/completers/python/jedi
url = https://github.com/davidhalter/jedi.git
[submodule "third_party/jedi"]
path = third_party/jedi
url = https://github.com/davidhalter/jedi
[submodule "python/ycm/completers/cs/OmniSharpServer"]
path = python/ycm/completers/cs/OmniSharpServer
url = https://github.com/nosami/OmniSharpServer.git
[submodule "third_party/requests-futures"]
path = third_party/requests-futures
url = https://github.com/ross/requests-futures
[submodule "third_party/requests"]
path = third_party/requests
url = https://github.com/kennethreitz/requests
[submodule "third_party/waitress"]
path = third_party/waitress
url = https://github.com/Pylons/waitress
[submodule "third_party/bottle"]
path = third_party/bottle
url = https://github.com/defnull/bottle
[submodule "third_party/frozendict"]
path = third_party/frozendict
url = https://github.com/slezica/python-frozendict
[submodule "third_party/argparse"]
path = third_party/argparse
url = https://github.com/bewest/argparse

View File

@ -4,10 +4,11 @@ python:
- "2.7"
install:
- pip install -r python/test_requirements.txt --use-mirrors
- sudo apt-get install mono-devel
compiler:
- gcc
- clang
script: flake8 --exclude=jedi --select=F,C9 --max-complexity=10 python && ./install.sh && nosetests python
script: ./run_tests.sh
env:
- YCM_TESTRUN=1 EXTRA_CMAKE_ARGS=""
- YCM_TESTRUN=1 EXTRA_CMAKE_ARGS="-DUSE_CLANG_COMPLETER=ON"
- USE_CLANG_COMPLETER="true"
- USE_CLANG_COMPLETER="false"

View File

@ -44,14 +44,22 @@ Here are the things you should do when creating an issue:
1. **Write a step-by-step procedure that when performed repeatedly reproduces
your issue.** If we can't reproduce the issue, then we can't fix it. It's
that simple.
2. **Create a test case for your issue**. This is critical. Don't talk about how
2. Put the following options in your vimrc:
```
let g:ycm_server_use_vim_stdout = 1
let g:ycm_server_log_level = 'debug'
```
Then start gvim/macvim (not console vim) from the console. As you use Vim,
you'll see the `ycmd` debug output stream in the console. Attach that to you
issue.
3. **Create a test case for your issue**. This is critical. Don't talk about how
"when I have X in my file" or similar, _create a file with X in it_ and put
the contents inside code blocks in your issue description. Try to make this
test file _as small as possible_. Don't just paste a huge, 500 line source
file you were editing and present that as a test. _Minimize_ the file so that
the problem is reproduced with the smallest possible amount of test data.
3. **Include your OS and OS version.**
4. **Include the output of `vim --version`.**
4. **Include your OS and OS version.**
5. **Include the output of `vim --version`.**
Creating good pull requests

View File

@ -89,8 +89,9 @@ local binary folder (for example `/usr/local/bin/mvim`) and then symlink it:
Install YouCompleteMe with [Vundle][].
**Remember:** YCM is a plugin with a compiled component. If you **update** YCM
using Vundle and the ycm_core library API has changed (happens rarely), YCM will
notify you to recompile it. You should then rerun the install process.
using Vundle and the ycm_support_libs library APIs have changed (happens
rarely), YCM will notify you to recompile it. You should then rerun the install
process.
It's recommended that you have the latest Xcode installed along with the latest
Command Line Tools (that you install from within Xcode).
@ -136,8 +137,9 @@ from source][vim-build] (don't worry, it's easy).
Install YouCompleteMe with [Vundle][].
**Remember:** YCM is a plugin with a compiled component. If you **update** YCM
using Vundle and the ycm_core library API has changed (happens rarely), YCM will
notify you to recompile it. You should then rerun the install process.
using Vundle and the ycm_support_libs library APIs have changed (happens
rarely), YCM will notify you to recompile it. You should then rerun the install
process.
Install development tools and CMake: `sudo apt-get install build-essential cmake`
@ -184,8 +186,9 @@ that platform).
See the _FAQ_ if you have any issues.
**Remember:** YCM is a plugin with a compiled component. If you **update** YCM
using Vundle and the ycm_core library API has changed (happens rarely), YCM will
notify you to recompile it. You should then rerun the install process.
using Vundle and the ycm_support_libs library APIs have changed (happens
rarely), YCM will notify you to recompile it. You should then rerun the install
process.
**Please follow the instructions carefully. Read EVERY WORD.**
@ -221,8 +224,8 @@ notify you to recompile it. You should then rerun the install process.
binaries from llvm.org][clang-download] if at all possible. Make sure you
download the correct archive file for your OS.
4. **Compile the `ycm_core` plugin plugin** (ha!) that YCM needs. This is the
C++ engine that YCM uses to get fast completions.
4. **Compile the `ycm_support_libs` libraries** that YCM needs. These libs
are the C++ engines that YCM uses to get fast completions.
You will need to have `cmake` installed in order to generate the required
makefiles. Linux users can install cmake with their package manager (`sudo
@ -261,7 +264,7 @@ notify you to recompile it. You should then rerun the install process.
Now that makefiles have been generated, simply run:
make ycm_core
make ycm_support_libs
For those who want to use the system version of libclang, you would pass
`-DUSE_SYSTEM_LIBCLANG=ON` to cmake _instead of_ the
@ -323,6 +326,13 @@ YCM automatically detects which completion engine would be the best in any
situation. On occasion, it queries several of them at once, merges the
outputs and presents the results to you.
### Client-server architecture
YCM has a client-server architecture; the Vim part of YCM is only a thin client
that talks to the `ycmd` HTTP+JSON server that has the vast majority of YCM
logic and functionality. The server is started and stopped automatically as you
start and stop Vim.
### Completion string ranking
The subsequence filter removes any completions that do not match the input, but
@ -498,6 +508,11 @@ yours truly.
Commands
--------
### The `:YcmRestartServer` command
If the `ycmd` completion server suddenly stops for some reason, you can restart
it with this command.
### The `:YcmForceCompileAndDiagnostics` command
Calling this command will force YCM to immediately recompile your file
@ -832,6 +847,64 @@ Default: `0`
let g:ycm_seed_identifiers_with_syntax = 0
### The `g:ycm_server_use_vim_stdout` option
By default, the `ycmd` completion server writes logs to logfiles. When this
option is set to `1`, the server writes logs to Vim's stdout (so you'll see them
in the console).
Default: `0`
let g:ycm_server_use_vim_stdout = 0
### The `g:ycm_server_keep_logfiles` option
When this option is set to `1`, the `ycmd` completion server will keep the
logfiles around after shutting down (they are deleted on shutdown by default).
To see where the logfiles are, call `:YcmDebugInfo`.
Default: `0`
let g:ycm_server_keep_logfiles = 0
### The `g:ycm_server_log_level` option
The logging level that the `ycmd` completion server uses. Valid values are the
following, from most verbose to least verbose:
- `debug`
- `info`
- `warning`
- `error`
- `critical`
Note that `debug` is _very_ verbose.
Default: `info`
let g:ycm_server_log_level = 'info'
### The `g:ycm_server_idle_suicide_seconds` option
This option sets the number of seconds of `ycmd` server idleness (no requests
received) after which the server stops itself. NOTE: the YCM Vim client sends a
shutdown request to the server when Vim is shutting down.
If your Vim crashes for instance, `ycmd` never gets the shutdown command and
becomes a zombie process. This option prevents such zombies from sticking around
forever.
The default option is `43200` seconds which is 12 hours. The reason for the
interval being this long is to prevent the server from shutting down if you
leave your computer (and Vim) turned on during the night.
The server "heartbeat" that checks whether this interval has passed occurs every
10 minutes.
Default: `43200`
let g:ycm_server_idle_suicide_seconds = 43200
### The `g:ycm_csharp_server_port` option
The port number (on `localhost`) on which the OmniSharp server should be

View File

@ -22,8 +22,6 @@ set cpo&vim
" This needs to be called outside of a function
let s:script_folder_path = escape( expand( '<sfile>:p:h' ), '\' )
let s:searched_and_results_found = 0
let s:should_use_filetype_completion = 0
let s:completion_start_column = 0
let s:omnifunc_mode = 0
let s:old_cursor_position = []
@ -31,28 +29,54 @@ let s:cursor_moved = 0
let s:moved_vertically_in_insert_mode = 0
let s:previous_num_chars_on_current_line = -1
let s:forced_syntastic_checker_for = {
\ 'cpp': 1,
\ 'c': 1,
\ 'objc': 1,
\ 'objcpp': 1,
\ }
function! youcompleteme#Enable()
" When vim is in diff mode, don't run
if &diff
return
endif
call s:SetUpBackwardsCompatibility()
py import sys
py import vim
exe 'python sys.path.insert( 0, "' . s:script_folder_path . '/../python" )'
py from ycm import extra_conf_store
py extra_conf_store.CallExtraConfYcmCorePreloadIfExists()
py from ycm import utils
py utils.AddThirdPartyFoldersToSysPath()
py from ycm import base
py base.LoadJsonDefaultsIntoVim()
py from ycm import vimsupport
py from ycm import user_options_store
py user_options_store.SetAll( base.BuildServerConf() )
if !pyeval( 'base.CompatibleWithYcmCore()')
echohl WarningMsg |
\ echomsg "YouCompleteMe unavailable: ycm_core too old, PLEASE RECOMPILE ycm_core" |
\ echomsg "YouCompleteMe unavailable: YCM support libs too old, PLEASE RECOMPILE" |
\ echohl None
return
endif
py from ycm.youcompleteme import YouCompleteMe
py ycm_state = YouCompleteMe()
py ycm_state = YouCompleteMe( user_options_store.GetAll() )
call s:SetUpCpoptions()
call s:SetUpCompleteopt()
call s:SetUpKeyMappings()
if g:ycm_register_as_syntastic_checker
call s:ForceSyntasticCFamilyChecker()
endif
if g:ycm_allow_changing_updatetime
set ut=2000
endif
augroup youcompleteme
autocmd!
@ -63,7 +87,10 @@ function! youcompleteme#Enable()
" is read. This is because youcompleteme#Enable() is called on VimEnter and
" that happens *after" BufRead/BufEnter has already triggered for the
" initial file.
autocmd BufRead,BufEnter * call s:OnBufferVisit()
" We also need to trigger buf init code on the FileType event because when
" the user does :enew and then :set ft=something, we need to run buf init
" code again.
autocmd BufRead,BufEnter,FileType * call s:OnBufferVisit()
autocmd BufUnload * call s:OnBufferUnload( expand( '<afile>:p' ) )
autocmd CursorHold,CursorHoldI * call s:OnCursorHold()
autocmd InsertLeave * call s:OnInsertLeave()
@ -71,19 +98,6 @@ function! youcompleteme#Enable()
autocmd VimLeave * call s:OnVimLeave()
augroup END
call s:SetUpCpoptions()
call s:SetUpCompleteopt()
call s:SetUpKeyMappings()
call s:SetUpBackwardsCompatibility()
if g:ycm_register_as_syntastic_checker
call s:ForceSyntasticCFamilyChecker()
endif
if g:ycm_allow_changing_updatetime
set ut=2000
endif
" Calling this once solves the problem of BufRead/BufEnter not triggering for
" the first loaded file. This should be the last command executed in this
" function!
@ -151,6 +165,11 @@ function! s:SetUpBackwardsCompatibility()
let g:ycm_complete_in_strings = 1
let g:ycm_complete_in_comments = 1
endif
" ycm_filetypes_to_completely_ignore is the old name for fileype_blacklist
if has_key( g:, 'ycm_filetypes_to_completely_ignore' )
let g:filetype_blacklist = g:ycm_filetypes_to_completely_ignore
endif
endfunction
@ -163,6 +182,12 @@ function! s:ForceSyntasticCFamilyChecker()
endfunction
function! s:ForcedAsSyntasticCheckerForCurrentFiletype()
return g:ycm_register_as_syntastic_checker &&
\ get( s:forced_syntastic_checker_for, &filetype, 0 )
endfunction
function! s:AllowedToCompleteInCurrentFile()
if empty( &filetype ) || getbufvar(winbufnr(winnr()), "&buftype") ==# 'nofile'
return 0
@ -221,7 +246,6 @@ endfunction
function! s:OnVimLeave()
py ycm_state.OnVimLeave()
py extra_conf_store.CallExtraConfVimCloseIfExists()
endfunction
@ -257,9 +281,6 @@ function! s:OnCursorHold()
endif
call s:SetUpCompleteopt()
" Order is important here; we need to extract any done diagnostics before
" reparsing the file again
call s:UpdateDiagnosticNotifications()
call s:OnFileReadyToParse()
endfunction
@ -269,6 +290,12 @@ function! s:OnFileReadyToParse()
" happen for special buffers.
call s:SetUpYcmChangedTick()
" Order is important here; we need to extract any done diagnostics before
" reparsing the file again. If we sent the new parse request first, then
" the response would always be pending when we called
" UpdateDiagnosticNotifications.
call s:UpdateDiagnosticNotifications()
let buffer_changed = b:changedtick != b:ycm_changedtick.file_ready_to_parse
if buffer_changed
py ycm_state.OnFileReadyToParse()
@ -326,7 +353,6 @@ function! s:OnCursorMovedNormalMode()
return
endif
call s:UpdateDiagnosticNotifications()
call s:OnFileReadyToParse()
endfunction
@ -337,7 +363,6 @@ function! s:OnInsertLeave()
endif
let s:omnifunc_mode = 0
call s:UpdateDiagnosticNotifications()
call s:OnFileReadyToParse()
py ycm_state.OnInsertLeave()
if g:ycm_autoclose_preview_window_after_completion ||
@ -407,10 +432,16 @@ endfunction
function! s:UpdateDiagnosticNotifications()
if get( g:, 'loaded_syntastic_plugin', 0 ) &&
\ pyeval( 'ycm_state.NativeFiletypeCompletionUsable()' ) &&
\ pyeval( 'ycm_state.DiagnosticsForCurrentFileReady()' ) &&
\ g:ycm_register_as_syntastic_checker
let should_display_diagnostics =
\ get( g:, 'loaded_syntastic_plugin', 0 ) &&
\ s:ForcedAsSyntasticCheckerForCurrentFiletype() &&
\ pyeval( 'ycm_state.NativeFiletypeCompletionUsable()' )
if !should_display_diagnostics
return
endif
if pyeval( 'ycm_state.DiagnosticsForCurrentFileReady()' )
SyntasticCheck
endif
endfunction
@ -494,29 +525,24 @@ function! s:InvokeCompletion()
endfunction
function! s:CompletionsForQuery( query, use_filetype_completer,
\ completion_start_column )
if a:use_filetype_completer
py completer = ycm_state.GetFiletypeCompleter()
else
py completer = ycm_state.GetGeneralCompleter()
endif
py completer.CandidatesForQueryAsync( vim.eval( 'a:query' ),
\ int( vim.eval( 'a:completion_start_column' ) ) )
let l:results_ready = 0
while !l:results_ready
let l:results_ready = pyeval( 'completer.AsyncCandidateRequestReady()' )
if complete_check()
let s:searched_and_results_found = 0
python << EOF
def GetCompletions( query ):
request = ycm_state.GetCurrentCompletionRequest()
request.Start( query )
while not request.Done():
if bool( int( vim.eval( 'complete_check()' ) ) ):
return { 'words' : [], 'refresh' : 'always'}
endif
endwhile
let l:results = pyeval( 'base.AdjustCandidateInsertionText( completer.CandidatesFromStoredRequest() )' )
let s:searched_and_results_found = len( l:results ) != 0
return { 'words' : l:results, 'refresh' : 'always' }
results = base.AdjustCandidateInsertionText( request.Response() )
return { 'words' : results, 'refresh' : 'always' }
EOF
function! s:CompletionsForQuery( query )
py results = GetCompletions( vim.eval( 'a:query' ) )
let results = pyeval( 'results' )
let s:searched_and_results_found = len( results.words ) != 0
return results
endfunction
@ -540,24 +566,13 @@ function! youcompleteme#Complete( findstart, base )
return -2
endif
" TODO: make this a function-local variable instead of a script-local one
let s:completion_start_column = pyeval( 'base.CompletionStartColumn()' )
let s:should_use_filetype_completion =
\ pyeval( 'ycm_state.ShouldUseFiletypeCompleter(' .
\ s:completion_start_column . ')' )
if !s:should_use_filetype_completion &&
\ !pyeval( 'ycm_state.ShouldUseGeneralCompleter(' .
\ s:completion_start_column . ')' )
" for vim, -2 means not found but don't trigger an error message
" see :h complete-functions
py request = ycm_state.CreateCompletionRequest()
if !pyeval( 'bool(request)' )
return -2
endif
return s:completion_start_column
return pyeval( 'request.CompletionStartColumn()' )
else
return s:CompletionsForQuery( a:base, s:should_use_filetype_completion,
\ s:completion_start_column )
return s:CompletionsForQuery( a:base )
endif
endfunction
@ -565,14 +580,26 @@ endfunction
function! youcompleteme#OmniComplete( findstart, base )
if a:findstart
let s:omnifunc_mode = 1
let s:completion_start_column = pyeval( 'base.CompletionStartColumn()' )
return s:completion_start_column
py request = ycm_state.CreateCompletionRequest( force_semantic = True )
return pyeval( 'request.CompletionStartColumn()' )
else
return s:CompletionsForQuery( a:base, 1, s:completion_start_column )
return s:CompletionsForQuery( a:base )
endif
endfunction
function! youcompleteme#ServerPid()
return pyeval( 'ycm_state.ServerPid()' )
endfunction
function! s:RestartServer()
py ycm_state.RestartServer()
endfunction
command! YcmRestartServer call s:RestartServer()
function! s:ShowDetailedDiagnostic()
py ycm_state.ShowDetailedDiagnostic()
endfunction
@ -584,7 +611,7 @@ command! YcmShowDetailedDiagnostic call s:ShowDetailedDiagnostic()
" required (currently that's on buffer save) OR when the SyntasticCheck command
" is invoked
function! youcompleteme#CurrentFileDiagnostics()
return pyeval( 'ycm_state.GetDiagnosticsForCurrentFile()' )
return pyeval( 'ycm_state.GetDiagnosticsFromStoredRequest()' )
endfunction
@ -598,37 +625,27 @@ endfunction
command! YcmDebugInfo call s:DebugInfo()
function! s:CompleterCommand(...)
" CompleterCommand will call the OnUserCommand function of a completer.
" If the first arguments is of the form "ft=..." it can be used to specify the
" completer to use (for example "ft=cpp"). Else the native filetype completer
" of the current buffer is used. If no native filetype completer is found and
" no completer was specified this throws an error. You can use "ft=ycm:omni"
" to select the omni completer or "ft=ycm:ident" to select the identifier
" completer. The remaining arguments will passed to the completer.
" no completer was specified this throws an error. You can use
" "ft=ycm:ident" to select the identifier completer.
" The remaining arguments will be passed to the completer.
let arguments = copy(a:000)
let completer = ''
if a:0 > 0 && strpart(a:1, 0, 3) == 'ft='
if a:1 == 'ft=ycm:omni'
py completer = ycm_state.GetOmniCompleter()
elseif a:1 == 'ft=ycm:ident'
py completer = ycm_state.GetGeneralCompleter()
else
py completer = ycm_state.GetFiletypeCompleterForFiletype(
\ vim.eval('a:1').lstrip('ft=') )
if a:1 == 'ft=ycm:ident'
let completer = 'identifier'
endif
let arguments = arguments[1:]
elseif pyeval( 'ycm_state.NativeFiletypeCompletionAvailable()' )
py completer = ycm_state.GetFiletypeCompleter()
else
echohl WarningMsg |
\ echomsg "No native completer found for current buffer." |
\ echomsg "Use ft=... as the first argument to specify a completer." |
\ echohl None
return
endif
py completer.OnUserCommand( vim.eval( 'l:arguments' ) )
py ycm_state.SendCommandRequest( vim.eval( 'l:arguments' ),
\ vim.eval( 'l:completer' ) )
endfunction
@ -645,9 +662,8 @@ endfunction
command! -nargs=* -complete=custom,youcompleteme#SubCommandsComplete
\ YcmCompleter call s:CompleterCommand(<f-args>)
function! youcompleteme#SubCommandsComplete( arglead, cmdline, cursorpos )
return join( pyeval( 'ycm_state.GetFiletypeCompleter().DefinedSubcommands()' ),
return join( pyeval( 'ycm_state.GetDefinedSubcommands()' ),
\ "\n")
endfunction
@ -668,14 +684,6 @@ function! s:ForceCompile()
break
endif
let getting_completions = pyeval(
\ 'ycm_state.GettingCompletions()' )
if !getting_completions
echom "Unable to retrieve diagnostics, see output of `:mes` for possible details."
return 0
endif
sleep 100m
endwhile
return 1
@ -701,7 +709,7 @@ function! s:ShowDiagnostics()
return
endif
let diags = pyeval( 'ycm_state.GetDiagnosticsForCurrentFile()' )
let diags = pyeval( 'ycm_state.GetDiagnosticsFromStoredRequest()' )
if !empty( diags )
call setloclist( 0, diags )
lopen

View File

@ -26,8 +26,8 @@ cmake_minimum_required( VERSION 2.8 )
project( BoostParts )
set( Python_ADDITIONAL_VERSIONS 2.7 2.6 2.5 )
find_package( PythonLibs 2.5 REQUIRED )
set( Python_ADDITIONAL_VERSIONS 2.7 2.6 )
find_package( PythonLibs 2.6 REQUIRED )
if ( NOT PYTHONLIBS_VERSION_STRING VERSION_LESS "3.0.0" )
message( FATAL_ERROR

View File

@ -138,19 +138,18 @@ endif()
# the compiler to output a warning during linking:
# clang: warning: argument unused during compilation: '-std=c++0x'
# This is caused by cmake passing this flag to the linking stage which it
# shouldn't do. It's ignored so it does no harm, but the warning is annoying and
# there's no way around the problem (the flag is correctly used during the
# compilation stage). We could use add_definitions(-std=c++0x), but this will
# break the llvm build since the flag will then be used when compiling C code
# too. Sadly there's no way around the warning.
# shouldn't do. It's ignored so it does no harm, but the warning is annoying.
#
# Putting the flag in add_definitions() works around the issue, even though it
# shouldn't in theory go there.
if ( CPP11_AVAILABLE )
message( "Your C++ compiler supports C++11, compiling in that mode." )
# Cygwin needs its hand held a bit; see issue #473
if ( CYGWIN AND CMAKE_COMPILER_IS_GNUCXX )
set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=gnu++0x" )
add_definitions( -std=gnu++0x )
else()
set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++0x" )
add_definitions( -std=c++0x )
endif()
else()
message(

View File

@ -17,10 +17,12 @@
cmake_minimum_required( VERSION 2.8 )
project( ycm_core )
project( ycm_support_libs )
set( CLIENT_LIB "ycm_client_support" )
set( SERVER_LIB "ycm_core" )
set( Python_ADDITIONAL_VERSIONS 2.7 2.6 2.5 )
find_package( PythonLibs 2.5 REQUIRED )
set( Python_ADDITIONAL_VERSIONS 2.7 2.6 )
find_package( PythonLibs 2.6 REQUIRED )
if ( NOT PYTHONLIBS_VERSION_STRING VERSION_LESS "3.0.0" )
message( FATAL_ERROR
@ -138,15 +140,15 @@ include_directories(
${CLANG_INCLUDES_DIR}
)
file( GLOB_RECURSE SOURCES *.h *.cpp )
file( GLOB_RECURSE SERVER_SOURCES *.h *.cpp )
# The test sources are a part of a different target, so we remove them
# The CMakeFiles cpp file is picked up when the user creates an in-source build,
# and we don't want that.
file( GLOB_RECURSE to_remove tests/*.h tests/*.cpp CMakeFiles/*.cpp )
# and we don't want that. We also remove client-specific code
file( GLOB_RECURSE to_remove tests/*.h tests/*.cpp CMakeFiles/*.cpp *client* )
if( to_remove )
list( REMOVE_ITEM SOURCES ${to_remove} )
list( REMOVE_ITEM SERVER_SOURCES ${to_remove} )
endif()
if ( USE_CLANG_COMPLETER )
@ -158,7 +160,7 @@ else()
file( GLOB_RECURSE to_remove_clang ClangCompleter/*.h ClangCompleter/*.cpp )
if( to_remove_clang )
list( REMOVE_ITEM SOURCES ${to_remove_clang} )
list( REMOVE_ITEM SERVER_SOURCES ${to_remove_clang} )
endif()
endif()
@ -217,11 +219,33 @@ endif()
#############################################################################
add_library( ${PROJECT_NAME} SHARED
${SOURCES}
# We don't actually need all of the files this picks up, just the ones needed by
# PythonSupport.cpp. But this is easier to maintain and dead code elemination
# will remove unused code.
file( GLOB CLIENT_SOURCES *.h *.cpp )
file( GLOB SERVER_SPECIFIC *ycm_core* )
if( SERVER_SPECIFIC )
list( REMOVE_ITEM CLIENT_SOURCES ${SERVER_SPECIFIC} )
endif()
add_library( ${CLIENT_LIB} SHARED
${CLIENT_SOURCES}
)
target_link_libraries( ${PROJECT_NAME}
target_link_libraries( ${CLIENT_LIB}
BoostParts
${PYTHON_LIBRARIES}
${EXTRA_LIBS}
)
#############################################################################
add_library( ${SERVER_LIB} SHARED
${SERVER_SOURCES}
)
target_link_libraries( ${SERVER_LIB}
BoostParts
${PYTHON_LIBRARIES}
${LIBCLANG_TARGET}
@ -231,35 +255,43 @@ target_link_libraries( ${PROJECT_NAME}
if( LIBCLANG_TARGET )
if( NOT WIN32 )
add_custom_command(
TARGET ${PROJECT_NAME}
TARGET ${SERVER_LIB}
POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy "${LIBCLANG_TARGET}" "$<TARGET_FILE_DIR:${PROJECT_NAME}>"
COMMAND ${CMAKE_COMMAND} -E copy "${LIBCLANG_TARGET}" "$<TARGET_FILE_DIR:${SERVER_LIB}>"
)
else()
add_custom_command(
TARGET ${PROJECT_NAME}
TARGET ${SERVER_LIB}
POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy "${PATH_TO_LLVM_ROOT}/bin/libclang.dll" "$<TARGET_FILE_DIR:${PROJECT_NAME}>")
COMMAND ${CMAKE_COMMAND} -E copy "${PATH_TO_LLVM_ROOT}/bin/libclang.dll" "$<TARGET_FILE_DIR:${SERVER_LIB}>")
endif()
endif()
#############################################################################
# Convenience target that builds both support libs.
add_custom_target( ${PROJECT_NAME}
DEPENDS ${CLIENT_LIB} ${SERVER_LIB} )
#############################################################################
# Things are a bit different on Macs when using an external libclang.dylib; here
# we want to make sure we use @loader_path/libclang.dylib instead of
# @rpath/libclang.dylib in the final ycm_core.so. If we use the @rpath version,
# then it may load the system libclang which the user explicitely does not want
# (otherwise the user would specify USE_SYSTEM_LIBCLANG). With @loader_path, we
# make sure that only the libclang.dylib present in the same directory as our
# ycm_core.so is used.
# @rpath/libclang.dylib in the final ycm_core.so. If we use the
# @rpath version, then it may load the system libclang which the user
# explicitely does not want (otherwise the user would specify
# USE_SYSTEM_LIBCLANG). With @loader_path, we make sure that only the
# libclang.dylib present in the same directory as our ycm_core.so
# is used.
if ( EXTERNAL_LIBCLANG_PATH AND APPLE )
add_custom_command( TARGET ${PROJECT_NAME}
add_custom_command( TARGET ${SERVER_LIB}
POST_BUILD
COMMAND install_name_tool
"-change"
"@rpath/libclang.dylib"
"@loader_path/libclang.dylib"
"$<TARGET_FILE:${PROJECT_NAME}>"
"$<TARGET_FILE:${SERVER_LIB}>"
)
endif()
@ -268,19 +300,24 @@ endif()
# We don't want the "lib" prefix, it can screw up python when it tries to search
# for our module
set_target_properties( ${PROJECT_NAME} PROPERTIES PREFIX "")
set_target_properties( ${CLIENT_LIB} PROPERTIES PREFIX "")
set_target_properties( ${SERVER_LIB} PROPERTIES PREFIX "")
if ( WIN32 OR CYGWIN )
# This is the extension for compiled Python modules on Windows
set_target_properties( ${PROJECT_NAME} PROPERTIES SUFFIX ".pyd")
set_target_properties( ${CLIENT_LIB} PROPERTIES SUFFIX ".pyd")
set_target_properties( ${SERVER_LIB} PROPERTIES SUFFIX ".pyd")
else()
# Even on macs, we want a .so extension instead of a .dylib which is what
# cmake would give us by default. Python won't recognize a .dylib as a module,
# but it will recognize a .so
set_target_properties( ${PROJECT_NAME} PROPERTIES SUFFIX ".so")
set_target_properties( ${CLIENT_LIB} PROPERTIES SUFFIX ".so")
set_target_properties( ${SERVER_LIB} PROPERTIES SUFFIX ".so")
endif()
set_target_properties( ${PROJECT_NAME} PROPERTIES
set_target_properties( ${CLIENT_LIB} PROPERTIES
LIBRARY_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/../../python )
set_target_properties( ${SERVER_LIB} PROPERTIES
LIBRARY_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/../../python )
#############################################################################

View File

@ -1,4 +1,4 @@
// Copyright (C) 2011, 2012 Strahinja Val Markovic <val@markovic.io>
// Copyright (C) 2011-2013 Strahinja Val Markovic <val@markovic.io>
//
// This file is part of YouCompleteMe.
//
@ -23,48 +23,22 @@
#include "standard.h"
#include "CandidateRepository.h"
#include "CompletionData.h"
#include "ConcurrentLatestValue.h"
#include "Utils.h"
#include "ClangUtils.h"
#include "ReleaseGil.h"
#include <clang-c/Index.h>
#include <boost/make_shared.hpp>
#include <boost/algorithm/string.hpp>
#include <boost/algorithm/cxx11/any_of.hpp>
#include <boost/shared_ptr.hpp>
using boost::algorithm::any_of;
using boost::algorithm::is_upper;
using boost::bind;
using boost::cref;
using boost::function;
using boost::lock_guard;
using boost::make_shared;
using boost::mutex;
using boost::packaged_task;
using boost::ref;
using boost::shared_lock;
using boost::shared_mutex;
using boost::shared_ptr;
using boost::thread;
using boost::thread_interrupted;
using boost::try_to_lock_t;
using boost::unique_future;
using boost::unique_lock;
using boost::unordered_map;
namespace YouCompleteMe {
extern const unsigned int MAX_ASYNC_THREADS;
extern const unsigned int MIN_ASYNC_THREADS;
ClangCompleter::ClangCompleter()
: clang_index_( clang_createIndex( 0, 0 ) ),
translation_unit_store_( clang_index_ ),
candidate_repository_( CandidateRepository::Instance() ),
threading_enabled_( false ),
time_to_die_( false ),
clang_data_ready_( false ) {
translation_unit_store_( clang_index_ ) {
// The libclang docs don't say what is the default value for crash recovery.
// I'm pretty sure it's turned on by default, but I'm not going to take any
// chances.
@ -73,47 +47,20 @@ ClangCompleter::ClangCompleter()
ClangCompleter::~ClangCompleter() {
{
unique_lock< shared_mutex > lock( time_to_die_mutex_ );
time_to_die_ = true;
}
sorting_threads_.interrupt_all();
sorting_threads_.join_all();
if ( clang_thread_ ) {
clang_thread_->interrupt();
clang_thread_->join();
}
// We need to destroy all TUs before calling clang_disposeIndex because
// the translation units need to be destroyed before the index is destroyed.
// Technically, a thread could still be holding onto a shared_ptr<TU> object
// when we destroy the clang index, but since we're shutting down, we don't
// really care.
// In practice, this situation shouldn't happen because the server threads are
// Python deamon threads and will all be killed before the main thread exits.
translation_unit_store_.RemoveAll();
clang_disposeIndex( clang_index_ );
}
// We need this mostly so that we can not use it in tests. Apparently the
// GoogleTest framework goes apeshit on us (on some platforms, in some
// occasions) if we enable threads by default.
void ClangCompleter::EnableThreading() {
threading_enabled_ = true;
InitThreads();
}
std::vector< Diagnostic > ClangCompleter::DiagnosticsForFile(
const std::string &filename ) {
shared_ptr< TranslationUnit > unit = translation_unit_store_.Get( filename );
if ( !unit )
return std::vector< Diagnostic >();
return unit->LatestDiagnostics();
}
bool ClangCompleter::UpdatingTranslationUnit( const std::string &filename ) {
ReleaseGil unlock;
shared_ptr< TranslationUnit > unit = translation_unit_store_.Get( filename );
if ( !unit )
@ -126,10 +73,11 @@ bool ClangCompleter::UpdatingTranslationUnit( const std::string &filename ) {
}
void ClangCompleter::UpdateTranslationUnit(
std::vector< Diagnostic > ClangCompleter::UpdateTranslationUnit(
const std::string &filename,
const std::vector< UnsavedFile > &unsaved_files,
const std::vector< std::string > &flags ) {
ReleaseGil unlock;
bool translation_unit_created;
shared_ptr< TranslationUnit > unit = translation_unit_store_.GetOrCreate(
filename,
@ -138,13 +86,14 @@ void ClangCompleter::UpdateTranslationUnit(
translation_unit_created );
if ( !unit )
return;
return std::vector< Diagnostic >();
try {
// There's no point in reparsing a TU that was just created, it was just
// parsed in the TU constructor
if ( !translation_unit_created )
unit->Reparse( unsaved_files );
return unit->Reparse( unsaved_files );
return unit->LatestDiagnostics();
}
catch ( ClangParseError & ) {
@ -153,30 +102,8 @@ void ClangCompleter::UpdateTranslationUnit(
// TU map.
translation_unit_store_.Remove( filename );
}
}
Future< void > ClangCompleter::UpdateTranslationUnitAsync(
std::string filename,
std::vector< UnsavedFile > unsaved_files,
std::vector< std::string > flags ) {
function< void() > functor =
boost::bind( &ClangCompleter::UpdateTranslationUnit,
boost::ref( *this ),
boost::move( filename ),
boost::move( unsaved_files ),
boost::move( flags ) );
shared_ptr< ClangPackagedTask > clang_packaged_task =
make_shared< ClangPackagedTask >();
clang_packaged_task->parsing_task_ = packaged_task< void >(
boost::move( functor ) );
unique_future< void > future =
clang_packaged_task->parsing_task_.get_future();
clang_task_.Set( clang_packaged_task );
return Future< void >( boost::move( future ) );
return std::vector< Diagnostic >();
}
@ -187,6 +114,7 @@ ClangCompleter::CandidatesForLocationInFile(
int column,
const std::vector< UnsavedFile > &unsaved_files,
const std::vector< std::string > &flags ) {
ReleaseGil unlock;
shared_ptr< TranslationUnit > unit =
translation_unit_store_.GetOrCreate( filename, unsaved_files, flags );
@ -199,58 +127,13 @@ ClangCompleter::CandidatesForLocationInFile(
}
Future< AsyncCompletions >
ClangCompleter::CandidatesForQueryAndLocationInFileAsync(
const std::string &query,
const std::string &filename,
int line,
int column,
const std::vector< UnsavedFile > &unsaved_files,
const std::vector< std::string > &flags ) {
// TODO: throw exception when threading is not enabled and this is called
if ( !threading_enabled_ )
return Future< AsyncCompletions >();
bool skip_clang_result_cache = ShouldSkipClangResultCache( query,
line,
column );
if ( skip_clang_result_cache ) {
// The clang thread is busy, return nothing
if ( UpdatingTranslationUnit( filename ) )
return Future< AsyncCompletions >();
{
lock_guard< mutex > lock( clang_data_ready_mutex_ );
clang_data_ready_ = false;
}
// Needed to "reset" the sorting threads to the start of their loop. This
// way any threads blocking on a read in sorting_task_.Get() are reset to
// wait on the clang_data_ready_condition_variable_.
sorting_threads_.interrupt_all();
}
// the sorting task needs to be set before the clang task (if any) just in
// case the clang task finishes (and therefore notifies a sorting thread to
// consume a sorting task) before the sorting task is set
unique_future< AsyncCompletions > future;
CreateSortingTask( query, future );
if ( skip_clang_result_cache ) {
CreateClangTask( filename, line, column, unsaved_files, flags );
}
return Future< AsyncCompletions >( boost::move( future ) );
}
Location ClangCompleter::GetDeclarationLocation(
const std::string &filename,
int line,
int column,
const std::vector< UnsavedFile > &unsaved_files,
const std::vector< std::string > &flags ) {
ReleaseGil unlock;
shared_ptr< TranslationUnit > unit =
translation_unit_store_.GetOrCreate( filename, unsaved_files, flags );
@ -268,6 +151,7 @@ Location ClangCompleter::GetDefinitionLocation(
int column,
const std::vector< UnsavedFile > &unsaved_files,
const std::vector< std::string > &flags ) {
ReleaseGil unlock;
shared_ptr< TranslationUnit > unit =
translation_unit_store_.GetOrCreate( filename, unsaved_files, flags );
@ -279,217 +163,10 @@ Location ClangCompleter::GetDefinitionLocation(
}
void ClangCompleter::DeleteCachesForFileAsync( const std::string &filename ) {
file_cache_delete_stack_.Push( filename );
}
void ClangCompleter::DeleteCaches() {
std::vector< std::string > filenames;
if ( !file_cache_delete_stack_.PopAllNoWait( filenames ) )
return;
foreach( const std::string & filename, filenames ) {
void ClangCompleter::DeleteCachesForFile( const std::string &filename ) {
ReleaseGil unlock;
translation_unit_store_.Remove( filename );
}
}
bool ClangCompleter::ShouldSkipClangResultCache( const std::string &query,
int line,
int column ) {
// We need query.empty() in addition to the second check because if we don't
// have it, then we have a problem in the following situation:
// The user has a variable 'foo' of type 'A' and a variable 'bar' of type 'B'.
// He then types in 'foo.' and we store the clang results and also return
// them. The user then deletes that code and types in 'bar.'. Without the
// query.empty() check we would return the results from the cache here (it's
// the same line and column!) which would be incorrect.
return query.empty() || latest_clang_results_
.NewPositionDifferentFromStoredPosition( line, column );
}
// Copy-ctor for unique_future is private in C++03 mode so we need to take it as
// an out param
void ClangCompleter::CreateSortingTask(
const std::string &query,
unique_future< AsyncCompletions > &future ) {
// Careful! The code in this function may burn your eyes.
function< CompletionDatas( const CompletionDatas & ) >
sort_candidates_for_query_functor =
boost::bind( &ClangCompleter::SortCandidatesForQuery,
boost::ref( *this ),
query,
_1 );
function< CompletionDatas() > operate_on_completion_data_functor =
boost::bind( &ClangResultsCache::OperateOnCompletionDatas< CompletionDatas >,
boost::cref( latest_clang_results_ ),
boost::move( sort_candidates_for_query_functor ) );
shared_ptr< packaged_task< AsyncCompletions > > task =
boost::make_shared< packaged_task< AsyncCompletions > >(
boost::bind( ReturnValueAsShared< std::vector< CompletionData > >,
boost::move( operate_on_completion_data_functor ) ) );
future = task->get_future();
sorting_task_.Set( task );
}
void ClangCompleter::CreateClangTask(
std::string filename,
int line,
int column,
std::vector< UnsavedFile > unsaved_files,
std::vector< std::string > flags ) {
latest_clang_results_.ResetWithNewLineAndColumn( line, column );
function< CompletionDatas() > candidates_for_location_functor =
boost::bind( &ClangCompleter::CandidatesForLocationInFile,
boost::ref( *this ),
boost::move( filename ),
line,
column,
boost::move( unsaved_files ),
boost::move( flags ) );
shared_ptr< ClangPackagedTask > clang_packaged_task =
make_shared< ClangPackagedTask >();
clang_packaged_task->completions_task_ =
packaged_task< AsyncCompletions >(
boost::bind( ReturnValueAsShared< std::vector< CompletionData > >,
boost::move( candidates_for_location_functor ) ) );
clang_task_.Set( clang_packaged_task );
}
std::vector< CompletionData > ClangCompleter::SortCandidatesForQuery(
const std::string &query,
const std::vector< CompletionData > &completion_datas ) {
Bitset query_bitset = LetterBitsetFromString( query );
bool query_has_uppercase_letters = any_of( query, is_upper() );
std::vector< const Candidate * > repository_candidates =
candidate_repository_.GetCandidatesForStrings( completion_datas );
std::vector< ResultAnd< CompletionData * > > data_and_results;
for ( uint i = 0; i < repository_candidates.size(); ++i ) {
const Candidate *candidate = repository_candidates[ i ];
if ( !candidate->MatchesQueryBitset( query_bitset ) )
continue;
Result result = candidate->QueryMatchResult( query,
query_has_uppercase_letters );
if ( result.IsSubsequence() ) {
ResultAnd< CompletionData * > data_and_result( &completion_datas[ i ],
result );
data_and_results.push_back( boost::move( data_and_result ) );
}
}
std::sort( data_and_results.begin(), data_and_results.end() );
std::vector< CompletionData > sorted_completion_datas;
sorted_completion_datas.reserve( data_and_results.size() );
foreach ( const ResultAnd< CompletionData * > &data_and_result,
data_and_results ) {
sorted_completion_datas.push_back( *data_and_result.extra_object_ );
}
return sorted_completion_datas;
}
void ClangCompleter::InitThreads() {
int threads_to_create =
std::max( MIN_ASYNC_THREADS,
std::min( MAX_ASYNC_THREADS, thread::hardware_concurrency() ) );
for ( int i = 0; i < threads_to_create; ++i ) {
sorting_threads_.create_thread( bind( &ClangCompleter::SortingThreadMain,
boost::ref( *this ) ) );
}
clang_thread_.reset( new thread( &ClangCompleter::ClangThreadMain,
boost::ref( *this ) ) );
}
void ClangCompleter::ClangThreadMain() {
while ( true ) {
try {
shared_ptr< ClangPackagedTask > task = clang_task_.Get();
bool has_completions_task = task->completions_task_.valid();
if ( has_completions_task )
task->completions_task_();
else
task->parsing_task_();
if ( !has_completions_task ) {
DeleteCaches();
continue;
}
unique_future< AsyncCompletions > future =
task->completions_task_.get_future();
latest_clang_results_.SetCompletionDatas( *future.get() );
{
lock_guard< mutex > lock( clang_data_ready_mutex_ );
clang_data_ready_ = true;
}
clang_data_ready_condition_variable_.notify_all();
} catch ( thread_interrupted & ) {
shared_lock< shared_mutex > lock( time_to_die_mutex_ );
if ( time_to_die_ )
return;
// else do nothing and re-enter the loop
}
}
}
void ClangCompleter::SortingThreadMain() {
while ( true ) {
try {
{
unique_lock< mutex > lock( clang_data_ready_mutex_ );
while ( !clang_data_ready_ ) {
clang_data_ready_condition_variable_.wait( lock );
}
}
shared_ptr< packaged_task< AsyncCompletions > > task =
sorting_task_.Get();
( *task )();
} catch ( thread_interrupted & ) {
shared_lock< shared_mutex > lock( time_to_die_mutex_ );
if ( time_to_die_ )
return;
// else do nothing and re-enter the loop
}
}
}
} // namespace YouCompleteMe

View File

@ -18,18 +18,11 @@
#ifndef CLANGCOMPLETE_H_WLKDU0ZV
#define CLANGCOMPLETE_H_WLKDU0ZV
#include "ConcurrentLatestValue.h"
#include "ConcurrentStack.h"
#include "Future.h"
#include "UnsavedFile.h"
#include "Diagnostic.h"
#include "ClangResultsCache.h"
#include "TranslationUnitStore.h"
#include <boost/utility.hpp>
#include <boost/thread/future.hpp>
#include <boost/thread/mutex.hpp>
#include <boost/unordered_map.hpp>
#include <string>
@ -37,46 +30,26 @@ typedef struct CXTranslationUnitImpl *CXTranslationUnit;
namespace YouCompleteMe {
class CandidateRepository;
class TranslationUnit;
struct CompletionData;
struct Location;
typedef std::vector< CompletionData > CompletionDatas;
typedef boost::shared_ptr< CompletionDatas > AsyncCompletions;
typedef boost::unordered_map < std::string,
boost::shared_ptr <
std::vector< std::string > > > FlagsForFile;
// TODO: document that all filename parameters must be absolute paths
// All filename parameters must be absolute paths.
class ClangCompleter : boost::noncopyable {
public:
ClangCompleter();
~ClangCompleter();
void EnableThreading();
std::vector< Diagnostic > DiagnosticsForFile( const std::string &filename );
bool UpdatingTranslationUnit( const std::string &filename );
// Public because of unit tests (gtest is not very thread-friendly)
void UpdateTranslationUnit(
std::vector< Diagnostic > UpdateTranslationUnit(
const std::string &filename,
const std::vector< UnsavedFile > &unsaved_files,
const std::vector< std::string > &flags );
// NOTE: params are taken by value on purpose! With a C++11 compiler we can
// avoid internal copies if params are taken by value (move ctors FTW)
Future< void > UpdateTranslationUnitAsync(
std::string filename,
std::vector< UnsavedFile > unsaved_files,
std::vector< std::string > flags );
// Public because of unit tests (gtest is not very thread-friendly)
std::vector< CompletionData > CandidatesForLocationInFile(
const std::string &filename,
int line,
@ -84,14 +57,6 @@ public:
const std::vector< UnsavedFile > &unsaved_files,
const std::vector< std::string > &flags );
Future< AsyncCompletions > CandidatesForQueryAndLocationInFileAsync(
const std::string &query,
const std::string &filename,
int line,
int column,
const std::vector< UnsavedFile > &unsaved_files,
const std::vector< std::string > &flags );
Location GetDeclarationLocation(
const std::string &filename,
int line,
@ -106,55 +71,10 @@ public:
const std::vector< UnsavedFile > &unsaved_files,
const std::vector< std::string > &flags );
void DeleteCachesForFileAsync( const std::string &filename );
void DeleteCachesForFile( const std::string &filename );
private:
void DeleteCaches();
// This is basically a union. Only one of the two tasks is set to something
// valid, the other task is invalid. Which one is valid depends on the caller.
struct ClangPackagedTask {
boost::packaged_task< AsyncCompletions > completions_task_;
boost::packaged_task< void > parsing_task_;
};
typedef ConcurrentLatestValue <
boost::shared_ptr <
boost::packaged_task< AsyncCompletions > > > LatestSortingTask;
typedef ConcurrentLatestValue <
boost::shared_ptr< ClangPackagedTask > > LatestClangTask;
typedef ConcurrentStack< std::string > FileCacheDeleteStack;
bool ShouldSkipClangResultCache( const std::string &query,
int line,
int column );
void CreateSortingTask( const std::string &query,
boost::unique_future< AsyncCompletions > &future );
// NOTE: params are taken by value on purpose! With a C++11 compiler we can
// avoid internal copies if params are taken by value (move ctors FTW)
void CreateClangTask(
std::string filename,
int line,
int column,
std::vector< UnsavedFile > unsaved_files,
std::vector< std::string > flags );
std::vector< CompletionData > SortCandidatesForQuery(
const std::string &query,
const std::vector< CompletionData > &completion_datas );
void InitThreads();
void ClangThreadMain();
void SortingThreadMain();
/////////////////////////////
// PRIVATE MEMBER VARIABLES
/////////////////////////////
@ -162,35 +82,6 @@ private:
CXIndex clang_index_;
TranslationUnitStore translation_unit_store_;
CandidateRepository &candidate_repository_;
bool threading_enabled_;
// TODO: use boost.atomic for time_to_die_
bool time_to_die_;
boost::shared_mutex time_to_die_mutex_;
// TODO: use boost.atomic for clang_data_ready_
bool clang_data_ready_;
boost::mutex clang_data_ready_mutex_;
boost::condition_variable clang_data_ready_condition_variable_;
ClangResultsCache latest_clang_results_;
FileCacheDeleteStack file_cache_delete_stack_;
// Unfortunately clang is not thread-safe so we need to be careful when we
// access it. Only one thread at a time is allowed to access any single
// translation unit. Currently we only use one thread to access clang and that
// is the thread represented by clang_thread_.
boost::scoped_ptr< boost::thread > clang_thread_;
boost::thread_group sorting_threads_;
mutable LatestClangTask clang_task_;
mutable LatestSortingTask sorting_task_;
};
} // namespace YouCompleteMe

View File

@ -1,84 +0,0 @@
// Copyright (C) 2011, 2012 Strahinja Val Markovic <val@markovic.io>
//
// This file is part of YouCompleteMe.
//
// YouCompleteMe is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// YouCompleteMe is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with YouCompleteMe. If not, see <http://www.gnu.org/licenses/>.
#ifndef CLANGRESULTSCACHE_H_REUWM3RU
#define CLANGRESULTSCACHE_H_REUWM3RU
#include "CompletionData.h"
#include <vector>
#include <boost/thread/locks.hpp>
#include <boost/thread/shared_mutex.hpp>
#include <boost/function.hpp>
#include <boost/config.hpp>
#include <boost/utility.hpp>
namespace YouCompleteMe {
struct CompletionData;
class ClangResultsCache : boost::noncopyable {
public:
ClangResultsCache() : line_( -1 ), column_( -1 ) {}
bool NewPositionDifferentFromStoredPosition( int new_line, int new_colum )
const;
void ResetWithNewLineAndColumn( int new_line, int new_colum );
void SetCompletionDatas(
const std::vector< CompletionData > new_completions ) {
completion_datas_ = new_completions;
}
#ifndef BOOST_NO_RVALUE_REFERENCES
# ifdef __clang__
# pragma clang diagnostic push
# pragma clang diagnostic ignored "-Wc++98-compat"
# endif //#ifdef __clang__
void SetCompletionDatas( std::vector< CompletionData > &&new_completions ) {
completion_datas_ = new_completions;
}
# ifdef __clang__
# pragma clang diagnostic pop
# endif //#ifdef __clang__
#endif //#ifndef BOOST_NO_RVALUE_REFERENCES
template< typename T >
T OperateOnCompletionDatas(
boost::function< T( const std::vector< CompletionData >& ) > operation )
const {
boost::shared_lock< boost::shared_mutex > reader_lock( access_mutex_ );
return operation( completion_datas_ );
}
private:
int line_;
int column_;
std::vector< CompletionData > completion_datas_;
mutable boost::shared_mutex access_mutex_;
};
} // namespace YouCompleteMe
#endif /* end of include guard: CLANGRESULTSCACHE_H_REUWM3RU */

View File

@ -112,11 +112,8 @@ std::vector< CXUnsavedFile > ToCXUnsavedFiles(
std::vector< CXUnsavedFile > clang_unsaved_files( unsaved_files.size() );
for ( uint i = 0; i < unsaved_files.size(); ++i ) {
X_VERIFY( unsaved_files[ i ].filename_ );
X_VERIFY( unsaved_files[ i ].contents_ );
X_VERIFY( unsaved_files[ i ].length_ );
clang_unsaved_files[ i ].Filename = unsaved_files[ i ].filename_;
clang_unsaved_files[ i ].Contents = unsaved_files[ i ].contents_;
clang_unsaved_files[ i ].Filename = unsaved_files[ i ].filename_.c_str();
clang_unsaved_files[ i ].Contents = unsaved_files[ i ].contents_.c_str();
clang_unsaved_files[ i ].Length = unsaved_files[ i ].length_;
}

View File

@ -37,6 +37,8 @@ std::string CXStringToString( CXString text );
std::vector< CompletionData > ToCompletionDataVector(
CXCodeCompleteResults *results );
// NOTE: CXUnsavedFiles store pointers to data in UnsavedFiles, so UnsavedFiles
// need to outlive CXUnsavedFiles!
std::vector< CXUnsavedFile > ToCXUnsavedFiles(
const std::vector< UnsavedFile > &unsaved_files );

View File

@ -18,20 +18,19 @@
#include "CompilationDatabase.h"
#include "ClangUtils.h"
#include "standard.h"
#include "ReleaseGil.h"
#include <boost/shared_ptr.hpp>
#include <boost/bind.hpp>
#include <boost/make_shared.hpp>
#include <boost/type_traits/remove_pointer.hpp>
#include <boost/thread/locks.hpp>
using boost::bind;
using boost::make_shared;
using boost::packaged_task;
using boost::lock_guard;
using boost::unique_lock;
using boost::try_to_lock_t;
using boost::remove_pointer;
using boost::shared_ptr;
using boost::thread;
using boost::unique_future;
using boost::function;
using boost::mutex;
namespace YouCompleteMe {
@ -39,22 +38,9 @@ typedef shared_ptr <
remove_pointer< CXCompileCommands >::type > CompileCommandsWrap;
void QueryThreadMain( CompilationDatabase::InfoTaskStack &info_task_stack ) {
while ( true ) {
try {
( *info_task_stack.Pop() )();
} catch ( boost::thread_interrupted & ) {
return;
}
}
}
CompilationDatabase::CompilationDatabase(
const std::string &path_to_directory )
: threading_enabled_( false ),
is_loaded_( false ) {
: is_loaded_( false ) {
CXCompilationDatabase_Error status;
compilation_database_ = clang_CompilationDatabase_fromDirectory(
path_to_directory.c_str(),
@ -68,27 +54,26 @@ CompilationDatabase::~CompilationDatabase() {
}
// We need this mostly so that we can not use it in tests. Apparently the
// GoogleTest framework goes apeshit on us if we enable threads by default.
void CompilationDatabase::EnableThreading() {
threading_enabled_ = true;
InitThreads();
}
bool CompilationDatabase::DatabaseSuccessfullyLoaded() {
return is_loaded_;
}
bool CompilationDatabase::AlreadyGettingFlags() {
unique_lock< mutex > lock( compilation_database_mutex_, try_to_lock_t() );
return !lock.owns_lock();
}
CompilationInfoForFile CompilationDatabase::GetCompilationInfoForFile(
const std::string &path_to_file ) {
ReleaseGil unlock;
CompilationInfoForFile info;
if ( !is_loaded_ )
return info;
// TODO: mutex protect calls to getCompileCommands and getDirectory
lock_guard< mutex > lock( compilation_database_mutex_ );
CompileCommandsWrap commands(
clang_CompilationDatabase_getCompileCommands(
@ -120,34 +105,5 @@ CompilationInfoForFile CompilationDatabase::GetCompilationInfoForFile(
return info;
}
Future< AsyncCompilationInfoForFile >
CompilationDatabase::GetCompilationInfoForFileAsync(
const std::string &path_to_file ) {
// TODO: throw exception when threading is not enabled and this is called
if ( !threading_enabled_ )
return Future< AsyncCompilationInfoForFile >();
function< CompilationInfoForFile() > functor =
boost::bind( &CompilationDatabase::GetCompilationInfoForFile,
boost::ref( *this ),
path_to_file );
InfoTask task =
make_shared< packaged_task< AsyncCompilationInfoForFile > >(
bind( ReturnValueAsShared< CompilationInfoForFile >,
functor ) );
unique_future< AsyncCompilationInfoForFile > future = task->get_future();
info_task_stack_.Push( task );
return Future< AsyncCompilationInfoForFile >( boost::move( future ) );
}
void CompilationDatabase::InitThreads() {
info_thread_ = boost::thread( QueryThreadMain,
boost::ref( info_task_stack_ ) );
}
} // namespace YouCompleteMe

View File

@ -18,15 +18,14 @@
#ifndef COMPILATIONDATABASE_H_ZT7MQXPG
#define COMPILATIONDATABASE_H_ZT7MQXPG
#include "Future.h"
#include "ConcurrentStack.h"
#include <vector>
#include <string>
#include <boost/utility.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/thread/mutex.hpp>
#include <clang-c/CXCompilationDatabase.h>
namespace YouCompleteMe {
struct CompilationInfoForFile {
@ -34,9 +33,8 @@ struct CompilationInfoForFile {
std::string compiler_working_dir_;
};
typedef boost::shared_ptr< CompilationInfoForFile >
AsyncCompilationInfoForFile;
// Access to Clang's internal CompilationDatabase. This class is thread-safe.
class CompilationDatabase : boost::noncopyable {
public:
CompilationDatabase( const std::string &path_to_directory );
@ -44,28 +42,20 @@ public:
bool DatabaseSuccessfullyLoaded();
void EnableThreading();
// Returns true when a separate thread is already getting flags; this is
// useful so that the caller doesn't need to block.
bool AlreadyGettingFlags();
// NOTE: Multiple calls to this function from separate threads will be
// serialized since Clang internals are not thread-safe.
CompilationInfoForFile GetCompilationInfoForFile(
const std::string &path_to_file );
Future< AsyncCompilationInfoForFile > GetCompilationInfoForFileAsync(
const std::string &path_to_file );
typedef boost::shared_ptr <
boost::packaged_task< AsyncCompilationInfoForFile > > InfoTask;
typedef ConcurrentStack< InfoTask > InfoTaskStack;
private:
void InitThreads();
bool threading_enabled_;
bool is_loaded_;
CXCompilationDatabase compilation_database_;
boost::thread info_thread_;
InfoTaskStack info_task_stack_;
boost::mutex compilation_database_mutex_;
};
} // namespace YouCompleteMe

View File

@ -92,25 +92,11 @@ void TranslationUnit::Destroy() {
std::vector< Diagnostic > TranslationUnit::LatestDiagnostics() {
std::vector< Diagnostic > diagnostics;
if ( !clang_translation_unit_ )
return diagnostics;
return std::vector< Diagnostic >();
unique_lock< mutex > lock( diagnostics_mutex_ );
// We don't need the latest diags after we return them once so we swap the
// internal data with a new, empty diag vector. This vector is then returned
// and on C++11 compilers a move ctor is invoked, thus no copy is created.
// Theoretically, just returning the value of a
// [boost::|std::]move(latest_diagnostics_) call _should_ leave the
// latest_diagnostics_ vector in an emtpy, valid state but I'm not going to
// rely on that. I just had to look this up in the standard to be sure, and
// future readers of this code (myself included) should not be forced to do
// that to understand what the hell is going on.
std::swap( latest_diagnostics_, diagnostics );
return diagnostics;
return latest_diagnostics_;
}
@ -125,12 +111,15 @@ bool TranslationUnit::IsCurrentlyUpdating() const {
}
void TranslationUnit::Reparse(
std::vector< Diagnostic > TranslationUnit::Reparse(
const std::vector< UnsavedFile > &unsaved_files ) {
std::vector< CXUnsavedFile > cxunsaved_files =
ToCXUnsavedFiles( unsaved_files );
Reparse( cxunsaved_files );
unique_lock< mutex > lock( diagnostics_mutex_ );
return latest_diagnostics_;
}
@ -158,6 +147,7 @@ std::vector< CompletionData > TranslationUnit::CandidatesForLocation(
ToCXUnsavedFiles( unsaved_files );
const CXUnsavedFile *unsaved = cxunsaved_files.size() > 0
? &cxunsaved_files[ 0 ] : NULL;
// codeCompleteAt reparses the TU if the underlying source file has changed on
// disk since the last time the TU was updated and there are no unsaved files.
// If there are unsaved files, then codeCompleteAt will parse the in-memory
@ -254,6 +244,7 @@ void TranslationUnit::Reparse( std::vector< CXUnsavedFile > &unsaved_files,
return;
CXUnsavedFile *unsaved = unsaved_files.size() > 0
? &unsaved_files[ 0 ] : NULL;
failure = clang_reparseTranslationUnit( clang_translation_unit_,
unsaved_files.size(),
unsaved,

View File

@ -18,8 +18,6 @@
#ifndef TRANSLATIONUNIT_H_XQ7I6SVA
#define TRANSLATIONUNIT_H_XQ7I6SVA
#include "ConcurrentLatestValue.h"
#include "Future.h"
#include "UnsavedFile.h"
#include "Diagnostic.h"
#include "Location.h"
@ -58,7 +56,8 @@ public:
bool IsCurrentlyUpdating() const;
void Reparse( const std::vector< UnsavedFile > &unsaved_files );
std::vector< Diagnostic > Reparse(
const std::vector< UnsavedFile > &unsaved_files );
void ReparseForIndexing( const std::vector< UnsavedFile > &unsaved_files );

View File

@ -21,6 +21,7 @@
#include "exceptions.h"
#include <boost/thread/locks.hpp>
#include <boost/make_shared.hpp>
#include <boost/functional/hash.hpp>
using boost::lock_guard;
@ -38,14 +39,17 @@ std::size_t HashForFlags( const std::vector< std::string > &flags ) {
} // unnamed namespace
TranslationUnitStore::TranslationUnitStore( CXIndex clang_index )
: clang_index_( clang_index ) {
}
TranslationUnitStore::~TranslationUnitStore() {
RemoveAll();
}
shared_ptr< TranslationUnit > TranslationUnitStore::GetOrCreate(
const std::string &filename,
const std::vector< UnsavedFile > &unsaved_files,
@ -104,24 +108,28 @@ shared_ptr< TranslationUnit > TranslationUnitStore::GetOrCreate(
return unit;
}
shared_ptr< TranslationUnit > TranslationUnitStore::Get(
const std::string &filename ) {
lock_guard< mutex > lock( filename_to_translation_unit_and_flags_mutex_ );
return GetNoLock( filename );
}
bool TranslationUnitStore::Remove( const std::string &filename ) {
lock_guard< mutex > lock( filename_to_translation_unit_and_flags_mutex_ );
Erase( filename_to_flags_hash_, filename );
return Erase( filename_to_translation_unit_, filename );
}
void TranslationUnitStore::RemoveAll() {
lock_guard< mutex > lock( filename_to_translation_unit_and_flags_mutex_ );
filename_to_translation_unit_.clear();
filename_to_flags_hash_.clear();
}
shared_ptr< TranslationUnit > TranslationUnitStore::GetNoLock(
const std::string &filename ) {
return FindWithDefault( filename_to_translation_unit_,

View File

@ -18,13 +18,13 @@
#ifndef UNSAVEDFILE_H_0GIYZQL4
#define UNSAVEDFILE_H_0GIYZQL4
#include <cstddef>
#include <string>
struct UnsavedFile {
UnsavedFile() : filename_( NULL ), contents_( NULL ), length_( 0 ) {}
UnsavedFile() : filename_( "" ), contents_( "" ), length_( 0 ) {}
const char *filename_;
const char *contents_;
std::string filename_;
std::string contents_;
unsigned long length_;
// We need this to be able to export this struct to Python via Boost.Python's

View File

@ -1,79 +0,0 @@
// Copyright (C) 2011, 2012 Strahinja Val Markovic <val@markovic.io>
//
// This file is part of YouCompleteMe.
//
// YouCompleteMe is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// YouCompleteMe is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with YouCompleteMe. If not, see <http://www.gnu.org/licenses/>.
#ifndef CONCURRENTLATESTVALUE_H_SYF1JPPG
#define CONCURRENTLATESTVALUE_H_SYF1JPPG
#include <boost/thread.hpp>
#include <boost/utility.hpp>
namespace YouCompleteMe {
// This is is basically a multi-consumer single-producer queue, only with the
// twist that we only care about the latest value set. So the GUI thread is the
// setter, and the worker threads are the workers. The workers wait in line on
// the condition variable and when the setter sets a value, a worker is chosen
// to consume it.
//
// The point is that we always want to have one "fresh" worker thread ready to
// work on our latest value. If a newer value is set, then we don't care what
// happens to the old values.
//
// This implementation is mutex-based and is not lock-free. Normally using a
// lock-free data structure makes more sense, but since the GUI thread goes
// through VimL and Python on every keystroke, there's really no point. Those 5
// nanoseconds it takes to lock a mutex are laughably negligible compared to the
// VimL/Python overhead.
template <typename T>
class ConcurrentLatestValue : boost::noncopyable {
public:
ConcurrentLatestValue() : empty_( true ) {}
void Set( const T &data ) {
{
boost::unique_lock< boost::mutex > lock( mutex_ );
latest_ = data;
empty_ = false;
}
condition_variable_.notify_one();
}
T Get() {
boost::unique_lock< boost::mutex > lock( mutex_ );
while ( empty_ ) {
condition_variable_.wait( lock );
}
empty_ = true;
return latest_;
}
private:
T latest_;
bool empty_;
boost::mutex mutex_;
boost::condition_variable condition_variable_;
};
} // namespace YouCompleteMe
#endif /* end of include guard: CONCURRENTLATESTVALUE_H_SYF1JPPG */

View File

@ -1,89 +0,0 @@
// Copyright (C) 2011, 2012 Strahinja Val Markovic <val@markovic.io>
//
// This file is part of YouCompleteMe.
//
// YouCompleteMe is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// YouCompleteMe is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with YouCompleteMe. If not, see <http://www.gnu.org/licenses/>.
#ifndef CONCURRENTSTACK_H_TGI0GOR6
#define CONCURRENTSTACK_H_TGI0GOR6
#include <boost/thread.hpp>
#include <boost/utility.hpp>
#include <stack>
namespace YouCompleteMe {
template <typename T>
class ConcurrentStack : boost::noncopyable {
public:
void Push( const T &data ) {
{
boost::unique_lock< boost::mutex > lock( mutex_ );
stack_.push( data );
}
condition_variable_.notify_one();
}
T Pop() {
boost::unique_lock< boost::mutex > lock( mutex_ );
while ( stack_.empty() ) {
condition_variable_.wait( lock );
}
T top = stack_.top();
stack_.pop();
return top;
}
// Gets all the items from the stack and appends them to the input vector.
// Does not wait for the stack to get items; if the stack is empty, returns
// false and does not touch the input vector.
bool PopAllNoWait( std::vector< T > &items ) {
boost::unique_lock< boost::mutex > lock( mutex_ );
if ( stack_.empty() )
return false;
int num_items = stack_.size();
items.reserve( num_items + items.size() );
for ( int i = 0; i < num_items; ++i ) {
items.push_back( stack_.top() );
stack_.pop();
}
return true;
}
bool Empty() {
boost::unique_lock< boost::mutex > lock( mutex_ );
return stack_.empty();
}
private:
std::stack< T > stack_;
boost::mutex mutex_;
boost::condition_variable condition_variable_;
};
} // namespace YouCompleteMe
#endif /* end of include guard: CONCURRENTSTACK_H_TGI0GOR6 */

View File

@ -1,78 +0,0 @@
// Copyright (C) 2011, 2012 Strahinja Val Markovic <val@markovic.io>
//
// This file is part of YouCompleteMe.
//
// YouCompleteMe is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// YouCompleteMe is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with YouCompleteMe. If not, see <http://www.gnu.org/licenses/>.
#ifndef FUTURE_H_NR1U6MZS
#define FUTURE_H_NR1U6MZS
#include <boost/thread.hpp>
#include <boost/make_shared.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/function.hpp>
namespace YouCompleteMe {
class Result;
typedef boost::shared_ptr< boost::packaged_task< void > > VoidTask;
template< typename T >
boost::shared_ptr< T > ReturnValueAsShared(
boost::function< T() > func ) {
return boost::make_shared< T >( func() );
}
template< typename T >
class Future {
public:
Future() {};
Future( boost::shared_future< T > future )
: future_( boost::move( future ) ) {}
bool ResultsReady() {
// It's OK to return true since GetResults will just return a
// default-constructed value if the future_ is uninitialized. If we don't
// return true for this case, any loop waiting on ResultsReady will wait
// forever.
if ( future_.get_state() == boost::future_state::uninitialized )
return true;
return future_.is_ready();
}
void Wait() {
future_.wait();
}
T GetResults() {
try {
return future_.get();
} catch ( boost::future_uninitialized & ) {
// Do nothing and return a T()
}
return T();
}
private:
boost::shared_future< T > future_;
};
} // namespace YouCompleteMe
#endif /* end of include guard: FUTURE_H_NR1U6MZS */

View File

@ -22,63 +22,18 @@
#include "IdentifierUtils.h"
#include "Result.h"
#include "Utils.h"
#include "ReleaseGil.h"
#include <boost/bind.hpp>
#include <boost/make_shared.hpp>
#include <algorithm>
using boost::packaged_task;
using boost::unique_future;
using boost::shared_ptr;
using boost::thread;
namespace YouCompleteMe {
typedef boost::function< std::vector< std::string >() >
FunctionReturnsStringVector;
extern const unsigned int MAX_ASYNC_THREADS = 4;
extern const unsigned int MIN_ASYNC_THREADS = 2;
namespace {
void QueryThreadMain(
IdentifierCompleter::LatestQueryTask &latest_query_task ) {
while ( true ) {
try {
( *latest_query_task.Get() )();
} catch ( boost::thread_interrupted & ) {
return;
}
}
}
void BufferIdentifiersThreadMain(
IdentifierCompleter::BufferIdentifiersTaskStack
&buffer_identifiers_task_stack ) {
while ( true ) {
try {
( *buffer_identifiers_task_stack.Pop() )();
} catch ( boost::thread_interrupted & ) {
return;
}
}
}
} // unnamed namespace
IdentifierCompleter::IdentifierCompleter()
: threading_enabled_( false ) {
}
IdentifierCompleter::IdentifierCompleter() {}
IdentifierCompleter::IdentifierCompleter(
const std::vector< std::string > &candidates )
: threading_enabled_( false ) {
const std::vector< std::string > &candidates ) {
identifier_database_.AddIdentifiers( candidates, "", "" );
}
@ -86,35 +41,16 @@ IdentifierCompleter::IdentifierCompleter(
IdentifierCompleter::IdentifierCompleter(
const std::vector< std::string > &candidates,
const std::string &filetype,
const std::string &filepath )
: threading_enabled_( false ) {
const std::string &filepath ) {
identifier_database_.AddIdentifiers( candidates, filetype, filepath );
}
IdentifierCompleter::~IdentifierCompleter() {
query_threads_.interrupt_all();
query_threads_.join_all();
if ( buffer_identifiers_thread_ ) {
buffer_identifiers_thread_->interrupt();
buffer_identifiers_thread_->join();
}
}
// We need this mostly so that we can not use it in tests. Apparently the
// GoogleTest framework goes apeshit on us if we enable threads by default.
void IdentifierCompleter::EnableThreading() {
threading_enabled_ = true;
InitThreads();
}
void IdentifierCompleter::AddIdentifiersToDatabase(
const std::vector< std::string > &new_candidates,
const std::string &filetype,
const std::string &filepath ) {
ReleaseGil unlock;
identifier_database_.AddIdentifiers( new_candidates,
filetype,
filepath );
@ -123,6 +59,7 @@ void IdentifierCompleter::AddIdentifiersToDatabase(
void IdentifierCompleter::AddIdentifiersToDatabaseFromTagFiles(
const std::vector< std::string > &absolute_paths_to_tag_files ) {
ReleaseGil unlock;
foreach( const std::string & path, absolute_paths_to_tag_files ) {
identifier_database_.AddIdentifiers(
ExtractIdentifiersFromTagsFile( path ) );
@ -130,27 +67,12 @@ void IdentifierCompleter::AddIdentifiersToDatabaseFromTagFiles(
}
void IdentifierCompleter::AddIdentifiersToDatabaseFromTagFilesAsync(
std::vector< std::string > absolute_paths_to_tag_files ) {
// TODO: throw exception when threading is not enabled and this is called
if ( !threading_enabled_ )
return;
boost::function< void() > functor =
boost::bind( &IdentifierCompleter::AddIdentifiersToDatabaseFromTagFiles,
boost::ref( *this ),
boost::move( absolute_paths_to_tag_files ) );
buffer_identifiers_task_stack_.Push(
boost::make_shared< packaged_task< void > >( boost::move( functor ) ) );
}
void IdentifierCompleter::AddIdentifiersToDatabaseFromBuffer(
const std::string &buffer_contents,
const std::string &filetype,
const std::string &filepath,
bool collect_from_comments_and_strings ) {
ReleaseGil unlock;
identifier_database_.ClearCandidatesStoredForFile( filetype, filepath );
std::string new_contents =
@ -165,28 +87,6 @@ void IdentifierCompleter::AddIdentifiersToDatabaseFromBuffer(
}
void IdentifierCompleter::AddIdentifiersToDatabaseFromBufferAsync(
std::string buffer_contents,
std::string filetype,
std::string filepath,
bool collect_from_comments_and_strings ) {
// TODO: throw exception when threading is not enabled and this is called
if ( !threading_enabled_ )
return;
boost::function< void() > functor =
boost::bind( &IdentifierCompleter::AddIdentifiersToDatabaseFromBuffer,
boost::ref( *this ),
boost::move( buffer_contents ),
boost::move( filetype ),
boost::move( filepath ),
collect_from_comments_and_strings );
buffer_identifiers_task_stack_.Push(
boost::make_shared< packaged_task< void > >( boost::move( functor ) ) );
}
std::vector< std::string > IdentifierCompleter::CandidatesForQuery(
const std::string &query ) const {
return CandidatesForQueryAndType( query, "" );
@ -196,6 +96,7 @@ std::vector< std::string > IdentifierCompleter::CandidatesForQuery(
std::vector< std::string > IdentifierCompleter::CandidatesForQueryAndType(
const std::string &query,
const std::string &filetype ) const {
ReleaseGil unlock;
std::vector< Result > results;
identifier_database_.ResultsForQueryAndType( query, filetype, results );
@ -209,46 +110,4 @@ std::vector< std::string > IdentifierCompleter::CandidatesForQueryAndType(
}
Future< AsyncResults > IdentifierCompleter::CandidatesForQueryAndTypeAsync(
const std::string &query,
const std::string &filetype ) const {
// TODO: throw exception when threading is not enabled and this is called
if ( !threading_enabled_ )
return Future< AsyncResults >();
FunctionReturnsStringVector functor =
boost::bind( &IdentifierCompleter::CandidatesForQueryAndType,
boost::cref( *this ),
query,
filetype );
QueryTask task =
boost::make_shared< packaged_task< AsyncResults > >(
boost::bind( ReturnValueAsShared< std::vector< std::string > >,
boost::move( functor ) ) );
unique_future< AsyncResults > future = task->get_future();
latest_query_task_.Set( task );
return Future< AsyncResults >( boost::move( future ) );
}
void IdentifierCompleter::InitThreads() {
int query_threads_to_create =
std::max( MIN_ASYNC_THREADS,
std::min( MAX_ASYNC_THREADS, thread::hardware_concurrency() ) );
for ( int i = 0; i < query_threads_to_create; ++i ) {
query_threads_.create_thread(
boost::bind( QueryThreadMain,
boost::ref( latest_query_task_ ) ) );
}
buffer_identifiers_thread_.reset(
new boost::thread( BufferIdentifiersThreadMain,
boost::ref( buffer_identifiers_task_stack_ ) ) );
}
} // namespace YouCompleteMe

View File

@ -18,10 +18,7 @@
#ifndef COMPLETER_H_7AR4UGXE
#define COMPLETER_H_7AR4UGXE
#include "ConcurrentLatestValue.h"
#include "ConcurrentStack.h"
#include "IdentifierDatabase.h"
#include "Future.h"
#include <boost/utility.hpp>
#include <boost/unordered_map.hpp>
@ -36,8 +33,6 @@ namespace YouCompleteMe {
class Candidate;
typedef boost::shared_ptr< std::vector< std::string > > AsyncResults;
class IdentifierCompleter : boost::noncopyable {
public:
@ -47,10 +42,6 @@ public:
const std::string &filetype,
const std::string &filepath );
~IdentifierCompleter();
void EnableThreading();
void AddIdentifiersToDatabase(
const std::vector< std::string > &new_candidates,
const std::string &filetype,
@ -59,25 +50,12 @@ public:
void AddIdentifiersToDatabaseFromTagFiles(
const std::vector< std::string > &absolute_paths_to_tag_files );
// NOTE: params are taken by value on purpose!
void AddIdentifiersToDatabaseFromTagFilesAsync(
std::vector< std::string > absolute_paths_to_tag_files );
void AddIdentifiersToDatabaseFromBuffer(
const std::string &buffer_contents,
const std::string &filetype,
const std::string &filepath,
bool collect_from_comments_and_strings );
// NOTE: params are taken by value on purpose! With a C++11 compiler we can
// avoid an expensive copy of buffer_contents if the param is taken by value
// (move ctors FTW)
void AddIdentifiersToDatabaseFromBufferAsync(
std::string buffer_contents,
std::string filetype,
std::string filepath,
bool collect_from_comments_and_strings );
// Only provided for tests!
std::vector< std::string > CandidatesForQuery(
const std::string &query ) const;
@ -86,37 +64,13 @@ public:
const std::string &query,
const std::string &filetype ) const;
Future< AsyncResults > CandidatesForQueryAndTypeAsync(
const std::string &query,
const std::string &filetype ) const;
typedef boost::shared_ptr <
boost::packaged_task< AsyncResults > > QueryTask;
typedef ConcurrentLatestValue< QueryTask > LatestQueryTask;
typedef ConcurrentStack< VoidTask > BufferIdentifiersTaskStack;
private:
void InitThreads();
/////////////////////////////
// PRIVATE MEMBER VARIABLES
/////////////////////////////
IdentifierDatabase identifier_database_;
bool threading_enabled_;
boost::thread_group query_threads_;
boost::scoped_ptr< boost::thread > buffer_identifiers_thread_;
mutable LatestQueryTask latest_query_task_;
BufferIdentifiersTaskStack buffer_identifiers_task_stack_;
};
} // namespace YouCompleteMe

View File

@ -20,6 +20,8 @@
#include "Result.h"
#include "Candidate.h"
#include "CandidateRepository.h"
#include "ReleaseGil.h"
#include <boost/algorithm/string.hpp>
#include <boost/algorithm/cxx11/any_of.hpp>
#include <vector>
@ -27,6 +29,7 @@
using boost::algorithm::any_of;
using boost::algorithm::is_upper;
using boost::python::len;
using boost::python::str;
using boost::python::extract;
using boost::python::object;
typedef boost::python::list pylist;
@ -35,6 +38,13 @@ namespace YouCompleteMe {
namespace {
std::string GetUtf8String( const boost::python::object &string_or_unicode ) {
extract< std::string > to_string( string_or_unicode );
if ( to_string.check() )
return to_string();
return extract< std::string >( str( string_or_unicode ).encode( "utf8" ) );
}
std::vector< const Candidate * > CandidatesFromObjectList(
const pylist &candidates,
const std::string &candidate_property ) {
@ -44,10 +54,10 @@ std::vector< const Candidate * > CandidatesFromObjectList(
for ( int i = 0; i < num_candidates; ++i ) {
if ( candidate_property.empty() ) {
candidate_strings.push_back( extract< std::string >( candidates[ i ] ) );
candidate_strings.push_back( GetUtf8String( candidates[ i ] ) );
} else {
object holder = extract< object >( candidates[ i ] );
candidate_strings.push_back( extract< std::string >(
candidate_strings.push_back( GetUtf8String(
holder[ candidate_property.c_str() ] ) );
}
}
@ -68,15 +78,16 @@ boost::python::list FilterAndSortCandidates(
return candidates;
}
int num_candidates = len( candidates );
std::vector< const Candidate * > repository_candidates =
CandidatesFromObjectList( candidates, candidate_property );
std::vector< ResultAnd< int > > object_and_results;
{
ReleaseGil unlock;
Bitset query_bitset = LetterBitsetFromString( query );
bool query_has_uppercase_letters = any_of( query, is_upper() );
int num_candidates = len( candidates );
std::vector< ResultAnd< int > > object_and_results;
for ( int i = 0; i < num_candidates; ++i ) {
const Candidate *candidate = repository_candidates[ i ];
@ -93,6 +104,7 @@ boost::python::list FilterAndSortCandidates(
}
std::sort( object_and_results.begin(), object_and_results.end() );
}
foreach ( const ResultAnd< int > &object_and_result,
object_and_results ) {

View File

@ -1,4 +1,4 @@
// Copyright (C) 2011, 2012 Strahinja Val Markovic <val@markovic.io>
// Copyright (C) 2013 Strahinja Val Markovic <val@markovic.io>
//
// This file is part of YouCompleteMe.
//
@ -15,29 +15,28 @@
// You should have received a copy of the GNU General Public License
// along with YouCompleteMe. If not, see <http://www.gnu.org/licenses/>.
#include "ClangResultsCache.h"
#include "standard.h"
#ifndef RELEASEGIL_H_RDIEBSQ1
#define RELEASEGIL_H_RDIEBSQ1
using boost::shared_mutex;
using boost::shared_lock;
using boost::unique_lock;
#include <boost/python.hpp>
namespace YouCompleteMe {
bool ClangResultsCache::NewPositionDifferentFromStoredPosition( int new_line,
int new_colum )
const {
shared_lock< shared_mutex > reader_lock( access_mutex_ );
return line_ != new_line || column_ != new_colum;
class ReleaseGil {
public:
ReleaseGil() {
thread_state_ = PyEval_SaveThread();
}
void ClangResultsCache::ResetWithNewLineAndColumn( int new_line,
int new_colum ) {
unique_lock< shared_mutex > reader_lock( access_mutex_ );
line_ = new_line;
column_ = new_colum;
completion_datas_.clear();
~ReleaseGil() {
PyEval_RestoreThread( thread_state_ );
}
private:
PyThreadState *thread_state_;
};
} // namespace YouCompleteMe
#endif /* end of include guard: RELEASEGIL_H_RDIEBSQ1 */

View File

@ -88,6 +88,7 @@ private:
template< class T >
struct ResultAnd {
// TODO: Swap the order of these parameters
ResultAnd( T extra_object, const Result &result )
: extra_object_( extra_object ), result_( result ) {}

View File

@ -28,7 +28,7 @@ endif()
add_subdirectory( gmock )
include_directories(
${ycm_core_SOURCE_DIR}
${ycm_support_libs_SOURCE_DIR}
)
include_directories(
@ -67,8 +67,9 @@ add_executable( ${PROJECT_NAME}
)
target_link_libraries( ${PROJECT_NAME}
ycm_core
gmock_main )
${SERVER_LIB}
${CLIENT_LIB}
gmock )
if ( NOT CMAKE_GENERATOR_IS_XCODE )

View File

@ -43,22 +43,21 @@ TEST( ClangCompleterTest, CandidatesForLocationInFile ) {
}
TEST( ClangCompleterTest, CandidatesForQueryAndLocationInFileAsync ) {
TEST( ClangCompleterTest, GetDefinitionLocation ) {
ClangCompleter completer;
completer.EnableThreading();
std::string filename = PathToTestFile( "basic.cpp" ).string();
Future< AsyncCompletions > completions_future =
completer.CandidatesForQueryAndLocationInFileAsync(
"",
PathToTestFile( "basic.cpp" ).string(),
11,
7,
// Clang operates on the reasonable assumption that line and column numbers
// are 1-based.
Location actual_location =
completer.GetDefinitionLocation(
filename,
9,
3,
std::vector< UnsavedFile >(),
std::vector< std::string >() );
completions_future.Wait();
EXPECT_TRUE( !completions_future.GetResults()->empty() );
EXPECT_EQ( Location( filename, 1, 8 ), actual_location );
}
} // namespace YouCompleteMe

View File

@ -217,6 +217,18 @@ TEST( IdentifierCompleterTest, ShorterAndLowercaseWins ) {
"STDIN_FILENO" ) );
}
TEST( IdentifierCompleterTest, AddIdentifiersToDatabaseFromBufferWorks ) {
IdentifierCompleter completer;
completer.AddIdentifiersToDatabaseFromBuffer( "foo foogoo ba",
"foo",
"/foo/bar",
false );
EXPECT_THAT( completer.CandidatesForQueryAndType( "oo", "foo" ),
ElementsAre( "foo",
"foogoo" ) );
}
TEST( IdentifierCompleterTest, TagsEndToEndWorks ) {
IdentifierCompleter completer;
std::vector< std::string > tag_files;

13
cpp/ycm/tests/main.cpp Normal file
View File

@ -0,0 +1,13 @@
#include "gtest/gtest.h"
#include "gmock/gmock.h"
#include <boost/python.hpp>
int main( int argc, char **argv ) {
Py_Initialize();
// Necessary because of usage of the ReleaseGil class
PyEval_InitThreads();
testing::InitGoogleMock(&argc, argv);
return RUN_ALL_TESTS();
}

View File

@ -2,7 +2,7 @@ struct Foo {
int x;
int y;
char c;
}
};
int main()
{

26
cpp/ycm/versioning.cpp Normal file
View File

@ -0,0 +1,26 @@
// Copyright (C) 2013 Strahinja Val Markovic <val@markovic.io>
//
// This file is part of YouCompleteMe.
//
// YouCompleteMe is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// YouCompleteMe is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with YouCompleteMe. If not, see <http://www.gnu.org/licenses/>.
namespace YouCompleteMe {
int YcmCoreVersion() {
// We increment this every time when we want to force users to recompile
// ycm_core.
return 7;
}
} // namespace YouCompleteMe

22
cpp/ycm/versioning.h Normal file
View File

@ -0,0 +1,22 @@
// Copyright (C) 2013 Strahinja Val Markovic <val@markovic.io>
//
// This file is part of YouCompleteMe.
//
// YouCompleteMe is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// YouCompleteMe is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with YouCompleteMe. If not, see <http://www.gnu.org/licenses/>.
namespace YouCompleteMe {
int YcmCoreVersion();
} // namespace YouCompleteMe

View File

@ -0,0 +1,44 @@
// Copyright (C) 2013 Strahinja Val Markovic <val@markovic.io>
//
// This file is part of YouCompleteMe.
//
// YouCompleteMe is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// YouCompleteMe is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with YouCompleteMe. If not, see <http://www.gnu.org/licenses/>.
#include "IdentifierCompleter.h"
#include "PythonSupport.h"
#include "versioning.h"
#include <boost/python.hpp>
#include <boost/utility.hpp>
BOOST_PYTHON_MODULE(ycm_client_support)
{
using namespace boost::python;
using namespace YouCompleteMe;
// Necessary because of usage of the ReleaseGil class
PyEval_InitThreads();
def( "FilterAndSortCandidates", FilterAndSortCandidates );
def( "YcmCoreVersion", YcmCoreVersion );
}
// Boost.Thread forces us to implement this.
// We don't use any thread-specific (local) storage so it's fine to implement
// this as an empty function.
namespace boost {
void tss_cleanup_implemented() {}
};

View File

@ -17,7 +17,7 @@
#include "IdentifierCompleter.h"
#include "PythonSupport.h"
#include "Future.h"
#include "versioning.h"
#ifdef USE_CLANG_COMPLETER
# include "ClangCompleter.h"
@ -33,8 +33,7 @@
#include <boost/utility.hpp>
#include <boost/python/suite/indexing/vector_indexing_suite.hpp>
bool HasClangSupport()
{
bool HasClangSupport() {
#ifdef USE_CLANG_COMPLETER
return true;
#else
@ -42,61 +41,37 @@ bool HasClangSupport()
#endif // USE_CLANG_COMPLETER
}
int YcmCoreVersion()
{
// We increment this every time when we want to force users to recompile
// ycm_core.
return 4;
}
BOOST_PYTHON_MODULE(ycm_core)
{
using namespace boost::python;
using namespace YouCompleteMe;
// Necessary because of usage of the ReleaseGil class
PyEval_InitThreads();
def( "HasClangSupport", HasClangSupport );
def( "FilterAndSortCandidates", FilterAndSortCandidates );
def( "YcmCoreVersion", YcmCoreVersion );
class_< IdentifierCompleter, boost::noncopyable >( "IdentifierCompleter" )
.def( "EnableThreading", &IdentifierCompleter::EnableThreading )
.def( "AddIdentifiersToDatabase",
&IdentifierCompleter::AddIdentifiersToDatabase )
.def( "AddIdentifiersToDatabaseFromTagFilesAsync",
&IdentifierCompleter::AddIdentifiersToDatabaseFromTagFilesAsync )
.def( "AddIdentifiersToDatabaseFromBufferAsync",
&IdentifierCompleter::AddIdentifiersToDatabaseFromBufferAsync )
.def( "CandidatesForQueryAndTypeAsync",
&IdentifierCompleter::CandidatesForQueryAndTypeAsync );
.def( "AddIdentifiersToDatabaseFromTagFiles",
&IdentifierCompleter::AddIdentifiersToDatabaseFromTagFiles )
.def( "AddIdentifiersToDatabaseFromBuffer",
&IdentifierCompleter::AddIdentifiersToDatabaseFromBuffer )
.def( "CandidatesForQueryAndType",
&IdentifierCompleter::CandidatesForQueryAndType );
// TODO: rename these *Vec classes to *Vector; don't forget the python file
class_< std::vector< std::string >,
boost::shared_ptr< std::vector< std::string > > >( "StringVec" )
.def( vector_indexing_suite< std::vector< std::string > >() );
class_< Future< AsyncResults > >( "FutureResults" )
.def( "ResultsReady", &Future< AsyncResults >::ResultsReady )
.def( "GetResults", &Future< AsyncResults >::GetResults );
class_< Future< void > >( "FutureVoid" )
.def( "ResultsReady", &Future< void >::ResultsReady )
.def( "GetResults", &Future< void >::GetResults );
#ifdef USE_CLANG_COMPLETER
def( "ClangVersion", ClangVersion );
class_< Future< AsyncCompletions > >( "FutureCompletions" )
.def( "ResultsReady", &Future< AsyncCompletions >::ResultsReady )
.def( "GetResults", &Future< AsyncCompletions >::GetResults );
class_< Future< AsyncCompilationInfoForFile > >(
"FutureCompilationInfoForFile" )
.def( "ResultsReady",
&Future< AsyncCompilationInfoForFile >::ResultsReady )
.def( "GetResults",
&Future< AsyncCompilationInfoForFile >::GetResults );
// CAREFUL HERE! For filename and contents we are referring directly to
// Python-allocated and -managed memory since we are accepting pointers to
// data members of python objects. We need to ensure that those objects
@ -116,17 +91,13 @@ BOOST_PYTHON_MODULE(ycm_core)
.def( vector_indexing_suite< std::vector< UnsavedFile > >() );
class_< ClangCompleter, boost::noncopyable >( "ClangCompleter" )
.def( "EnableThreading", &ClangCompleter::EnableThreading )
.def( "DiagnosticsForFile", &ClangCompleter::DiagnosticsForFile )
.def( "GetDeclarationLocation", &ClangCompleter::GetDeclarationLocation )
.def( "GetDefinitionLocation", &ClangCompleter::GetDefinitionLocation )
.def( "DeleteCachesForFileAsync",
&ClangCompleter::DeleteCachesForFileAsync )
.def( "DeleteCachesForFile", &ClangCompleter::DeleteCachesForFile )
.def( "UpdatingTranslationUnit", &ClangCompleter::UpdatingTranslationUnit )
.def( "UpdateTranslationUnitAsync",
&ClangCompleter::UpdateTranslationUnitAsync )
.def( "CandidatesForQueryAndLocationInFileAsync",
&ClangCompleter::CandidatesForQueryAndLocationInFileAsync );
.def( "UpdateTranslationUnit", &ClangCompleter::UpdateTranslationUnit )
.def( "CandidatesForLocationInFile",
&ClangCompleter::CandidatesForLocationInFile );
class_< CompletionData >( "CompletionData" )
.def( "TextToInsertInBuffer", &CompletionData::TextToInsertInBuffer )
@ -160,13 +131,12 @@ BOOST_PYTHON_MODULE(ycm_core)
class_< CompilationDatabase, boost::noncopyable >(
"CompilationDatabase", init< std::string >() )
.def( "EnableThreading", &CompilationDatabase::EnableThreading )
.def( "DatabaseSuccessfullyLoaded",
&CompilationDatabase::DatabaseSuccessfullyLoaded )
.def( "AlreadyGettingFlags",
&CompilationDatabase::AlreadyGettingFlags )
.def( "GetCompilationInfoForFile",
&CompilationDatabase::GetCompilationInfoForFile )
.def( "GetCompilationInfoForFileAsync",
&CompilationDatabase::GetCompilationInfoForFileAsync );
&CompilationDatabase::GetCompilationInfoForFile );
class_< CompilationInfoForFile,
boost::shared_ptr< CompilationInfoForFile > >(

View File

@ -1,4 +1,4 @@
*youcompleteme* YouCompleteMe: a code-completion engine for Vim
*youcompleteme.txt* YouCompleteMe: a code-completion engine for Vim
===============================================================================
Contents ~
@ -10,20 +10,22 @@ Contents ~
5. Full Installation Guide |youcompleteme-full-installation-guide|
6. User Guide |youcompleteme-user-guide|
1. General Usage |youcompleteme-general-usage|
2. Completion string ranking |youcompleteme-completion-string-ranking|
3. General Semantic Completion Engine Usage |youcompleteme-general-semantic-completion-engine-usage|
4. C-family Semantic Completion Engine Usage |youcompleteme-c-family-semantic-completion-engine-usage|
5. Python semantic completion |youcompleteme-python-semantic-completion|
6. C# semantic completion |youcompleteme-c-semantic-completion|
7. Semantic completion for other languages |youcompleteme-semantic-completion-for-other-languages|
8. Writing New Semantic Completers |youcompleteme-writing-new-semantic-completers|
9. Syntastic integration |youcompleteme-syntastic-integration|
2. Client-server architecture |youcompleteme-client-server-architecture|
3. Completion string ranking |youcompleteme-completion-string-ranking|
4. General Semantic Completion Engine Usage |youcompleteme-general-semantic-completion-engine-usage|
5. C-family Semantic Completion Engine Usage |youcompleteme-c-family-semantic-completion-engine-usage|
6. Python semantic completion |youcompleteme-python-semantic-completion|
7. C# semantic completion |youcompleteme-c-semantic-completion|
8. Semantic completion for other languages |youcompleteme-semantic-completion-for-other-languages|
9. Writing New Semantic Completers |youcompleteme-writing-new-semantic-completers|
10. Syntastic integration |youcompleteme-syntastic-integration|
7. Commands |youcompleteme-commands|
1. The |:YcmForceCompileAndDiagnostics| command
2. The |:YcmDiags| command
3. The |:YcmShowDetailedDiagnostic| command
4. The |:YcmDebugInfo| command
5. The |:YcmCompleter| command
1. The |:YcmRestartServer| command
2. The |:YcmForceCompileAndDiagnostics| command
3. The |:YcmDiags| command
4. The |:YcmShowDetailedDiagnostic| command
5. The |:YcmDebugInfo| command
6. The |:YcmCompleter| command
8. YcmCompleter subcommands |youcompleteme-ycmcompleter-subcommands|
1. The |GoToDeclaration| subcommand
2. The |GoToDefinition| subcommand
@ -45,23 +47,27 @@ Contents ~
10. The |g:ycm_collect_identifiers_from_comments_and_strings| option
11. The |g:ycm_collect_identifiers_from_tags_files| option
12. The |g:ycm_seed_identifiers_with_syntax| option
13. The |g:ycm_csharp_server_port| option
14. The |g:ycm_auto_start_csharp_server| option
15. The |g:ycm_auto_stop_csharp_server| option
16. The |g:ycm_add_preview_to_completeopt| option
17. The |g:ycm_autoclose_preview_window_after_completion| option
18. The |g:ycm_autoclose_preview_window_after_insertion| option
19. The |g:ycm_max_diagnostics_to_display| option
20. The |g:ycm_key_list_select_completion| option
21. The |g:ycm_key_list_previous_completion| option
22. The |g:ycm_key_invoke_completion| option
23. The |g:ycm_key_detailed_diagnostics| option
24. The |g:ycm_global_ycm_extra_conf| option
25. The |g:ycm_confirm_extra_conf| option
26. The |g:ycm_extra_conf_globlist| option
27. The |g:ycm_filepath_completion_use_working_dir| option
28. The |g:ycm_semantic_triggers| option
29. The |g:ycm_cache_omnifunc| option
13. The |g:ycm_server_use_vim_stdout| option
14. The |g:ycm_server_keep_logfiles| option
15. The |g:ycm_server_log_level| option
16. The |g:ycm_server_idle_suicide_seconds| option
17. The |g:ycm_csharp_server_port| option
18. The |g:ycm_auto_start_csharp_server| option
19. The |g:ycm_auto_stop_csharp_server| option
20. The |g:ycm_add_preview_to_completeopt| option
21. The |g:ycm_autoclose_preview_window_after_completion| option
22. The |g:ycm_autoclose_preview_window_after_insertion| option
23. The |g:ycm_max_diagnostics_to_display| option
24. The |g:ycm_key_list_select_completion| option
25. The |g:ycm_key_list_previous_completion| option
26. The |g:ycm_key_invoke_completion| option
27. The |g:ycm_key_detailed_diagnostics| option
28. The |g:ycm_global_ycm_extra_conf| option
29. The |g:ycm_confirm_extra_conf| option
30. The |g:ycm_extra_conf_globlist| option
31. The |g:ycm_filepath_completion_use_working_dir| option
32. The |g:ycm_semantic_triggers| option
33. The |g:ycm_cache_omnifunc| option
10. FAQ |youcompleteme-faq|
1. I get a linker warning regarding |libpython| on Mac when compiling YCM
2. I get a weird window at the top of my file when I use the semantic engine |youcompleteme-i-get-weird-window-at-top-of-my-file-when-i-use-semantic-engine|
@ -182,8 +188,9 @@ local binary folder (for example '/usr/local/bin/mvim') and then symlink it:
Install YouCompleteMe with Vundle [11].
**Remember:** YCM is a plugin with a compiled component. If you **update** YCM
using Vundle and the ycm_core library API has changed (happens rarely), YCM
will notify you to recompile it. You should then rerun the install process.
using Vundle and the ycm_support_libs library APIs have changed (happens
rarely), YCM will notify you to recompile it. You should then rerun the install
process.
It's recommended that you have the latest Xcode installed along with the latest
Command Line Tools (that you install from within Xcode).
@ -230,8 +237,9 @@ from source [14] (don't worry, it's easy).
Install YouCompleteMe with Vundle [11].
**Remember:** YCM is a plugin with a compiled component. If you **update** YCM
using Vundle and the ycm_core library API has changed (happens rarely), YCM
will notify you to recompile it. You should then rerun the install process.
using Vundle and the ycm_support_libs library APIs have changed (happens
rarely), YCM will notify you to recompile it. You should then rerun the install
process.
Install development tools and CMake: 'sudo apt-get install build-essential
cmake'
@ -281,8 +289,9 @@ that platform).
See the _FAQ_ if you have any issues.
**Remember:** YCM is a plugin with a compiled component. If you **update** YCM
using Vundle and the ycm_core library API has changed (happens rarely), YCM
will notify you to recompile it. You should then rerun the install process.
using Vundle and the ycm_support_libs library APIs have changed (happens
rarely), YCM will notify you to recompile it. You should then rerun the install
process.
**Please follow the instructions carefully. Read EVERY WORD.**
@ -319,8 +328,8 @@ will notify you to recompile it. You should then rerun the install process.
official binaries from llvm.org [18] if at all possible. Make sure you
download the correct archive file for your OS.
4. **Compile the 'ycm_core' plugin plugin** (ha!) that YCM needs. This is
the C++ engine that YCM uses to get fast completions.
4. **Compile the 'ycm_support_libs' libraries** that YCM needs. These libs
are the C++ engines that YCM uses to get fast completions.
You will need to have 'cmake' installed in order to generate the required
makefiles. Linux users can install cmake with their package manager
@ -359,7 +368,7 @@ will notify you to recompile it. You should then rerun the install process.
<
Now that makefiles have been generated, simply run:
>
make ycm_core
make ycm_support_libs
<
For those who want to use the system version of libclang, you would pass
'-DUSE_SYSTEM_LIBCLANG=ON' to cmake _instead of_ the
@ -427,6 +436,15 @@ YCM automatically detects which completion engine would be the best in any
situation. On occasion, it queries several of them at once, merges the outputs
and presents the results to you.
-------------------------------------------------------------------------------
*youcompleteme-client-server-architecture*
Client-server architecture ~
YCM has a client-server architecture; the Vim part of YCM is only a thin client
that talks to the 'ycmd' HTTP+JSON server that has the vast majority of YCM
logic and functionality. The server is started and stopped automatically as you
start and stop Vim.
-------------------------------------------------------------------------------
*youcompleteme-completion-string-ranking*
Completion string ranking ~
@ -624,6 +642,12 @@ yours truly.
*youcompleteme-commands*
Commands ~
-------------------------------------------------------------------------------
The *:YcmRestartServer* command
If the 'ycmd' completion server suddenly stops for some reason, you can restart
it with this command.
-------------------------------------------------------------------------------
The *:YcmForceCompileAndDiagnostics* command
@ -694,7 +718,7 @@ The *GoToDeclaration* subcommand
Looks up the symbol under the cursor and jumps to its declaration.
Supported in filetypes: 'c, cpp, objc, objcpp, python'
Supported in filetypes: 'c, cpp, objc, objcpp, python, cs'
-------------------------------------------------------------------------------
The *GoToDefinition* subcommand
@ -706,7 +730,7 @@ when the definition of the symbol is in the current translation unit. A
translation unit consists of the file you are editing and all the files you are
including with '#include' directives (directly or indirectly) in that file.
Supported in filetypes: 'c, cpp, objc, objcpp, python'
Supported in filetypes: 'c, cpp, objc, objcpp, python, cs'
-------------------------------------------------------------------------------
The *GoToDefinitionElseDeclaration* subcommand
@ -715,7 +739,7 @@ Looks up the symbol under the cursor and jumps to its definition if possible;
if the definition is not accessible from the current translation unit, jumps to
the symbol's declaration.
Supported in filetypes: 'c, cpp, objc, objcpp, python'
Supported in filetypes: 'c, cpp, objc, objcpp, python, cs'
-------------------------------------------------------------------------------
The *ClearCompilationFlagCache* subcommand
@ -982,6 +1006,64 @@ Default: '0'
let g:ycm_seed_identifiers_with_syntax = 0
<
-------------------------------------------------------------------------------
The *g:ycm_server_use_vim_stdout* option
By default, the 'ycmd' completion server writes logs to logfiles. When this
option is set to '1', the server writes logs to Vim's stdout (so you'll see
them in the console).
Default: '0'
>
let g:ycm_server_use_vim_stdout = 0
<
-------------------------------------------------------------------------------
The *g:ycm_server_keep_logfiles* option
When this option is set to '1', the 'ycmd' completion server will keep the
logfiles around after shutting down (they are deleted on shutdown by default).
To see where the logfiles are, call |:YcmDebugInfo|.
Default: '0'
>
let g:ycm_server_keep_logfiles = 0
<
-------------------------------------------------------------------------------
The *g:ycm_server_log_level* option
The logging level that the 'ycmd' completion server uses. Valid values are the
following, from most verbose to least verbose: - 'debug' - 'info' - 'warning' -
'error' - 'critical'
Note that 'debug' is _very_ verbose.
Default: 'info'
>
let g:ycm_server_log_level = 'info'
<
-------------------------------------------------------------------------------
The *g:ycm_server_idle_suicide_seconds* option
This option sets the number of seconds of 'ycmd' server idleness (no requests
received) after which the server stops itself. NOTE: the YCM Vim client sends a
shutdown request to the server when Vim is shutting down.
If your Vim crashes for instance, 'ycmd' never gets the shutdown command and
becomes a zombie process. This option prevents such zombies from sticking
around forever.
The default option is '43200' seconds which is 12 hours. The reason for the
interval being this long is to prevent the server from shutting down if you
leave your computer (and Vim) turned on during the night.
The server "heartbeat" that checks whether this interval has passed occurs
every 10 minutes.
Default: '43200'
>
let g:ycm_server_idle_suicide_seconds = 43200
<
-------------------------------------------------------------------------------
The *g:ycm_csharp_server_port* option
The port number (on 'localhost') on which the OmniSharp server should be
@ -1575,6 +1657,8 @@ License ~
This software is licensed under the GPL v3 license [31]. © 2012 Strahinja Val
Markovic <val@markovic.io>.
Image: Bitdeli Badge [32]
===============================================================================
*youcompleteme-references*
References ~
@ -1610,5 +1694,7 @@ References ~
[29] https://groups.google.com/forum/?hl=en#!forum/ycm-users
[30] https://github.com/Valloric/YouCompleteMe/issues?state=open
[31] http://www.gnu.org/copyleft/gpl.html
[32] https://bitdeli.com/free
[33] https://d2weczhvl823v0.cloudfront.net/Valloric/youcompleteme/trend.png
vim: ft=help

View File

@ -77,7 +77,7 @@ function install {
cmake -G "Unix Makefiles" "$@" . $ycm_dir/cpp
fi
make -j $(num_cores) ycm_core
make -j $(num_cores) ycm_support_libs
popd
rm -rf $build_dir
}
@ -106,6 +106,22 @@ function usage {
exit 0
}
function check_third_party_libs {
libs_present=true
for folder in third_party/*; do
num_files_in_folder=$(find $folder -maxdepth 1 -mindepth 1 | wc -l)
if [[ $num_files_in_folder -eq 0 ]]; then
libs_present=false
fi
done
if ! $libs_present; then
echo "Some folders in ./third_party are empty; you probably forgot to run:"
printf "\n\tgit submodule update --init --recursive\n\n"
exit 1
fi
}
cmake_args=""
omnisharp_completer=false
for flag in $@; do
@ -130,6 +146,8 @@ if [[ $cmake_args == *-DUSE_SYSTEM_LIBCLANG=ON* ]] && \
usage
fi
check_third_party_libs
if ! command_exists cmake; then
echo "CMake is required to build YouCompleteMe."
cmake_install
@ -138,7 +156,7 @@ fi
if [ -z "$YCM_TESTRUN" ]; then
install $cmake_args $EXTRA_CMAKE_ARGS
else
testrun $cmake_args -DUSE_DEV_FLAGS=ON $EXTRA_CMAKE_ARGS
testrun $cmake_args $EXTRA_CMAKE_ARGS
fi
if $omnisharp_completer; then

View File

@ -37,11 +37,14 @@ let s:script_folder_path = escape( expand( '<sfile>:p:h' ), '\' )
function! s:HasYcmCore()
let path_prefix = s:script_folder_path . '/../python/'
if filereadable(path_prefix . 'ycm_core.so')
if filereadable(path_prefix . 'ycm_client_support.so') &&
\ filereadable(path_prefix . 'ycm_core.so')
return 1
elseif filereadable(path_prefix . 'ycm_core.pyd')
elseif filereadable(path_prefix . 'ycm_client_support.pyd') &&
\ filereadable(path_prefix . 'ycm_core.pyd')
return 1
elseif filereadable(path_prefix . 'ycm_core.dll')
elseif filereadable(path_prefix . 'ycm_client_support.dll') &&
\ filereadable(path_prefix . 'ycm_core.dll')
return 1
endif
return 0
@ -52,7 +55,8 @@ let g:ycm_check_if_ycm_core_present =
if g:ycm_check_if_ycm_core_present && !s:HasYcmCore()
echohl WarningMsg |
\ echomsg "ycm_core.[so|pyd|dll] not detected; you need to compile " .
\ echomsg "ycm_client_support.[so|pyd|dll] and " .
\ "ycm_core.[so|pyd|dll] not detected; you need to compile " .
\ "YCM before using it. Read the docs!" |
\ echohl None
finish
@ -60,31 +64,11 @@ endif
let g:loaded_youcompleteme = 1
let g:ycm_min_num_of_chars_for_completion =
\ get( g:, 'ycm_min_num_of_chars_for_completion', 2 )
let g:ycm_min_num_identifier_candidate_chars =
\ get( g:, 'ycm_min_num_identifier_candidate_chars', 0 )
let g:ycm_filetype_whitelist =
\ get( g:, 'ycm_filetype_whitelist', {
\ '*' : 1,
\ } )
" The fallback to g:ycm_filetypes_to_completely_ignore is here because of
" backwards compatibility with previous versions of YCM.
let g:ycm_filetype_blacklist =
\ get( g:, 'ycm_filetype_blacklist',
\ get( g:, 'ycm_filetypes_to_completely_ignore', {
\ 'notes' : 1,
\ 'markdown' : 1,
\ 'text' : 1,
\ 'unite' : 1,
\ 'tagbar' : 1,
\ } ) )
let g:ycm_filetype_specific_completion_to_disable =
\ get( g:, 'ycm_filetype_specific_completion_to_disable', {} )
" NOTE: Most defaults are in default_settings.json. They are loaded into Vim
" global with the 'ycm_' prefix if such a key does not already exist; thus, the
" user can override the defaults.
" The only defaults that are here are the ones that are only relevant to the YCM
" Vim client and not the server.
let g:ycm_register_as_syntastic_checker =
\ get( g:, 'ycm_register_as_syntastic_checker', 1 )
@ -95,30 +79,12 @@ let g:ycm_allow_changing_updatetime =
let g:ycm_add_preview_to_completeopt =
\ get( g:, 'ycm_add_preview_to_completeopt', 0 )
let g:ycm_complete_in_comments =
\ get( g:, 'ycm_complete_in_comments', 0 )
let g:ycm_complete_in_strings =
\ get( g:, 'ycm_complete_in_strings', 1 )
let g:ycm_collect_identifiers_from_comments_and_strings =
\ get( g:, 'ycm_collect_identifiers_from_comments_and_strings', 0 )
let g:ycm_collect_identifiers_from_tags_files =
\ get( g:, 'ycm_collect_identifiers_from_tags_files', 0 )
let g:ycm_seed_identifiers_with_syntax =
\ get( g:, 'ycm_seed_identifiers_with_syntax', 0 )
let g:ycm_autoclose_preview_window_after_completion =
\ get( g:, 'ycm_autoclose_preview_window_after_completion', 0 )
let g:ycm_autoclose_preview_window_after_insertion =
\ get( g:, 'ycm_autoclose_preview_window_after_insertion', 0 )
let g:ycm_max_diagnostics_to_display =
\ get( g:, 'ycm_max_diagnostics_to_display', 30 )
let g:ycm_key_list_select_completion =
\ get( g:, 'ycm_key_list_select_completion', ['<TAB>', '<Down>'] )
@ -131,34 +97,21 @@ let g:ycm_key_invoke_completion =
let g:ycm_key_detailed_diagnostics =
\ get( g:, 'ycm_key_detailed_diagnostics', '<leader>d' )
let g:ycm_global_ycm_extra_conf =
\ get( g:, 'ycm_global_ycm_extra_conf', '' )
let g:ycm_confirm_extra_conf =
\ get( g:, 'ycm_confirm_extra_conf', 1 )
let g:ycm_extra_conf_globlist =
\ get( g:, 'ycm_extra_conf_globlist', [] )
let g:ycm_filepath_completion_use_working_dir =
\ get( g:, 'ycm_filepath_completion_use_working_dir', 0 )
" Default semantic triggers are in python/ycm/completers/completer.py, these
" just append new triggers to the default dict.
let g:ycm_semantic_triggers =
\ get( g:, 'ycm_semantic_triggers', {} )
let g:ycm_cache_omnifunc =
\ get( g:, 'ycm_cache_omnifunc', 1 )
let g:ycm_auto_start_csharp_server =
\ get( g:, 'ycm_auto_start_csharp_server', 1 )
let g:ycm_server_use_vim_stdout =
\ get( g:, 'ycm_server_use_vim_stdout', 0 )
let g:ycm_auto_stop_csharp_server =
\ get( g:, 'ycm_auto_stop_csharp_server', 1 )
let g:ycm_server_log_level =
\ get( g:, 'ycm_server_log_level', 'info' )
let g:ycm_server_keep_logfiles =
\ get( g:, 'ycm_server_keep_logfiles', 0 )
let g:ycm_server_idle_suicide_seconds =
\ get( g:, 'ycm_server_idle_suicide_seconds', 43200 )
let g:ycm_csharp_server_port =
\ get( g:, 'ycm_csharp_server_port', 2000 )
" On-demand loading. Let's use the autoload folder and not slow down vim's
" startup procedure.

8
print_todos.sh Executable file
View File

@ -0,0 +1,8 @@
#!/bin/bash
ag \
--ignore gmock \
--ignore jedi/ \
--ignore OmniSharpServer \
--ignore testdata \
TODO \
cpp/ycm python autoload plugin

View File

@ -1,3 +1,6 @@
flake8>=2.0
mock>=1.0.1
nose>=1.3.0
PyHamcrest>=1.7.2
WebTest>=2.0.9

View File

@ -17,21 +17,42 @@
# You should have received a copy of the GNU General Public License
# along with YouCompleteMe. If not, see <http://www.gnu.org/licenses/>.
import os
import re
import vim
from ycm import vimsupport
from ycm import utils
from ycm import user_options_store
import ycm_client_support
YCM_VAR_PREFIX = 'ycm_'
def BuildServerConf():
"""Builds a dictionary mapping YCM Vim user options to values. Option names
don't have the 'ycm_' prefix."""
vim_globals = vimsupport.GetReadOnlyVimGlobals( force_python_objects = True )
server_conf = {}
for key, value in vim_globals.items():
if not key.startswith( YCM_VAR_PREFIX ):
continue
try:
import ycm_core
except ImportError as e:
vimsupport.PostVimMessage(
'Error importing ycm_core. Are you sure you have placed a version 3.2+ '
'libclang.[so|dll|dylib] in folder "{0}"? See the Installation Guide in '
'the docs. Full error: {1}'.format(
os.path.dirname( os.path.dirname( os.path.abspath( __file__ ) ) ),
str( e ) ) )
new_value = int( value )
except:
new_value = value
new_key = key[ len( YCM_VAR_PREFIX ): ]
server_conf[ new_key ] = new_value
return server_conf
def LoadJsonDefaultsIntoVim():
defaults = user_options_store.DefaultOptions()
vim_defaults = {}
for key, value in defaults.iteritems():
vim_defaults[ 'ycm_' + key ] = value
vimsupport.LoadDictIntoVimGlobals( vim_defaults, overwrite = False )
def CompletionStartColumn():
@ -121,11 +142,11 @@ def AdjustCandidateInsertionText( candidates ):
return new_candidates
COMPATIBLE_WITH_CORE_VERSION = 4
COMPATIBLE_WITH_CORE_VERSION = 7
def CompatibleWithYcmCore():
try:
current_core_version = ycm_core.YcmCoreVersion()
current_core_version = ycm_client_support.YcmCoreVersion()
except AttributeError:
return False

View File

View File

@ -0,0 +1,154 @@
#!/usr/bin/env python
#
# Copyright (C) 2013 Strahinja Val Markovic <val@markovic.io>
#
# This file is part of YouCompleteMe.
#
# YouCompleteMe is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# YouCompleteMe is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with YouCompleteMe. If not, see <http://www.gnu.org/licenses/>.
import vim
import json
import requests
import urlparse
from retries import retries
from requests_futures.sessions import FuturesSession
from concurrent.futures import ThreadPoolExecutor
from ycm import vimsupport
from ycm.server.responses import ServerError, UnknownExtraConf
HEADERS = {'content-type': 'application/json'}
EXECUTOR = ThreadPoolExecutor( max_workers = 10 )
# Setting this to None seems to screw up the Requests/urllib3 libs.
DEFAULT_TIMEOUT_SEC = 30
class BaseRequest( object ):
def __init__( self ):
pass
def Start( self ):
pass
def Done( self ):
return True
def Response( self ):
return {}
# This is the blocking version of the method. See below for async.
# |timeout| is num seconds to tolerate no response from server before giving
# up; see Requests docs for details (we just pass the param along).
@staticmethod
def PostDataToHandler( data, handler, timeout = DEFAULT_TIMEOUT_SEC ):
return JsonFromFuture( BaseRequest.PostDataToHandlerAsync( data,
handler,
timeout ) )
# This returns a future! Use JsonFromFuture to get the value.
# |timeout| is num seconds to tolerate no response from server before giving
# up; see Requests docs for details (we just pass the param along).
@staticmethod
def PostDataToHandlerAsync( data, handler, timeout = DEFAULT_TIMEOUT_SEC ):
def PostData( data, handler, timeout ):
return BaseRequest.session.post( _BuildUri( handler ),
data = json.dumps( data ),
headers = HEADERS,
timeout = timeout )
@retries( 5, delay = 0.5, backoff = 1.5 )
def DelayedPostData( data, handler ):
return requests.post( _BuildUri( handler ),
data = json.dumps( data ),
headers = HEADERS )
if not _CheckServerIsHealthyWithCache():
return EXECUTOR.submit( DelayedPostData, data, handler )
return PostData( data, handler, timeout )
session = FuturesSession( executor = EXECUTOR )
server_location = 'http://localhost:6666'
def BuildRequestData( start_column = None,
query = None,
include_buffer_data = True ):
line, column = vimsupport.CurrentLineAndColumn()
filepath = vimsupport.GetCurrentBufferFilepath()
request_data = {
'filetypes': vimsupport.CurrentFiletypes(),
'line_num': line,
'column_num': column,
'start_column': start_column,
'line_value': vim.current.line,
'filepath': filepath
}
if include_buffer_data:
request_data[ 'file_data' ] = vimsupport.GetUnsavedAndCurrentBufferData()
if query:
request_data[ 'query' ] = query
return request_data
def JsonFromFuture( future ):
response = future.result()
if response.status_code == requests.codes.server_error:
_RaiseExceptionForData( response.json() )
# We let Requests handle the other status types, we only handle the 500
# error code.
response.raise_for_status()
if response.text:
return response.json()
return None
def _BuildUri( handler ):
return urlparse.urljoin( BaseRequest.server_location, handler )
SERVER_HEALTHY = False
def _CheckServerIsHealthyWithCache():
global SERVER_HEALTHY
def _ServerIsHealthy():
response = requests.get( _BuildUri( 'healthy' ) )
response.raise_for_status()
return response.json()
if SERVER_HEALTHY:
return True
try:
SERVER_HEALTHY = _ServerIsHealthy()
return SERVER_HEALTHY
except:
return False
def _RaiseExceptionForData( data ):
if data[ 'exception' ][ 'TYPE' ] == UnknownExtraConf.__name__:
raise UnknownExtraConf( data[ 'exception' ][ 'extra_conf_file' ] )
raise ServerError( '{0}: {1}'.format( data[ 'exception' ][ 'TYPE' ],
data[ 'message' ] ) )

View File

@ -0,0 +1,88 @@
#!/usr/bin/env python
#
# Copyright (C) 2013 Strahinja Val Markovic <val@markovic.io>
#
# This file is part of YouCompleteMe.
#
# YouCompleteMe is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# YouCompleteMe is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with YouCompleteMe. If not, see <http://www.gnu.org/licenses/>.
import vim
from ycm.client.base_request import BaseRequest, BuildRequestData, ServerError
from ycm import vimsupport
from ycm.utils import ToUtf8IfNeeded
class CommandRequest( BaseRequest ):
def __init__( self, arguments, completer_target = None ):
super( CommandRequest, self ).__init__()
self._arguments = arguments
self._completer_target = ( completer_target if completer_target
else 'filetype_default' )
self._is_goto_command = (
arguments and arguments[ 0 ].startswith( 'GoTo' ) )
self._response = None
def Start( self ):
request_data = BuildRequestData()
request_data.update( {
'completer_target': self._completer_target,
'command_arguments': self._arguments
} )
try:
self._response = self.PostDataToHandler( request_data,
'run_completer_command' )
except ServerError as e:
vimsupport.PostVimMessage( e )
def Response( self ):
return self._response
def RunPostCommandActionsIfNeeded( self ):
if not self._is_goto_command or not self.Done() or not self._response:
return
if isinstance( self._response, list ):
defs = [ _BuildQfListItem( x ) for x in self._response ]
vim.eval( 'setqflist( %s )' % repr( defs ) )
vim.eval( 'youcompleteme#OpenGoToList()' )
else:
vimsupport.JumpToLocation( self._response[ 'filepath' ],
self._response[ 'line_num' ] + 1,
self._response[ 'column_num' ] + 1)
def SendCommandRequest( arguments, completer ):
request = CommandRequest( arguments, completer )
# This is a blocking call.
request.Start()
request.RunPostCommandActionsIfNeeded()
return request.Response()
def _BuildQfListItem( goto_data_item ):
qf_item = {}
if 'filepath' in goto_data_item:
qf_item[ 'filename' ] = ToUtf8IfNeeded( goto_data_item[ 'filepath' ] )
if 'description' in goto_data_item:
qf_item[ 'text' ] = ToUtf8IfNeeded( goto_data_item[ 'description' ] )
if 'line_num' in goto_data_item:
qf_item[ 'lnum' ] = goto_data_item[ 'line_num' ] + 1
if 'column_num' in goto_data_item:
qf_item[ 'col' ] = goto_data_item[ 'column_num' ]
return qf_item

View File

@ -0,0 +1,81 @@
#!/usr/bin/env python
#
# Copyright (C) 2013 Strahinja Val Markovic <val@markovic.io>
#
# This file is part of YouCompleteMe.
#
# YouCompleteMe is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# YouCompleteMe is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with YouCompleteMe. If not, see <http://www.gnu.org/licenses/>.
from ycm import base
from ycm import vimsupport
from ycm.client.base_request import ( BaseRequest, BuildRequestData,
JsonFromFuture )
TIMEOUT_SECONDS = 0.5
class CompletionRequest( BaseRequest ):
def __init__( self, force_semantic = False ):
super( CompletionRequest, self ).__init__()
self._completion_start_column = base.CompletionStartColumn()
# This field is also used by the omni_completion_request subclass
self.request_data = BuildRequestData( self._completion_start_column )
if force_semantic:
self.request_data[ 'force_semantic' ] = True
def CompletionStartColumn( self ):
return self._completion_start_column
def Start( self, query ):
self.request_data[ 'query' ] = query
self._response_future = self.PostDataToHandlerAsync( self.request_data,
'completions',
TIMEOUT_SECONDS )
def Done( self ):
return self._response_future.done()
def Response( self ):
if not self._response_future:
return []
try:
return [ _ConvertCompletionDataToVimData( x )
for x in JsonFromFuture( self._response_future ) ]
except Exception as e:
vimsupport.PostVimMessage( str( e ) )
return []
def _ConvertCompletionDataToVimData( completion_data ):
# see :h complete-items for a description of the dictionary fields
vim_data = {
'word' : completion_data[ 'insertion_text' ],
'dup' : 1,
}
if 'menu_text' in completion_data:
vim_data[ 'abbr' ] = completion_data[ 'menu_text' ]
if 'extra_menu_info' in completion_data:
vim_data[ 'menu' ] = completion_data[ 'extra_menu_info' ]
if 'kind' in completion_data:
vim_data[ 'kind' ] = completion_data[ 'kind' ]
if 'detailed_info' in completion_data:
vim_data[ 'info' ] = completion_data[ 'detailed_info' ]
return vim_data

View File

@ -0,0 +1,93 @@
#!/usr/bin/env python
#
# Copyright (C) 2013 Strahinja Val Markovic <val@markovic.io>
#
# This file is part of YouCompleteMe.
#
# YouCompleteMe is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# YouCompleteMe is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with YouCompleteMe. If not, see <http://www.gnu.org/licenses/>.
from ycm import vimsupport
from ycm.server.responses import UnknownExtraConf
from ycm.client.base_request import ( BaseRequest, BuildRequestData,
JsonFromFuture )
class EventNotification( BaseRequest ):
def __init__( self, event_name, extra_data = None ):
super( EventNotification, self ).__init__()
self._event_name = event_name
self._extra_data = extra_data
self._cached_response = None
def Start( self ):
request_data = BuildRequestData()
if self._extra_data:
request_data.update( self._extra_data )
request_data[ 'event_name' ] = self._event_name
self._response_future = self.PostDataToHandlerAsync( request_data,
'event_notification' )
def Done( self ):
return self._response_future.done()
def Response( self ):
if self._cached_response:
return self._cached_response
if not self._response_future or self._event_name != 'FileReadyToParse':
return []
try:
try:
self._cached_response = JsonFromFuture( self._response_future )
except UnknownExtraConf as e:
if vimsupport.Confirm( str( e ) ):
_LoadExtraConfFile( e.extra_conf_file )
except Exception as e:
vimsupport.PostVimMessage( str( e ) )
if not self._cached_response:
return []
self._cached_response = [ _ConvertDiagnosticDataToVimData( x )
for x in self._cached_response ]
return self._cached_response
def _ConvertDiagnosticDataToVimData( diagnostic ):
# see :h getqflist for a description of the dictionary fields
# Note that, as usual, Vim is completely inconsistent about whether
# line/column numbers are 1 or 0 based in its various APIs. Here, it wants
# them to be 1-based.
return {
'bufnr' : vimsupport.GetBufferNumberForFilename( diagnostic[ 'filepath' ]),
'lnum' : diagnostic[ 'line_num' ] + 1,
'col' : diagnostic[ 'column_num' ] + 1,
'text' : diagnostic[ 'text' ],
'type' : diagnostic[ 'kind' ],
'valid' : 1
}
def SendEventNotificationAsync( event_name, extra_data = None ):
event = EventNotification( event_name, extra_data )
event.Start()
def _LoadExtraConfFile( filepath ):
BaseRequest.PostDataToHandler( { 'filepath': filepath },
'load_extra_conf_file' )

View File

@ -0,0 +1,39 @@
#!/usr/bin/env python
#
# Copyright (C) 2013 Strahinja Val Markovic <val@markovic.io>
#
# This file is part of YouCompleteMe.
#
# YouCompleteMe is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# YouCompleteMe is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with YouCompleteMe. If not, see <http://www.gnu.org/licenses/>.
from ycm.client.completion_request import CompletionRequest
class OmniCompletionRequest( CompletionRequest ):
def __init__( self, omni_completer ):
super( OmniCompletionRequest, self ).__init__()
self._omni_completer = omni_completer
def Start( self, query ):
self.request_data[ 'query' ] = query
self._results = self._omni_completer.ComputeCandidates( self.request_data )
def Done( self ):
return True
def Response( self ):
return self._results

View File

@ -18,175 +18,154 @@
# along with YouCompleteMe. If not, see <http://www.gnu.org/licenses/>.
import os
import vim
import logging
import ycm_core
from collections import defaultdict
from ycm.completers.general_completer import GeneralCompleter
from ycm.completers.general import syntax_parse
from ycm import vimsupport
# from ycm.completers.general import syntax_parse
from ycm import utils
from ycm.utils import ToUtf8IfNeeded
from ycm.server import responses
MAX_IDENTIFIER_COMPLETIONS_RETURNED = 10
MIN_NUM_COMPLETION_START_CHARS = int( vimsupport.GetVariableValue(
"g:ycm_min_num_of_chars_for_completion" ) )
MIN_NUM_CANDIDATE_SIZE_CHARS = int( vimsupport.GetVariableValue(
"g:ycm_min_num_identifier_candidate_chars" ) )
SYNTAX_FILENAME = 'YCM_PLACEHOLDER_FOR_SYNTAX'
class IdentifierCompleter( GeneralCompleter ):
def __init__( self ):
super( IdentifierCompleter, self ).__init__()
self.completer = ycm_core.IdentifierCompleter()
self.completer.EnableThreading()
self.tags_file_last_mtime = defaultdict( int )
self.filetypes_with_keywords_loaded = set()
def __init__( self, user_options ):
super( IdentifierCompleter, self ).__init__( user_options )
self._completer = ycm_core.IdentifierCompleter()
self._tags_file_last_mtime = defaultdict( int )
self._logger = logging.getLogger( __name__ )
def ShouldUseNow( self, start_column ):
return self.QueryLengthAboveMinThreshold( start_column )
def ShouldUseNow( self, request_data ):
return self.QueryLengthAboveMinThreshold( request_data )
def CandidatesForQueryAsync( self, query, unused_start_column ):
filetype = vim.eval( "&filetype" )
self.completions_future = self.completer.CandidatesForQueryAndTypeAsync(
utils.SanitizeQuery( query ),
filetype )
def ComputeCandidates( self, request_data ):
if not self.ShouldUseNow( request_data ):
return []
completions = self._completer.CandidatesForQueryAndType(
ToUtf8IfNeeded( utils.SanitizeQuery( request_data[ 'query' ] ) ),
ToUtf8IfNeeded( request_data[ 'filetypes' ][ 0 ] ) )
completions = completions[ : MAX_IDENTIFIER_COMPLETIONS_RETURNED ]
completions = _RemoveSmallCandidates(
completions, self.user_options[ 'min_num_identifier_candidate_chars' ] )
return [ responses.BuildCompletionData( x ) for x in completions ]
def AddIdentifier( self, identifier ):
filetype = vim.eval( "&filetype" )
filepath = vim.eval( "expand('%:p')" )
def AddIdentifier( self, identifier, request_data ):
filetype = request_data[ 'filetypes' ][ 0 ]
filepath = request_data[ 'filepath' ]
if not filetype or not filepath or not identifier:
return
vector = ycm_core.StringVec()
vector.append( identifier )
self.completer.AddIdentifiersToDatabase( vector,
filetype,
filepath )
vector.append( ToUtf8IfNeeded( identifier ) )
self._logger.info( 'Adding ONE buffer identifier for file: %s', filepath )
self._completer.AddIdentifiersToDatabase( vector,
ToUtf8IfNeeded( filetype ),
ToUtf8IfNeeded( filepath ) )
def AddPreviousIdentifier( self ):
self.AddIdentifier( PreviousIdentifier() )
def AddPreviousIdentifier( self, request_data ):
self.AddIdentifier(
_PreviousIdentifier(
self.user_options[ 'min_num_of_chars_for_completion' ],
request_data ),
request_data )
def AddIdentifierUnderCursor( self ):
cursor_identifier = vim.eval( 'expand("<cword>")' )
def AddIdentifierUnderCursor( self, request_data ):
cursor_identifier = _GetCursorIdentifier( request_data )
if not cursor_identifier:
return
stripped_cursor_identifier = ''.join( ( x for x in
cursor_identifier if
utils.IsIdentifierChar( x ) ) )
if not stripped_cursor_identifier:
return
self.AddIdentifier( stripped_cursor_identifier )
self.AddIdentifier( cursor_identifier, request_data )
def AddBufferIdentifiers( self ):
# TODO: use vimsupport.GetFiletypes; also elsewhere in file
filetype = vim.eval( "&filetype" )
filepath = vim.eval( "expand('%:p')" )
collect_from_comments_and_strings = vimsupport.GetBoolValue(
"g:ycm_collect_identifiers_from_comments_and_strings" )
def AddBufferIdentifiers( self, request_data ):
filetype = request_data[ 'filetypes' ][ 0 ]
filepath = request_data[ 'filepath' ]
collect_from_comments_and_strings = bool( self.user_options[
'collect_identifiers_from_comments_and_strings' ] )
if not filetype or not filepath:
return
text = "\n".join( vim.current.buffer )
self.completer.AddIdentifiersToDatabaseFromBufferAsync(
text,
filetype,
filepath,
text = request_data[ 'file_data' ][ filepath ][ 'contents' ]
self._logger.info( 'Adding buffer identifiers for file: %s', filepath )
self._completer.AddIdentifiersToDatabaseFromBuffer(
ToUtf8IfNeeded( text ),
ToUtf8IfNeeded( filetype ),
ToUtf8IfNeeded( filepath ),
collect_from_comments_and_strings )
def AddIdentifiersFromTagFiles( self ):
tag_files = vim.eval( 'tagfiles()' )
current_working_directory = os.getcwd()
def AddIdentifiersFromTagFiles( self, tag_files ):
absolute_paths_to_tag_files = ycm_core.StringVec()
for tag_file in tag_files:
absolute_tag_file = os.path.join( current_working_directory,
tag_file )
try:
current_mtime = os.path.getmtime( absolute_tag_file )
current_mtime = os.path.getmtime( tag_file )
except:
continue
last_mtime = self.tags_file_last_mtime[ absolute_tag_file ]
last_mtime = self._tags_file_last_mtime[ tag_file ]
# We don't want to repeatedly process the same file over and over; we only
# process if it's changed since the last time we looked at it
if current_mtime <= last_mtime:
continue
self.tags_file_last_mtime[ absolute_tag_file ] = current_mtime
absolute_paths_to_tag_files.append( absolute_tag_file )
self._tags_file_last_mtime[ tag_file ] = current_mtime
absolute_paths_to_tag_files.append( ToUtf8IfNeeded( tag_file ) )
if not absolute_paths_to_tag_files:
return
self.completer.AddIdentifiersToDatabaseFromTagFilesAsync(
self._completer.AddIdentifiersToDatabaseFromTagFiles(
absolute_paths_to_tag_files )
def AddIdentifiersFromSyntax( self ):
filetype = vim.eval( "&filetype" )
if filetype in self.filetypes_with_keywords_loaded:
return
def AddIdentifiersFromSyntax( self, keyword_list, filetypes ):
keyword_vector = ycm_core.StringVec()
for keyword in keyword_list:
keyword_vector.append( ToUtf8IfNeeded( keyword ) )
self.filetypes_with_keywords_loaded.add( filetype )
keyword_set = syntax_parse.SyntaxKeywordsForCurrentBuffer()
keywords = ycm_core.StringVec()
for keyword in keyword_set:
keywords.append( keyword )
filepath = SYNTAX_FILENAME + filetype
self.completer.AddIdentifiersToDatabase( keywords,
filetype,
filepath )
filepath = SYNTAX_FILENAME + filetypes[ 0 ]
self._completer.AddIdentifiersToDatabase( keyword_vector,
ToUtf8IfNeeded( filetypes[ 0 ] ),
ToUtf8IfNeeded( filepath ) )
def OnFileReadyToParse( self ):
self.AddBufferIdentifiers()
if vimsupport.GetBoolValue( 'g:ycm_collect_identifiers_from_tags_files' ):
self.AddIdentifiersFromTagFiles()
if vimsupport.GetBoolValue( 'g:ycm_seed_identifiers_with_syntax' ):
self.AddIdentifiersFromSyntax()
def OnFileReadyToParse( self, request_data ):
self.AddBufferIdentifiers( request_data )
if 'tag_files' in request_data:
self.AddIdentifiersFromTagFiles( request_data[ 'tag_files' ] )
if 'syntax_keywords' in request_data:
self.AddIdentifiersFromSyntax( request_data[ 'syntax_keywords' ],
request_data[ 'filetypes' ] )
def OnInsertLeave( self ):
self.AddIdentifierUnderCursor()
def OnInsertLeave( self, request_data ):
self.AddIdentifierUnderCursor( request_data )
def OnCurrentIdentifierFinished( self ):
self.AddPreviousIdentifier()
def OnCurrentIdentifierFinished( self, request_data ):
self.AddPreviousIdentifier( request_data )
def CandidatesFromStoredRequest( self ):
if not self.completions_future:
return []
completions = self.completions_future.GetResults()[
: MAX_IDENTIFIER_COMPLETIONS_RETURNED ]
completions = _RemoveSmallCandidates( completions )
# We will never have duplicates in completions so with 'dup':1 we tell Vim
# to add this candidate even if it's a duplicate of an existing one (which
# will never happen). This saves us some expensive string matching
# operations in Vim.
return [ { 'word': x, 'dup': 1 } for x in completions ]
def PreviousIdentifier():
line_num, column_num = vimsupport.CurrentLineAndColumn()
buffer = vim.current.buffer
line = buffer[ line_num ]
def _PreviousIdentifier( min_num_completion_start_chars, request_data ):
line_num = request_data[ 'line_num' ]
column_num = request_data[ 'column_num' ]
filepath = request_data[ 'filepath' ]
contents_per_line = (
request_data[ 'file_data' ][ filepath ][ 'contents' ].split( '\n' ) )
line = contents_per_line[ line_num ]
end_column = column_num
@ -196,7 +175,7 @@ def PreviousIdentifier():
# Look at the previous line if we reached the end of the current one
if end_column == 0:
try:
line = buffer[ line_num - 1]
line = contents_per_line[ line_num - 1 ]
except:
return ""
end_column = len( line )
@ -208,15 +187,52 @@ def PreviousIdentifier():
while start_column > 0 and utils.IsIdentifierChar( line[ start_column - 1 ] ):
start_column -= 1
if end_column - start_column < MIN_NUM_COMPLETION_START_CHARS:
if end_column - start_column < min_num_completion_start_chars:
return ""
return line[ start_column : end_column ]
def _RemoveSmallCandidates( candidates ):
if MIN_NUM_CANDIDATE_SIZE_CHARS == 0:
def _RemoveSmallCandidates( candidates, min_num_candidate_size_chars ):
if min_num_candidate_size_chars == 0:
return candidates
return [ x for x in candidates if len( x ) >= MIN_NUM_CANDIDATE_SIZE_CHARS ]
return [ x for x in candidates if len( x ) >= min_num_candidate_size_chars ]
# This is meant to behave like 'expand("<cword")' in Vim, thus starting at the
# cursor column and returning the "cursor word". If the cursor is not on a valid
# character, it searches forward until a valid identifier is found.
def _GetCursorIdentifier( request_data ):
def FindFirstValidChar( line, column ):
current_column = column
while not utils.IsIdentifierChar( line[ current_column ] ):
current_column += 1
return current_column
def FindIdentifierStart( line, valid_char_column ):
identifier_start = valid_char_column
while identifier_start > 0 and utils.IsIdentifierChar( line[
identifier_start - 1 ] ):
identifier_start -= 1
return identifier_start
def FindIdentifierEnd( line, valid_char_column ):
identifier_end = valid_char_column
while identifier_end < len( line ) - 1 and utils.IsIdentifierChar( line[
identifier_end + 1 ] ):
identifier_end += 1
return identifier_end + 1
column_num = request_data[ 'column_num' ]
line = request_data[ 'line_value' ]
try:
valid_char_column = FindFirstValidChar( line, column_num )
return line[ FindIdentifierStart( line, valid_char_column ) :
FindIdentifierEnd( line, valid_char_column ) ]
except:
return ''

View File

@ -19,17 +19,18 @@
import vim
from ycm import vimsupport
from ycm import base
from ycm.completers.completer import Completer
from ycm.client.base_request import BuildRequestData
OMNIFUNC_RETURNED_BAD_VALUE = 'Omnifunc returned bad value to YCM!'
OMNIFUNC_NOT_LIST = ( 'Omnifunc did not return a list or a dict with a "words" '
' list when expected.' )
class OmniCompleter( Completer ):
def __init__( self ):
super( OmniCompleter, self ).__init__()
self.omnifunc = None
self.stored_candidates = None
def __init__( self, user_options ):
super( OmniCompleter, self ).__init__( user_options )
self._omnifunc = None
def SupportedFiletypes( self ):
@ -37,43 +38,53 @@ class OmniCompleter( Completer ):
def ShouldUseCache( self ):
return vimsupport.GetBoolValue( "g:ycm_cache_omnifunc" )
return bool( self.user_options[ 'cache_omnifunc' ] )
def ShouldUseNow( self, start_column ):
if self.ShouldUseCache():
return super( OmniCompleter, self ).ShouldUseNow( start_column )
return self.ShouldUseNowInner( start_column )
def ShouldUseNowInner( self, start_column ):
if not self.omnifunc:
# We let the caller call this without passing in request_data. This is useful
# for figuring out should we even be preparing the "real" request_data in
# omni_completion_request. The real request_data is much bigger and takes
# longer to prepare, and we want to avoid creating it twice.
def ShouldUseNow( self, request_data = None ):
if not self._omnifunc:
return False
return super( OmniCompleter, self ).ShouldUseNowInner( start_column )
if not request_data:
request_data = _BuildRequestDataSubstitute()
def CandidatesForQueryAsync( self, query, unused_start_column ):
if self.ShouldUseCache():
return super( OmniCompleter, self ).CandidatesForQueryAsync(
query, unused_start_column )
return super( OmniCompleter, self ).ShouldUseNow( request_data )
return self.ShouldUseNowInner( request_data )
def ShouldUseNowInner( self, request_data ):
if not self._omnifunc:
return False
return super( OmniCompleter, self ).ShouldUseNowInner( request_data )
def ComputeCandidates( self, request_data ):
if self.ShouldUseCache():
return super( OmniCompleter, self ).ComputeCandidates(
request_data )
else:
return self.CandidatesForQueryAsyncInner( query, unused_start_column )
if self.ShouldUseNowInner( request_data ):
return self.ComputeCandidatesInner( request_data )
return []
def CandidatesForQueryAsyncInner( self, query, unused_start_column ):
if not self.omnifunc:
self.stored_candidates = None
return
def ComputeCandidatesInner( self, request_data ):
if not self._omnifunc:
return []
try:
return_value = int( vim.eval( self.omnifunc + '(1,"")' ) )
return_value = int( vim.eval( self._omnifunc + '(1,"")' ) )
if return_value < 0:
self.stored_candidates = None
return
return []
omnifunc_call = [ self.omnifunc,
omnifunc_call = [ self._omnifunc,
"(0,'",
vimsupport.EscapeForVim( query ),
vimsupport.EscapeForVim( request_data[ 'query' ] ),
"')" ]
items = vim.eval( ''.join( omnifunc_call ) )
@ -83,30 +94,20 @@ class OmniCompleter( Completer ):
if not hasattr( items, '__iter__' ):
raise TypeError( OMNIFUNC_NOT_LIST )
self.stored_candidates = filter( bool, items )
return filter( bool, items )
except ( TypeError, ValueError ) as error:
vimsupport.PostVimMessage(
OMNIFUNC_RETURNED_BAD_VALUE + ' ' + str( error ) )
self.stored_candidates = None
return
return []
def AsyncCandidateRequestReadyInner( self ):
return True
def OnFileReadyToParse( self, request_data ):
self._omnifunc = vim.eval( '&omnifunc' )
def OnFileReadyToParse( self ):
self.omnifunc = vim.eval( '&omnifunc' )
def _BuildRequestDataSubstitute():
data = BuildRequestData( include_buffer_data = False )
data[ 'start_column' ] = base.CompletionStartColumn()
return data
def CandidatesFromStoredRequest( self ):
if self.ShouldUseCache():
return super( OmniCompleter, self ).CandidatesFromStoredRequest()
else:
return self.CandidatesFromStoredRequestInner()
def CandidatesFromStoredRequestInner( self ):
return self.stored_candidates if self.stored_candidates else []

View File

@ -0,0 +1,127 @@
#!/usr/bin/env python
#
# Copyright (C) 2013 Strahinja Val Markovic <val@markovic.io>
#
# This file is part of YouCompleteMe.
#
# YouCompleteMe is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# YouCompleteMe is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with YouCompleteMe. If not, see <http://www.gnu.org/licenses/>.
from nose.tools import eq_
from ycm.completers.all import identifier_completer
def GetCursorIdentifier_StartOfLine_test():
eq_( 'foo',
identifier_completer._GetCursorIdentifier(
{
'column_num': 0,
'line_value': 'foo'
} ) )
eq_( 'fooBar',
identifier_completer._GetCursorIdentifier(
{
'column_num': 0,
'line_value': 'fooBar'
} ) )
def GetCursorIdentifier_EndOfLine_test():
eq_( 'foo',
identifier_completer._GetCursorIdentifier(
{
'column_num': 2,
'line_value': 'foo'
} ) )
def GetCursorIdentifier_PastEndOfLine_test():
eq_( '',
identifier_completer._GetCursorIdentifier(
{
'column_num': 10,
'line_value': 'foo'
} ) )
def GetCursorIdentifier_NegativeColumn_test():
eq_( '',
identifier_completer._GetCursorIdentifier(
{
'column_num': -10,
'line_value': 'foo'
} ) )
def GetCursorIdentifier_StartOfLine_StopsAtNonIdentifierChar_test():
eq_( 'foo',
identifier_completer._GetCursorIdentifier(
{
'column_num': 0,
'line_value': 'foo(goo)'
} ) )
def GetCursorIdentifier_AtNonIdentifier_test():
eq_( 'goo',
identifier_completer._GetCursorIdentifier(
{
'column_num': 3,
'line_value': 'foo(goo)'
} ) )
def GetCursorIdentifier_WalksForwardForIdentifier_test():
eq_( 'foo',
identifier_completer._GetCursorIdentifier(
{
'column_num': 0,
'line_value': ' foo'
} ) )
def GetCursorIdentifier_FindsNothingForward_test():
eq_( '',
identifier_completer._GetCursorIdentifier(
{
'column_num': 4,
'line_value': 'foo ()***()'
} ) )
def GetCursorIdentifier_SingleCharIdentifier_test():
eq_( 'f',
identifier_completer._GetCursorIdentifier(
{
'column_num': 0,
'line_value': ' f '
} ) )
def GetCursorIdentifier_StartsInMiddleOfIdentifier_test():
eq_( 'foobar',
identifier_completer._GetCursorIdentifier(
{
'column_num': 3,
'line_value': 'foobar'
} ) )
def GetCursorIdentifier_LineEmpty_test():
eq_( '',
identifier_completer._GetCursorIdentifier(
{
'column_num': 11,
'line_value': ''
} ) )

View File

@ -20,9 +20,9 @@
import ycm_core
from ycm.completers.cpp.clang_completer import ClangCompleter
def GetCompleter():
def GetCompleter( user_options ):
if ycm_core.HasClangSupport():
return ClangCompleter()
return ClangCompleter( user_options )
else:
return None

View File

@ -18,16 +18,12 @@
# along with YouCompleteMe. If not, see <http://www.gnu.org/licenses/>.
import abc
import vim
import ycm_core
from ycm import vimsupport
import ycm_client_support
from ycm.utils import ToUtf8IfNeeded, ForceSemanticCompletion
from ycm.completers.completer_utils import TriggersForFiletype
NO_USER_COMMANDS = 'This completer does not define any commands.'
MIN_NUM_CHARS = int( vimsupport.GetVariableValue(
"g:ycm_min_num_of_chars_for_completion" ) )
class Completer( object ):
"""A base class for all Completers in YCM.
@ -36,10 +32,11 @@ class Completer( object ):
calling on your Completer:
ShouldUseNow() is called with the start column of where a potential completion
string should start. For instance, if the user's input is 'foo.bar' and the
cursor is on the 'r' in 'bar', start_column will be the 0-based index of 'b'
in the line. Your implementation of ShouldUseNow() should return True if your
semantic completer should be used and False otherwise.
string should start and the current line (string) the cursor is on. For
instance, if the user's input is 'foo.bar' and the cursor is on the 'r' in
'bar', start_column will be the 0-based index of 'b' in the line. Your
implementation of ShouldUseNow() should return True if your semantic completer
should be used and False otherwise.
This is important to get right. You want to return False if you can't provide
completions because then the identifier completer will kick in, and that's
@ -61,44 +58,21 @@ class Completer( object ):
and will NOT re-query your completer but will in fact provide fuzzy-search on
the candidate strings that were stored in the cache.
CandidatesForQueryAsync() is the main entry point when the user types. For
ComputeCandidates() is the main entry point when the user types. For
"foo.bar", the user query is "bar" and completions matching this string should
be shown. The job of CandidatesForQueryAsync() is to merely initiate this
request, which will hopefully be processed in a background thread. You may
want to subclass ThreadedCompleter instead of Completer directly.
be shown. It should return the list of candidates. The format of the result
can be a list of strings or a more complicated list of dictionaries. Use
ycm.server.responses.BuildCompletionData to build the detailed response. See
clang_completer.py to see how its used in practice.
AsyncCandidateRequestReady() is the function that is repeatedly polled until
it returns True. If CandidatesForQueryAsync() started a background task of
collecting the required completions, AsyncCandidateRequestReady() would check
the state of that task and return False until it was completed.
CandidatesFromStoredRequest() should return the list of candidates. This is
what YCM calls after AsyncCandidateRequestReady() returns True. The format of
the result can be a list of strings or a more complicated list of
dictionaries. See ':h complete-items' for the format, and clang_completer.py
to see how its used in practice.
Again, you probably want to override ComputeCandidatesInner().
You also need to implement the SupportedFiletypes() function which should
return a list of strings, where the strings are Vim filetypes your completer
supports.
clang_completer.py is a good example of a "complicated" completer that
maintains its own internal cache and therefore directly overrides the "main"
functions in the API instead of the *Inner versions. A good example of a
simple completer that does not do this is omni_completer.py.
If you're confident your completer doesn't need a background task (think
again, you probably do) because you can "certainly" furnish a response in
under 10ms, then you can perform your backend processing in a synchronous
fashion. You may also need to do this because of technical restrictions (much
like omni_completer.py has to do it because accessing Vim internals is not
thread-safe). But even if you're certain, still try to do the processing in a
background thread. Your completer is unlikely to be merged if it does not,
because synchronous processing will block Vim's GUI thread and that's a very,
VERY bad thing (so try not to do it!). Again, you may want to subclass
ThreadedCompleter instead of Completer directly; ThreadedCompleter will
abstract away the use of a background thread for you. See
threaded_completer.py.
clang_completer.py is a good example of a "complicated" completer. A good
example of a simple completer is ultisnips_completer.py.
The On* functions are provided for your convenience. They are called when
their specific events occur. For instance, the identifier completer collects
@ -108,42 +82,48 @@ class Completer( object ):
One special function is OnUserCommand. It is called when the user uses the
command :YcmCompleter and is passed all extra arguments used on command
invocation (e.g. OnUserCommand(['first argument', 'second'])). This can be
used for completer-specific commands such as reloading external
configuration.
used for completer-specific commands such as reloading external configuration.
When the command is called with no arguments you should print a short summary
of the supported commands or point the user to the help section where this
information can be found."""
information can be found.
Override the Shutdown() member function if your Completer subclass needs to do
custom cleanup logic on server shutdown."""
__metaclass__ = abc.ABCMeta
def __init__( self ):
self.triggers_for_filetype = TriggersForFiletype()
def __init__( self, user_options ):
self.user_options = user_options
self.min_num_chars = user_options[ 'min_num_of_chars_for_completion' ]
self.triggers_for_filetype = TriggersForFiletype(
user_options[ 'semantic_triggers' ] )
self.completions_future = None
self.completions_cache = None
self.completion_start_column = None
self._completions_cache = None
# It's highly likely you DON'T want to override this function but the *Inner
# version of it.
def ShouldUseNow( self, start_column ):
inner_says_yes = self.ShouldUseNowInner( start_column )
def ShouldUseNow( self, request_data ):
inner_says_yes = self.ShouldUseNowInner( request_data )
if not inner_says_yes:
self.completions_cache = None
self._completions_cache = None
previous_results_were_empty = ( self.completions_cache and
self.completions_cache.CacheValid(
start_column ) and
not self.completions_cache.raw_completions )
previous_results_were_empty = ( self._completions_cache and
self._completions_cache.CacheValid(
request_data[ 'line_num' ],
request_data[ 'start_column' ] ) and
not self._completions_cache.raw_completions )
return inner_says_yes and not previous_results_were_empty
def ShouldUseNowInner( self, start_column ):
line = vim.current.line
line_length = len( line )
def ShouldUseNowInner( self, request_data ):
current_line = request_data[ 'line_value' ]
start_column = request_data[ 'start_column' ]
line_length = len( current_line )
if not line_length or start_column - 1 >= line_length:
return False
filetype = self._CurrentFiletype()
filetype = self._CurrentFiletype( request_data[ 'filetypes' ] )
triggers = self.triggers_for_filetype[ filetype ]
for trigger in triggers:
@ -151,7 +131,7 @@ class Completer( object ):
trigger_length = len( trigger )
while True:
line_index = start_column + index
if line_index < 0 or line[ line_index ] != trigger[ index ]:
if line_index < 0 or current_line[ line_index ] != trigger[ index ]:
break
if abs( index ) == trigger_length:
@ -160,154 +140,113 @@ class Completer( object ):
return False
def QueryLengthAboveMinThreshold( self, start_column ):
query_length = vimsupport.CurrentColumn() - start_column
return query_length >= MIN_NUM_CHARS
def QueryLengthAboveMinThreshold( self, request_data ):
query_length = request_data[ 'column_num' ] - request_data[ 'start_column' ]
return query_length >= self.min_num_chars
# It's highly likely you DON'T want to override this function but the *Inner
# version of it.
def CandidatesForQueryAsync( self, query, start_column ):
self.completion_start_column = start_column
def ComputeCandidates( self, request_data ):
if ( not ForceSemanticCompletion( request_data ) and
not self.ShouldUseNow( request_data ) ):
return []
if query and self.completions_cache and self.completions_cache.CacheValid(
start_column ):
self.completions_cache.filtered_completions = (
self.FilterAndSortCandidates(
self.completions_cache.raw_completions,
query ) )
candidates = self._GetCandidatesFromSubclass( request_data )
if request_data[ 'query' ]:
candidates = self.FilterAndSortCandidates( candidates,
request_data[ 'query' ] )
return candidates
def _GetCandidatesFromSubclass( self, request_data ):
if ( self._completions_cache and
self._completions_cache.CacheValid( request_data[ 'line_num' ],
request_data[ 'start_column' ] ) ):
return self._completions_cache.raw_completions
else:
self.completions_cache = None
self.CandidatesForQueryAsyncInner( query, start_column )
self._completions_cache = CompletionsCache()
self._completions_cache.raw_completions = self.ComputeCandidatesInner(
request_data )
self._completions_cache.line = request_data[ 'line_num' ]
self._completions_cache.column = request_data[ 'start_column' ]
return self._completions_cache.raw_completions
def ComputeCandidatesInner( self, request_data ):
pass
def DefinedSubcommands( self ):
return []
def EchoUserCommandsHelpMessage( self ):
def UserCommandsHelpMessage( self ):
subcommands = self.DefinedSubcommands()
if subcommands:
vimsupport.EchoText( 'Supported commands are:\n' +
return ( 'Supported commands are:\n' +
'\n'.join( subcommands ) +
'\nSee the docs for information on what they do.' )
else:
vimsupport.EchoText( 'No supported subcommands' )
return 'This Completer has no supported subcommands.'
def FilterAndSortCandidates( self, candidates, query ):
if not candidates:
return []
if hasattr( candidates, 'words' ):
candidates = candidates.words
items_are_objects = 'word' in candidates[ 0 ]
# We need to handle both an omni_completer style completer and a server
# style completer
if 'words' in candidates:
candidates = candidates[ 'words' ]
matches = ycm_core.FilterAndSortCandidates(
sort_property = ''
if 'word' in candidates[ 0 ]:
sort_property = 'word'
elif 'insertion_text' in candidates[ 0 ]:
sort_property = 'insertion_text'
matches = ycm_client_support.FilterAndSortCandidates(
candidates,
'word' if items_are_objects else '',
query )
sort_property,
ToUtf8IfNeeded( query ) )
return matches
def CandidatesForQueryAsyncInner( self, query, start_column ):
def OnFileReadyToParse( self, request_data ):
pass
# It's highly likely you DON'T want to override this function but the *Inner
# version of it.
def AsyncCandidateRequestReady( self ):
if self.completions_cache:
return True
else:
return self.AsyncCandidateRequestReadyInner()
def AsyncCandidateRequestReadyInner( self ):
if not self.completions_future:
# We return True so that the caller can extract the default value from the
# future
return True
return self.completions_future.ResultsReady()
# It's highly likely you DON'T want to override this function but the *Inner
# version of it.
def CandidatesFromStoredRequest( self ):
if self.completions_cache:
return self.completions_cache.filtered_completions
else:
self.completions_cache = CompletionsCache()
self.completions_cache.raw_completions = self.CandidatesFromStoredRequestInner()
self.completions_cache.line, _ = vimsupport.CurrentLineAndColumn()
self.completions_cache.column = self.completion_start_column
return self.completions_cache.raw_completions
def CandidatesFromStoredRequestInner( self ):
if not self.completions_future:
return []
return self.completions_future.GetResults()
def OnFileReadyToParse( self ):
def OnBufferVisit( self, request_data ):
pass
def OnCursorMovedInsertMode( self ):
def OnBufferUnload( self, request_data ):
pass
def OnCursorMovedNormalMode( self ):
def OnInsertLeave( self, request_data ):
pass
def OnBufferVisit( self ):
def OnUserCommand( self, arguments, request_data ):
raise NotImplementedError( NO_USER_COMMANDS )
def OnCurrentIdentifierFinished( self, request_data ):
pass
def OnBufferUnload( self, deleted_buffer_file ):
pass
def OnCursorHold( self ):
pass
def OnInsertLeave( self ):
pass
def OnVimLeave( self ):
pass
def OnUserCommand( self, arguments ):
vimsupport.PostVimMessage( NO_USER_COMMANDS )
def OnCurrentIdentifierFinished( self ):
pass
def DiagnosticsForCurrentFileReady( self ):
return False
def GetDiagnosticsForCurrentFile( self ):
def GetDiagnosticsForCurrentFile( self, request_data ):
return []
def ShowDetailedDiagnostic( self ):
def GetDetailedDiagnostic( self, request_data ):
pass
def GettingCompletions( self ):
return False
def _CurrentFiletype( self ):
filetypes = vimsupport.CurrentFiletypes()
def _CurrentFiletype( self, filetypes ):
supported = self.SupportedFiletypes()
for filetype in filetypes:
@ -322,10 +261,14 @@ class Completer( object ):
pass
def DebugInfo( self ):
def DebugInfo( self, request_data ):
return ''
def Shutdown( self ):
pass
class CompletionsCache( object ):
def __init__( self ):
self.line = -1
@ -334,9 +277,7 @@ class CompletionsCache( object ):
self.filtered_completions = []
def CacheValid( self, start_column ):
completion_line, _ = vimsupport.CurrentLineAndColumn()
completion_column = start_column
return completion_line == self.line and completion_column == self.column
def CacheValid( self, current_line, start_column ):
return current_line == self.line and start_column == self.column

View File

@ -19,7 +19,7 @@
from collections import defaultdict
from copy import deepcopy
import vim
import os
DEFAULT_FILETYPE_TRIGGERS = {
'c' : ['->', '.'],
@ -58,12 +58,21 @@ def _FiletypeDictUnion( dict_one, dict_two ):
return final_dict
def TriggersForFiletype():
user_triggers = _FiletypeTriggerDictFromSpec(
vim.eval( 'g:ycm_semantic_triggers' ) )
def TriggersForFiletype( user_triggers ):
default_triggers = _FiletypeTriggerDictFromSpec(
DEFAULT_FILETYPE_TRIGGERS )
return _FiletypeDictUnion( default_triggers, user_triggers )
return _FiletypeDictUnion( default_triggers, dict( user_triggers ) )
def _PathToCompletersFolder():
dir_of_current_script = os.path.dirname( os.path.abspath( __file__ ) )
return os.path.join( dir_of_current_script )
def PathToFiletypeCompleterPluginLoader( filetype ):
return os.path.join( _PathToCompletersFolder(), filetype, 'hook.py' )
def FiletypeCompleterExistsForFiletype( filetype ):
return os.path.exists( PathToFiletypeCompleterPluginLoader( filetype ) )

View File

@ -18,116 +18,84 @@
# along with YouCompleteMe. If not, see <http://www.gnu.org/licenses/>.
from collections import defaultdict
import vim
import ycm_core
from ycm import vimsupport
from ycm.server import responses
from ycm import extra_conf_store
from ycm.utils import ToUtf8IfNeeded
from ycm.completers.completer import Completer
from ycm.completers.cpp.flags import Flags
from ycm.completers.cpp.flags import Flags, PrepareFlagsForClang
CLANG_FILETYPES = set( [ 'c', 'cpp', 'objc', 'objcpp' ] )
MAX_DIAGNOSTICS_TO_DISPLAY = int( vimsupport.GetVariableValue(
"g:ycm_max_diagnostics_to_display" ) )
MIN_LINES_IN_FILE_TO_PARSE = 5
PARSING_FILE_MESSAGE = 'Still parsing file, no completions yet.'
NO_COMPILE_FLAGS_MESSAGE = 'Still no compile flags, no completions yet.'
INVALID_FILE_MESSAGE = 'File is invalid.'
NO_COMPLETIONS_MESSAGE = 'No completions found; errors in the file?'
FILE_TOO_SHORT_MESSAGE = (
'File is less than {0} lines long; not compiling.'.format(
MIN_LINES_IN_FILE_TO_PARSE ) )
NO_DIAGNOSTIC_MESSAGE = 'No diagnostic for current line!'
class ClangCompleter( Completer ):
def __init__( self ):
super( ClangCompleter, self ).__init__()
self.completer = ycm_core.ClangCompleter()
self.completer.EnableThreading()
self.contents_holder = []
self.filename_holder = []
self.last_prepared_diagnostics = []
self.parse_future = None
self.flags = Flags()
self.diagnostic_store = None
# We set this flag when a compilation request comes in while one is already
# in progress. We use this to trigger the pending request after the previous
# one completes (from GetDiagnosticsForCurrentFile because that's the only
# method that knows when the compilation has finished).
self.extra_parse_desired = False
def __init__( self, user_options ):
super( ClangCompleter, self ).__init__( user_options )
self._max_diagnostics_to_display = user_options[
'max_diagnostics_to_display' ]
self._completer = ycm_core.ClangCompleter()
self._flags = Flags()
self._diagnostic_store = None
def SupportedFiletypes( self ):
return CLANG_FILETYPES
def GetUnsavedFilesVector( self ):
# CAREFUL HERE! For UnsavedFile filename and contents we are referring
# directly to Python-allocated and -managed memory since we are accepting
# pointers to data members of python objects. We need to ensure that those
# objects outlive our UnsavedFile objects. This is why we need the
# contents_holder and filename_holder lists, to make sure the string objects
# are still around when we call CandidatesForQueryAndLocationInFile. We do
# this to avoid an extra copy of the entire file contents.
def GetUnsavedFilesVector( self, request_data ):
files = ycm_core.UnsavedFileVec()
self.contents_holder = []
self.filename_holder = []
for buffer in vimsupport.GetUnsavedBuffers():
if not ClangAvailableForBuffer( buffer ):
for filename, file_data in request_data[ 'file_data' ].iteritems():
if not ClangAvailableForFiletypes( file_data[ 'filetypes' ] ):
continue
contents = '\n'.join( buffer )
name = buffer.name
if not contents or not name:
contents = file_data[ 'contents' ]
if not contents or not filename:
continue
self.contents_holder.append( contents )
self.filename_holder.append( name )
unsaved_file = ycm_core.UnsavedFile()
unsaved_file.contents_ = self.contents_holder[ -1 ]
unsaved_file.length_ = len( self.contents_holder[ -1 ] )
unsaved_file.filename_ = self.filename_holder[ -1 ]
utf8_contents = ToUtf8IfNeeded( contents )
unsaved_file.contents_ = utf8_contents
unsaved_file.length_ = len( utf8_contents )
unsaved_file.filename_ = ToUtf8IfNeeded( filename )
files.append( unsaved_file )
return files
def CandidatesForQueryAsync( self, query, start_column ):
filename = vim.current.buffer.name
def ComputeCandidatesInner( self, request_data ):
filename = request_data[ 'filepath' ]
if not filename:
return
if self.completer.UpdatingTranslationUnit( filename ):
vimsupport.PostVimMessage( 'Still parsing file, no completions yet.' )
self.completions_future = None
return
if self._completer.UpdatingTranslationUnit( ToUtf8IfNeeded( filename ) ):
raise RuntimeError( PARSING_FILE_MESSAGE )
flags = self.flags.FlagsForFile( filename )
flags = self._FlagsForRequest( request_data )
if not flags:
vimsupport.PostVimMessage( 'Still no compile flags, no completions yet.' )
self.completions_future = None
return
raise RuntimeError( NO_COMPILE_FLAGS_MESSAGE )
# TODO: sanitize query, probably in C++ code
files = ycm_core.UnsavedFileVec()
if not query:
files = self.GetUnsavedFilesVector()
line, _ = vim.current.window.cursor
column = start_column + 1
self.completions_future = (
self.completer.CandidatesForQueryAndLocationInFileAsync(
query,
filename,
files = self.GetUnsavedFilesVector( request_data )
line = request_data[ 'line_num' ] + 1
column = request_data[ 'start_column' ] + 1
results = self._completer.CandidatesForLocationInFile(
ToUtf8IfNeeded( filename ),
line,
column,
files,
flags ) )
flags )
def CandidatesFromStoredRequest( self ):
if not self.completions_future:
return []
results = [ CompletionDataToDict( x ) for x in
self.completions_future.GetResults() ]
if not results:
vimsupport.PostVimMessage( 'No completions found; errors in the file?' )
return results
raise RuntimeError( NO_COMPLETIONS_MESSAGE )
return [ ConvertCompletionData( x ) for x in results ]
def DefinedSubcommands( self ):
@ -137,157 +105,118 @@ class ClangCompleter( Completer ):
'ClearCompilationFlagCache']
def OnUserCommand( self, arguments ):
def OnUserCommand( self, arguments, request_data ):
if not arguments:
self.EchoUserCommandsHelpMessage()
return
raise ValueError( self.UserCommandsHelpMessage() )
command = arguments[ 0 ]
if command == 'GoToDefinition':
self._GoToDefinition()
return self._GoToDefinition( request_data )
elif command == 'GoToDeclaration':
self._GoToDeclaration()
return self._GoToDeclaration( request_data )
elif command == 'GoToDefinitionElseDeclaration':
self._GoToDefinitionElseDeclaration()
return self._GoToDefinitionElseDeclaration( request_data )
elif command == 'ClearCompilationFlagCache':
self._ClearCompilationFlagCache()
return self._ClearCompilationFlagCache()
raise ValueError( self.UserCommandsHelpMessage() )
def _LocationForGoTo( self, goto_function ):
filename = vim.current.buffer.name
def _LocationForGoTo( self, goto_function, request_data ):
filename = request_data[ 'filepath' ]
if not filename:
return None
raise ValueError( INVALID_FILE_MESSAGE )
flags = self.flags.FlagsForFile( filename )
flags = self._FlagsForRequest( request_data )
if not flags:
vimsupport.PostVimMessage( 'Still no compile flags, can\'t compile.' )
return None
raise ValueError( NO_COMPILE_FLAGS_MESSAGE )
files = self.GetUnsavedFilesVector()
line, column = vimsupport.CurrentLineAndColumn()
# Making the line & column 1-based instead of 0-based
line += 1
column += 1
return getattr( self.completer, goto_function )(
filename,
files = self.GetUnsavedFilesVector( request_data )
line = request_data[ 'line_num' ] + 1
column = request_data[ 'column_num' ] + 1
return getattr( self._completer, goto_function )(
ToUtf8IfNeeded( filename ),
line,
column,
files,
flags )
def _GoToDefinition( self ):
location = self._LocationForGoTo( 'GetDefinitionLocation' )
def _GoToDefinition( self, request_data ):
location = self._LocationForGoTo( 'GetDefinitionLocation', request_data )
if not location or not location.IsValid():
vimsupport.PostVimMessage( 'Can\'t jump to definition.' )
return
raise RuntimeError( 'Can\'t jump to definition.' )
vimsupport.JumpToLocation( location.filename_,
location.line_number_,
location.column_number_ )
return responses.BuildGoToResponse( location.filename_,
location.line_number_ - 1,
location.column_number_ - 1)
def _GoToDeclaration( self ):
location = self._LocationForGoTo( 'GetDeclarationLocation' )
def _GoToDeclaration( self, request_data ):
location = self._LocationForGoTo( 'GetDeclarationLocation', request_data )
if not location or not location.IsValid():
vimsupport.PostVimMessage( 'Can\'t jump to declaration.' )
return
raise RuntimeError( 'Can\'t jump to declaration.' )
vimsupport.JumpToLocation( location.filename_,
location.line_number_,
location.column_number_ )
return responses.BuildGoToResponse( location.filename_,
location.line_number_ - 1,
location.column_number_ - 1)
def _GoToDefinitionElseDeclaration( self ):
location = self._LocationForGoTo( 'GetDefinitionLocation' )
def _GoToDefinitionElseDeclaration( self, request_data ):
location = self._LocationForGoTo( 'GetDefinitionLocation', request_data )
if not location or not location.IsValid():
location = self._LocationForGoTo( 'GetDeclarationLocation' )
location = self._LocationForGoTo( 'GetDeclarationLocation', request_data )
if not location or not location.IsValid():
vimsupport.PostVimMessage( 'Can\'t jump to definition or declaration.' )
return
raise RuntimeError( 'Can\'t jump to definition or declaration.' )
return responses.BuildGoToResponse( location.filename_,
location.line_number_ - 1,
location.column_number_ - 1)
vimsupport.JumpToLocation( location.filename_,
location.line_number_,
location.column_number_ )
def _ClearCompilationFlagCache( self ):
self.flags.Clear()
self._flags.Clear()
def OnFileReadyToParse( self, request_data ):
filename = request_data[ 'filepath' ]
contents = request_data[ 'file_data' ][ filename ][ 'contents' ]
if contents.count( '\n' ) < MIN_LINES_IN_FILE_TO_PARSE:
raise ValueError( FILE_TOO_SHORT_MESSAGE )
def OnFileReadyToParse( self ):
if vimsupport.NumLinesInBuffer( vim.current.buffer ) < 5:
self.parse_future = None
return
filename = vim.current.buffer.name
if not filename:
return
raise ValueError( INVALID_FILE_MESSAGE )
if self.completer.UpdatingTranslationUnit( filename ):
self.extra_parse_desired = True
return
flags = self.flags.FlagsForFile( filename )
flags = self._FlagsForRequest( request_data )
if not flags:
self.parse_future = None
return
raise ValueError( NO_COMPILE_FLAGS_MESSAGE )
self.parse_future = self.completer.UpdateTranslationUnitAsync(
filename,
self.GetUnsavedFilesVector(),
diagnostics = self._completer.UpdateTranslationUnit(
ToUtf8IfNeeded( filename ),
self.GetUnsavedFilesVector( request_data ),
flags )
self.extra_parse_desired = False
self._diagnostic_store = DiagnosticsToDiagStructure( diagnostics )
return [ ConvertToDiagnosticResponse( x ) for x in
diagnostics[ : self._max_diagnostics_to_display ] ]
def OnBufferUnload( self, deleted_buffer_file ):
self.completer.DeleteCachesForFileAsync( deleted_buffer_file )
def OnBufferUnload( self, request_data ):
self._completer.DeleteCachesForFile(
ToUtf8IfNeeded( request_data[ 'unloaded_buffer' ] ) )
def DiagnosticsForCurrentFileReady( self ):
if not self.parse_future:
return False
def GetDetailedDiagnostic( self, request_data ):
current_line = request_data[ 'line_num' ] + 1
current_column = request_data[ 'column_num' ] + 1
current_file = request_data[ 'filepath' ]
return self.parse_future.ResultsReady()
if not self._diagnostic_store:
raise ValueError( NO_DIAGNOSTIC_MESSAGE )
def GettingCompletions( self ):
return self.completer.UpdatingTranslationUnit( vim.current.buffer.name )
def GetDiagnosticsForCurrentFile( self ):
if self.DiagnosticsForCurrentFileReady():
diagnostics = self.completer.DiagnosticsForFile( vim.current.buffer.name )
self.diagnostic_store = DiagnosticsToDiagStructure( diagnostics )
self.last_prepared_diagnostics = [ DiagnosticToDict( x ) for x in
diagnostics[ : MAX_DIAGNOSTICS_TO_DISPLAY ] ]
self.parse_future = None
if self.extra_parse_desired:
self.OnFileReadyToParse()
return self.last_prepared_diagnostics
def ShowDetailedDiagnostic( self ):
current_line, current_column = vimsupport.CurrentLineAndColumn()
# CurrentLineAndColumn() numbers are 0-based, clang numbers are 1-based
current_line += 1
current_column += 1
current_file = vim.current.buffer.name
if not self.diagnostic_store:
vimsupport.PostVimMessage( "No diagnostic for current line!" )
return
diagnostics = self.diagnostic_store[ current_file ][ current_line ]
diagnostics = self._diagnostic_store[ current_file ][ current_line ]
if not diagnostics:
vimsupport.PostVimMessage( "No diagnostic for current line!" )
return
raise ValueError( NO_DIAGNOSTIC_MESSAGE )
closest_diagnostic = None
distance_to_closest_diagnostic = 999
@ -298,50 +227,36 @@ class ClangCompleter( Completer ):
distance_to_closest_diagnostic = distance
closest_diagnostic = diagnostic
vimsupport.EchoText( closest_diagnostic.long_formatted_text_ )
return responses.BuildDisplayMessageResponse(
closest_diagnostic.long_formatted_text_ )
def ShouldUseNow( self, start_column ):
# We don't want to use the Completer API cache, we use one in the C++ code.
return self.ShouldUseNowInner( start_column )
def DebugInfo( self ):
filename = vim.current.buffer.name
def DebugInfo( self, request_data ):
filename = request_data[ 'filepath' ]
if not filename:
return ''
flags = self.flags.FlagsForFile( filename ) or []
flags = self._FlagsForRequest( request_data ) or []
source = extra_conf_store.ModuleFileForSourceFile( filename )
return 'Flags for {0} loaded from {1}:\n{2}'.format( filename,
source,
list( flags ) )
# TODO: make these functions module-local
def CompletionDataToDict( completion_data ):
# see :h complete-items for a description of the dictionary fields
return {
'word' : completion_data.TextToInsertInBuffer(),
'abbr' : completion_data.MainCompletionText(),
'menu' : completion_data.ExtraMenuInfo(),
'kind' : completion_data.kind_,
'info' : completion_data.DetailedInfoForPreviewWindow(),
'dup' : 1,
}
def _FlagsForRequest( self, request_data ):
filename = request_data[ 'filepath' ]
if 'compilation_flags' in request_data:
return PrepareFlagsForClang( request_data[ 'compilation_flags' ],
filename )
return self._flags.FlagsForFile( filename )
def DiagnosticToDict( diagnostic ):
# see :h getqflist for a description of the dictionary fields
return {
# TODO: wrap the bufnr generation into a function
'bufnr' : int( vim.eval( "bufnr('{0}', 1)".format(
diagnostic.filename_ ) ) ),
'lnum' : diagnostic.line_number_,
'col' : diagnostic.column_number_,
'text' : diagnostic.text_,
'type' : diagnostic.kind_,
'valid' : 1
}
def ConvertCompletionData( completion_data ):
return responses.BuildCompletionData(
insertion_text = completion_data.TextToInsertInBuffer(),
menu_text = completion_data.MainCompletionText(),
extra_menu_info = completion_data.ExtraMenuInfo(),
kind = completion_data.kind_,
detailed_info = completion_data.DetailedInfoForPreviewWindow() )
def DiagnosticsToDiagStructure( diagnostics ):
@ -352,12 +267,19 @@ def DiagnosticsToDiagStructure( diagnostics ):
return structure
def ClangAvailableForBuffer( buffer_object ):
filetypes = vimsupport.FiletypesForBuffer( buffer_object )
def ClangAvailableForFiletypes( filetypes ):
return any( [ filetype in CLANG_FILETYPES for filetype in filetypes ] )
def InCFamilyFile():
return any( [ filetype in CLANG_FILETYPES for filetype in
vimsupport.CurrentFiletypes() ] )
def InCFamilyFile( filetypes ):
return ClangAvailableForFiletypes( filetypes )
def ConvertToDiagnosticResponse( diagnostic ):
return responses.BuildDiagnosticData( diagnostic.filename_,
diagnostic.line_number_ - 1,
diagnostic.column_number_ - 1,
diagnostic.text_,
diagnostic.kind_ )

View File

@ -19,8 +19,8 @@
import ycm_core
import os
from ycm import vimsupport
from ycm import extra_conf_store
from ycm.utils import ToUtf8IfNeeded
NO_EXTRA_CONF_FILENAME_MESSAGE = ( 'No {0} file detected, so no compile flags '
'are available. Thus no semantic support for C/C++/ObjC/ObjC++. Go READ THE '
@ -47,8 +47,8 @@ class Flags( object ):
module = extra_conf_store.ModuleForSourceFile( filename )
if not module:
if not self.no_extra_conf_file_warning_posted:
vimsupport.PostVimMessage( NO_EXTRA_CONF_FILENAME_MESSAGE )
self.no_extra_conf_file_warning_posted = True
raise RuntimeError( NO_EXTRA_CONF_FILENAME_MESSAGE )
return None
results = module.FlagsForFile( filename )
@ -59,7 +59,7 @@ class Flags( object ):
flags = list( results[ 'flags' ] )
if add_special_clang_flags:
flags += self.special_clang_flags
sanitized_flags = _PrepareFlagsForClang( flags, filename )
sanitized_flags = PrepareFlagsForClang( flags, filename )
if results[ 'do_cache' ]:
self.flags_for_file[ filename ] = sanitized_flags
@ -95,7 +95,7 @@ class Flags( object ):
self.flags_for_file.clear()
def _PrepareFlagsForClang( flags, filename ):
def PrepareFlagsForClang( flags, filename ):
flags = _RemoveUnusedFlags( flags, filename )
flags = _SanitizeFlags( flags )
return flags
@ -121,7 +121,7 @@ def _SanitizeFlags( flags ):
vector = ycm_core.StringVec()
for flag in sanitized_flags:
vector.append( flag )
vector.append( ToUtf8IfNeeded( flag ) )
return vector

View File

@ -20,9 +20,9 @@
import ycm_core
from ycm.completers.cpp.clang_completer import ClangCompleter
def GetCompleter():
def GetCompleter( user_options ):
if ycm_core.HasClangSupport():
return ClangCompleter()
return ClangCompleter( user_options )
else:
return None

View File

@ -18,39 +18,37 @@
# You should have received a copy of the GNU General Public License
# along with YouCompleteMe. If not, see <http://www.gnu.org/licenses/>.
import vim
import os
from sys import platform
import glob
from ycm.completers.threaded_completer import ThreadedCompleter
from ycm import vimsupport
from ycm.completers.completer import Completer
from ycm.server import responses
from ycm import utils
import urllib2
import urllib
import urlparse
import json
import subprocess
import tempfile
import logging
SERVER_NOT_FOUND_MSG = ( 'OmniSharp server binary not found at {0}. ' +
'Did you compile it? You can do so by running ' +
'"./install.sh --omnisharp-completer".' )
class CsharpCompleter( ThreadedCompleter ):
class CsharpCompleter( Completer ):
"""
A Completer that uses the Omnisharp server as completion engine.
"""
def __init__( self ):
super( CsharpCompleter, self ).__init__()
def __init__( self, user_options ):
super( CsharpCompleter, self ).__init__( user_options )
self._omnisharp_port = None
if vimsupport.GetBoolValue( 'g:ycm_auto_start_csharp_server' ):
self._StartServer()
self._logger = logging.getLogger( __name__ )
def OnVimLeave( self ):
if ( vimsupport.GetBoolValue( 'g:ycm_auto_stop_csharp_server' ) and
def Shutdown( self ):
if ( self.user_options[ 'auto_start_csharp_server' ] and
self._ServerIsRunning() ):
self._StopServer()
@ -60,79 +58,83 @@ class CsharpCompleter( ThreadedCompleter ):
return [ 'cs' ]
def ComputeCandidates( self, unused_query, unused_start_column ):
return [ { 'word': str( completion[ 'CompletionText' ] ),
'menu': str( completion[ 'DisplayText' ] ),
'info': str( completion[ 'Description' ] ) }
for completion in self._GetCompletions() ]
def ComputeCandidatesInner( self, request_data ):
return [ responses.BuildCompletionData(
completion[ 'CompletionText' ],
completion[ 'DisplayText' ],
completion[ 'Description' ] )
for completion in self._GetCompletions( request_data ) ]
def DefinedSubcommands( self ):
return [ 'StartServer',
'StopServer',
'RestartServer',
'ServerRunning',
'GoToDefinition',
'GoToDeclaration',
'GoToDefinitionElseDeclaration' ]
def OnUserCommand( self, arguments ):
def OnFileReadyToParse( self, request_data ):
if ( not self._omnisharp_port and
self.user_options[ 'auto_start_csharp_server' ] ):
self._StartServer( request_data )
def OnUserCommand( self, arguments, request_data ):
if not arguments:
self.EchoUserCommandsHelpMessage()
return
raise ValueError( self.UserCommandsHelpMessage() )
command = arguments[ 0 ]
if command == 'StartServer':
self._StartServer()
self._StartServer( request_data )
elif command == 'StopServer':
self._StopServer()
elif command == 'RestartServer':
if self._ServerIsRunning():
self._StopServer()
self._StartServer()
self._StartServer( request_data )
elif command == 'ServerRunning':
return self._ServerIsRunning()
elif command in [ 'GoToDefinition',
'GoToDeclaration',
'GoToDefinitionElseDeclaration' ]:
self._GoToDefinition()
return self._GoToDefinition( request_data )
raise ValueError( self.UserCommandsHelpMessage() )
def DebugInfo( self ):
if self._ServerIsRunning():
return 'Server running at: {}\nLogfiles:\n{}\n{}'.format(
self._PortToHost(), self._filename_stdout, self._filename_stderr )
return 'Server running at: {0}\nLogfiles:\n{1}\n{2}'.format(
self._ServerLocation(), self._filename_stdout, self._filename_stderr )
else:
return 'Server is not running'
def _StartServer( self ):
def _StartServer( self, request_data ):
""" Start the OmniSharp server """
self._omnisharp_port = self._FindFreePort()
solutionfiles, folder = _FindSolutionFiles()
self._logger.info( 'startup' )
self._omnisharp_port = utils.GetUnusedLocalhostPort()
solutionfiles, folder = _FindSolutionFiles( request_data[ 'filepath' ] )
if len( solutionfiles ) == 0:
vimsupport.PostVimMessage(
raise RuntimeError(
'Error starting OmniSharp server: no solutionfile found' )
return
elif len( solutionfiles ) == 1:
solutionfile = solutionfiles[ 0 ]
else:
choice = vimsupport.PresentDialog(
'Which solutionfile should be loaded?',
[ str( i ) + " " + solution for i, solution in
enumerate( solutionfiles ) ] )
if choice == -1:
vimsupport.PostVimMessage( 'OmniSharp not started' )
return
else:
solutionfile = solutionfiles[ choice ]
raise RuntimeError(
'Found multiple solution files instead of one!\n{0}'.format(
solutionfiles ) )
omnisharp = os.path.join(
os.path.abspath( os.path.dirname( __file__ ) ),
'OmniSharpServer/OmniSharp/bin/Debug/OmniSharp.exe' )
if not os.path.isfile( omnisharp ):
vimsupport.PostVimMessage( SERVER_NOT_FOUND_MSG.format( omnisharp ) )
return
raise RuntimeError( SERVER_NOT_FOUND_MSG.format( omnisharp ) )
if not platform.startswith( 'win' ):
omnisharp = 'mono ' + omnisharp
@ -142,8 +144,8 @@ class CsharpCompleter( ThreadedCompleter ):
command = [ omnisharp + ' -p ' + str( self._omnisharp_port ) + ' -s ' +
path_to_solutionfile ]
filename_format = ( tempfile.gettempdir() +
'/omnisharp_{port}_{sln}_{std}.log' )
filename_format = os.path.join( utils.PathToTempDir(),
'omnisharp_{port}_{sln}_{std}.log' )
self._filename_stdout = filename_format.format(
port=self._omnisharp_port, sln=solutionfile, std='stdout' )
@ -154,82 +156,72 @@ class CsharpCompleter( ThreadedCompleter ):
with open( self._filename_stdout, 'w' ) as fstdout:
subprocess.Popen( command, stdout=fstdout, stderr=fstderr, shell=True )
vimsupport.PostVimMessage( 'Starting OmniSharp server' )
self._logger.info( 'Starting OmniSharp server' )
def _StopServer( self ):
""" Stop the OmniSharp server """
self._GetResponse( '/stopserver' )
self._omnisharp_port = None
vimsupport.PostVimMessage( 'Stopping OmniSharp server' )
self._logger.info( 'Stopping OmniSharp server' )
def _GetCompletions( self ):
def _GetCompletions( self, request_data ):
""" Ask server for completions """
completions = self._GetResponse( '/autocomplete', self._DefaultParameters() )
completions = self._GetResponse( '/autocomplete',
self._DefaultParameters( request_data ) )
return completions if completions != None else []
def _GoToDefinition( self ):
def _GoToDefinition( self, request_data ):
""" Jump to definition of identifier under cursor """
definition = self._GetResponse( '/gotodefinition', self._DefaultParameters() )
definition = self._GetResponse( '/gotodefinition',
self._DefaultParameters( request_data ) )
if definition[ 'FileName' ] != None:
vimsupport.JumpToLocation( definition[ 'FileName' ],
return responses.BuildGoToResponse( definition[ 'FileName' ],
definition[ 'Line' ],
definition[ 'Column' ] )
else:
vimsupport.PostVimMessage( 'Can\'t jump to definition' )
raise RuntimeError( 'Can\'t jump to definition' )
def _DefaultParameters( self ):
def _DefaultParameters( self, request_data ):
""" Some very common request parameters """
line, column = vimsupport.CurrentLineAndColumn()
parameters = {}
parameters[ 'line' ], parameters[ 'column' ] = line + 1, column + 1
parameters[ 'buffer' ] = '\n'.join( vim.current.buffer )
parameters[ 'filename' ] = vim.current.buffer.name
parameters[ 'line' ] = request_data[ 'line_num' ] + 1
parameters[ 'column' ] = request_data[ 'column_num' ] + 1
filepath = request_data[ 'filepath' ]
parameters[ 'buffer' ] = request_data[ 'file_data' ][ filepath ][
'contents' ]
parameters[ 'filename' ] = filepath
return parameters
def _ServerIsRunning( self ):
""" Check if our OmniSharp server is running """
return ( self._omnisharp_port != None and
self._GetResponse( '/checkalivestatus', silent=True ) != None )
def _FindFreePort( self ):
""" Find port without an OmniSharp server running on it """
port = int( vimsupport.GetVariableValue(
'g:ycm_csharp_server_port' ) )
while self._GetResponse( '/checkalivestatus',
silent=True,
port=port ) != None:
port += 1
return port
def _PortToHost( self, port=None ):
if port == None:
port = self._omnisharp_port
return 'http://localhost:' + str( port )
def _GetResponse( self, endPoint, parameters={}, silent=False, port=None ):
""" Handle communication with server """
target = urlparse.urljoin( self._PortToHost( port ), endPoint )
parameters = urllib.urlencode( parameters )
try:
return bool( self._omnisharp_port and
self._GetResponse( '/checkalivestatus', silent = True ) )
except:
return False
def _ServerLocation( self ):
return 'http://localhost:' + str( self._omnisharp_port )
def _GetResponse( self, handler, parameters = {}, silent = False ):
""" Handle communication with server """
# TODO: Replace usage of urllib with Requests
target = urlparse.urljoin( self._ServerLocation(), handler )
parameters = urllib.urlencode( parameters )
response = urllib2.urlopen( target, parameters )
return json.loads( response.read() )
except Exception:
# TODO: Add logging for this case. We can't post a Vim message because Vim
# crashes when that's done from a no-GUI thread.
return None
def _FindSolutionFiles():
def _FindSolutionFiles( filepath ):
""" Find solution files by searching upwards in the file tree """
folder = os.path.dirname( vim.current.buffer.name )
folder = os.path.dirname( filepath )
solutionfiles = glob.glob1( folder, '*.sln' )
while not solutionfiles:
lastfolder = folder

View File

@ -17,5 +17,5 @@
from ycm.completers.cs.cs_completer import CsharpCompleter
def GetCompleter():
return CsharpCompleter()
def GetCompleter( user_options ):
return CsharpCompleter( user_options )

View File

@ -16,26 +16,21 @@
# You should have received a copy of the GNU General Public License
# along with YouCompleteMe. If not, see <http://www.gnu.org/licenses/>.
import vim
import os
import re
from ycm import vimsupport
from ycm.completers.threaded_completer import ThreadedCompleter
from ycm.completers.completer import Completer
from ycm.completers.cpp.clang_completer import InCFamilyFile
from ycm.completers.cpp.flags import Flags
from ycm.server import responses
USE_WORKING_DIR = vimsupport.GetBoolValue(
'g:ycm_filepath_completion_use_working_dir' )
class FilenameCompleter( ThreadedCompleter ):
class FilenameCompleter( Completer ):
"""
General completer that provides filename and filepath completions.
"""
def __init__( self ):
super( FilenameCompleter, self ).__init__()
def __init__( self, user_options ):
super( FilenameCompleter, self ).__init__( user_options )
self._flags = Flags()
self._path_regex = re.compile( """
@ -57,25 +52,35 @@ class FilenameCompleter( ThreadedCompleter ):
self._include_regex = re.compile( include_regex_common )
def AtIncludeStatementStart( self, start_column ):
return ( InCFamilyFile() and
def AtIncludeStatementStart( self, request_data ):
start_column = request_data[ 'start_column' ]
current_line = request_data[ 'line_value' ]
filepath = request_data[ 'filepath' ]
filetypes = request_data[ 'file_data' ][ filepath ][ 'filetypes' ]
return ( InCFamilyFile( filetypes ) and
self._include_start_regex.match(
vim.current.line[ :start_column ] ) )
current_line[ :start_column ] ) )
def ShouldUseNowInner( self, start_column ):
return ( start_column and ( vim.current.line[ start_column - 1 ] == '/' or
self.AtIncludeStatementStart( start_column ) ) )
def ShouldUseNowInner( self, request_data ):
start_column = request_data[ 'start_column' ]
current_line = request_data[ 'line_value' ]
return ( start_column and ( current_line[ start_column - 1 ] == '/' or
self.AtIncludeStatementStart( request_data ) ) )
def SupportedFiletypes( self ):
return []
def ComputeCandidates( self, unused_query, start_column ):
line = vim.current.line[ :start_column ]
def ComputeCandidatesInner( self, request_data ):
current_line = request_data[ 'line_value' ]
start_column = request_data[ 'start_column' ]
filepath = request_data[ 'filepath' ]
filetypes = request_data[ 'file_data' ][ filepath ][ 'filetypes' ]
line = current_line[ :start_column ]
if InCFamilyFile():
if InCFamilyFile( filetypes ):
include_match = self._include_regex.search( line )
if include_match:
path_dir = line[ include_match.end(): ]
@ -83,20 +88,26 @@ class FilenameCompleter( ThreadedCompleter ):
# http://gcc.gnu.org/onlinedocs/cpp/Include-Syntax.html
include_current_file_dir = '<' not in include_match.group()
return _GenerateCandidatesForPaths(
self.GetPathsIncludeCase( path_dir, include_current_file_dir ) )
self.GetPathsIncludeCase( path_dir,
include_current_file_dir,
filepath ) )
path_match = self._path_regex.search( line )
path_dir = os.path.expanduser( path_match.group() ) if path_match else ''
return _GenerateCandidatesForPaths( _GetPathsStandardCase( path_dir ) )
return _GenerateCandidatesForPaths(
_GetPathsStandardCase(
path_dir,
self.user_options[ 'filepath_completion_use_working_dir' ],
filepath ) )
def GetPathsIncludeCase( self, path_dir, include_current_file_dir ):
def GetPathsIncludeCase( self, path_dir, include_current_file_dir, filepath ):
paths = []
include_paths = self._flags.UserIncludePaths( vim.current.buffer.name )
include_paths = self._flags.UserIncludePaths( filepath )
if include_current_file_dir:
include_paths.append( os.path.dirname( vim.current.buffer.name ) )
include_paths.append( os.path.dirname( filepath ) )
for include_path in include_paths:
try:
@ -110,9 +121,9 @@ class FilenameCompleter( ThreadedCompleter ):
return sorted( set( paths ) )
def _GetPathsStandardCase( path_dir ):
if not USE_WORKING_DIR and not path_dir.startswith( '/' ):
path_dir = os.path.join( os.path.dirname( vim.current.buffer.name ),
def _GetPathsStandardCase( path_dir, use_working_dir, filepath ):
if not use_working_dir and not path_dir.startswith( '/' ):
path_dir = os.path.join( os.path.dirname( filepath ),
path_dir )
try:
@ -135,8 +146,8 @@ def _GenerateCandidatesForPaths( absolute_paths ):
seen_basenames.add( basename )
is_dir = os.path.isdir( absolute_path )
completion_dicts.append( { 'word': basename,
'dup': 1,
'menu': '[Dir]' if is_dir else '[File]' } )
completion_dicts.append(
responses.BuildCompletionData( basename,
'[Dir]' if is_dir else '[File]' ) )
return completion_dicts

View File

@ -21,13 +21,7 @@
from ycm.completers.completer import Completer
from ycm.completers.all.identifier_completer import IdentifierCompleter
from ycm.completers.general.filename_completer import FilenameCompleter
try:
from ycm.completers.general.ultisnips_completer import UltiSnipsCompleter
USE_ULTISNIPS_COMPLETER = True
except ImportError:
USE_ULTISNIPS_COMPLETER = False
class GeneralCompleterStore( Completer ):
@ -38,12 +32,11 @@ class GeneralCompleterStore( Completer ):
GeneralCompleterStore are passed to all general completers.
"""
def __init__( self ):
super( GeneralCompleterStore, self ).__init__()
self._identifier_completer = IdentifierCompleter()
self._filename_completer = FilenameCompleter()
self._ultisnips_completer = ( UltiSnipsCompleter()
if USE_ULTISNIPS_COMPLETER else None )
def __init__( self, user_options ):
super( GeneralCompleterStore, self ).__init__( user_options )
self._identifier_completer = IdentifierCompleter( user_options )
self._filename_completer = FilenameCompleter( user_options )
self._ultisnips_completer = UltiSnipsCompleter( user_options )
self._non_filename_completers = filter( lambda x: x,
[ self._ultisnips_completer,
self._identifier_completer ] )
@ -58,17 +51,21 @@ class GeneralCompleterStore( Completer ):
return set()
def ShouldUseNow( self, start_column ):
def GetIdentifierCompleter( self ):
return self._identifier_completer
def ShouldUseNow( self, request_data ):
self._current_query_completers = []
if self._filename_completer.ShouldUseNow( start_column ):
if self._filename_completer.ShouldUseNow( request_data ):
self._current_query_completers = [ self._filename_completer ]
return True
should_use_now = False
for completer in self._non_filename_completers:
should_use_this_completer = completer.ShouldUseNow( start_column )
should_use_this_completer = completer.ShouldUseNow( request_data )
should_use_now = should_use_now or should_use_this_completer
if should_use_this_completer:
@ -77,69 +74,49 @@ class GeneralCompleterStore( Completer ):
return should_use_now
def CandidatesForQueryAsync( self, query, start_column ):
for completer in self._current_query_completers:
completer.CandidatesForQueryAsync( query, start_column )
def ComputeCandidates( self, request_data ):
if not self.ShouldUseNow( request_data ):
return []
def AsyncCandidateRequestReady( self ):
return all( x.AsyncCandidateRequestReady() for x in
self._current_query_completers )
def CandidatesFromStoredRequest( self ):
candidates = []
for completer in self._current_query_completers:
candidates += completer.CandidatesFromStoredRequest()
candidates += completer.ComputeCandidates( request_data )
return candidates
def OnFileReadyToParse( self ):
def OnFileReadyToParse( self, request_data ):
for completer in self._all_completers:
completer.OnFileReadyToParse()
completer.OnFileReadyToParse( request_data )
def OnCursorMovedInsertMode( self ):
def OnBufferVisit( self, request_data ):
for completer in self._all_completers:
completer.OnCursorMovedInsertMode()
completer.OnBufferVisit( request_data )
def OnCursorMovedNormalMode( self ):
def OnBufferUnload( self, request_data ):
for completer in self._all_completers:
completer.OnCursorMovedNormalMode()
completer.OnBufferUnload( request_data )
def OnBufferVisit( self ):
def OnInsertLeave( self, request_data ):
for completer in self._all_completers:
completer.OnBufferVisit()
completer.OnInsertLeave( request_data )
def OnBufferUnload( self, deleted_buffer_file ):
def OnCurrentIdentifierFinished( self, request_data ):
for completer in self._all_completers:
completer.OnBufferUnload( deleted_buffer_file )
def OnCursorHold( self ):
for completer in self._all_completers:
completer.OnCursorHold()
def OnInsertLeave( self ):
for completer in self._all_completers:
completer.OnInsertLeave()
def OnVimLeave( self ):
for completer in self._all_completers:
completer.OnVimLeave()
def OnCurrentIdentifierFinished( self ):
for completer in self._all_completers:
completer.OnCurrentIdentifierFinished()
completer.OnCurrentIdentifierFinished( request_data )
def GettingCompletions( self ):
for completer in self._all_completers:
completer.GettingCompletions()
def Shutdown( self ):
for completer in self._all_completers:
completer.Shutdown()

View File

@ -19,7 +19,7 @@
# along with YouCompleteMe. If not, see <http://www.gnu.org/licenses/>.
from ycm.completers.general_completer import GeneralCompleter
from UltiSnips import UltiSnips_Manager
from ycm.server import responses
class UltiSnipsCompleter( GeneralCompleter ):
@ -27,42 +27,28 @@ class UltiSnipsCompleter( GeneralCompleter ):
General completer that provides UltiSnips snippet names in completions.
"""
def __init__( self ):
super( UltiSnipsCompleter, self ).__init__()
def __init__( self, user_options ):
super( UltiSnipsCompleter, self ).__init__( user_options )
self._candidates = None
self._filtered_candidates = None
def ShouldUseNowInner( self, start_column ):
return self.QueryLengthAboveMinThreshold( start_column )
def ShouldUseNow( self, request_data ):
return self.QueryLengthAboveMinThreshold( request_data )
def CandidatesForQueryAsync( self, query, unused_start_column ):
self._filtered_candidates = self.FilterAndSortCandidates( self._candidates,
query )
def AsyncCandidateRequestReady( self ):
return True
def CandidatesFromStoredRequest( self ):
return self._filtered_candidates if self._filtered_candidates else []
def OnBufferVisit( self ):
self._candidates = _GetCandidates()
def _GetCandidates():
try:
rawsnips = UltiSnips_Manager._snips( '', 1 )
# UltiSnips_Manager._snips() returns a class instance where:
# class.trigger - name of snippet trigger word ( e.g. defn or testcase )
# class.description - description of the snippet
return [ { 'word': str( snip.trigger ),
'menu': str( '<snip> ' + snip.description.encode('utf-8') ) }
for snip in rawsnips ]
except:
def ComputeCandidates( self, request_data ):
if not self.ShouldUseNow( request_data ):
return []
return self.FilterAndSortCandidates(
self._candidates, request_data[ 'query' ] )
def OnBufferVisit( self, request_data ):
raw_candidates = request_data.get( 'ultisnips_snippets', [] )
self._candidates = [
responses.BuildCompletionData(
str( snip[ 'trigger' ] ),
str( '<snip> ' + snip[ 'description' ].encode( 'utf-8' ) ) )
for snip in raw_candidates ]

View File

@ -29,8 +29,8 @@ class GeneralCompleter( Completer ):
Subclass Completer directly.
"""
def __init__( self ):
super( GeneralCompleter, self ).__init__()
def __init__( self, user_options ):
super( GeneralCompleter, self ).__init__( user_options )
def SupportedFiletypes( self ):

View File

@ -20,8 +20,8 @@
import ycm_core
from ycm.completers.cpp.clang_completer import ClangCompleter
def GetCompleter():
def GetCompleter( user_options ):
if ycm_core.HasClangSupport():
return ClangCompleter()
return ClangCompleter( user_options )
else:
return None

View File

@ -20,9 +20,9 @@
import ycm_core
from ycm.completers.cpp.clang_completer import ClangCompleter
def GetCompleter():
def GetCompleter( user_options ):
if ycm_core.HasClangSupport():
return ClangCompleter()
return ClangCompleter( user_options )
else:
return None

View File

@ -17,5 +17,5 @@
from ycm.completers.python.jedi_completer import JediCompleter
def GetCompleter():
return JediCompleter()
def GetCompleter( user_options ):
return JediCompleter( user_options )

@ -1 +0,0 @@
Subproject commit d5d12716b1d67df9cbaa4d3ea0c90e47c0023208

View File

@ -19,34 +19,25 @@
# You should have received a copy of the GNU General Public License
# along with YouCompleteMe. If not, see <http://www.gnu.org/licenses/>.
import vim
from ycm.completers.threaded_completer import ThreadedCompleter
from ycm import vimsupport
from ycm.completers.completer import Completer
from ycm.server import responses
import sys
from os.path import join, abspath, dirname
# We need to add the jedi package to sys.path, but it's important that we clean
# up after ourselves, because ycm.YouCompletMe.GetFiletypeCompleterForFiletype
# removes sys.path[0] after importing completers.python.hook
sys.path.insert( 0, join( abspath( dirname( __file__ ) ), 'jedi' ) )
try:
import jedi
except ImportError:
vimsupport.PostVimMessage(
raise ImportError(
'Error importing jedi. Make sure the jedi submodule has been checked out. '
'In the YouCompleteMe folder, run "git submodule update --init --recursive"')
sys.path.pop( 0 )
class JediCompleter( ThreadedCompleter ):
class JediCompleter( Completer ):
"""
A Completer that uses the Jedi completion engine.
https://jedi.readthedocs.org/en/latest/
"""
def __init__( self ):
super( JediCompleter, self ).__init__()
def __init__( self, user_options ):
super( JediCompleter, self ).__init__( user_options )
def SupportedFiletypes( self ):
@ -54,113 +45,109 @@ class JediCompleter( ThreadedCompleter ):
return [ 'python' ]
def _GetJediScript( self ):
contents = '\n'.join( vim.current.buffer )
line, column = vimsupport.CurrentLineAndColumn()
def _GetJediScript( self, request_data ):
filename = request_data[ 'filepath' ]
contents = request_data[ 'file_data' ][ filename ][ 'contents' ]
# Jedi expects lines to start at 1, not 0
line += 1
filename = vim.current.buffer.name
line = request_data[ 'line_num' ] + 1
column = request_data[ 'column_num' ]
return jedi.Script( contents, line, column, filename )
def ComputeCandidates( self, unused_query, unused_start_column ):
script = self._GetJediScript()
return [ { 'word': str( completion.name ),
'menu': str( completion.description ),
'info': str( completion.doc ) }
def ComputeCandidatesInner( self, request_data ):
script = self._GetJediScript( request_data )
return [ responses.BuildCompletionData(
str( completion.name ),
str( completion.description ),
str( completion.doc ) )
for completion in script.completions() ]
def DefinedSubcommands( self ):
return [ "GoToDefinition",
"GoToDeclaration",
"GoToDefinitionElseDeclaration" ]
return [ 'GoToDefinition',
'GoToDeclaration',
'GoToDefinitionElseDeclaration' ]
def OnUserCommand( self, arguments ):
def OnUserCommand( self, arguments, request_data ):
if not arguments:
self.EchoUserCommandsHelpMessage()
return
raise ValueError( self.UserCommandsHelpMessage() )
command = arguments[ 0 ]
if command == 'GoToDefinition':
self._GoToDefinition()
return self._GoToDefinition( request_data )
elif command == 'GoToDeclaration':
self._GoToDeclaration()
return self._GoToDeclaration( request_data )
elif command == 'GoToDefinitionElseDeclaration':
self._GoToDefinitionElseDeclaration()
return self._GoToDefinitionElseDeclaration( request_data )
raise ValueError( self.UserCommandsHelpMessage() )
def _GoToDefinition( self ):
definitions = self._GetDefinitionsList()
def _GoToDefinition( self, request_data ):
definitions = self._GetDefinitionsList( request_data )
if definitions:
self._JumpToLocation( definitions )
return self._BuildGoToResponse( definitions )
else:
vimsupport.PostVimMessage( 'Can\'t jump to definition.' )
raise RuntimeError( 'Can\'t jump to definition.' )
def _GoToDeclaration( self ):
definitions = self._GetDefinitionsList( declaration = True )
def _GoToDeclaration( self, request_data ):
definitions = self._GetDefinitionsList( request_data, declaration = True )
if definitions:
self._JumpToLocation( definitions )
return self._BuildGoToResponse( definitions )
else:
vimsupport.PostVimMessage( 'Can\'t jump to declaration.' )
raise RuntimeError( 'Can\'t jump to declaration.' )
def _GoToDefinitionElseDeclaration( self ):
definitions = self._GetDefinitionsList() or \
self._GetDefinitionsList( declaration = True )
def _GoToDefinitionElseDeclaration( self, request_data ):
definitions = ( self._GetDefinitionsList( request_data ) or
self._GetDefinitionsList( request_data, declaration = True ) )
if definitions:
self._JumpToLocation( definitions )
return self._BuildGoToResponse( definitions )
else:
vimsupport.PostVimMessage( 'Can\'t jump to definition or declaration.' )
raise RuntimeError( 'Can\'t jump to definition or declaration.' )
def _GetDefinitionsList( self, declaration = False ):
def _GetDefinitionsList( self, request_data, declaration = False ):
definitions = []
script = self._GetJediScript()
script = self._GetJediScript( request_data )
try:
if declaration:
definitions = script.goto_definitions()
else:
definitions = script.goto_assignments()
except jedi.NotFoundError:
vimsupport.PostVimMessage(
"Cannot follow nothing. Put your cursor on a valid name." )
except Exception as e:
vimsupport.PostVimMessage(
"Caught exception, aborting. Full error: " + str( e ) )
raise RuntimeError(
'Cannot follow nothing. Put your cursor on a valid name.' )
return definitions
def _JumpToLocation( self, definition_list ):
def _BuildGoToResponse( self, definition_list ):
if len( definition_list ) == 1:
definition = definition_list[ 0 ]
if definition.in_builtin_module():
if definition.is_keyword:
vimsupport.PostVimMessage(
"Cannot get the definition of Python keywords." )
raise RuntimeError(
'Cannot get the definition of Python keywords.' )
else:
vimsupport.PostVimMessage( "Builtin modules cannot be displayed." )
raise RuntimeError( 'Builtin modules cannot be displayed.' )
else:
vimsupport.JumpToLocation( definition.module_path,
definition.line,
definition.column + 1 )
return responses.BuildGoToResponse( definition.module_path,
definition.line - 1,
definition.column )
else:
# multiple definitions
defs = []
for definition in definition_list:
if definition.in_builtin_module():
defs.append( {'text': 'Builtin ' + \
definition.description.encode( 'utf-8' ) } )
defs.append( responses.BuildDescriptionOnlyGoToResponse(
'Builtin ' + definition.description ) )
else:
defs.append( {'filename': definition.module_path.encode( 'utf-8' ),
'lnum': definition.line,
'col': definition.column + 1,
'text': definition.description.encode( 'utf-8' ) } )
defs.append(
responses.BuildGoToResponse( definition.module_path,
definition.line - 1,
definition.column,
definition.description ) )
return defs
vim.eval( 'setqflist( %s )' % repr( defs ) )
vim.eval( 'youcompleteme#OpenGoToList()' )

View File

@ -1,102 +0,0 @@
#!/usr/bin/env python
#
# Copyright (C) 2011, 2012 Strahinja Val Markovic <val@markovic.io>
#
# This file is part of YouCompleteMe.
#
# YouCompleteMe is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# YouCompleteMe is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with YouCompleteMe. If not, see <http://www.gnu.org/licenses/>.
import abc
from threading import Thread, Event
from ycm.completers.completer import Completer
class ThreadedCompleter( Completer ):
"""A subclass of Completer that abstracts away the use of a background thread.
This is a great class to subclass for your custom completer. It will provide
you with async computation of candidates when you implement the
ComputeCandidates() method; no need to worry about threads, locks, events or
similar.
If you use this class as the base class for your Completer, you DON'T need to
provide implementations for the CandidatesForQueryAsync(),
AsyncCandidateRequestReady() and CandidatesFromStoredRequest() functions. Just
implement ComputeCandidates().
For examples of subclasses of this class, see the following files:
python/completers/general/filename_completer.py
"""
def __init__( self ):
super( ThreadedCompleter, self ).__init__()
self._query_ready = Event()
self._candidates_ready = Event()
self._candidates = None
self._start_completion_thread()
def _start_completion_thread( self ):
self._completion_thread = Thread( target=self.SetCandidates )
self._completion_thread.daemon = True
self._completion_thread.start()
def CandidatesForQueryAsyncInner( self, query, start_column ):
self._candidates = None
self._candidates_ready.clear()
self._query = query
self._start_column = start_column
self._query_ready.set()
def AsyncCandidateRequestReadyInner( self ):
return WaitAndClearIfSet( self._candidates_ready, timeout=0.005 )
def CandidatesFromStoredRequestInner( self ):
return self._candidates or []
@abc.abstractmethod
def ComputeCandidates( self, query, start_column ):
"""This function should compute the candidates to show to the user.
The return value should be of the same type as that for
CandidatesFromStoredRequest()."""
pass
def SetCandidates( self ):
while True:
try:
WaitAndClearIfSet( self._query_ready )
self._candidates = self.ComputeCandidates( self._query,
self._start_column )
except:
self._query_ready.clear()
self._candidates = []
self._candidates_ready.set()
def WaitAndClearIfSet( event, timeout=None ):
"""Given an |event| and a |timeout|, waits for the event a maximum of timeout
seconds. After waiting, clears the event if it's set and returns the state of
the event before it was cleared."""
# We can't just do flag_is_set = event.wait( timeout ) because that breaks on
# Python 2.6
event.wait( timeout )
flag_is_set = event.is_set()
if flag_is_set:
event.clear()
return flag_is_set

View File

@ -24,25 +24,30 @@ import imp
import random
import string
import sys
import vim
from ycm import vimsupport
import logging
from threading import Lock
from ycm import user_options_store
from ycm.server.responses import UnknownExtraConf
from fnmatch import fnmatch
# Constants
YCM_EXTRA_CONF_FILENAME = '.ycm_extra_conf.py'
CONFIRM_CONF_FILE_MESSAGE = ('Found {0}. Load? \n\n(Question can be turned '
'off with options, see YCM docs)')
GLOBAL_YCM_EXTRA_CONF_FILE = os.path.expanduser(
vimsupport.GetVariableValue( "g:ycm_global_ycm_extra_conf" )
)
# Singleton variables
_module_for_module_file = {}
_module_for_module_file_lock = Lock()
_module_file_for_source_file = {}
_module_file_for_source_file_lock = Lock()
def Reset():
global _module_for_module_file, _module_file_for_source_file
_module_for_module_file = {}
_module_file_for_source_file = {}
def ModuleForSourceFile( filename ):
return _Load( ModuleFileForSourceFile( filename ) )
return Load( ModuleFileForSourceFile( filename ) )
def ModuleFileForSourceFile( filename ):
@ -50,34 +55,49 @@ def ModuleFileForSourceFile( filename ):
order and return the filename of the first module that was allowed to load.
If no module was found or allowed to load, None is returned."""
with _module_file_for_source_file_lock:
if not filename in _module_file_for_source_file:
for module_file in _ExtraConfModuleSourceFilesForFile( filename ):
if _Load( module_file ):
if Load( module_file ):
_module_file_for_source_file[ filename ] = module_file
break
return _module_file_for_source_file.setdefault( filename )
def CallExtraConfYcmCorePreloadIfExists():
_CallExtraConfMethod( 'YcmCorePreload' )
def CallGlobalExtraConfYcmCorePreloadIfExists():
_CallGlobalExtraConfMethod( 'YcmCorePreload' )
def CallExtraConfVimCloseIfExists():
_CallExtraConfMethod( 'VimClose' )
def Shutdown():
# VimClose is for the sake of backwards compatibility; it's a no-op when it
# doesn't exist.
_CallGlobalExtraConfMethod( 'VimClose' )
_CallGlobalExtraConfMethod( 'Shutdown' )
def _CallExtraConfMethod( function_name ):
vim_current_working_directory = vim.eval( 'getcwd()' )
path_to_dummy = os.path.join( vim_current_working_directory, 'DUMMY_FILE' )
module = ModuleForSourceFile( path_to_dummy )
if not module or not hasattr( module, function_name ):
def _CallGlobalExtraConfMethod( function_name ):
logger = _Logger()
global_ycm_extra_conf = _GlobalYcmExtraConfFileLocation()
if not ( global_ycm_extra_conf and
os.path.exists( global_ycm_extra_conf ) ):
logger.debug( 'No global extra conf, not calling method ' + function_name )
return
module = Load( global_ycm_extra_conf, force = True )
if not module or not hasattr( module, function_name ):
logger.debug( 'Global extra conf not loaded or no function ' +
function_name )
return
logger.info( 'Calling global extra conf method {0} on conf file {1}'.format(
function_name, global_ycm_extra_conf ) )
getattr( module, function_name )()
def _Disable( module_file ):
"""Disables the loading of a module for the current session."""
with _module_for_module_file_lock:
_module_for_module_file[ module_file ] = None
@ -86,20 +106,25 @@ def _ShouldLoad( module_file ):
decide using a white-/blacklist and ask the user for confirmation as a
fallback."""
if ( module_file == GLOBAL_YCM_EXTRA_CONF_FILE or
not vimsupport.GetBoolValue( 'g:ycm_confirm_extra_conf' ) ):
if ( module_file == _GlobalYcmExtraConfFileLocation() or
not user_options_store.Value( 'confirm_extra_conf' ) ):
return True
globlist = vimsupport.GetVariableValue( 'g:ycm_extra_conf_globlist' )
globlist = user_options_store.Value( 'extra_conf_globlist' )
for glob in globlist:
is_blacklisted = glob[0] == '!'
if _MatchesGlobPattern( module_file, glob.lstrip('!') ):
return not is_blacklisted
return vimsupport.Confirm( CONFIRM_CONF_FILE_MESSAGE.format( module_file ) )
# We disable the file if it's unknown so that we don't ask the user about it
# repeatedly. Raising UnknownExtraConf should result in the client sending
# another request to load the module file if the user explicitly chooses to do
# that.
_Disable( module_file )
raise UnknownExtraConf( module_file )
def _Load( module_file, force = False ):
def Load( module_file, force = False ):
"""Load and return the module contained in a file.
Using force = True the module will be loaded regardless
of the criteria in _ShouldLoad.
@ -109,11 +134,13 @@ def _Load( module_file, force = False ):
return None
if not force:
with _module_for_module_file_lock:
if module_file in _module_for_module_file:
return _module_for_module_file[ module_file ]
if not _ShouldLoad( module_file ):
return _Disable( module_file )
_Disable( module_file )
return None
# This has to be here because a long time ago, the ycm_extra_conf.py files
# used to import clang_helpers.py from the cpp folder. This is not needed
@ -123,6 +150,7 @@ def _Load( module_file, force = False ):
module = imp.load_source( _RandomName(), module_file )
del sys.path[ 0 ]
with _module_for_module_file_lock:
_module_for_module_file[ module_file ] = module
return module
@ -139,15 +167,16 @@ def _MatchesGlobPattern( filename, glob ):
def _ExtraConfModuleSourceFilesForFile( filename ):
"""For a given filename, search all parent folders for YCM_EXTRA_CONF_FILENAME
files that will compute the flags necessary to compile the file.
If GLOBAL_YCM_EXTRA_CONF_FILE exists it is returned as a fallback."""
If _GlobalYcmExtraConfFileLocation() exists it is returned as a fallback."""
for folder in _PathsToAllParentFolders( filename ):
candidate = os.path.join( folder, YCM_EXTRA_CONF_FILENAME )
if os.path.exists( candidate ):
yield candidate
if ( GLOBAL_YCM_EXTRA_CONF_FILE
and os.path.exists( GLOBAL_YCM_EXTRA_CONF_FILE ) ):
yield GLOBAL_YCM_EXTRA_CONF_FILE
global_ycm_extra_conf = _GlobalYcmExtraConfFileLocation()
if ( global_ycm_extra_conf
and os.path.exists( global_ycm_extra_conf ) ):
yield global_ycm_extra_conf
def _PathsToAllParentFolders( filename ):
@ -188,3 +217,12 @@ def _DirectoryOfThisScript():
def _RandomName():
"""Generates a random module name."""
return ''.join( random.choice( string.ascii_lowercase ) for x in range( 15 ) )
def _GlobalYcmExtraConfFileLocation():
return os.path.expanduser(
user_options_store.Value( 'global_ycm_extra_conf' ) )
def _Logger():
return logging.getLogger( __name__ )

View File

View File

@ -0,0 +1,32 @@
{
"filepath_completion_use_working_dir": 0,
"min_num_of_chars_for_completion": 2,
"min_num_identifier_candidate_chars": 0,
"semantic_triggers": {},
"filetype_specific_completion_to_disable": {
"gitcommit": 1
},
"seed_identifiers_with_syntax": 0,
"collect_identifiers_from_comments_and_strings": 0,
"collect_identifiers_from_tags_files": 0,
"extra_conf_globlist": [],
"global_ycm_extra_conf": "",
"confirm_extra_conf": 1,
"complete_in_comments": 0,
"complete_in_strings": 1,
"max_diagnostics_to_display": 30,
"filetype_whitelist": {
"*": 1
},
"filetype_blacklist": {
"tagbar": 1,
"qf": 1,
"notes": 1,
"markdown": 1,
"unite": 1,
"text": 1
},
"auto_start_csharp_server": 1,
"auto_stop_csharp_server": 1,
"csharp_server_port": 2000
}

View File

@ -0,0 +1,222 @@
#!/usr/bin/env python
#
# Copyright (C) 2013 Strahinja Val Markovic <val@markovic.io>
#
# This file is part of YouCompleteMe.
#
# YouCompleteMe is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# YouCompleteMe is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with YouCompleteMe. If not, see <http://www.gnu.org/licenses/>.
from os import path
try:
import ycm_core
except ImportError as e:
raise RuntimeError(
'Error importing ycm_core. Are you sure you have placed a '
'version 3.2+ libclang.[so|dll|dylib] in folder "{0}"? '
'See the Installation Guide in the docs. Full error: {1}'.format(
path.realpath( path.join( path.abspath( __file__ ), '../../..' ) ),
str( e ) ) )
import atexit
import logging
import json
import bottle
import httplib
from bottle import request, response
import server_state
from ycm import user_options_store
from ycm.server.responses import BuildExceptionResponse
from ycm import extra_conf_store
# num bytes for the request body buffer; request.json only works if the request
# size is less than this
bottle.Request.MEMFILE_MAX = 300 * 1024
# TODO: rename these to _lower_case
SERVER_STATE = None
LOGGER = logging.getLogger( __name__ )
app = bottle.Bottle()
@app.post( '/event_notification' )
def EventNotification():
LOGGER.info( 'Received event notification' )
request_data = request.json
event_name = request_data[ 'event_name' ]
LOGGER.debug( 'Event name: %s', event_name )
event_handler = 'On' + event_name
getattr( SERVER_STATE.GetGeneralCompleter(), event_handler )( request_data )
filetypes = request_data[ 'filetypes' ]
response_data = None
if SERVER_STATE.FiletypeCompletionUsable( filetypes ):
response_data = getattr( SERVER_STATE.GetFiletypeCompleter( filetypes ),
event_handler )( request_data )
if response_data:
return _JsonResponse( response_data )
@app.post( '/run_completer_command' )
def RunCompleterCommand():
LOGGER.info( 'Received command request' )
request_data = request.json
completer = _GetCompleterForRequestData( request_data )
return _JsonResponse( completer.OnUserCommand(
request_data[ 'command_arguments' ],
request_data ) )
@app.post( '/completions' )
def GetCompletions():
LOGGER.info( 'Received completion request' )
request_data = request.json
do_filetype_completion = SERVER_STATE.ShouldUseFiletypeCompleter(
request_data )
LOGGER.debug( 'Using filetype completion: %s', do_filetype_completion )
filetypes = request_data[ 'filetypes' ]
completer = ( SERVER_STATE.GetFiletypeCompleter( filetypes ) if
do_filetype_completion else
SERVER_STATE.GetGeneralCompleter() )
return _JsonResponse( completer.ComputeCandidates( request_data ) )
@app.get( '/user_options' )
def GetUserOptions():
LOGGER.info( 'Received user options GET request' )
return _JsonResponse( dict( SERVER_STATE.user_options ) )
@app.get( '/healthy' )
def GetHealthy():
LOGGER.info( 'Received health request' )
return _JsonResponse( True )
@app.post( '/user_options' )
def SetUserOptions():
LOGGER.info( 'Received user options POST request' )
UpdateUserOptions( request.json )
@app.post( '/semantic_completion_available' )
def FiletypeCompletionAvailable():
LOGGER.info( 'Received filetype completion available request' )
return _JsonResponse( SERVER_STATE.FiletypeCompletionAvailable(
request.json[ 'filetypes' ] ) )
@app.post( '/defined_subcommands' )
def DefinedSubcommands():
LOGGER.info( 'Received defined subcommands request' )
completer = _GetCompleterForRequestData( request.json )
return _JsonResponse( completer.DefinedSubcommands() )
@app.post( '/detailed_diagnostic' )
def GetDetailedDiagnostic():
LOGGER.info( 'Received detailed diagnostic request' )
request_data = request.json
completer = _GetCompleterForRequestData( request_data )
return _JsonResponse( completer.GetDetailedDiagnostic( request_data ) )
@app.post( '/load_extra_conf_file' )
def LoadExtraConfFile():
LOGGER.info( 'Received extra conf load request' )
request_data = request.json
extra_conf_store.Load( request_data[ 'filepath' ], force = True )
@app.post( '/debug_info' )
def DebugInfo():
LOGGER.info( 'Received debug info request' )
output = []
has_clang_support = ycm_core.HasClangSupport()
output.append( 'Server has Clang support compiled in: {0}'.format(
has_clang_support ) )
if has_clang_support:
output.append( 'Clang version: ' + ycm_core.ClangVersion() )
request_data = request.json
try:
output.append(
_GetCompleterForRequestData( request_data ).DebugInfo( request_data) )
except:
pass
return _JsonResponse( '\n'.join( output ) )
# The type of the param is Bottle.HTTPError
@app.error( httplib.INTERNAL_SERVER_ERROR )
def ErrorHandler( httperror ):
return _JsonResponse( BuildExceptionResponse( httperror.exception,
httperror.traceback ) )
def _JsonResponse( data ):
response.set_header( 'Content-Type', 'application/json' )
return json.dumps( data, default = _UniversalSerialize )
def _UniversalSerialize( obj ):
serialized = obj.__dict__.copy()
serialized[ 'TYPE' ] = type( obj ).__name__
return serialized
def _GetCompleterForRequestData( request_data ):
completer_target = request_data.get( 'completer_target', None )
if completer_target == 'identifier':
return SERVER_STATE.GetGeneralCompleter().GetIdentifierCompleter()
elif completer_target == 'filetype_default' or not completer_target:
return SERVER_STATE.GetFiletypeCompleter( request_data[ 'filetypes' ] )
else:
return SERVER_STATE.GetFiletypeCompleter( [ completer_target ] )
@atexit.register
def ServerShutdown():
LOGGER.info( 'Server shutting down' )
if SERVER_STATE:
SERVER_STATE.Shutdown()
extra_conf_store.Shutdown()
def UpdateUserOptions( options ):
global SERVER_STATE
if not options:
return
user_options_store.SetAll( options )
SERVER_STATE = server_state.ServerState( options )
def SetServerStateToDefaults():
global SERVER_STATE, LOGGER
LOGGER = logging.getLogger( __name__ )
user_options_store.LoadDefaults()
SERVER_STATE = server_state.ServerState( user_options_store.GetAll() )
extra_conf_store.Reset()

View File

@ -0,0 +1,104 @@
#!/usr/bin/env python
#
# Copyright (C) 2013 Strahinja Val Markovic <val@markovic.io>
#
# This file is part of YouCompleteMe.
#
# YouCompleteMe is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# YouCompleteMe is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with YouCompleteMe. If not, see <http://www.gnu.org/licenses/>.
import os
CONFIRM_CONF_FILE_MESSAGE = ('Found {0}. Load? \n\n(Question can be turned '
'off with options, see YCM docs)')
class ServerError( Exception ):
def __init__( self, message ):
super( ServerError, self ).__init__( message )
class UnknownExtraConf( ServerError ):
def __init__( self, extra_conf_file ):
message = CONFIRM_CONF_FILE_MESSAGE.format( extra_conf_file )
super( UnknownExtraConf, self ).__init__( message )
self.extra_conf_file = extra_conf_file
def BuildGoToResponse( filepath, line_num, column_num, description = None ):
response = {
'filepath': os.path.realpath( filepath ),
'line_num': line_num,
'column_num': column_num
}
if description:
response[ 'description' ] = description
return response
def BuildDescriptionOnlyGoToResponse( text ):
return {
'description': text,
}
# TODO: Look at all the callers and ensure they are not using this instead of an
# exception.
def BuildDisplayMessageResponse( text ):
return {
'message': text
}
def BuildCompletionData( insertion_text,
extra_menu_info = None,
detailed_info = None,
menu_text = None,
kind = None ):
completion_data = {
'insertion_text': insertion_text
}
if extra_menu_info:
completion_data[ 'extra_menu_info' ] = extra_menu_info
if menu_text:
completion_data[ 'menu_text' ] = menu_text
if detailed_info:
completion_data[ 'detailed_info' ] = detailed_info
if kind:
completion_data[ 'kind' ] = kind
return completion_data
def BuildDiagnosticData( filepath,
line_num,
column_num,
text,
kind ):
return {
'filepath': filepath,
'line_num': line_num,
'column_num': column_num,
'text': text,
'kind': kind
}
def BuildExceptionResponse( exception, traceback ):
return {
'exception': exception,
'message': str( exception ),
'traceback': traceback
}

View File

@ -0,0 +1,113 @@
#!/usr/bin/env python
#
# Copyright (C) 2013 Strahinja Val Markovic <val@markovic.io>
#
# This file is part of YouCompleteMe.
#
# YouCompleteMe is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# YouCompleteMe is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with YouCompleteMe. If not, see <http://www.gnu.org/licenses/>.
import imp
import os
from ycm.utils import ForceSemanticCompletion
from ycm.completers.general.general_completer_store import GeneralCompleterStore
from ycm.completers.completer_utils import PathToFiletypeCompleterPluginLoader
class ServerState( object ):
def __init__( self, user_options ):
self._user_options = user_options
self._filetype_completers = {}
self._gencomp = GeneralCompleterStore( self._user_options )
@property
def user_options( self ):
return self._user_options
def Shutdown( self ):
for completer in self._filetype_completers.itervalues():
if completer:
completer.Shutdown()
self._gencomp.Shutdown()
def _GetFiletypeCompleterForFiletype( self, filetype ):
try:
return self._filetype_completers[ filetype ]
except KeyError:
pass
module_path = PathToFiletypeCompleterPluginLoader( filetype )
completer = None
supported_filetypes = [ filetype ]
if os.path.exists( module_path ):
module = imp.load_source( filetype, module_path )
completer = module.GetCompleter( self._user_options )
if completer:
supported_filetypes.extend( completer.SupportedFiletypes() )
for supported_filetype in supported_filetypes:
self._filetype_completers[ supported_filetype ] = completer
return completer
def GetFiletypeCompleter( self, current_filetypes ):
completers = [ self._GetFiletypeCompleterForFiletype( filetype )
for filetype in current_filetypes ]
for completer in completers:
if completer:
return completer
raise ValueError( 'No semantic completer exists for filetypes: {0}'.format(
current_filetypes ) )
def FiletypeCompletionAvailable( self, filetypes ):
try:
self.GetFiletypeCompleter( filetypes )
return True
except:
return False
def FiletypeCompletionUsable( self, filetypes ):
return ( self.CurrentFiletypeCompletionEnabled( filetypes ) and
self.FiletypeCompletionAvailable( filetypes ) )
def ShouldUseGeneralCompleter( self, request_data ):
return self._gencomp.ShouldUseNow( request_data )
def ShouldUseFiletypeCompleter( self, request_data ):
filetypes = request_data[ 'filetypes' ]
if self.FiletypeCompletionUsable( filetypes ):
return ( ForceSemanticCompletion( request_data ) or
self.GetFiletypeCompleter( filetypes ).ShouldUseNow(
request_data ) )
return False
def GetGeneralCompleter( self ):
return self._gencomp
def CurrentFiletypeCompletionEnabled( self, current_filetypes ):
filetype_to_disable = self._user_options[
'filetype_specific_completion_to_disable' ]
return not all([ x in filetype_to_disable for x in current_filetypes ])

View File

@ -0,0 +1,34 @@
#!/usr/bin/env python
#
# Copyright (C) 2013 Strahinja Val Markovic <val@markovic.io>
#
# This file is part of YouCompleteMe.
#
# YouCompleteMe is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# YouCompleteMe is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with YouCompleteMe. If not, see <http://www.gnu.org/licenses/>.
import sys
import os
def SetUpPythonPath():
# We want to have the YouCompleteMe/python directory on the Python PATH
# because all the code already assumes that it's there. This is a relic from
# before the client/server architecture.
# TODO: Fix things so that this is not needed anymore when we split ycmd into
# a separate repository.
sys.path.insert( 0, os.path.join(
os.path.dirname( os.path.abspath( __file__ ) ),
'../..' ) )
from ycm import utils
utils.AddThirdPartyFoldersToSysPath()

View File

View File

@ -0,0 +1,451 @@
#!/usr/bin/env python
#
# Copyright (C) 2013 Strahinja Val Markovic <val@markovic.io>
#
# This file is part of YouCompleteMe.
#
# YouCompleteMe is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# YouCompleteMe is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with YouCompleteMe. If not, see <http://www.gnu.org/licenses/>.
import os
import httplib
import time
from ..server_utils import SetUpPythonPath
SetUpPythonPath()
from webtest import TestApp
from .. import handlers
from ..responses import BuildCompletionData, UnknownExtraConf
from nose.tools import ok_, eq_, with_setup
from hamcrest import ( assert_that, has_items, has_entry, contains,
contains_string, has_entries, contains_inanyorder )
import bottle
bottle.debug( True )
# TODO: Split this file into multiple files.
def BuildRequest( **kwargs ):
filepath = kwargs[ 'filepath' ] if 'filepath' in kwargs else '/foo'
contents = kwargs[ 'contents' ] if 'contents' in kwargs else ''
filetype = kwargs[ 'filetype' ] if 'filetype' in kwargs else 'foo'
request = {
'query': '',
'line_num': 0,
'column_num': 0,
'start_column': 0,
'filetypes': [ filetype ],
'filepath': filepath,
'line_value': contents,
'file_data': {
filepath: {
'contents': contents,
'filetypes': [ filetype ]
}
}
}
for key, value in kwargs.iteritems():
if key in [ 'contents', 'filetype', 'filepath' ]:
continue
request[ key ] = value
if key == 'line_num':
lines = contents.splitlines()
if len( lines ) > 1:
# NOTE: assumes 0-based line_num
request[ 'line_value' ] = lines[ value ]
return request
# TODO: Make the other tests use this helper too instead of BuildCompletionData
def CompletionEntryMatcher( insertion_text ):
return has_entry( 'insertion_text', insertion_text )
def Setup():
handlers.SetServerStateToDefaults()
def PathToTestDataDir():
dir_of_current_script = os.path.dirname( os.path.abspath( __file__ ) )
return os.path.join( dir_of_current_script, 'testdata' )
def PathToTestFile( test_basename ):
return os.path.join( PathToTestDataDir(), test_basename )
@with_setup( Setup )
def GetCompletions_IdentifierCompleter_Works_test():
app = TestApp( handlers.app )
event_data = BuildRequest( contents = 'foo foogoo ba',
event_name = 'FileReadyToParse' )
app.post_json( '/event_notification', event_data )
completion_data = BuildRequest( contents = 'oo foo foogoo ba',
query = 'oo',
column_num = 2 )
eq_( [ BuildCompletionData( 'foo' ),
BuildCompletionData( 'foogoo' ) ],
app.post_json( '/completions', completion_data ).json )
@with_setup( Setup )
def GetCompletions_CsCompleter_Works_test():
app = TestApp( handlers.app )
filepath = PathToTestFile( 'testy/Program.cs' )
contents = open( filepath ).read()
event_data = BuildRequest( filepath = filepath,
filetype = 'cs',
contents = contents,
event_name = 'FileReadyToParse' )
app.post_json( '/event_notification', event_data )
# We need to wait until the server has started up.
while True:
result = app.post_json( '/run_completer_command',
BuildRequest( completer_target = 'filetype_default',
command_arguments = ['ServerRunning'],
filetype = 'cs' ) ).json
if result:
break
time.sleep( 0.2 )
completion_data = BuildRequest( filepath = filepath,
filetype = 'cs',
contents = contents,
line_num = 8,
column_num = 11,
start_column = 11 )
results = app.post_json( '/completions', completion_data ).json
assert_that( results, has_items( CompletionEntryMatcher( 'CursorLeft' ),
CompletionEntryMatcher( 'CursorSize' ) ) )
@with_setup( Setup )
def GetCompletions_ClangCompleter_WorksWithExplicitFlags_test():
app = TestApp( handlers.app )
contents = """
struct Foo {
int x;
int y;
char c;
};
int main()
{
Foo foo;
foo.
}
"""
# 0-based line and column!
completion_data = BuildRequest( filepath = '/foo.cpp',
filetype = 'cpp',
contents = contents,
line_num = 10,
column_num = 6,
start_column = 6,
compilation_flags = ['-x', 'c++'] )
results = app.post_json( '/completions', completion_data ).json
assert_that( results, has_items( CompletionEntryMatcher( 'c' ),
CompletionEntryMatcher( 'x' ),
CompletionEntryMatcher( 'y' ) ) )
@with_setup( Setup )
def GetCompletions_ClangCompleter_UnknownExtraConfException_test():
app = TestApp( handlers.app )
filepath = PathToTestFile( 'basic.cpp' )
completion_data = BuildRequest( filepath = filepath,
filetype = 'cpp',
contents = open( filepath ).read(),
force_semantic = True )
response = app.post_json( '/completions',
completion_data,
expect_errors = True )
eq_( response.status_code, httplib.INTERNAL_SERVER_ERROR )
assert_that( response.json,
has_entry( 'exception',
has_entry( 'TYPE', UnknownExtraConf.__name__ ) ) )
@with_setup( Setup )
def GetCompletions_ClangCompleter_WorksWhenExtraConfExplicitlyAllowed_test():
app = TestApp( handlers.app )
app.post_json( '/load_extra_conf_file',
{ 'filepath': PathToTestFile( '.ycm_extra_conf.py' ) } )
filepath = PathToTestFile( 'basic.cpp' )
completion_data = BuildRequest( filepath = filepath,
filetype = 'cpp',
contents = open( filepath ).read(),
line_num = 10,
column_num = 6,
start_column = 6 )
results = app.post_json( '/completions', completion_data ).json
assert_that( results, has_items( CompletionEntryMatcher( 'c' ),
CompletionEntryMatcher( 'x' ),
CompletionEntryMatcher( 'y' ) ) )
@with_setup( Setup )
def GetCompletions_ClangCompleter_ForceSemantic_OnlyFileteredCompletions_test():
app = TestApp( handlers.app )
contents = """
int main()
{
int foobar;
int floozar;
int gooboo;
int bleble;
fooar
}
"""
# 0-based line and column!
completion_data = BuildRequest( filepath = '/foo.cpp',
filetype = 'cpp',
force_semantic = True,
contents = contents,
line_num = 8,
column_num = 7,
start_column = 7,
query = 'fooar',
compilation_flags = ['-x', 'c++'] )
results = app.post_json( '/completions', completion_data ).json
assert_that( results,
contains_inanyorder( CompletionEntryMatcher( 'foobar' ),
CompletionEntryMatcher( 'floozar' ) ) )
@with_setup( Setup )
def GetCompletions_ForceSemantic_Works_test():
app = TestApp( handlers.app )
completion_data = BuildRequest( filetype = 'python',
force_semantic = True )
results = app.post_json( '/completions', completion_data ).json
assert_that( results, has_items( CompletionEntryMatcher( 'abs' ),
CompletionEntryMatcher( 'open' ),
CompletionEntryMatcher( 'bool' ) ) )
@with_setup( Setup )
def GetCompletions_IdentifierCompleter_SyntaxKeywordsAdded_test():
app = TestApp( handlers.app )
event_data = BuildRequest( event_name = 'FileReadyToParse',
syntax_keywords = ['foo', 'bar', 'zoo'] )
app.post_json( '/event_notification', event_data )
completion_data = BuildRequest( contents = 'oo ',
query = 'oo',
column_num = 2 )
eq_( [ BuildCompletionData( 'foo' ),
BuildCompletionData( 'zoo' ) ],
app.post_json( '/completions', completion_data ).json )
@with_setup( Setup )
def GetCompletions_UltiSnipsCompleter_Works_test():
app = TestApp( handlers.app )
event_data = BuildRequest(
event_name = 'BufferVisit',
ultisnips_snippets = [
{'trigger': 'foo', 'description': 'bar'},
{'trigger': 'zoo', 'description': 'goo'},
] )
app.post_json( '/event_notification', event_data )
completion_data = BuildRequest( contents = 'oo ',
query = 'oo',
column_num = 2 )
eq_( [ BuildCompletionData( 'foo', '<snip> bar' ),
BuildCompletionData( 'zoo', '<snip> goo' ) ],
app.post_json( '/completions', completion_data ).json )
@with_setup( Setup )
def RunCompleterCommand_GoTo_Jedi_ZeroBasedLineAndColumn_test():
app = TestApp( handlers.app )
contents = """
def foo():
pass
foo()
"""
goto_data = BuildRequest( completer_target = 'filetype_default',
command_arguments = ['GoToDefinition'],
line_num = 4,
contents = contents,
filetype = 'python',
filepath = '/foo.py' )
# 0-based line and column!
eq_( {
'filepath': '/foo.py',
'line_num': 1,
'column_num': 4
},
app.post_json( '/run_completer_command', goto_data ).json )
@with_setup( Setup )
def RunCompleterCommand_GoTo_Clang_ZeroBasedLineAndColumn_test():
app = TestApp( handlers.app )
contents = """
struct Foo {
int x;
int y;
char c;
};
int main()
{
Foo foo;
return 0;
}
"""
goto_data = BuildRequest( completer_target = 'filetype_default',
command_arguments = ['GoToDefinition'],
compilation_flags = ['-x', 'c++'],
line_num = 9,
column_num = 2,
contents = contents,
filetype = 'cpp' )
# 0-based line and column!
eq_( {
'filepath': '/foo',
'line_num': 1,
'column_num': 7
},
app.post_json( '/run_completer_command', goto_data ).json )
@with_setup( Setup )
def DefinedSubcommands_Works_test():
app = TestApp( handlers.app )
subcommands_data = BuildRequest( completer_target = 'python' )
eq_( [ 'GoToDefinition',
'GoToDeclaration',
'GoToDefinitionElseDeclaration' ],
app.post_json( '/defined_subcommands', subcommands_data ).json )
@with_setup( Setup )
def DefinedSubcommands_WorksWhenNoExplicitCompleterTargetSpecified_test():
app = TestApp( handlers.app )
subcommands_data = BuildRequest( filetype = 'python' )
eq_( [ 'GoToDefinition',
'GoToDeclaration',
'GoToDefinitionElseDeclaration' ],
app.post_json( '/defined_subcommands', subcommands_data ).json )
@with_setup( Setup )
def Diagnostics_ClangCompleter_ZeroBasedLineAndColumn_test():
app = TestApp( handlers.app )
contents = """
struct Foo {
int x // semicolon missing here!
int y;
int c;
int d;
};
"""
event_data = BuildRequest( compilation_flags = ['-x', 'c++'],
event_name = 'FileReadyToParse',
contents = contents,
filetype = 'cpp' )
results = app.post_json( '/event_notification', event_data ).json
assert_that( results,
contains(
has_entries( { 'text': contains_string( "expected ';'" ),
'line_num': 2,
'column_num': 7 } ) ) )
@with_setup( Setup )
def GetDetailedDiagnostic_ClangCompleter_Works_test():
app = TestApp( handlers.app )
contents = """
struct Foo {
int x // semicolon missing here!
int y;
int c;
int d;
};
"""
diag_data = BuildRequest( compilation_flags = ['-x', 'c++'],
line_num = 2,
contents = contents,
filetype = 'cpp' )
event_data = diag_data.copy()
event_data.update( {
'event_name': 'FileReadyToParse',
} )
app.post_json( '/event_notification', event_data )
results = app.post_json( '/detailed_diagnostic', diag_data ).json
assert_that( results,
has_entry( 'message', contains_string( "expected ';'" ) ) )
@with_setup( Setup )
def FiletypeCompletionAvailable_Works_test():
app = TestApp( handlers.app )
request_data = {
'filetypes': ['python']
}
ok_( app.post_json( '/semantic_completion_available',
request_data ).json )
@with_setup( Setup )
def UserOptions_Works_test():
app = TestApp( handlers.app )
options = app.get( '/user_options' ).json
ok_( len( options ) )
options[ 'foobar' ] = 'zoo'
app.post_json( '/user_options', options )
eq_( options, app.get( '/user_options' ).json )

View File

@ -0,0 +1,5 @@
def FlagsForFile( filename ):
return {
'flags': ['-x', 'c++'],
'do_cache': True
}

View File

@ -0,0 +1,13 @@
struct Foo {
int x;
int y;
char c;
};
int main()
{
Foo foo;
// The location after the dot is line 11, col 7
foo.
}

View File

@ -0,0 +1,12 @@
using System;
namespace testy
{
class MainClass
{
public static void Main (string[] args)
{
Console.
}
}
}

View File

@ -0,0 +1,22 @@
using System.Reflection;
using System.Runtime.CompilerServices;
// Information about this assembly is defined by the following attributes.
// Change them to the values specific to your project.
[assembly: AssemblyTitle ("testy")]
[assembly: AssemblyDescription ("")]
[assembly: AssemblyConfiguration ("")]
[assembly: AssemblyCompany ("")]
[assembly: AssemblyProduct ("")]
[assembly: AssemblyCopyright ("valloric")]
[assembly: AssemblyTrademark ("")]
[assembly: AssemblyCulture ("")]
// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}".
// The form "{Major}.{Minor}.*" will automatically update the build and revision,
// and "{Major}.{Minor}.{Build}.*" will update just the revision.
[assembly: AssemblyVersion ("1.0.*")]
// The following attributes are used to specify the signing key for the assembly,
// if desired. See the Mono documentation for more information about signing.
//[assembly: AssemblyDelaySign(false)]
//[assembly: AssemblyKeyFile("")]

View File

@ -0,0 +1,41 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">x86</Platform>
<ProductVersion>10.0.0</ProductVersion>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>{0C99F719-E00E-4CCD-AB9F-FEFBCD97C51F}</ProjectGuid>
<OutputType>Exe</OutputType>
<RootNamespace>testy</RootNamespace>
<AssemblyName>testy</AssemblyName>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x86' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug</OutputPath>
<DefineConstants>DEBUG;</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<Externalconsole>true</Externalconsole>
<PlatformTarget>x86</PlatformTarget>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x86' ">
<DebugType>full</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release</OutputPath>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<Externalconsole>true</Externalconsole>
<PlatformTarget>x86</PlatformTarget>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
</ItemGroup>
<ItemGroup>
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
</Project>

View File

@ -0,0 +1,20 @@

Microsoft Visual Studio Solution File, Format Version 11.00
# Visual Studio 2010
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "testy", "testy.csproj", "{0C99F719-E00E-4CCD-AB9F-FEFBCD97C51F}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|x86 = Debug|x86
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{0C99F719-E00E-4CCD-AB9F-FEFBCD97C51F}.Debug|x86.ActiveCfg = Debug|x86
{0C99F719-E00E-4CCD-AB9F-FEFBCD97C51F}.Debug|x86.Build.0 = Debug|x86
{0C99F719-E00E-4CCD-AB9F-FEFBCD97C51F}.Release|x86.ActiveCfg = Release|x86
{0C99F719-E00E-4CCD-AB9F-FEFBCD97C51F}.Release|x86.Build.0 = Release|x86
EndGlobalSection
GlobalSection(MonoDevelopProperties) = preSolution
StartupItem = testy.csproj
EndGlobalSection
EndGlobal

View File

@ -0,0 +1,13 @@
<Properties>
<MonoDevelop.Ide.Workspace ActiveConfiguration="Debug|x86" />
<MonoDevelop.Ide.Workbench ActiveDocument="Program.cs">
<Files>
<File FileName="Program.cs" Line="7" Column="25" />
<File FileName="Properties/AssemblyInfo.cs" Line="1" Column="1" />
</Files>
</MonoDevelop.Ide.Workbench>
<MonoDevelop.Ide.DebuggingService.Breakpoints>
<BreakpointStore />
</MonoDevelop.Ide.DebuggingService.Breakpoints>
<MonoDevelop.Ide.DebuggingService.PinnedWatches />
</Properties>

View File

@ -0,0 +1,78 @@
#!/usr/bin/env python
#
# Copyright (C) 2013 Strahinja Val Markovic <val@markovic.io>
#
# This file is part of YouCompleteMe.
#
# YouCompleteMe is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# YouCompleteMe is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with YouCompleteMe. If not, see <http://www.gnu.org/licenses/>.
import time
import os
import copy
from ycm import utils
from threading import Thread, Lock
# This class implements the Bottle plugin API:
# http://bottlepy.org/docs/dev/plugindev.html
#
# The idea here is to decorate every route handler automatically so that on
# every request, we log when the request was made. Then a watchdog thread checks
# every check_interval_seconds whether the server has been idle for a time
# greater that the passed-in idle_suicide_seconds. If it has, we kill the
# server.
#
# We want to do this so that if something goes bonkers in Vim and the server
# never gets killed by the client, we don't end up with lots of zombie servers.
class WatchdogPlugin( object ):
name = 'watchdog'
api = 2
def __init__( self,
idle_suicide_seconds,
check_interval_seconds = 60 * 10 ):
self._check_interval_seconds = check_interval_seconds
self._idle_suicide_seconds = idle_suicide_seconds
self._last_request_time = time.time()
self._last_request_time_lock = Lock()
if idle_suicide_seconds <= 0:
return
self._watchdog_thread = Thread( target = self._WatchdogMain )
self._watchdog_thread.daemon = True
self._watchdog_thread.start()
def _GetLastRequestTime( self ):
with self._last_request_time_lock:
return copy.deepcopy( self._last_request_time )
def _SetLastRequestTime( self, new_value ):
with self._last_request_time_lock:
self._last_request_time = new_value
def _WatchdogMain( self ):
while True:
time.sleep( self._check_interval_seconds )
if time.time() - self._GetLastRequestTime() > self._idle_suicide_seconds:
utils.TerminateProcess( os.getpid() )
def __call__( self, callback ):
def wrapper( *args, **kwargs ):
self._SetLastRequestTime( time.time() )
return callback( *args, **kwargs )
return wrapper

97
python/ycm/server/ycmd.py Executable file
View File

@ -0,0 +1,97 @@
#!/usr/bin/env python
#
# Copyright (C) 2013 Strahinja Val Markovic <val@markovic.io>
#
# This file is part of YouCompleteMe.
#
# YouCompleteMe is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# YouCompleteMe is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with YouCompleteMe. If not, see <http://www.gnu.org/licenses/>.
from server_utils import SetUpPythonPath
SetUpPythonPath()
import sys
import logging
import json
import argparse
import waitress
import signal
from ycm import user_options_store
from ycm import extra_conf_store
from ycm.server.watchdog_plugin import WatchdogPlugin
def YcmCoreSanityCheck():
if 'ycm_core' in sys.modules:
raise RuntimeError( 'ycm_core already imported, ycmd has a bug!' )
# We manually call sys.exit() on SIGTERM and SIGINT so that atexit handlers are
# properly executed.
def SetUpSignalHandler():
def SignalHandler( signum, frame ):
sys.exit()
for sig in [ signal.SIGTERM,
signal.SIGINT ]:
signal.signal( sig, SignalHandler )
def Main():
parser = argparse.ArgumentParser()
parser.add_argument( '--host', type = str, default = 'localhost',
help = 'server hostname')
parser.add_argument( '--port', type = int, default = 6666,
help = 'server port')
parser.add_argument( '--log', type = str, default = 'info',
help = 'log level, one of '
'[debug|info|warning|error|critical]' )
parser.add_argument( '--idle_suicide_seconds', type = int, default = 0,
help = 'num idle seconds before server shuts down')
parser.add_argument( '--options_file', type = str, default = '',
help = 'file with user options, in JSON format' )
args = parser.parse_args()
numeric_level = getattr( logging, args.log.upper(), None )
if not isinstance( numeric_level, int ):
raise ValueError( 'Invalid log level: %s' % args.log )
# Has to be called before any call to logging.getLogger()
logging.basicConfig( format = '%(asctime)s - %(levelname)s - %(message)s',
level = numeric_level )
options = None
if args.options_file:
options = json.load( open( args.options_file, 'r' ) )
user_options_store.SetAll( options )
# This ensures that ycm_core is not loaded before extra conf
# preload was run.
YcmCoreSanityCheck()
extra_conf_store.CallGlobalExtraConfYcmCorePreloadIfExists()
# This can't be a top-level import because it transitively imports
# ycm_core which we want to be imported ONLY after extra conf
# preload has executed.
from ycm.server import handlers
handlers.UpdateUserOptions( options )
SetUpSignalHandler()
handlers.app.install( WatchdogPlugin( args.idle_suicide_seconds ) )
waitress.serve( handlers.app,
host = args.host,
port = args.port,
threads = 10 )
if __name__ == "__main__":
Main()

View File

@ -0,0 +1,49 @@
#!/usr/bin/env python
#
# Copyright (C) 2013 Strahinja Val Markovic <val@markovic.io>
#
# This file is part of YouCompleteMe.
#
# YouCompleteMe is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# YouCompleteMe is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with YouCompleteMe. If not, see <http://www.gnu.org/licenses/>.
import json
import os
from frozendict import frozendict
_USER_OPTIONS = {}
def SetAll( new_options ):
global _USER_OPTIONS
_USER_OPTIONS = frozendict( new_options )
def GetAll():
return _USER_OPTIONS
def Value( key ):
return _USER_OPTIONS[ key ]
def LoadDefaults():
SetAll( DefaultOptions() )
def DefaultOptions():
settings_path = os.path.join(
os.path.dirname( os.path.abspath( __file__ ) ),
'server/default_settings.json' )
with open( settings_path ) as f:
return json.loads( f.read() )

View File

@ -17,6 +17,18 @@
# You should have received a copy of the GNU General Public License
# along with YouCompleteMe. If not, see <http://www.gnu.org/licenses/>.
import tempfile
import os
import sys
import signal
import functools
import socket
import stat
WIN_PYTHON27_PATH = 'C:\python27\pythonw.exe'
WIN_PYTHON26_PATH = 'C:\python26\pythonw.exe'
def IsIdentifierChar( char ):
return char.isalnum() or char == '_'
@ -24,3 +36,99 @@ def IsIdentifierChar( char ):
def SanitizeQuery( query ):
return query.strip()
def ToUtf8IfNeeded( string_or_unicode ):
if isinstance( string_or_unicode, unicode ):
return string_or_unicode.encode( 'utf8' )
return string_or_unicode
def PathToTempDir():
tempdir = os.path.join( tempfile.gettempdir(), 'ycm_temp' )
if not os.path.exists( tempdir ):
os.makedirs( tempdir )
# Needed to support multiple users working on the same machine;
# see issue 606.
MakeFolderAccessibleToAll( tempdir )
return tempdir
def MakeFolderAccessibleToAll( path_to_folder ):
current_stat = os.stat( path_to_folder )
# readable, writable and executable by everyone
flags = ( current_stat.st_mode | stat.S_IROTH | stat.S_IWOTH | stat.S_IXOTH
| stat.S_IRGRP | stat.S_IWGRP | stat.S_IXGRP )
os.chmod( path_to_folder, flags )
def GetUnusedLocalhostPort():
sock = socket.socket()
# This tells the OS to give us any free port in the range [1024 - 65535]
sock.bind( ( '', 0 ) )
port = sock.getsockname()[ 1 ]
sock.close()
return port
def PathToPythonInterpreter():
# This is a bit tricky. Normally, sys.executable has the full path to the
# Python interpreter. But this code is also executed from inside Vim's
# embedded Python. On Unix machines, even that Python returns a good value for
# sys.executable, but on Windows it returns the path to the Vim binary, which
# is useless to us (issue #581). So we check the common install location for
# Python on Windows, first for Python 2.7 and then for 2.6.
#
# I'm open to better ideas on how to do this.
if OnWindows():
if os.path.exists( WIN_PYTHON27_PATH ):
return WIN_PYTHON27_PATH
elif os.path.exists( WIN_PYTHON26_PATH ):
return WIN_PYTHON26_PATH
raise RuntimeError( 'Python 2.7/2.6 not installed!' )
else:
return sys.executable
def OnWindows():
return sys.platform == 'win32'
# From here: http://stackoverflow.com/a/8536476/1672783
def TerminateProcess( pid ):
if OnWindows():
import ctypes
PROCESS_TERMINATE = 1
handle = ctypes.windll.kernel32.OpenProcess( PROCESS_TERMINATE,
False,
pid )
ctypes.windll.kernel32.TerminateProcess( handle, -1 )
ctypes.windll.kernel32.CloseHandle( handle )
else:
os.kill( pid, signal.SIGTERM )
def AddThirdPartyFoldersToSysPath():
path_to_third_party = os.path.join(
os.path.dirname( os.path.abspath( __file__ ) ),
'../../third_party' )
for folder in os.listdir( path_to_third_party ):
sys.path.insert( 0, os.path.realpath( os.path.join( path_to_third_party,
folder ) ) )
def Memoize( obj ):
cache = obj.cache = {}
@functools.wraps( obj )
def memoizer( *args, **kwargs ):
key = str( args ) + str( kwargs )
if key not in cache:
cache[ key ] = obj( *args, **kwargs )
return cache[ key ]
return memoizer
def ForceSemanticCompletion( request_data ):
return ( 'force_semantic' in request_data and
bool( request_data[ 'force_semantic' ] ) )

View File

@ -18,6 +18,8 @@
# along with YouCompleteMe. If not, see <http://www.gnu.org/licenses/>.
import vim
import os
import json
def CurrentLineAndColumn():
"""Returns the 0-based current line and 0-based current column."""
@ -46,12 +48,74 @@ def TextAfterCursor():
return vim.current.line[ CurrentColumn(): ]
def GetUnsavedBuffers():
def BufferModified( buffer_number ):
to_eval = 'getbufvar({0}, "&mod")'.format( buffer_number )
return GetBoolValue( to_eval )
# Note the difference between buffer OPTIONS and VARIABLES; the two are not
# the same.
def GetBufferOption( buffer_object, option ):
# The 'options' property is only available in recent (7.4+) Vim builds
if hasattr( buffer_object, 'options' ):
return buffer_object.options[ option ]
return ( x for x in vim.buffers if BufferModified( x.number ) )
to_eval = 'getbufvar({0}, "&{1}")'.format( buffer_object.number, option )
return GetVariableValue( to_eval )
def GetUnsavedAndCurrentBufferData():
def BufferModified( buffer_object ):
return bool( int( GetBufferOption( buffer_object, 'mod' ) ) )
buffers_data = {}
for buffer_object in vim.buffers:
if not ( BufferModified( buffer_object ) or
buffer_object == vim.current.buffer ):
continue
buffers_data[ GetBufferFilepath( buffer_object ) ] = {
'contents': '\n'.join( buffer_object ),
'filetypes': FiletypesForBuffer( buffer_object )
}
return buffers_data
def GetBufferNumberForFilename( filename, open_file_if_needed = True ):
return int( vim.eval( "bufnr('{0}', {1})".format(
os.path.realpath( filename ),
int( open_file_if_needed ) ) ) )
def GetCurrentBufferFilepath():
return GetBufferFilepath( vim.current.buffer )
def GetBufferFilepath( buffer_object ):
if buffer_object.name:
return buffer_object.name
# Buffers that have just been created by a command like :enew don't have any
# buffer name so we use the buffer number for that.
return os.path.join( os.getcwd(), str( buffer_object.number ) )
# Given a dict like {'a': 1}, loads it into Vim as if you ran 'let g:a = 1'
# When |overwrite| is True, overwrites the existing value in Vim.
def LoadDictIntoVimGlobals( new_globals, overwrite = True ):
extend_option = '"force"' if overwrite else '"keep"'
# We need to use json.dumps because that won't use the 'u' prefix on strings
# which Vim would bork on.
vim.eval( 'extend( g:, {0}, {1})'.format( json.dumps( new_globals ),
extend_option ) )
# Changing the returned dict will NOT change the value in Vim.
def GetReadOnlyVimGlobals( force_python_objects = False ):
if force_python_objects:
return vim.eval( 'g:' )
try:
# vim.vars is fairly new so it might not exist
return vim.vars
except:
return vim.eval( 'g:' )
# Both |line| and |column| need to be 1-based
@ -59,7 +123,7 @@ def JumpToLocation( filename, line, column ):
# Add an entry to the jumplist
vim.command( "normal! m'" )
if filename != vim.current.buffer.name:
if filename != GetCurrentBufferFilepath():
# We prefix the command with 'keepjumps' so that opening the file is not
# recorded in the jumplist. So when we open the file and move the cursor to
# a location in it, the user can use CTRL-O to jump back to the original
@ -73,18 +137,24 @@ def JumpToLocation( filename, line, column ):
vim.command( 'normal! zz' )
def NumLinesInBuffer( buffer ):
def NumLinesInBuffer( buffer_object ):
# This is actually less than obvious, that's why it's wrapped in a function
return len( buffer )
return len( buffer_object )
# Calling this function from the non-GUI thread will sometimes crash Vim. At the
# time of writing, YCM only uses the GUI thread inside Vim (this used to not be
# the case).
def PostVimMessage( message ):
# TODO: Check are we on the main thread or not, and if not, force a crash
# here. This should make it impossible to accidentally call this from a
# non-GUI thread which *sometimes* crashes Vim because Vim is not thread-safe.
# A consistent crash should force us to notice the error.
vim.command( "echohl WarningMsg | echomsg '{0}' | echohl None"
.format( EscapeForVim( message ) ) )
vim.command( "echohl WarningMsg | echom '{0}' | echohl None"
.format( EscapeForVim( str( message ) ) ) )
# Unlike PostVimMesasge, this supports messages with newlines in them because it
# uses 'echo' instead of 'echomsg'. This also means that the message will NOT
# appear in Vim's message log.
def PostMultiLineNotice( message ):
vim.command( "echohl WarningMsg | echo '{0}' | echohl None"
.format( EscapeForVim( str( message ) ) ) )
def PresentDialog( message, choices, default_choice_index = 0 ):
@ -128,15 +198,13 @@ def EscapeForVim( text ):
def CurrentFiletypes():
ft_string = vim.eval( "&filetype" )
return ft_string.split( '.' )
return vim.eval( "&filetype" ).split( '.' )
def FiletypesForBuffer( buffer_object ):
# NOTE: Getting &ft for other buffers only works when the buffer has been
# visited by the user at least once, which is true for modified buffers
ft_string = vim.eval( 'getbufvar({0}, "&ft")'.format( buffer_object.number ) )
return ft_string.split( '.' )
return GetBufferOption( buffer_object, 'ft' ).split( '.' )
def GetVariableValue( variable ):
@ -145,3 +213,8 @@ def GetVariableValue( variable ):
def GetBoolValue( variable ):
return bool( int( vim.eval( variable ) ) )
def GetIntValue( variable ):
return int( vim.eval( variable ) )

View File

@ -17,203 +17,301 @@
# You should have received a copy of the GNU General Public License
# along with YouCompleteMe. If not, see <http://www.gnu.org/licenses/>.
import imp
import os
import vim
import ycm_core
import subprocess
import tempfile
import json
from ycm import vimsupport
from ycm import utils
from ycm.completers.all.omni_completer import OmniCompleter
from ycm.completers.general.general_completer_store import GeneralCompleterStore
from ycm.completers.general import syntax_parse
from ycm.completers.completer_utils import FiletypeCompleterExistsForFiletype
from ycm.client.base_request import BaseRequest, BuildRequestData
from ycm.client.command_request import SendCommandRequest
from ycm.client.completion_request import CompletionRequest
from ycm.client.omni_completion_request import OmniCompletionRequest
from ycm.client.event_notification import ( SendEventNotificationAsync,
EventNotification )
from ycm.server.responses import ServerError
try:
from UltiSnips import UltiSnips_Manager
USE_ULTISNIPS_DATA = True
except ImportError:
USE_ULTISNIPS_DATA = False
FILETYPE_SPECIFIC_COMPLETION_TO_DISABLE = vim.eval(
'g:ycm_filetype_specific_completion_to_disable' )
SERVER_CRASH_MESSAGE_STDERR_FILE = 'The ycmd server SHUT DOWN with output:\n'
SERVER_CRASH_MESSAGE_SAME_STDERR = (
'The ycmd server shut down, check console output for logs!' )
class YouCompleteMe( object ):
def __init__( self ):
self.gencomp = GeneralCompleterStore()
self.omnicomp = OmniCompleter()
self.filetype_completers = {}
def __init__( self, user_options ):
self._user_options = user_options
self._omnicomp = OmniCompleter( user_options )
self._latest_completion_request = None
self._latest_file_parse_request = None
self._server_stdout = None
self._server_stderr = None
self._server_popen = None
self._filetypes_with_keywords_loaded = set()
self._temp_options_filename = None
self._SetupServer()
def GetGeneralCompleter( self ):
return self.gencomp
def _SetupServer( self ):
server_port = utils.GetUnusedLocalhostPort()
with tempfile.NamedTemporaryFile( delete = False ) as options_file:
self._temp_options_filename = options_file.name
json.dump( dict( self._user_options ), options_file )
args = [ utils.PathToPythonInterpreter(),
_PathToServerScript(),
'--port={0}'.format( server_port ),
'--options_file={0}'.format( options_file.name ),
'--log={0}'.format( self._user_options[ 'server_log_level' ] ),
'--idle_suicide_seconds={0}'.format(
self._user_options[ 'server_idle_suicide_seconds' ] ) ]
BaseRequest.server_location = 'http://localhost:' + str( server_port )
if self._user_options[ 'server_use_vim_stdout' ]:
self._server_popen = subprocess.Popen( args )
else:
filename_format = os.path.join( utils.PathToTempDir(),
'server_{port}_{std}.log' )
self._server_stdout = filename_format.format( port = server_port,
std = 'stdout' )
self._server_stderr = filename_format.format( port = server_port,
std = 'stderr' )
with open( self._server_stderr, 'w' ) as fstderr:
with open( self._server_stdout, 'w' ) as fstdout:
self._server_popen = subprocess.Popen( args,
stdout = fstdout,
stderr = fstderr )
self._NotifyUserIfServerCrashed()
def _IsServerAlive( self ):
returncode = self._server_popen.poll()
# When the process hasn't finished yet, poll() returns None.
return returncode is None
def _NotifyUserIfServerCrashed( self ):
if self._IsServerAlive():
return
if self._server_stderr:
with open( self._server_stderr, 'r' ) as server_stderr_file:
vimsupport.PostMultiLineNotice( SERVER_CRASH_MESSAGE_STDERR_FILE +
server_stderr_file.read() )
else:
vimsupport.PostVimMessage( SERVER_CRASH_MESSAGE_SAME_STDERR )
def ServerPid( self ):
if not self._server_popen:
return -1
return self._server_popen.pid
def RestartServer( self ):
vimsupport.PostVimMessage( 'Restarting ycmd server...' )
self.OnVimLeave()
self._SetupServer()
def CreateCompletionRequest( self, force_semantic = False ):
# We have to store a reference to the newly created CompletionRequest
# because VimScript can't store a reference to a Python object across
# function calls... Thus we need to keep this request somewhere.
if ( not self.NativeFiletypeCompletionAvailable() and
self.CurrentFiletypeCompletionEnabled() and
self._omnicomp.ShouldUseNow() ):
self._latest_completion_request = OmniCompletionRequest( self._omnicomp )
else:
self._latest_completion_request = ( CompletionRequest( force_semantic )
if self._IsServerAlive() else
None )
return self._latest_completion_request
def SendCommandRequest( self, arguments, completer ):
if self._IsServerAlive():
return SendCommandRequest( arguments, completer )
def GetDefinedSubcommands( self ):
if self._IsServerAlive():
return BaseRequest.PostDataToHandler( BuildRequestData(),
'defined_subcommands' )
else:
return []
def GetCurrentCompletionRequest( self ):
return self._latest_completion_request
def GetOmniCompleter( self ):
return self.omnicomp
def GetFiletypeCompleter( self ):
filetypes = vimsupport.CurrentFiletypes()
completers = [ self.GetFiletypeCompleterForFiletype( filetype )
for filetype in filetypes ]
if not completers:
return None
# Try to find a native completer first
for completer in completers:
if completer and completer is not self.omnicomp:
return completer
# Return the omni completer for the first filetype
return completers[0]
def GetFiletypeCompleterForFiletype( self, filetype ):
try:
return self.filetype_completers[ filetype ]
except KeyError:
pass
module_path = _PathToFiletypeCompleterPluginLoader( filetype )
completer = None
supported_filetypes = [ filetype ]
if os.path.exists( module_path ):
module = imp.load_source( filetype, module_path )
completer = module.GetCompleter()
if completer:
supported_filetypes.extend( completer.SupportedFiletypes() )
else:
completer = self.omnicomp
for supported_filetype in supported_filetypes:
self.filetype_completers[ supported_filetype ] = completer
return completer
def ShouldUseGeneralCompleter( self, start_column ):
return self.gencomp.ShouldUseNow( start_column )
def ShouldUseFiletypeCompleter( self, start_column ):
if self.FiletypeCompletionUsable():
return self.GetFiletypeCompleter().ShouldUseNow(
start_column )
return False
return self._omnicomp
def NativeFiletypeCompletionAvailable( self ):
completer = self.GetFiletypeCompleter()
return bool( completer ) and completer is not self.omnicomp
def FiletypeCompletionAvailable( self ):
return bool( self.GetFiletypeCompleter() )
return any( [ FiletypeCompleterExistsForFiletype( x ) for x in
vimsupport.CurrentFiletypes() ] )
def NativeFiletypeCompletionUsable( self ):
return ( _CurrentFiletypeCompletionEnabled() and
return ( self.CurrentFiletypeCompletionEnabled() and
self.NativeFiletypeCompletionAvailable() )
def FiletypeCompletionUsable( self ):
return ( _CurrentFiletypeCompletionEnabled() and
self.FiletypeCompletionAvailable() )
def OnFileReadyToParse( self ):
self.gencomp.OnFileReadyToParse()
self._omnicomp.OnFileReadyToParse( None )
if self.FiletypeCompletionUsable():
self.GetFiletypeCompleter().OnFileReadyToParse()
if not self._IsServerAlive():
self._NotifyUserIfServerCrashed()
extra_data = {}
if self._user_options[ 'collect_identifiers_from_tags_files' ]:
extra_data[ 'tag_files' ] = _GetTagFiles()
if self._user_options[ 'seed_identifiers_with_syntax' ]:
self._AddSyntaxDataIfNeeded( extra_data )
self._latest_file_parse_request = EventNotification( 'FileReadyToParse',
extra_data )
self._latest_file_parse_request.Start()
def OnBufferUnload( self, deleted_buffer_file ):
self.gencomp.OnBufferUnload( deleted_buffer_file )
if self.FiletypeCompletionUsable():
self.GetFiletypeCompleter().OnBufferUnload( deleted_buffer_file )
if not self._IsServerAlive():
return
SendEventNotificationAsync( 'BufferUnload',
{ 'unloaded_buffer': deleted_buffer_file } )
def OnBufferVisit( self ):
self.gencomp.OnBufferVisit()
if self.FiletypeCompletionUsable():
self.GetFiletypeCompleter().OnBufferVisit()
if not self._IsServerAlive():
return
extra_data = {}
_AddUltiSnipsDataIfNeeded( extra_data )
SendEventNotificationAsync( 'BufferVisit', extra_data )
def OnInsertLeave( self ):
self.gencomp.OnInsertLeave()
if self.FiletypeCompletionUsable():
self.GetFiletypeCompleter().OnInsertLeave()
if not self._IsServerAlive():
return
SendEventNotificationAsync( 'InsertLeave' )
def OnVimLeave( self ):
self.gencomp.OnVimLeave()
if self._IsServerAlive():
self._server_popen.terminate()
os.remove( self._temp_options_filename )
if self.FiletypeCompletionUsable():
self.GetFiletypeCompleter().OnVimLeave()
if not self._user_options[ 'server_keep_logfiles' ]:
if self._server_stderr:
os.remove( self._server_stderr )
if self._server_stdout:
os.remove( self._server_stdout )
def OnCurrentIdentifierFinished( self ):
if not self._IsServerAlive():
return
SendEventNotificationAsync( 'CurrentIdentifierFinished' )
def DiagnosticsForCurrentFileReady( self ):
if self.FiletypeCompletionUsable():
return self.GetFiletypeCompleter().DiagnosticsForCurrentFileReady()
return False
return bool( self._latest_file_parse_request and
self._latest_file_parse_request.Done() )
def GetDiagnosticsForCurrentFile( self ):
if self.FiletypeCompletionUsable():
return self.GetFiletypeCompleter().GetDiagnosticsForCurrentFile()
def GetDiagnosticsFromStoredRequest( self ):
if self.DiagnosticsForCurrentFileReady():
to_return = self._latest_file_parse_request.Response()
# We set the diagnostics request to None because we want to prevent
# Syntastic from repeatedly refreshing the buffer with the same diags.
# Setting this to None makes DiagnosticsForCurrentFileReady return False
# until the next request is created.
self._latest_file_parse_request = None
return to_return
return []
def ShowDetailedDiagnostic( self ):
if self.FiletypeCompletionUsable():
return self.GetFiletypeCompleter().ShowDetailedDiagnostic()
def GettingCompletions( self ):
if self.FiletypeCompletionUsable():
return self.GetFiletypeCompleter().GettingCompletions()
return False
def OnCurrentIdentifierFinished( self ):
self.gencomp.OnCurrentIdentifierFinished()
if self.FiletypeCompletionUsable():
self.GetFiletypeCompleter().OnCurrentIdentifierFinished()
if not self._IsServerAlive():
return
try:
debug_info = BaseRequest.PostDataToHandler( BuildRequestData(),
'detailed_diagnostic' )
if 'message' in debug_info:
vimsupport.EchoText( debug_info[ 'message' ] )
except ServerError as e:
vimsupport.PostVimMessage( str( e ) )
def DebugInfo( self ):
completers = set( self.filetype_completers.values() )
completers.add( self.gencomp )
output = []
for completer in completers:
if not completer:
continue
debug = completer.DebugInfo()
if debug:
output.append( debug )
if self._IsServerAlive():
debug_info = BaseRequest.PostDataToHandler( BuildRequestData(),
'debug_info' )
else:
debug_info = 'Server crashed, no debug info from server'
debug_info += '\nServer running at: {0}'.format(
BaseRequest.server_location )
debug_info += '\nServer process ID: {0}'.format( self._server_popen.pid )
if self._server_stderr or self._server_stdout:
debug_info += '\nServer logfiles:\n {0}\n {1}'.format(
self._server_stdout,
self._server_stderr )
has_clang_support = ycm_core.HasClangSupport()
output.append( 'Has Clang support compiled in: {0}'.format(
has_clang_support ) )
if has_clang_support:
output.append( ycm_core.ClangVersion() )
return '\n'.join( output )
return debug_info
def _CurrentFiletypeCompletionEnabled():
def CurrentFiletypeCompletionEnabled( self ):
filetypes = vimsupport.CurrentFiletypes()
return not all([ x in FILETYPE_SPECIFIC_COMPLETION_TO_DISABLE
for x in filetypes ])
filetype_to_disable = self._user_options[
'filetype_specific_completion_to_disable' ]
return not all([ x in filetype_to_disable for x in filetypes ])
def _PathToCompletersFolder():
def _AddSyntaxDataIfNeeded( self, extra_data ):
filetype = vimsupport.CurrentFiletypes()[ 0 ]
if filetype in self._filetypes_with_keywords_loaded:
return
self._filetypes_with_keywords_loaded.add( filetype )
extra_data[ 'syntax_keywords' ] = list(
syntax_parse.SyntaxKeywordsForCurrentBuffer() )
def _GetTagFiles():
tag_files = vim.eval( 'tagfiles()' )
current_working_directory = os.getcwd()
return [ os.path.join( current_working_directory, x ) for x in tag_files ]
def _PathToServerScript():
dir_of_current_script = os.path.dirname( os.path.abspath( __file__ ) )
return os.path.join( dir_of_current_script, 'completers' )
return os.path.join( dir_of_current_script, 'server/ycmd.py' )
def _PathToFiletypeCompleterPluginLoader( filetype ):
return os.path.join( _PathToCompletersFolder(), filetype, 'hook.py' )
def _AddUltiSnipsDataIfNeeded( extra_data ):
if not USE_ULTISNIPS_DATA:
return
try:
rawsnips = UltiSnips_Manager._snips( '', 1 )
except:
return
# UltiSnips_Manager._snips() returns a class instance where:
# class.trigger - name of snippet trigger word ( e.g. defn or testcase )
# class.description - description of the snippet
extra_data[ 'ultisnips_snippets' ] = [ { 'trigger': x.trigger,
'description': x.description
} for x in rawsnips ]

46
run_tests.sh Executable file
View File

@ -0,0 +1,46 @@
#!/usr/bin/env bash
set -e
function usage {
echo "Usage: $0 [--no-clang-completer]"
exit 0
}
flake8 --select=F,C9 --max-complexity=10 python
use_clang_completer=true
for flag in $@; do
case "$flag" in
--no-clang-completer)
use_clang_completer=false
;;
*)
usage
;;
esac
done
if [ -n "$USE_CLANG_COMPLETER" ]; then
use_clang_completer=$USE_CLANG_COMPLETER
fi
if $use_clang_completer; then
extra_cmake_args="-DUSE_CLANG_COMPLETER=ON -DUSE_DEV_FLAGS=ON"
else
extra_cmake_args="-DUSE_DEV_FLAGS=ON"
fi
EXTRA_CMAKE_ARGS=$extra_cmake_args YCM_TESTRUN=1 ./install.sh --omnisharp-completer
for directory in third_party/*; do
if [ -d "${directory}" ]; then
export PYTHONPATH=$PWD/${directory}:$PYTHONPATH
fi
done
if $use_clang_completer; then
nosetests -v python
else
nosetests -v --exclude=".*Clang.*" python
fi

View File

@ -19,6 +19,7 @@ astyle \
--recursive \
--exclude=gmock \
--exclude=testdata \
--exclude=ycm_client_support.cpp \
--exclude=ycm_core.cpp \
--exclude=CustomAssert.h \
--exclude=CustomAssert.cpp \

1
third_party/argparse vendored Submodule

@ -0,0 +1 @@
Subproject commit 46af816db4812eab5f4639717bf1ad2eb17cc1ff

1
third_party/bottle vendored Submodule

@ -0,0 +1 @@
Subproject commit 154369b2b9f7393ca9d3d73de7e046e2342cf00a

1
third_party/frozendict vendored Submodule

@ -0,0 +1 @@
Subproject commit 2ea45f3f429c5283f9f24738812f0ee152a8f4c1

1
third_party/jedi vendored Submodule

@ -0,0 +1 @@
Subproject commit 099fe4eeb3544005a8e2ffdaae43fd8a12c82f16

44
third_party/pythonfutures/CHANGES vendored Executable file
View File

@ -0,0 +1,44 @@
2.1.4
=====
- Ported the library again from Python 3.2.5 to get the latest bug fixes
2.1.3
=====
- Fixed race condition in wait(return_when=ALL_COMPLETED)
(http://bugs.python.org/issue14406) -- thanks Ralf Schmitt
- Added missing setUp() methods to several test classes
2.1.2
=====
- Fixed installation problem on Python 3.1
2.1.1
=====
- Fixed missing 'concurrent' package declaration in setup.py
2.1
===
- Moved the code from the 'futures' package to 'concurrent.futures' to provide
a drop in backport that matches the code in Python 3.2 standard library
- Deprecated the old 'futures' package
2.0
===
- Changed implementation to match PEP 3148
1.0
===
Initial release.

21
third_party/pythonfutures/LICENSE vendored Executable file
View File

@ -0,0 +1,21 @@
Copyright 2009 Brian Quinlan. All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY BRIAN QUINLAN "AS IS" AND ANY EXPRESS OR IMPLIED
WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
HALL THE FREEBSD PROJECT OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@ -0,0 +1,3 @@
from pkgutil import extend_path
__path__ = extend_path(__path__, __name__)

Some files were not shown because too many files have changed in this diff Show More