diff options
author | Karel Kočí <cynerd@email.cz> | 2016-06-30 16:11:56 +0200 |
---|---|---|
committer | Karel Kočí <cynerd@email.cz> | 2016-06-30 16:11:56 +0200 |
commit | 9931e0888b2419326ae10ebbfae532261c5c125f (patch) | |
tree | 7504be5daccbb7b7d1ea396754de47b11ed790e5 /vim/bundle/YouCompleteMe/python/ycm/vimsupport.py | |
parent | e573b3020c032400eed60b649a2cbf55266e6bb0 (diff) | |
download | myconfigs-9931e0888b2419326ae10ebbfae532261c5c125f.tar.gz myconfigs-9931e0888b2419326ae10ebbfae532261c5c125f.tar.bz2 myconfigs-9931e0888b2419326ae10ebbfae532261c5c125f.zip |
Fix submodules
Diffstat (limited to 'vim/bundle/YouCompleteMe/python/ycm/vimsupport.py')
m--------- | vim/bundle/YouCompleteMe | 0 | ||||
-rw-r--r-- | vim/bundle/YouCompleteMe/python/ycm/vimsupport.py | 978 |
2 files changed, 0 insertions, 978 deletions
diff --git a/vim/bundle/YouCompleteMe b/vim/bundle/YouCompleteMe new file mode 160000 +Subproject 0de1c0c9bb13ce82172b472c676035cd47cf6a6 diff --git a/vim/bundle/YouCompleteMe/python/ycm/vimsupport.py b/vim/bundle/YouCompleteMe/python/ycm/vimsupport.py deleted file mode 100644 index c4a600c..0000000 --- a/vim/bundle/YouCompleteMe/python/ycm/vimsupport.py +++ /dev/null @@ -1,978 +0,0 @@ -# Copyright (C) 2011, 2012 Google Inc. -# -# 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 __future__ import unicode_literals -from __future__ import print_function -from __future__ import division -from __future__ import absolute_import -from future import standard_library -standard_library.install_aliases() -from builtins import * # noqa - -from future.utils import iterkeys -import vim -import os -import tempfile -import json -import re -from collections import defaultdict -from ycmd.utils import ToUnicode, ToBytes -from ycmd import user_options_store - -BUFFER_COMMAND_MAP = { 'same-buffer' : 'edit', - 'horizontal-split' : 'split', - 'vertical-split' : 'vsplit', - 'new-tab' : 'tabedit' } - -FIXIT_OPENING_BUFFERS_MESSAGE_FORMAT = ( - 'The requested operation will apply changes to {0} files which are not ' - 'currently open. This will therefore open {0} new files in the hidden ' - 'buffers. The quickfix list can then be used to review the changes. No ' - 'files will be written to disk. Do you wish to continue?' ) - - -def CurrentLineAndColumn(): - """Returns the 0-based current line and 0-based current column.""" - # See the comment in CurrentColumn about the calculation for the line and - # column number - 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.""" - - # vim's columns are 1-based while vim.current.line columns are 0-based - # ... but vim.current.window.cursor (which returns a (line, column) tuple) - # columns are 0-based, while the line from that same tuple is 1-based. - # vim.buffers buffer objects OTOH have 0-based lines and columns. - # Pigs have wings and I'm a loopy purple duck. Everything makes sense now. - return vim.current.window.cursor[ 1 ] - - -def CurrentLineContents(): - return ToUnicode( vim.current.line ) - - -def TextAfterCursor(): - """Returns the text after CurrentColumn.""" - return ToUnicode( vim.current.line[ CurrentColumn(): ] ) - - -def TextBeforeCursor(): - """Returns the text before CurrentColumn.""" - return ToUnicode( vim.current.line[ :CurrentColumn() ] ) - - -# Expects version_string in 'MAJOR.MINOR.PATCH' format, e.g. '7.4.301' -def VimVersionAtLeast( version_string ): - major, minor, patch = [ int( x ) for x in version_string.split( '.' ) ] - - # For Vim 7.4.301, v:version is '704' - 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 ) ) - - -# Note the difference between buffer OPTIONS and VARIABLES; the two are not -# the same. -def GetBufferOption( buffer_object, option ): - # NOTE: We used to check for the 'options' property on the buffer_object which - # is available in recent versions of Vim and would then use: - # - # buffer_object.options[ option ] - # - # to read the value, BUT this caused annoying flickering when the - # buffer_object was a hidden buffer (with option = 'ft'). This was all due to - # a Vim bug. Until this is fixed, we won't use it. - - 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 ) ] = { - # Add a newline to match what gets saved to disk. See #1455 for details. - 'contents': '\n'.join( ToUnicode( x ) for x in 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 - # 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. Also, os.getcwd() throws - # an exception when the CWD has been deleted so we handle that. - 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 ): - # libclang can give us diagnostics that point "outside" the file; Vim borks - # on these. - 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' ] ) ) - - -# Returns the ID of the newly added match -# Both line and column numbers are 1-based -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 ) ) - - -# Clamps the line and column numbers so that they are not past the contents of -# the buffer. Numbers are 1-based byte offsets. -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 SetQuickFixList( quickfix_list, focus = False, autoclose = False ): - """Populate the quickfix list and open it. List should be in qflist format: - see ":h setqflist" for details. When focus is set to True, the quickfix - window becomes the active window. When autoclose is set to True, the quickfix - window is automatically closed after an entry is selected.""" - vim.eval( 'setqflist( {0} )'.format( json.dumps( quickfix_list ) ) ) - OpenQuickFixList( focus, autoclose ) - - -def OpenQuickFixList( focus = False, autoclose = False ): - """Open the quickfix list to full width at the bottom of the screen with its - height automatically set to fit all entries. This behavior can be overridden - by using the YcmQuickFixOpened autocommand. - See the SetQuickFixList function for the focus and autoclose options.""" - vim.command( 'botright copen' ) - - SetFittingHeightForCurrentWindow() - - if autoclose: - # This autocommand is automatically removed when the quickfix window is - # closed. - vim.command( 'au WinLeave <buffer> q' ) - - if VariableExists( '#User#YcmQuickFixOpened' ): - vim.command( 'doautocmd User YcmQuickFixOpened' ) - - if not focus: - JumpToPreviousWindow() - - -def SetFittingHeightForCurrentWindow(): - window_width = GetIntValue( 'winwidth( 0 )' ) - fitting_height = 0 - for line in vim.current.buffer: - fitting_height += len( line ) // window_width + 1 - vim.command( '{0}wincmd _'.format( fitting_height ) ) - - -def ConvertDiagnosticsToQfList( diagnostics ): - def ConvertDiagnosticToQfFormat( 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. The documentation states quite clearly that it - # expects a byte offset, by which it means "1-based column number" as - # described in :h getqflist ("the first column is 1"). - location = diagnostic[ 'location' ] - line_num = location[ 'line_num' ] - - # libclang can give us diagnostics that point "outside" the file; Vim borks - # on these. - 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' : text, - 'type' : diagnostic[ 'kind' ][ 0 ], - 'valid' : 1 - } - - return [ ConvertDiagnosticToQfFormat( x ) for x in diagnostics ] - - -def GetVimGlobalsKeys(): - return vim.eval( 'keys( g: )' ) - - -def VimExpressionToPythonType( vim_expression ): - """Returns a Python type from the return value of the supplied Vim expression. - If the expression returns a list, dict or other non-string type, then it is - returned unmodified. If the string return can be converted to an - integer, returns an integer, otherwise returns the result converted to a - Unicode string.""" - - result = vim.eval( vim_expression ) - if not ( isinstance( result, str ) or isinstance( result, bytes ) ): - return result - - try: - return int( result ) - except ValueError: - return ToUnicode( 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'\ ' ) - - -# Both |line| and |column| need to be 1-based -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 ) - - # Center the screen on the jumped-to location - vim.command( 'normal! zz' ) - return True - # 'filename' is not opened in any tab pages - return False - - -# Maps User command to vim command -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 - - -# Both |line| and |column| need to be 1-based -def JumpToLocation( filename, line, column ): - # Add an entry to the jumplist - vim.command( "normal! m'" ) - - 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 - # location, not to the start of the newly opened file. - # Sadly this fails on random occasions and the undesired jump remains in the - # jumplist. - 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 ) ) ) - # When the file we are trying to jump to has a swap file - # Vim opens swap-exists-choices dialog and throws vim.error with E325 error, - # or KeyboardInterrupt after user selects one of the options. - except vim.error as e: - if 'E325' not in str( e ): - raise - # Do nothing if the target file is still not opened (user chose (Q)uit) - if filename != GetCurrentBufferFilepath(): - return - # Thrown when user chooses (A)bort in .swp message box - except KeyboardInterrupt: - return - vim.current.window.cursor = ( line, column - 1 ) - - # Center the screen on the jumped-to location - vim.command( 'normal! zz' ) - - -def NumLinesInBuffer( buffer_object ): - # This is actually less than obvious, that's why it's wrapped in a function - 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). -# We redraw the screen before displaying the message to avoid the "Press ENTER -# or type command to continue" prompt when editing a new C-family file. -def PostVimMessage( message ): - vim.command( "redraw | echohl WarningMsg | echom '{0}' | echohl None" - .format( EscapeForVim( ToUnicode( 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( ToUnicode( 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( ToUnicode( message ) ), - EscapeForVim( ToUnicode( "\n" .join( choices ) ) ), - default_choice_index + 1 ) - return int( vim.eval( to_eval ) ) - 1 - - -def Confirm( message ): - """Display |message| with Ok/Cancel operations. Returns True if the user - selects Ok""" - 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 ToUnicode( text ).split( '\n' ): - EchoLine( line ) - - -# Echos text but truncates it so that it all fits on one line -def EchoTextVimWidth( text ): - vim_width = GetIntValue( '&columns' ) - truncated_text = ToUnicode( text )[ : int( vim_width * 0.9 ) ] - truncated_text.replace( '\n', ' ' ) - - old_ruler = GetIntValue( '&ruler' ) - old_showcmd = GetIntValue( '&showcmd' ) - vim.command( 'set noruler noshowcmd' ) - vim.command( 'redraw' ) - - EchoText( truncated_text, False ) - - SetVariableValue( '&ruler', old_ruler ) - SetVariableValue( '&showcmd', old_showcmd ) - - -def EscapeForVim( text ): - return ToUnicode( text.replace( "'", "''" ) ) - - -def CurrentFiletypes(): - return VimExpressionToPythonType( "&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 - 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, json.dumps( 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 _SortChunksByFile( chunks ): - """Sort the members of the list |chunks| (which must be a list of dictionaries - conforming to ycmd.responses.FixItChunk) by their filepath. Returns a new - list in arbitrary order.""" - - chunks_by_file = defaultdict( list ) - - for chunk in chunks: - filepath = chunk[ 'range' ][ 'start' ][ 'filepath' ] - chunks_by_file[ filepath ].append( chunk ) - - return chunks_by_file - - -def _GetNumNonVisibleFiles( file_list ): - """Returns the number of file in the iterable list of files |file_list| which - are not curerntly open in visible windows""" - return len( - [ f for f in file_list - if not BufferIsVisible( GetBufferNumberForFilename( f, False ) ) ] ) - - -def _OpenFileInSplitIfNeeded( filepath ): - """Ensure that the supplied filepath is open in a visible window, opening a - new split if required. Returns the buffer number of the file and an indication - of whether or not a new split was opened. - - If the supplied filename is already open in a visible window, return just - return its buffer number. If the supplied file is not visible in a window - in the current tab, opens it in a new vertical split. - - Returns a tuple of ( buffer_num, split_was_opened ) indicating the buffer - number and whether or not this method created a new split. If the user opts - not to open a file, or if opening fails, this method raises RuntimeError, - otherwise, guarantees to return a visible buffer number in buffer_num.""" - - buffer_num = GetBufferNumberForFilename( filepath, False ) - - # We only apply changes in the current tab page (i.e. "visible" windows). - # Applying changes in tabs does not lead to a better user experience, as the - # quickfix list no longer works as you might expect (doesn't jump into other - # tabs), and the complexity of choosing where to apply edits is significant. - if BufferIsVisible( buffer_num ): - # file is already open and visible, just return that buffer number (and an - # idicator that we *didn't* open a split) - return ( buffer_num, False ) - - # The file is not open in a visible window, so we open it in a split. - # We open the file with a small, fixed height. This means that we don't - # make the current buffer the smallest after a series of splits. - OpenFilename( filepath, { - 'focus': True, - 'fix': True, - 'size': GetIntValue( '&previewheight' ), - } ) - - # OpenFilename returns us to the original cursor location. This is what we - # want, because we don't want to disorientate the user, but we do need to - # know the (now open) buffer number for the filename - buffer_num = GetBufferNumberForFilename( filepath, False ) - if not BufferIsVisible( buffer_num ): - # This happens, for example, if there is a swap file and the user - # selects the "Quit" or "Abort" options. We just raise an exception to - # make it clear to the user that the abort has left potentially - # partially-applied changes. - raise RuntimeError( - 'Unable to open file: {0}\nFixIt/Refactor operation ' - 'aborted prior to completion. Your files have not been ' - 'fully updated. Please use undo commands to revert the ' - 'applied changes.'.format( filepath ) ) - - # We opened this file in a split - return ( buffer_num, True ) - - -def ReplaceChunks( chunks ): - """Apply the source file deltas supplied in |chunks| to arbitrary files. - |chunks| is a list of changes defined by ycmd.responses.FixItChunk, - which may apply arbitrary modifications to arbitrary files. - - If a file specified in a particular chunk is not currently open in a visible - buffer (i.e., one in a window visible in the current tab), we: - - issue a warning to the user that we're going to open new files (and offer - her the option to abort cleanly) - - open the file in a new split, make the changes, then hide the buffer. - - If for some reason a file could not be opened or changed, raises RuntimeError. - Otherwise, returns no meaningful value.""" - - # We apply the edits file-wise for efficiency, and because we must track the - # file-wise offset deltas (caused by the modifications to the text). - chunks_by_file = _SortChunksByFile( chunks ) - - # We sort the file list simply to enable repeatable testing - sorted_file_list = sorted( iterkeys( chunks_by_file ) ) - - # Make sure the user is prepared to have her screen mutilated by the new - # buffers - num_files_to_open = _GetNumNonVisibleFiles( sorted_file_list ) - - if num_files_to_open > 0: - if not Confirm( - FIXIT_OPENING_BUFFERS_MESSAGE_FORMAT.format( num_files_to_open ) ): - return - - # Store the list of locations where we applied changes. We use this to display - # the quickfix window showing the user where we applied changes. - locations = [] - - for filepath in sorted_file_list: - ( buffer_num, close_window ) = _OpenFileInSplitIfNeeded( filepath ) - - ReplaceChunksInBuffer( chunks_by_file[ filepath ], - vim.buffers[ buffer_num ], - locations ) - - # When opening tons of files, we don't want to have a split for each new - # file, as this simply does not scale, so we open the window, make the - # edits, then hide the window. - if close_window: - # Some plugins (I'm looking at you, syntastic) might open a location list - # for the window we just opened. We don't want that location list hanging - # around, so we close it. lclose is a no-op if there is no location list. - vim.command( 'lclose' ) - - # Note that this doesn't lose our changes. It simply "hides" the buffer, - # which can later be re-accessed via the quickfix list or `:ls` - vim.command( 'hide' ) - - # Open the quickfix list, populated with entries for each location we changed. - if locations: - SetQuickFixList( locations ) - - EchoTextVimWidth( "Applied " + str( len( chunks ) ) + " changes" ) - - -def ReplaceChunksInBuffer( chunks, vim_buffer, locations ): - """Apply changes in |chunks| to the buffer-like object |buffer|. Append each - chunk's start to the list |locations|""" - - # We need to track the difference in length, but ensuring we apply fixes - # in ascending order of insertion point. - chunks.sort( key = lambda chunk: ( - chunk[ 'range' ][ 'start' ][ 'line_num' ], - chunk[ 'range' ][ 'start' ][ 'column_num' ] - ) ) - - # Remember the line number we're processing. Negative line number means we - # haven't processed any lines yet (by nature of being not equal to any - # real line number). - last_line = -1 - - line_delta = 0 - for chunk in chunks: - if chunk[ 'range' ][ 'start' ][ 'line_num' ] != last_line: - # If this chunk is on a different line than the previous chunk, - # then ignore previous deltas (as offsets won't have changed). - 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, - locations ) - line_delta += new_line_delta - char_delta += new_char_delta - - -# Replace the chunk of text specified by a contiguous range with the supplied -# text. -# * start and end are objects with line_num and column_num properties -# * the range is inclusive -# * indices are all 1-based -# * the returned character delta is the delta for the last line -# -# returns the delta (in lines and characters) that any position after the end -# needs to be adjusted by. -# -# NOTE: Works exclusively with bytes() instances and byte offsets as returned -# by ycmd and used within the Vim buffers -def ReplaceChunk( start, end, replacement_text, line_delta, char_delta, - vim_buffer, locations = None ): - # ycmd's results are all 1-based, but vim's/python's are all 0-based - # (so we do -1 on all of the values) - 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 - - # NOTE: replacement_text is unicode, but all our offsets are byte offsets, - # so we convert to bytes - replacement_lines = ToBytes( replacement_text ).splitlines( False ) - if not replacement_lines: - replacement_lines = [ bytes( b'' ) ] - 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[:] - - if locations is not None: - locations.append( { - 'bufnr': vim_buffer.number, - 'filename': vim_buffer.name, - # line and column numbers are 1-based in qflist - 'lnum': start_line + 1, - 'col': start_column + 1, - 'text': replacement_text, - 'type': 'F', - } ) - - 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 """ - - # This isn't something that comes naturally to Vim. Vim only wants to show - # tags and/or actual files in the preview window, so we have to hack it a - # little bit. We generate a temporary file name and "open" that, then write - # the data to it. We make sure the buffer can't be edited or saved. Other - # approaches include simply opening a split, but we want to take advantage of - # the existing Vim options for preview window height, etc. - - ClosePreviewWindow() - - OpenFileInPreviewWindow( vim.eval( 'tempname()' ) ) - - if JumpToPreviewWindow(): - # We actually got to the preview window. By default the preview window can't - # be changed, so we make it writable, write to it, then make it read only - # again. - 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 - - # We need to prevent closing the window causing a warning about unsaved - # file, so we pretend to Vim that the buffer has not been changed. - vim.current.buffer.options[ 'modified' ] = False - - JumpToPreviousWindow() - else: - # We couldn't get to the preview window, but we still want to give the user - # the information we have. The only remaining option is to echo to the - # status area. - EchoText( message ) - - -def CheckFilename( filename ): - """Check if filename is openable.""" - try: - # We don't want to check for encoding issues when trying to open the file - # so we open it in binary mode. - open( filename, mode = 'rb' ).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.strerror ) ) - - -def BufferIsVisibleForFilename( filename ): - """Check if a buffer exists for a specific file.""" - buffer_number = GetBufferNumberForFilename( filename, False ) - return BufferIsVisible( buffer_number ) - - -def CloseBuffersForFilename( filename ): - """Close all buffers for a specific file.""" - buffer_number = GetBufferNumberForFilename( filename, False ) - while buffer_number != -1: - vim.command( 'silent! bwipeout! {0}'.format( buffer_number ) ) - new_buffer_number = GetBufferNumberForFilename( filename, False ) - if buffer_number == new_buffer_number: - raise RuntimeError( "Buffer {0} for filename '{1}' should already be " - "wiped out.".format( buffer_number, filename ) ) - buffer_number = new_buffer_number - - -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.""" - - # Set the options. - command = GetVimCommand( options.get( 'command', 'horizontal-split' ), - 'horizontal-split' ) - size = ( options.get( 'size', '' ) if command in [ 'split', 'vsplit' ] else - '' ) - focus = options.get( 'focus', False ) - - # There is no command in Vim to return to the previous tab so we need to - # remember the current tab if needed. - if not focus and command == 'tabedit': - previous_tab = GetIntValue( 'tabpagenr()' ) - else: - previous_tab = None - - # Open the file - CheckFilename( filename ) - try: - vim.command( '{0}{1} {2}'.format( size, command, filename ) ) - # When the file we are trying to jump to has a swap file, - # Vim opens swap-exists-choices dialog and throws vim.error with E325 error, - # or KeyboardInterrupt after user selects one of the options which actually - # opens the file (Open read-only/Edit anyway). - except vim.error as e: - if 'E325' not in str( e ): - raise - - # Otherwise, the user might have chosen Quit. This is detectable by the - # current file not being the target file - if filename != GetCurrentBufferFilepath(): - return - except KeyboardInterrupt: - # Raised when the user selects "Abort" after swap-exists-choices - return - - _SetUpLoadedBuffer( command, - filename, - options.get( 'fix', False ), - options.get( 'position', 'start' ), - options.get( 'watch', False ) ) - - # Vim automatically set the focus to the opened file so we need to get the - # focus back (if the focus option is disabled) when opening a new tab or - # window. - if not focus: - if command == 'tabedit': - JumpToTab( previous_tab ) - if command in [ 'split', 'vsplit' ]: - JumpToPreviousWindow() - - -def _SetUpLoadedBuffer( command, filename, fix, position, watch ): - """After opening a buffer, configure it according to the supplied options, - which are as defined by the OpenFilename method.""" - - if command == 'split': - vim.current.window.options[ 'winfixheight' ] = fix - if command == 'vsplit': - vim.current.window.options[ 'winfixwidth' ] = fix - - if watch: - vim.current.buffer.options[ 'autoread' ] = True - vim.command( "exec 'au BufEnter <buffer> :silent! checktime {0}'" - .format( filename ) ) - - if position == 'end': - vim.command( 'silent! normal G zz' ) |