123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740 |
- import vim
- import os
- import tempfile
- import json
- import re
- from ycmd.utils import ToUtf8IfNeeded
- from ycmd import user_options_store
- BUFFER_COMMAND_MAP = { 'same-buffer' : 'edit',
- 'horizontal-split' : 'split',
- 'vertical-split' : 'vsplit',
- 'new-tab' : 'tabedit' }
- def CurrentLineAndColumn():
- """Returns the 0-based current line and 0-based current column."""
-
-
- line, column = vim.current.window.cursor
- line -= 1
- return line, column
- def CurrentColumn():
- """Returns the 0-based current column. Do NOT access the CurrentColumn in
- vim.current.line. It doesn't exist yet when the cursor is at the end of the
- line. Only the chars before the current column exist in vim.current.line."""
-
-
-
-
-
- return vim.current.window.cursor[ 1 ]
- def CurrentLineContents():
- return vim.current.line
- def TextAfterCursor():
- """Returns the text after CurrentColumn."""
- return vim.current.line[ CurrentColumn(): ]
- def TextBeforeCursor():
- """Returns the text before CurrentColumn."""
- return vim.current.line[ :CurrentColumn() ]
- def VimVersionAtLeast( version_string ):
- major, minor, patch = [ int( x ) for x in version_string.split( '.' ) ]
-
- actual_major_and_minor = GetIntValue( 'v:version' )
- matching_major_and_minor = major * 100 + minor
- if actual_major_and_minor != matching_major_and_minor:
- return actual_major_and_minor > matching_major_and_minor
- return GetBoolValue( 'has("patch{0}")'.format( patch ) )
- def GetBufferOption( buffer_object, option ):
-
-
-
-
-
-
-
-
- to_eval = 'getbufvar({0}, "&{1}")'.format( buffer_object.number, option )
- return GetVariableValue( to_eval )
- def BufferModified( buffer_object ):
- return bool( int( GetBufferOption( buffer_object, 'mod' ) ) )
- def GetUnsavedAndCurrentBufferData():
- 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 ) + '\n',
- 'filetypes': FiletypesForBuffer( buffer_object )
- }
- return buffers_data
- def GetBufferNumberForFilename( filename, open_file_if_needed = True ):
- return GetIntValue( u"bufnr('{0}', {1})".format(
- EscapeForVim( os.path.realpath( filename ) ),
- int( open_file_if_needed ) ) )
- def GetCurrentBufferFilepath():
- return GetBufferFilepath( vim.current.buffer )
- def BufferIsVisible( buffer_number ):
- if buffer_number < 0:
- return False
- window_number = GetIntValue( "bufwinnr({0})".format( buffer_number ) )
- return window_number != -1
- def GetBufferFilepath( buffer_object ):
- if buffer_object.name:
- return buffer_object.name
-
-
-
- try:
- folder_path = os.getcwd()
- except OSError:
- folder_path = tempfile.gettempdir()
- return os.path.join( folder_path, str( buffer_object.number ) )
- def UnplaceSignInBuffer( buffer_number, sign_id ):
- if buffer_number < 0:
- return
- vim.command(
- 'try | exec "sign unplace {0} buffer={1}" | catch /E158/ | endtry'.format(
- sign_id, buffer_number ) )
- def PlaceSign( sign_id, line_num, buffer_num, is_error = True ):
-
-
- if line_num < 1:
- line_num = 1
- sign_name = 'YcmError' if is_error else 'YcmWarning'
- vim.command( 'sign place {0} line={1} name={2} buffer={3}'.format(
- sign_id, line_num, sign_name, buffer_num ) )
- def PlaceDummySign( sign_id, buffer_num, line_num ):
- if buffer_num < 0 or line_num < 0:
- return
- vim.command( 'sign define ycm_dummy_sign' )
- vim.command(
- 'sign place {0} name=ycm_dummy_sign line={1} buffer={2}'.format(
- sign_id,
- line_num,
- buffer_num,
- )
- )
- def UnPlaceDummySign( sign_id, buffer_num ):
- if buffer_num < 0:
- return
- vim.command( 'sign undefine ycm_dummy_sign' )
- vim.command( 'sign unplace {0} buffer={1}'.format( sign_id, buffer_num ) )
- def ClearYcmSyntaxMatches():
- matches = VimExpressionToPythonType( 'getmatches()' )
- for match in matches:
- if match[ 'group' ].startswith( 'Ycm' ):
- vim.eval( 'matchdelete({0})'.format( match[ 'id' ] ) )
- def AddDiagnosticSyntaxMatch( line_num,
- column_num,
- line_end_num = None,
- column_end_num = None,
- is_error = True ):
- group = 'YcmErrorSection' if is_error else 'YcmWarningSection'
- if not line_end_num:
- line_end_num = line_num
- line_num, column_num = LineAndColumnNumbersClamped( line_num, column_num )
- line_end_num, column_end_num = LineAndColumnNumbersClamped( line_end_num,
- column_end_num )
- if not column_end_num:
- return GetIntValue(
- "matchadd('{0}', '\%{1}l\%{2}c')".format( group, line_num, column_num ) )
- else:
- return GetIntValue(
- "matchadd('{0}', '\%{1}l\%{2}c\_.\\{{-}}\%{3}l\%{4}c')".format(
- group, line_num, column_num, line_end_num, column_end_num ) )
- def LineAndColumnNumbersClamped( line_num, column_num ):
- new_line_num = line_num
- new_column_num = column_num
- max_line = len( vim.current.buffer )
- if line_num and line_num > max_line:
- new_line_num = max_line
- max_column = len( vim.current.buffer[ new_line_num - 1 ] )
- if column_num and column_num > max_column:
- new_column_num = max_column
- return new_line_num, new_column_num
- def SetLocationList( diagnostics ):
- """Diagnostics should be in qflist format; see ":h setqflist" for details."""
- vim.eval( 'setloclist( 0, {0} )'.format( json.dumps( diagnostics ) ) )
- def ConvertDiagnosticsToQfList( diagnostics ):
- def ConvertDiagnosticToQfFormat( diagnostic ):
-
-
-
-
- location = diagnostic[ 'location' ]
- line_num = location[ 'line_num' ]
-
-
- if line_num < 1:
- line_num = 1
- text = diagnostic[ 'text' ]
- if diagnostic.get( 'fixit_available', False ):
- text += ' (FixIt available)'
- return {
- 'bufnr' : GetBufferNumberForFilename( location[ 'filepath' ] ),
- 'lnum' : line_num,
- 'col' : location[ 'column_num' ],
- 'text' : ToUtf8IfNeeded( text ),
- 'type' : diagnostic[ 'kind' ][ 0 ],
- 'valid' : 1
- }
- return [ ConvertDiagnosticToQfFormat( x ) for x in diagnostics ]
- def LoadDictIntoVimGlobals( new_globals, overwrite = True ):
- extend_option = '"force"' if overwrite else '"keep"'
-
-
- vim.eval( 'extend( g:, {0}, {1})'.format( json.dumps( new_globals ),
- extend_option ) )
- def GetReadOnlyVimGlobals( force_python_objects = False ):
- if force_python_objects:
- return vim.eval( 'g:' )
- try:
-
- return vim.vars
- except:
- return vim.eval( 'g:' )
- def VimExpressionToPythonType( vim_expression ):
- result = vim.eval( vim_expression )
- if not isinstance( result, basestring ):
- return result
- try:
- return int( result )
- except ValueError:
- return result
- def HiddenEnabled( buffer_object ):
- return bool( int( GetBufferOption( buffer_object, 'hid' ) ) )
- def BufferIsUsable( buffer_object ):
- return not BufferModified( buffer_object ) or HiddenEnabled( buffer_object )
- def EscapedFilepath( filepath ):
- return filepath.replace( ' ' , r'\ ' )
- def TryJumpLocationInOpenedTab( filename, line, column ):
- filepath = os.path.realpath( filename )
- for tab in vim.tabpages:
- for win in tab.windows:
- if win.buffer.name == filepath:
- vim.current.tabpage = tab
- vim.current.window = win
- vim.current.window.cursor = ( line, column - 1 )
-
- vim.command( 'normal! zz' )
- return True
-
- return False
- def GetVimCommand( user_command, default = 'edit' ):
- vim_command = BUFFER_COMMAND_MAP.get( user_command, default )
- if vim_command == 'edit' and not BufferIsUsable( vim.current.buffer ):
- vim_command = 'split'
- return vim_command
- def JumpToLocation( filename, line, column ):
-
- vim.command( "normal! m'" )
- if filename != GetCurrentBufferFilepath():
-
-
-
-
-
-
- user_command = user_options_store.Value( 'goto_buffer_command' )
- if user_command == 'new-or-existing-tab':
- if TryJumpLocationInOpenedTab( filename, line, column ):
- return
- user_command = 'new-tab'
- vim_command = GetVimCommand( user_command )
- try:
- vim.command( 'keepjumps {0} {1}'.format( vim_command,
- EscapedFilepath( filename ) ) )
-
-
-
- except vim.error as e:
- if 'E325' not in str( e ):
- raise
-
- if filename != GetCurrentBufferFilepath():
- return
-
- except KeyboardInterrupt:
- return
- vim.current.window.cursor = ( line, column - 1 )
-
- vim.command( 'normal! zz' )
- def NumLinesInBuffer( buffer_object ):
-
- return len( buffer_object )
- def PostVimMessage( message ):
- vim.command( "redraw | echohl WarningMsg | echom '{0}' | echohl None"
- .format( EscapeForVim( str( message ) ) ) )
- def PostMultiLineNotice( message ):
- vim.command( "echohl WarningMsg | echo '{0}' | echohl None"
- .format( EscapeForVim( str( message ) ) ) )
- def PresentDialog( message, choices, default_choice_index = 0 ):
- """Presents the user with a dialog where a choice can be made.
- This will be a dialog for gvim users or a question in the message buffer
- for vim users or if `set guioptions+=c` was used.
- choices is list of alternatives.
- default_choice_index is the 0-based index of the default element
- that will get choosen if the user hits <CR>. Use -1 for no default.
- PresentDialog will return a 0-based index into the list
- or -1 if the dialog was dismissed by using <Esc>, Ctrl-C, etc.
- See also:
- :help confirm() in vim (Note that vim uses 1-based indexes)
- Example call:
- PresentDialog("Is this a nice example?", ["Yes", "No", "May&be"])
- Is this a nice example?
- [Y]es, (N)o, May(b)e:"""
- to_eval = "confirm('{0}', '{1}', {2})".format( EscapeForVim( message ),
- EscapeForVim( "\n" .join( choices ) ), default_choice_index + 1 )
- return int( vim.eval( to_eval ) ) - 1
- def Confirm( message ):
- return bool( PresentDialog( message, [ "Ok", "Cancel" ] ) == 0 )
- def EchoText( text, log_as_message = True ):
- def EchoLine( text ):
- command = 'echom' if log_as_message else 'echo'
- vim.command( "{0} '{1}'".format( command, EscapeForVim( text ) ) )
- for line in str( text ).split( '\n' ):
- EchoLine( line )
- def EchoTextVimWidth( text ):
- vim_width = GetIntValue( '&columns' )
- truncated_text = ToUtf8IfNeeded( text )[ : int( vim_width * 0.9 ) ]
- truncated_text.replace( '\n', ' ' )
- old_ruler = GetIntValue( '&ruler' )
- old_showcmd = GetIntValue( '&showcmd' )
- vim.command( 'set noruler noshowcmd' )
- EchoText( truncated_text, False )
- vim.command( 'let &ruler = {0}'.format( old_ruler ) )
- vim.command( 'let &showcmd = {0}'.format( old_showcmd ) )
- def EscapeForVim( text ):
- return text.replace( "'", "''" )
- def CurrentFiletypes():
- return vim.eval( "&filetype" ).split( '.' )
- def FiletypesForBuffer( buffer_object ):
-
-
- return GetBufferOption( buffer_object, 'ft' ).split( '.' )
- def VariableExists( variable ):
- return GetBoolValue( "exists( '{0}' )".format( EscapeForVim( variable ) ) )
- def SetVariableValue( variable, value ):
- vim.command( "let {0} = '{1}'".format( variable, EscapeForVim( value ) ) )
- def GetVariableValue( variable ):
- return vim.eval( variable )
- def GetBoolValue( variable ):
- return bool( int( vim.eval( variable ) ) )
- def GetIntValue( variable ):
- return int( vim.eval( variable ) )
- def ReplaceChunksList( chunks, vim_buffer = None ):
- if vim_buffer is None:
- vim_buffer = vim.current.buffer
-
-
- chunks.sort( key = lambda chunk: (
- chunk[ 'range' ][ 'start' ][ 'line_num' ],
- chunk[ 'range' ][ 'start' ][ 'column_num' ]
- ) )
-
-
-
- last_line = -1
- line_delta = 0
- for chunk in chunks:
- if chunk[ 'range' ][ 'start' ][ 'line_num' ] != last_line:
-
-
- last_line = chunk[ 'range' ][ 'end' ][ 'line_num' ]
- char_delta = 0
- ( new_line_delta, new_char_delta ) = ReplaceChunk(
- chunk[ 'range' ][ 'start' ],
- chunk[ 'range' ][ 'end' ],
- chunk[ 'replacement_text' ],
- line_delta, char_delta,
- vim_buffer )
- line_delta += new_line_delta
- char_delta += new_char_delta
- def ReplaceChunk( start, end, replacement_text, line_delta, char_delta,
- vim_buffer ):
-
-
- start_line = start[ 'line_num' ] - 1 + line_delta
- end_line = end[ 'line_num' ] - 1 + line_delta
- source_lines_count = end_line - start_line + 1
- start_column = start[ 'column_num' ] - 1 + char_delta
- end_column = end[ 'column_num' ] - 1
- if source_lines_count == 1:
- end_column += char_delta
- replacement_lines = replacement_text.splitlines( False )
- if not replacement_lines:
- replacement_lines = [ '' ]
- replacement_lines_count = len( replacement_lines )
- end_existing_text = vim_buffer[ end_line ][ end_column : ]
- start_existing_text = vim_buffer[ start_line ][ : start_column ]
- new_char_delta = ( len( replacement_lines[ -1 ] )
- - ( end_column - start_column ) )
- if replacement_lines_count > 1:
- new_char_delta -= start_column
- replacement_lines[ 0 ] = start_existing_text + replacement_lines[ 0 ]
- replacement_lines[ -1 ] = replacement_lines[ -1 ] + end_existing_text
- vim_buffer[ start_line : end_line + 1 ] = replacement_lines[:]
- new_line_delta = replacement_lines_count - source_lines_count
- return ( new_line_delta, new_char_delta )
- def InsertNamespace( namespace ):
- if VariableExists( 'g:ycm_csharp_insert_namespace_expr' ):
- expr = GetVariableValue( 'g:ycm_csharp_insert_namespace_expr' )
- if expr:
- SetVariableValue( "g:ycm_namespace_to_insert", namespace )
- vim.eval( expr )
- return
- pattern = '^\s*using\(\s\+[a-zA-Z0-9]\+\s\+=\)\?\s\+[a-zA-Z0-9.]\+\s*;\s*'
- line = SearchInCurrentBuffer( pattern )
- existing_line = LineTextInCurrentBuffer( line )
- existing_indent = re.sub( r"\S.*", "", existing_line )
- new_line = "{0}using {1};\n\n".format( existing_indent, namespace )
- replace_pos = { 'line_num': line + 1, 'column_num': 1 }
- ReplaceChunk( replace_pos, replace_pos, new_line, 0, 0 )
- PostVimMessage( "Add namespace: {0}".format( namespace ) )
- def SearchInCurrentBuffer( pattern ):
- return GetIntValue( "search('{0}', 'Wcnb')".format( EscapeForVim( pattern )))
- def LineTextInCurrentBuffer( line ):
- return vim.current.buffer[ line ]
- def ClosePreviewWindow():
- """ Close the preview window if it is present, otherwise do nothing """
- vim.command( 'silent! pclose!' )
- def JumpToPreviewWindow():
- """ Jump the vim cursor to the preview window, which must be active. Returns
- boolean indicating if the cursor ended up in the preview window """
- vim.command( 'silent! wincmd P' )
- return vim.current.window.options[ 'previewwindow' ]
- def JumpToPreviousWindow():
- """ Jump the vim cursor to its previous window position """
- vim.command( 'silent! wincmd p' )
- def JumpToTab( tab_number ):
- """Jump to Vim tab with corresponding number """
- vim.command( 'silent! tabn {0}'.format( tab_number ) )
- def OpenFileInPreviewWindow( filename ):
- """ Open the supplied filename in the preview window """
- vim.command( 'silent! pedit! ' + filename )
- def WriteToPreviewWindow( message ):
- """ Display the supplied message in the preview window """
-
-
-
-
-
-
- ClosePreviewWindow()
- OpenFileInPreviewWindow( vim.eval( 'tempname()' ) )
- if JumpToPreviewWindow():
-
-
-
- vim.current.buffer.options[ 'modifiable' ] = True
- vim.current.buffer.options[ 'readonly' ] = False
- vim.current.buffer[:] = message.splitlines()
- vim.current.buffer.options[ 'buftype' ] = 'nofile'
- vim.current.buffer.options[ 'swapfile' ] = False
- vim.current.buffer.options[ 'modifiable' ] = False
- vim.current.buffer.options[ 'readonly' ] = True
-
-
- vim.current.buffer.options[ 'modified' ] = False
- JumpToPreviousWindow()
- else:
-
-
-
- EchoText( message )
- def CheckFilename( filename ):
- """Check if filename is openable."""
- try:
- open( filename ).close()
- except TypeError:
- raise RuntimeError( "'{0}' is not a valid filename".format( filename ) )
- except IOError as error:
- raise RuntimeError(
- "filename '{0}' cannot be opened. {1}".format( filename, error ) )
- def BufferExistsForFilename( filename ):
- """Check if a buffer exists for a specific file."""
- return GetBufferNumberForFilename( filename, False ) is not -1
- def CloseBuffersForFilename( filename ):
- """Close all buffers for a specific file."""
- buffer_number = GetBufferNumberForFilename( filename, False )
- while buffer_number is not -1:
- vim.command( 'silent! bwipeout! {0}'.format( buffer_number ) )
- buffer_number = GetBufferNumberForFilename( filename, False )
- def OpenFilename( filename, options = {} ):
- """Open a file in Vim. Following options are available:
- - command: specify which Vim command is used to open the file. Choices
- are same-buffer, horizontal-split, vertical-split, and new-tab (default:
- horizontal-split);
- - size: set the height of the window for a horizontal split or the width for
- a vertical one (default: '');
- - fix: set the winfixheight option for a horizontal split or winfixwidth for
- a vertical one (default: False). See :h winfix for details;
- - focus: focus the opened file (default: False);
- - watch: automatically watch for changes (default: False). This is useful
- for logs;
- - position: set the position where the file is opened (default: start).
- Choices are start and end."""
-
- command = GetVimCommand( options.get( 'command', 'horizontal-split' ),
- 'horizontal-split' )
- size = ( options.get( 'size', '' ) if command in [ 'split', 'vsplit' ] else
- '' )
- focus = options.get( 'focus', False )
- watch = options.get( 'watch', False )
- position = options.get( 'position', 'start' )
-
-
- if not focus and command is 'tabedit':
- previous_tab = GetIntValue( 'tabpagenr()' )
-
- CheckFilename( filename )
- vim.command( 'silent! {0}{1} {2}'.format( size, command, filename ) )
- if command is 'split':
- vim.current.window.options[ 'winfixheight' ] = options.get( 'fix', False )
- if command is 'vsplit':
- vim.current.window.options[ 'winfixwidth' ] = options.get( 'fix', False )
- if watch:
- vim.current.buffer.options[ 'autoread' ] = True
- vim.command( "exec 'au BufEnter <buffer> :silent! checktime {0}'"
- .format( filename ) )
- if position is 'end':
- vim.command( 'silent! normal G zz' )
-
-
-
- if not focus:
- if command is 'tabedit':
- JumpToTab( previous_tab )
- if command in [ 'split', 'vsplit' ]:
- JumpToPreviousWindow()
|