Auto merge of #1917 - puremourning:fixit-multiple-files-upstream, r=Valloric
[READY] Support FixIt and Refactor commands across multiple files ## Apply FixIt chunks across files Previously, FixIts could only apply to the current buffer. Now we can apply FixIts across files, even if they are not currently open in the user's Vim. We obey existing configuration about how/where to open new files and apply changes in such a way as to not scribble the user's filesystem, and allow full undo history, like existing FixIt commands. Vim's python API actually makes this really quite easy. To achieve this, we sort the chunks by filename, apply the existing logic unchanged. The only difference is that `ReplaceChunk` no longer implicitly assumes it applies to `vim.current.buffer`. ## Recognise 'FixIt' responses for any subcommand Previously, we used the subcommand name to determine the type of response to expect. This coupled the client and the server and didn't allow us to apply FixIts for a "Refactor" command, or "GoTo" for a, theoretical "Find" command. Now 'GoTo' and 'FixIt' commands don't need to start with those prefixes. For 'FixIt' we can detect the response type by looking for the 'fixits' entry in the response. ## Other changes The `vimsupport.OpenFilename` method needed to handle the presence of Vim swap files and the various user responses. This pushed the cyclomatic complexity over the threshold, so the code was partially obfuscated to accommodate the cyclomatic complexity gods. <!-- Reviewable:start --> [<img src="https://reviewable.io/review_button.png" height=40 alt="Review on Reviewable"/>](https://reviewable.io/reviews/valloric/youcompleteme/1917) <!-- Reviewable:end -->
This commit is contained in:
commit
be0de73c42
201
README.md
201
README.md
@ -28,6 +28,9 @@ YouCompleteMe: a code-completion engine for Vim
|
||||
- [Diagnostic highlighting groups](#diagnostic-highlighting-groups)
|
||||
- [Commands](#commands)
|
||||
- [YcmCompleter subcommands](#ycmcompleter-subcommands)
|
||||
- [Go to declaration/definition/etc. commands](#goto-commands)
|
||||
- [Semantic type information and documentation](#semantic-information-commands)
|
||||
- [Refactoring and FixIt commands](#refactoring-and-fixit-commands)
|
||||
- [Options](#options)
|
||||
- [FAQ](#faq)
|
||||
- [Contributor Code of Conduct](#contributor-code-of-conduct)
|
||||
@ -104,9 +107,21 @@ features plus extra:
|
||||
- Supertab
|
||||
- neocomplcache
|
||||
|
||||
YCM also provides semantic go-to-definition/declaration commands for C-family
|
||||
languages & Python. Expect more IDE features powered by the various YCM semantic
|
||||
engines in the future.
|
||||
### And that's not all...
|
||||
|
||||
YCM also provides [semantic IDE-like features](#quick-feature-summary) in a
|
||||
number of languages, including:
|
||||
|
||||
- finding declarations, definitions, usages, etc. of identifiers,
|
||||
- displaying type information for classes, variables, functions etc.,
|
||||
- displaying documentation for methods, members, etc. in the preview window,
|
||||
- fixing common coding errors, like missing semi-colons, typos, etc.,
|
||||
- semantic renaming of variables across files (JavaScript only).
|
||||
|
||||
Features vary by file type, so make sure to check out the [file type feature
|
||||
summary](#quick-feature-summary) and the
|
||||
[full list of completer subcommands](#ycmcompleter-subcommands) to
|
||||
find out what's available for your favourite languages.
|
||||
|
||||
You'll also find that YCM has filepath completers (try typing `./` in a file)
|
||||
and a completer that integrates with [UltiSnips][].
|
||||
@ -682,6 +697,7 @@ Quick Feature Summary
|
||||
### JavaScript
|
||||
|
||||
* Intelligent auto-completion
|
||||
* Renaming variables (`RefactorRename <new name>`)
|
||||
* Go to definition, find references (`GoToDefinition`, `GoToReferences`)
|
||||
* Type information for identifiers (`GetType`)
|
||||
* View documentation comments for identifiers (`GetDoc`)
|
||||
@ -1106,20 +1122,25 @@ purpose.
|
||||
|
||||
### The `:YcmCompleter` command
|
||||
|
||||
This command can be used to invoke completer-specific commands. If the first
|
||||
This command gives access to a number of additional [IDE-like
|
||||
features](#quick-feature-summary) in YCM, for things like semantic GoTo, type
|
||||
information, FixIt and refactoring.
|
||||
|
||||
Technically the command invokes completer-specific commands. If the first
|
||||
argument is of the form `ft=...` the completer for that file type will be used
|
||||
(for example `ft=cpp`), else the native completer of the current buffer will be
|
||||
used.
|
||||
Call `YcmCompleter` without further arguments for information about the
|
||||
commands you can call for the selected completer.
|
||||
Call `YcmCompleter` without further arguments for a list of the
|
||||
commands you can call for the current completer.
|
||||
|
||||
See the _YcmCompleter subcommands_ section for more information on the available
|
||||
subcommands.
|
||||
See the [file type feature summary](#quick-feature-summary) for an overview of
|
||||
the features available for each file type. See the _YcmCompleter subcommands_
|
||||
section for more information on the available subcommands and their usage.
|
||||
|
||||
YcmCompleter subcommands
|
||||
------------------------
|
||||
|
||||
[See the docs for the `YcmCompleter` command before tackling this section.]
|
||||
NOTE: See the docs for the `YcmCompleter` command before tackling this section.
|
||||
|
||||
The invoked subcommand is automatically routed to the currently active semantic
|
||||
completer, so `:YcmCompleter GoToDefinition` will invoke the `GoToDefinition`
|
||||
@ -1131,23 +1152,26 @@ You may also want to map the subcommands to something less verbose; for
|
||||
instance, `nnoremap <leader>jd :YcmCompleter GoTo<CR>`
|
||||
maps the `<leader>jd` sequence to the longer subcommand invocation.
|
||||
|
||||
The various `GoTo*` subcommands add entries to Vim's `jumplist` so you can use
|
||||
### GoTo commands
|
||||
|
||||
These commands are useful for jumping around and exploring code. When moving
|
||||
the cursor, the subcommands add entries to Vim's `jumplist` so you can use
|
||||
`CTRL-O` to jump back to where you where before invoking the command (and
|
||||
`CTRL-I` to jump forward; see `:h jumplist` for details).
|
||||
|
||||
### The `GoToInclude` subcommand
|
||||
#### The `GoToInclude` subcommand
|
||||
|
||||
Looks up the current line for a header and jumps to it.
|
||||
|
||||
Supported in filetypes: `c, cpp, objc, objcpp`
|
||||
|
||||
### The `GoToDeclaration` subcommand
|
||||
#### The `GoToDeclaration` subcommand
|
||||
|
||||
Looks up the symbol under the cursor and jumps to its declaration.
|
||||
|
||||
Supported in filetypes: `c, cpp, objc, objcpp, cs, go, python, rust`
|
||||
|
||||
### The `GoToDefinition` subcommand
|
||||
#### The `GoToDefinition` subcommand
|
||||
|
||||
Looks up the symbol under the cursor and jumps to its definition.
|
||||
|
||||
@ -1159,7 +1183,7 @@ with `#include` directives (directly or indirectly) in that file.
|
||||
Supported in filetypes: `c, cpp, objc, objcpp, cs, go, javascript, python,
|
||||
rust, typescript`
|
||||
|
||||
### The `GoTo` subcommand
|
||||
#### The `GoTo` subcommand
|
||||
|
||||
This command tries to perform the "most sensible" GoTo operation it can.
|
||||
Currently, this means that it tries to look up the symbol under the cursor and
|
||||
@ -1170,7 +1194,7 @@ jump to it. For C#, implementations are also considered and preferred.
|
||||
|
||||
Supported in filetypes: `c, cpp, objc, objcpp, cs, go, javascript, python, rust`
|
||||
|
||||
### The `GoToImprecise` subcommand
|
||||
#### The `GoToImprecise` subcommand
|
||||
|
||||
WARNING: This command trades correctness for speed!
|
||||
|
||||
@ -1183,7 +1207,7 @@ latency.
|
||||
|
||||
Supported in filetypes: `c, cpp, objc, objcpp`
|
||||
|
||||
### The `GoToReferences` subcommand
|
||||
#### The `GoToReferences` subcommand
|
||||
|
||||
This command attempts to find all of the references within the project to the
|
||||
identifier under the cursor and populates the quickfix list with those
|
||||
@ -1191,19 +1215,28 @@ locations.
|
||||
|
||||
Supported in filetypes: `javascript, python, typescript`
|
||||
|
||||
### The `ClearCompilationFlagCache` subcommand
|
||||
#### The `GoToImplementation` subcommand
|
||||
|
||||
YCM caches the flags it gets from the `FlagsForFile` function in your
|
||||
`ycm_extra_conf.py` file if you return them with the `do_cache` parameter set to
|
||||
`True`. The cache is in memory and is never invalidated (unless you restart Vim
|
||||
of course).
|
||||
Looks up the symbol under the cursor and jumps to its implementation (i.e.
|
||||
non-interface). If there are multiple implementations, instead provides a list
|
||||
of implementations to choose from.
|
||||
|
||||
This command clears that cache entirely. YCM will then re-query your
|
||||
`FlagsForFile` function as needed in the future.
|
||||
Supported in filetypes: `cs`
|
||||
|
||||
Supported in filetypes: `c, cpp, objc, objcpp`
|
||||
#### The `GoToImplementationElseDeclaration` subcommand
|
||||
|
||||
### The `GetType` subcommand
|
||||
Looks up the symbol under the cursor and jumps to its implementation if one,
|
||||
else jump to its declaration. If there are multiple implementations, instead
|
||||
provides a list of implementations to choose from.
|
||||
|
||||
Supported in filetypes: `cs`
|
||||
|
||||
### Semantic information commands
|
||||
|
||||
These commands are useful for finding static information about the code, such
|
||||
as the types of variables, viewing declarations and documentation strings.
|
||||
|
||||
#### The `GetType` subcommand
|
||||
|
||||
Echos the type of the variable or method under the cursor, and where it differs,
|
||||
the derived type.
|
||||
@ -1234,7 +1267,7 @@ NOTE: Causes re-parsing of the current translation unit.
|
||||
|
||||
Supported in filetypes: `c, cpp, objc, objcpp, javascript, typescript`
|
||||
|
||||
### The `GetParent` subcommand
|
||||
#### The `GetParent` subcommand
|
||||
|
||||
Echos the semantic parent of the point under the cursor.
|
||||
|
||||
@ -1265,7 +1298,26 @@ NOTE: Causes re-parsing of the current translation unit.
|
||||
|
||||
Supported in filetypes: `c, cpp, objc, objcpp`
|
||||
|
||||
### The `FixIt` subcommand
|
||||
#### The `GetDoc` subcommand
|
||||
|
||||
Displays the preview window populated with quick info about the identifier
|
||||
under the cursor. Depending on the file type, this includes things like:
|
||||
|
||||
* The type or declaration of identifier,
|
||||
* Doxygen/javadoc comments,
|
||||
* Python docstrings,
|
||||
* etc.
|
||||
|
||||
Supported in filetypes: `c, cpp, objc, objcpp, cs, python, typescript,
|
||||
javascript`
|
||||
|
||||
### Refactoring and FixIt commands
|
||||
|
||||
These commands make changes to your source code in order to perform refactoring
|
||||
or code correction. YouCompleteMe does not perform any action which cannot be
|
||||
undone, and never saves or writes files to the disk.
|
||||
|
||||
#### The `FixIt` subcommand
|
||||
|
||||
Where available, attempts to make changes to the buffer to correct the
|
||||
diagnostic closest to the cursor position.
|
||||
@ -1290,8 +1342,8 @@ indication).
|
||||
NOTE: Causes re-parsing of the current translation unit.
|
||||
|
||||
NOTE: After applying a fix-it, the diagnostics UI is not immediately updated.
|
||||
This is due to a technical restriction in vim, and moving the cursor, or issuing
|
||||
the the `:YcmForceCompileAndDiagnostics` command will refresh the diagnostics.
|
||||
This is due to a technical restriction in Vim. Moving the cursor, or issuing
|
||||
the `:YcmForceCompileAndDiagnostics` command will refresh the diagnostics.
|
||||
Repeated invocations of the `FixIt` command on a given line, however, _do_ apply
|
||||
all diagnostics as expected without requiring refreshing of the diagnostics UI.
|
||||
This is particularly useful where there are multiple diagnostics on one line, or
|
||||
@ -1299,34 +1351,81 @@ where after fixing one diagnostic, another fix-it is available.
|
||||
|
||||
Supported in filetypes: `c, cpp, objc, objcpp, cs`
|
||||
|
||||
### The `GetDoc` subcommand
|
||||
#### The `RefactorRename <new name>` subcommand
|
||||
|
||||
Displays the preview window populated with quick info about the identifier
|
||||
under the cursor. This includes, depending on the language, things like:
|
||||
In supported file types, this command attempts to perform a semantic rename of
|
||||
the identifier under the cursor. This includes renaming declarations,
|
||||
definitions and usages of the identifier, or any other language-appropriate
|
||||
action. The specific behavior is defined by the semantic engine in use.
|
||||
|
||||
* The type or declaration of identifier
|
||||
* Doxygen/javadoc comments
|
||||
* Python docstrings
|
||||
* etc.
|
||||
Similar to `FixIt`, this command applies automatic modifications to your source
|
||||
files. Rename operations may involve changes to multiple files, which may or may
|
||||
not be open in Vim buffers at the time. YouCompleteMe handles all of this for
|
||||
you. The behavior is described in [the following section](#multi-file-refactor).
|
||||
|
||||
Supported in filetypes: `c, cpp, objc, objcpp, cs, python, typescript,
|
||||
javascript`
|
||||
Supported in filetypes: `javascript` (variables only)
|
||||
|
||||
### The `StartServer` subcommand
|
||||
#### Multi-file Refactor
|
||||
|
||||
When a Refactor or FixIt command touches multiple files, YouCompleteMe attempts
|
||||
to apply those modifications to any existing open, visible buffer in the current
|
||||
tab. If no such buffer can be found, YouCompleteMe opens the file in a new
|
||||
small horizontal split at the top of the current window, applies the change,
|
||||
and then *hides* the window. NOTE: The buffer remains open, and must be
|
||||
manually saved. A confirmation dialog is opened prior to doing this to remind
|
||||
you that this is about to happen.
|
||||
|
||||
Once the modifications have been made, the quickfix list (see `:help quickfix`)
|
||||
is opened and populated with the locations of all modifications. This can be
|
||||
used to review all automatic changes made. Typically, use the `CTRL-W
|
||||
<enter>` combination to open the selected file in a new split.
|
||||
|
||||
The buffers are *not* saved automatically. That is, you must save the modified
|
||||
buffers manually after reviewing the changes from the quickfix list. Changes
|
||||
can be undone using Vim's powerful undo features (see `:help undo`). Note
|
||||
that Vim's undo is per-buffer, so to undo all changes, the undo commands must
|
||||
be applied in each modified buffer separately.
|
||||
|
||||
NOTE: While applying modifications, Vim may find files which are already open
|
||||
and have a swap file. The command is aborted if you select Abort or Quit in any
|
||||
such prompts. This leaves the Refactor operation partially complete and must be
|
||||
manually corrected using Vim's undo features. The quickfix list is *not*
|
||||
populated in this case. Inspect `:buffers` or equivalent (see `:help buffers`)
|
||||
to see the buffers that were opened by the command.
|
||||
|
||||
### Miscellaneous commands
|
||||
|
||||
These commands are for general administration, rather than IDE-like features.
|
||||
They cover things like the semantic engine server instance and compilation
|
||||
flags.
|
||||
|
||||
#### The `ClearCompilationFlagCache` subcommand
|
||||
|
||||
YCM caches the flags it gets from the `FlagsForFile` function in your
|
||||
`ycm_extra_conf.py` file if you return them with the `do_cache` parameter set to
|
||||
`True`. The cache is in memory and is never invalidated (unless you restart Vim
|
||||
of course).
|
||||
|
||||
This command clears that cache entirely. YCM will then re-query your
|
||||
`FlagsForFile` function as needed in the future.
|
||||
|
||||
Supported in filetypes: `c, cpp, objc, objcpp`
|
||||
|
||||
#### The `StartServer` subcommand
|
||||
|
||||
Starts the semantic-engine-as-localhost-server for those semantic engines that
|
||||
work as separate servers that YCM talks to.
|
||||
|
||||
Supported in filetypes: `cs, go, javascript, rust`
|
||||
|
||||
### The `StopServer` subcommand
|
||||
#### The `StopServer` subcommand
|
||||
|
||||
Stops the semantic-engine-as-localhost-server for those semantic engines that
|
||||
work as separate servers that YCM talks to.
|
||||
|
||||
Supported in filetypes: `cs, go, javascript, rust`
|
||||
|
||||
### The `RestartServer` subcommand
|
||||
#### The `RestartServer` subcommand
|
||||
|
||||
Restarts the semantic-engine-as-localhost-server for those semantic engines that
|
||||
work as separate servers that YCM talks to.
|
||||
@ -1340,7 +1439,7 @@ python binary to use to restart the Python semantic engine.
|
||||
|
||||
Supported in filetypes: `cs, python, rust`
|
||||
|
||||
### The `ReloadSolution` subcommand
|
||||
#### The `ReloadSolution` subcommand
|
||||
|
||||
Instruct the Omnisharp server to clear its cache and reload all files from disk.
|
||||
This is useful when files are added, removed, or renamed in the solution, files
|
||||
@ -1348,22 +1447,6 @@ are changed outside of Vim, or whenever Omnisharp cache is out-of-sync.
|
||||
|
||||
Supported in filetypes: `cs`
|
||||
|
||||
### The `GoToImplementation` subcommand
|
||||
|
||||
Looks up the symbol under the cursor and jumps to its implementation (i.e.
|
||||
non-interface). If there are multiple implementations, instead provides a list
|
||||
of implementations to choose from.
|
||||
|
||||
Supported in filetypes: `cs`
|
||||
|
||||
### The `GoToImplementationElseDeclaration` subcommand
|
||||
|
||||
Looks up the symbol under the cursor and jumps to its implementation if one,
|
||||
else jump to its declaration. If there are multiple implementations, instead
|
||||
provides a list of implementations to choose from.
|
||||
|
||||
Supported in filetypes: `cs`
|
||||
|
||||
Functions
|
||||
--------
|
||||
|
||||
@ -1896,7 +1979,7 @@ let g:ycm_csharp_server_port = 0
|
||||
By default, when YCM inserts a namespace, it will insert the `using` statement
|
||||
under the nearest `using` statement. You may prefer that the `using` statement is
|
||||
inserted somewhere, for example, to preserve sorting. If so, you can set this
|
||||
option to override this behaviour.
|
||||
option to override this behavior.
|
||||
|
||||
When this option is set, instead of inserting the `using` statement itself, YCM
|
||||
will set the global variable `g:ycm_namespace_to_insert` to the namespace to
|
||||
|
@ -5,6 +5,7 @@ Contents ~
|
||||
|
||||
1. Introduction |youcompleteme-introduction|
|
||||
2. Intro |youcompleteme-intro|
|
||||
1. And that's not all... |youcompleteme-thats-not-all...|
|
||||
3. Installation |youcompleteme-installation|
|
||||
1. Mac OS X Installation |youcompleteme-mac-os-x-installation|
|
||||
2. Ubuntu Linux x64 Installation |youcompleteme-ubuntu-linux-x64-installation|
|
||||
@ -48,23 +49,29 @@ Contents ~
|
||||
6. The |:YcmToggleLogs| command
|
||||
7. The |:YcmCompleter| command
|
||||
7. YcmCompleter subcommands |youcompleteme-ycmcompleter-subcommands|
|
||||
1. GoTo commands |youcompleteme-goto-commands|
|
||||
1. The |GoToInclude| subcommand
|
||||
2. The |GoToDeclaration| subcommand
|
||||
3. The |GoToDefinition| subcommand
|
||||
4. The |GoTo| subcommand
|
||||
5. The |GoToImprecise| subcommand
|
||||
6. The |GoToReferences| subcommand
|
||||
7. The |ClearCompilationFlagCache| subcommand
|
||||
8. The |GetType| subcommand
|
||||
9. The |GetParent| subcommand
|
||||
10. The |FixIt| subcommand
|
||||
11. The |GetDoc| subcommand
|
||||
12. The |StartServer| subcommand
|
||||
13. The |StopServer| subcommand
|
||||
14. The |RestartServer| subcommand
|
||||
15. The |ReloadSolution| subcommand
|
||||
16. The |GoToImplementation| subcommand
|
||||
17. The |GoToImplementationElseDeclaration| subcommand
|
||||
7. The |GoToImplementation| subcommand
|
||||
8. The |GoToImplementationElseDeclaration| subcommand
|
||||
2. Semantic information commands |youcompleteme-semantic-information-commands|
|
||||
1. The |GetType| subcommand
|
||||
2. The |GetParent| subcommand
|
||||
3. The |GetDoc| subcommand
|
||||
3. Refactoring and FixIt commands |youcompleteme-refactoring-fixit-commands|
|
||||
1. The |FixIt| subcommand
|
||||
2. The 'RefactorRename <new name>' subcommand |RefactorRename-new-name|
|
||||
3. Multi-file Refactor |youcompleteme-multi-file-refactor|
|
||||
4. Miscellaneous commands |youcompleteme-miscellaneous-commands|
|
||||
1. The |ClearCompilationFlagCache| subcommand
|
||||
2. The |StartServer| subcommand
|
||||
3. The |StopServer| subcommand
|
||||
4. The |RestartServer| subcommand
|
||||
5. The |ReloadSolution| subcommand
|
||||
8. Functions |youcompleteme-functions|
|
||||
1. The |youcompleteme#GetErrorCount| function
|
||||
2. The |youcompleteme#GetWarningCount| function
|
||||
@ -193,6 +200,10 @@ Image: Build Status [1] Image: Build status [3]
|
||||
|
||||
- YcmCompleter subcommands
|
||||
|
||||
- Go to declaration/definition/etc. commands
|
||||
- Semantic type information and documentation
|
||||
- Refactoring and FixIt commands
|
||||
|
||||
- Options
|
||||
- FAQ
|
||||
- Contributor Code of Conduct
|
||||
@ -280,9 +291,22 @@ features plus extra:
|
||||
- Supertab
|
||||
- neocomplcache
|
||||
|
||||
YCM also provides semantic go-to-definition/declaration commands for C-family
|
||||
languages & Python. Expect more IDE features powered by the various YCM
|
||||
semantic engines in the future.
|
||||
-------------------------------------------------------------------------------
|
||||
*youcompleteme-thats-not-all...*
|
||||
And that's not all... ~
|
||||
|
||||
YCM also provides semantic IDE-like features in a number of languages,
|
||||
including:
|
||||
|
||||
- finding declarations, definitions, usages, etc. of identifiers,
|
||||
- displaying type information for classes, variables, functions etc.,
|
||||
- displaying documentation for methods, members, etc. in the preview window,
|
||||
- fixing common coding errors, like missing semi-colons, typos, etc.,
|
||||
- semantic renaming of variables across files (JavaScript only).
|
||||
|
||||
Features vary by file type, so make sure to check out the file type feature
|
||||
summary and the full list of completer subcommands to find out what's available
|
||||
for your favourite languages.
|
||||
|
||||
You'll also find that YCM has filepath completers (try typing './' in a file)
|
||||
and a completer that integrates with UltiSnips [17].
|
||||
@ -911,6 +935,7 @@ TypeScript ~
|
||||
JavaScript ~
|
||||
|
||||
- Intelligent auto-completion
|
||||
- Renaming variables ('RefactorRename <new name>')
|
||||
- Go to definition, find references (|GoToDefinition|, |GoToReferences|)
|
||||
- Type information for identifiers (|GetType|)
|
||||
- View documentation comments for identifiers (|GetDoc|)
|
||||
@ -1372,20 +1397,24 @@ purpose.
|
||||
-------------------------------------------------------------------------------
|
||||
The *:YcmCompleter* command
|
||||
|
||||
This command can be used to invoke completer-specific commands. If the first
|
||||
This command gives access to a number of additional IDE-like features in YCM,
|
||||
for things like semantic GoTo, type information, FixIt and refactoring.
|
||||
|
||||
Technically the command invokes completer-specific commands. If the first
|
||||
argument is of the form 'ft=...' the completer for that file type will be used
|
||||
(for example 'ft=cpp'), else the native completer of the current buffer will be
|
||||
used. Call 'YcmCompleter' without further arguments for information about the
|
||||
commands you can call for the selected completer.
|
||||
used. Call 'YcmCompleter' without further arguments for a list of the commands
|
||||
you can call for the current completer.
|
||||
|
||||
See the _YcmCompleter subcommands_ section for more information on the
|
||||
available subcommands.
|
||||
See the file type feature summary for an overview of the features available for
|
||||
each file type. See the _YcmCompleter subcommands_ section for more information
|
||||
on the available subcommands and their usage.
|
||||
|
||||
===============================================================================
|
||||
*youcompleteme-ycmcompleter-subcommands*
|
||||
YcmCompleter subcommands ~
|
||||
|
||||
[See the docs for the 'YcmCompleter' command before tackling this section.]
|
||||
NOTE: See the docs for the 'YcmCompleter' command before tackling this section.
|
||||
|
||||
The invoked subcommand is automatically routed to the currently active semantic
|
||||
completer, so ':YcmCompleter GoToDefinition' will invoke the |GoToDefinition|
|
||||
@ -1397,7 +1426,12 @@ You may also want to map the subcommands to something less verbose; for
|
||||
instance, 'nnoremap <leader>jd :YcmCompleter GoTo<CR>' maps the '<leader>jd'
|
||||
sequence to the longer subcommand invocation.
|
||||
|
||||
The various 'GoTo*' subcommands add entries to Vim's 'jumplist' so you can use
|
||||
-------------------------------------------------------------------------------
|
||||
*youcompleteme-goto-commands*
|
||||
GoTo commands ~
|
||||
|
||||
These commands are useful for jumping around and exploring code. When moving
|
||||
the cursor, the subcommands add entries to Vim's 'jumplist' so you can use
|
||||
'CTRL-O' to jump back to where you where before invoking the command (and
|
||||
'CTRL-I' to jump forward; see ':h jumplist' for details).
|
||||
|
||||
@ -1465,17 +1499,29 @@ locations.
|
||||
Supported in filetypes: 'javascript, python, typescript'
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
The *ClearCompilationFlagCache* subcommand
|
||||
The *GoToImplementation* subcommand
|
||||
|
||||
YCM caches the flags it gets from the 'FlagsForFile' function in your
|
||||
'ycm_extra_conf.py' file if you return them with the 'do_cache' parameter set
|
||||
to 'True'. The cache is in memory and is never invalidated (unless you restart
|
||||
Vim of course).
|
||||
Looks up the symbol under the cursor and jumps to its implementation (i.e. non-
|
||||
interface). If there are multiple implementations, instead provides a list of
|
||||
implementations to choose from.
|
||||
|
||||
This command clears that cache entirely. YCM will then re-query your
|
||||
'FlagsForFile' function as needed in the future.
|
||||
Supported in filetypes: 'cs'
|
||||
|
||||
Supported in filetypes: 'c, cpp, objc, objcpp'
|
||||
-------------------------------------------------------------------------------
|
||||
The *GoToImplementationElseDeclaration* subcommand
|
||||
|
||||
Looks up the symbol under the cursor and jumps to its implementation if one,
|
||||
else jump to its declaration. If there are multiple implementations, instead
|
||||
provides a list of implementations to choose from.
|
||||
|
||||
Supported in filetypes: 'cs'
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
*youcompleteme-semantic-information-commands*
|
||||
Semantic information commands ~
|
||||
|
||||
These commands are useful for finding static information about the code, such
|
||||
as the types of variables, viewing declarations and documentation strings.
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
The *GetType* subcommand
|
||||
@ -1535,6 +1581,28 @@ NOTE: Causes re-parsing of the current translation unit.
|
||||
|
||||
Supported in filetypes: 'c, cpp, objc, objcpp'
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
The *GetDoc* subcommand
|
||||
|
||||
Displays the preview window populated with quick info about the identifier
|
||||
under the cursor. Depending on the file type, this includes things like:
|
||||
|
||||
- The type or declaration of identifier,
|
||||
- Doxygen/javadoc comments,
|
||||
- Python docstrings,
|
||||
- etc.
|
||||
|
||||
Supported in filetypes: 'c, cpp, objc, objcpp, cs, python, typescript,
|
||||
javascript'
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
*youcompleteme-refactoring-fixit-commands*
|
||||
Refactoring and FixIt commands ~
|
||||
|
||||
These commands make changes to your source code in order to perform refactoring
|
||||
or code correction. YouCompleteMe does not perform any action which cannot be
|
||||
undone, and never saves or writes files to the disk.
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
The *FixIt* subcommand
|
||||
|
||||
@ -1561,29 +1629,82 @@ indication).
|
||||
NOTE: Causes re-parsing of the current translation unit.
|
||||
|
||||
NOTE: After applying a fix-it, the diagnostics UI is not immediately updated.
|
||||
This is due to a technical restriction in vim, and moving the cursor, or
|
||||
issuing the the |:YcmForceCompileAndDiagnostics| command will refresh the
|
||||
diagnostics. Repeated invocations of the |FixIt| command on a given line,
|
||||
however, _do_ apply all diagnostics as expected without requiring refreshing of
|
||||
the diagnostics UI. This is particularly useful where there are multiple
|
||||
This is due to a technical restriction in Vim. Moving the cursor, or issuing
|
||||
the |:YcmForceCompileAndDiagnostics| command will refresh the diagnostics.
|
||||
Repeated invocations of the |FixIt| command on a given line, however, _do_
|
||||
apply all diagnostics as expected without requiring refreshing of the
|
||||
diagnostics UI. This is particularly useful where there are multiple
|
||||
diagnostics on one line, or where after fixing one diagnostic, another fix-it
|
||||
is available.
|
||||
|
||||
Supported in filetypes: 'c, cpp, objc, objcpp, cs'
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
The *GetDoc* subcommand
|
||||
*RefactorRename-new-name*
|
||||
The 'RefactorRename <new name>' subcommand ~
|
||||
|
||||
Displays the preview window populated with quick info about the identifier
|
||||
under the cursor. This includes, depending on the language, things like:
|
||||
In supported file types, this command attempts to perform a semantic rename of
|
||||
the identifier under the cursor. This includes renaming declarations,
|
||||
definitions and usages of the identifier, or any other language-appropriate
|
||||
action. The specific behavior is defined by the semantic engine in use.
|
||||
|
||||
- The type or declaration of identifier
|
||||
- Doxygen/javadoc comments
|
||||
- Python docstrings
|
||||
- etc.
|
||||
Similar to |FixIt|, this command applies automatic modifications to your source
|
||||
files. Rename operations may involve changes to multiple files, which may or
|
||||
may not be open in Vim buffers at the time. YouCompleteMe handles all of this
|
||||
for you. The behavior is described in the following section.
|
||||
|
||||
Supported in filetypes: 'c, cpp, objc, objcpp, cs, python, typescript,
|
||||
javascript'
|
||||
Supported in filetypes: 'javascript' (variables only)
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
*youcompleteme-multi-file-refactor*
|
||||
Multi-file Refactor ~
|
||||
|
||||
When a Refactor or FixIt command touches multiple files, YouCompleteMe attempts
|
||||
to apply those modifications to any existing open, visible buffer in the
|
||||
current tab. If no such buffer can be found, YouCompleteMe opens the file in a
|
||||
new small horizontal split at the top of the current window, applies the
|
||||
change, and then _hides_ the window. NOTE: The buffer remains open, and must be
|
||||
manually saved. A confirmation dialog is opened prior to doing this to remind
|
||||
you that this is about to happen.
|
||||
|
||||
Once the modifications have been made, the quickfix list (see ':help quickfix')
|
||||
is opened and populated with the locations of all modifications. This can be
|
||||
used to review all automatic changes made. Typically, use the 'CTRL-W <enter>'
|
||||
combination to open the selected file in a new split.
|
||||
|
||||
The buffers are _not_ saved automatically. That is, you must save the modified
|
||||
buffers manually after reviewing the changes from the quickfix list. Changes
|
||||
can be undone using Vim's powerful undo features (see ':help undo'). Note that
|
||||
Vim's undo is per-buffer, so to undo all changes, the undo commands must be
|
||||
applied in each modified buffer separately.
|
||||
|
||||
NOTE: While applying modifications, Vim may find files which are already open
|
||||
and have a swap file. The command is aborted if you select Abort or Quit in any
|
||||
such prompts. This leaves the Refactor operation partially complete and must be
|
||||
manually corrected using Vim's undo features. The quickfix list is _not_
|
||||
populated in this case. Inspect ':buffers' or equivalent (see ':help buffers')
|
||||
to see the buffers that were opened by the command.
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
*youcompleteme-miscellaneous-commands*
|
||||
Miscellaneous commands ~
|
||||
|
||||
These commands are for general administration, rather than IDE-like features.
|
||||
They cover things like the semantic engine server instance and compilation
|
||||
flags.
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
The *ClearCompilationFlagCache* subcommand
|
||||
|
||||
YCM caches the flags it gets from the 'FlagsForFile' function in your
|
||||
'ycm_extra_conf.py' file if you return them with the 'do_cache' parameter set
|
||||
to 'True'. The cache is in memory and is never invalidated (unless you restart
|
||||
Vim of course).
|
||||
|
||||
This command clears that cache entirely. YCM will then re-query your
|
||||
'FlagsForFile' function as needed in the future.
|
||||
|
||||
Supported in filetypes: 'c, cpp, objc, objcpp'
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
The *StartServer* subcommand
|
||||
@ -1623,24 +1744,6 @@ files are changed outside of Vim, or whenever Omnisharp cache is out-of-sync.
|
||||
|
||||
Supported in filetypes: 'cs'
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
The *GoToImplementation* subcommand
|
||||
|
||||
Looks up the symbol under the cursor and jumps to its implementation (i.e. non-
|
||||
interface). If there are multiple implementations, instead provides a list of
|
||||
implementations to choose from.
|
||||
|
||||
Supported in filetypes: 'cs'
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
The *GoToImplementationElseDeclaration* subcommand
|
||||
|
||||
Looks up the symbol under the cursor and jumps to its implementation if one,
|
||||
else jump to its declaration. If there are multiple implementations, instead
|
||||
provides a list of implementations to choose from.
|
||||
|
||||
Supported in filetypes: 'cs'
|
||||
|
||||
===============================================================================
|
||||
*youcompleteme-functions*
|
||||
Functions ~
|
||||
@ -2137,7 +2240,7 @@ The *g:ycm_csharp_insert_namespace_expr* option
|
||||
By default, when YCM inserts a namespace, it will insert the 'using' statement
|
||||
under the nearest 'using' statement. You may prefer that the 'using' statement
|
||||
is inserted somewhere, for example, to preserve sorting. If so, you can set
|
||||
this option to override this behaviour.
|
||||
this option to override this behavior.
|
||||
|
||||
When this option is set, instead of inserting the 'using' statement itself, YCM
|
||||
will set the global variable 'g:ycm_namespace_to_insert' to the namespace to
|
||||
|
@ -33,10 +33,6 @@ class CommandRequest( BaseRequest ):
|
||||
self._arguments = _EnsureBackwardsCompatibility( arguments )
|
||||
self._completer_target = ( completer_target if completer_target
|
||||
else 'filetype_default' )
|
||||
self._is_goto_command = (
|
||||
self._arguments and self._arguments[ 0 ].startswith( 'GoTo' ) )
|
||||
self._is_fixit_command = (
|
||||
self._arguments and self._arguments[ 0 ].startswith( 'FixIt' ) )
|
||||
self._response = None
|
||||
|
||||
|
||||
@ -61,29 +57,31 @@ class CommandRequest( BaseRequest ):
|
||||
if not self.Done() or self._response is None:
|
||||
return
|
||||
|
||||
if self._is_goto_command:
|
||||
return self._HandleGotoResponse()
|
||||
|
||||
if self._is_fixit_command:
|
||||
return self._HandleFixitResponse()
|
||||
|
||||
# If not a dictionary or a list, the response is necessarily a
|
||||
# scalar: boolean, number, string, etc. In this case, we print
|
||||
# it to the user.
|
||||
if not isinstance( self._response, ( dict, list ) ):
|
||||
return self._HandleBasicResponse()
|
||||
|
||||
if 'fixits' in self._response:
|
||||
return self._HandleFixitResponse()
|
||||
|
||||
if 'message' in self._response:
|
||||
return self._HandleMessageResponse()
|
||||
|
||||
if 'detailed_info' in self._response:
|
||||
return self._HandleDetailedInfoResponse()
|
||||
|
||||
# The only other type of response we understand is GoTo, and that is the
|
||||
# only one that we can't detect just by inspecting the response (it should
|
||||
# either be a single location or a list)
|
||||
return self._HandleGotoResponse()
|
||||
|
||||
|
||||
def _HandleGotoResponse( self ):
|
||||
if isinstance( self._response, list ):
|
||||
defs = [ _BuildQfListItem( x ) for x in self._response ]
|
||||
vim.eval( 'setqflist( %s )' % repr( defs ) )
|
||||
vimsupport.SetQuickFixList(
|
||||
[ _BuildQfListItem( x ) for x in self._response ] )
|
||||
vim.eval( 'youcompleteme#OpenGoToList()' )
|
||||
else:
|
||||
vimsupport.JumpToLocation( self._response[ 'filepath' ],
|
||||
@ -96,12 +94,10 @@ class CommandRequest( BaseRequest ):
|
||||
vimsupport.EchoText( "No fixits found for current line" )
|
||||
else:
|
||||
chunks = self._response[ 'fixits' ][ 0 ][ 'chunks' ]
|
||||
|
||||
vimsupport.ReplaceChunksList( chunks )
|
||||
|
||||
vimsupport.EchoTextVimWidth( "FixIt applied "
|
||||
+ str( len( chunks ) )
|
||||
+ " changes" )
|
||||
try:
|
||||
vimsupport.ReplaceChunks( chunks )
|
||||
except RuntimeError as e:
|
||||
vimsupport.PostMultiLineNotice( e.message )
|
||||
|
||||
|
||||
def _HandleBasicResponse( self ):
|
||||
|
@ -18,7 +18,9 @@
|
||||
from ycm.test_utils import MockVimModule
|
||||
MockVimModule()
|
||||
|
||||
import json
|
||||
from mock import patch, call
|
||||
from nose.tools import ok_
|
||||
from ycm.client.command_request import CommandRequest
|
||||
|
||||
|
||||
@ -85,6 +87,177 @@ class GoToResponse_QuickFix_test:
|
||||
self._request.RunPostCommandActionsIfNeeded()
|
||||
|
||||
vim_eval.assert_has_calls( [
|
||||
call( 'setqflist( {0} )'.format( repr( expected_qf_list ) ) ),
|
||||
call( 'setqflist( {0} )'.format( json.dumps( expected_qf_list ) ) ),
|
||||
call( 'youcompleteme#OpenGoToList()' ),
|
||||
] )
|
||||
|
||||
|
||||
class Response_Detection_test:
|
||||
|
||||
def BasicResponse_test( self ):
|
||||
def _BasicResponseTest( command, response ):
|
||||
with patch( 'vim.command' ) as vim_command:
|
||||
request = CommandRequest( [ command ] )
|
||||
request._response = response
|
||||
request.RunPostCommandActionsIfNeeded()
|
||||
vim_command.assert_called_with( "echom '{0}'".format( response ) )
|
||||
|
||||
tests = [
|
||||
[ 'AnythingYouLike', True ],
|
||||
[ 'GoToEvenWorks', 10 ],
|
||||
[ 'FixItWorks', 'String!' ],
|
||||
[ 'and8434fd andy garbag!', 10.3 ],
|
||||
]
|
||||
|
||||
for test in tests:
|
||||
yield _BasicResponseTest, test[ 0 ], test[ 1 ]
|
||||
|
||||
|
||||
def FixIt_Response_Empty_test( self ):
|
||||
# Ensures we recognise and handle fixit responses which indicate that there
|
||||
# are no fixits available
|
||||
def EmptyFixItTest( command ):
|
||||
with patch( 'ycm.vimsupport.ReplaceChunks' ) as replace_chunks:
|
||||
with patch( 'ycm.vimsupport.EchoText' ) as echo_text:
|
||||
request = CommandRequest( [ command ] )
|
||||
request._response = {
|
||||
'fixits': []
|
||||
}
|
||||
request.RunPostCommandActionsIfNeeded()
|
||||
|
||||
echo_text.assert_called_with( 'No fixits found for current line' )
|
||||
replace_chunks.assert_not_called()
|
||||
|
||||
for test in [ 'FixIt', 'Refactor', 'GoToHell', 'any_old_garbade!!!21' ]:
|
||||
yield EmptyFixItTest, test
|
||||
|
||||
|
||||
def FixIt_Response_test( self ):
|
||||
# Ensures we recognise and handle fixit responses with some dummy chunk data
|
||||
def FixItTest( command, response, chunks ):
|
||||
with patch( 'ycm.vimsupport.ReplaceChunks' ) as replace_chunks:
|
||||
with patch( 'ycm.vimsupport.EchoText' ) as echo_text:
|
||||
request = CommandRequest( [ command ] )
|
||||
request._response = response
|
||||
request.RunPostCommandActionsIfNeeded()
|
||||
|
||||
replace_chunks.assert_called_with( chunks )
|
||||
echo_text.assert_not_called()
|
||||
|
||||
basic_fixit = {
|
||||
'fixits': [ {
|
||||
'chunks': [ {
|
||||
'dummy chunk contents': True
|
||||
} ]
|
||||
} ]
|
||||
}
|
||||
basic_fixit_chunks = basic_fixit[ 'fixits' ][ 0 ][ 'chunks' ]
|
||||
|
||||
multi_fixit = {
|
||||
'fixits': [ {
|
||||
'chunks': [ {
|
||||
'dummy chunk contents': True
|
||||
} ]
|
||||
}, {
|
||||
'additional fixits are ignored currently': True
|
||||
} ]
|
||||
}
|
||||
multi_fixit_first_chunks = multi_fixit[ 'fixits' ][ 0 ][ 'chunks' ]
|
||||
|
||||
tests = [
|
||||
[ 'AnythingYouLike', basic_fixit, basic_fixit_chunks ],
|
||||
[ 'GoToEvenWorks', basic_fixit, basic_fixit_chunks ],
|
||||
[ 'FixItWorks', basic_fixit, basic_fixit_chunks ],
|
||||
[ 'and8434fd andy garbag!', basic_fixit, basic_fixit_chunks ],
|
||||
[ 'additional fixits ignored', multi_fixit, multi_fixit_first_chunks ],
|
||||
]
|
||||
|
||||
for test in tests:
|
||||
yield FixItTest, test[ 0 ], test[ 1 ], test[ 2 ]
|
||||
|
||||
|
||||
def Message_Response_test( self ):
|
||||
# Ensures we correctly recognise and handle responses with a message to show
|
||||
# to the user
|
||||
|
||||
def MessageTest( command, message ):
|
||||
with patch( 'ycm.vimsupport.EchoText' ) as echo_text:
|
||||
request = CommandRequest( [ command ] )
|
||||
request._response = { 'message': message }
|
||||
request.RunPostCommandActionsIfNeeded()
|
||||
echo_text.assert_called_with( message )
|
||||
|
||||
tests = [
|
||||
[ '___________', 'This is a message' ],
|
||||
[ '', 'this is also a message' ],
|
||||
[ 'GetType', 'std::string' ],
|
||||
]
|
||||
|
||||
for test in tests:
|
||||
yield MessageTest, test[ 0 ], test[ 1 ]
|
||||
|
||||
|
||||
def Detailed_Info_test( self ):
|
||||
# Ensures we correctly detect and handle detailed_info responses which are
|
||||
# used to display information in the preview window
|
||||
|
||||
def DetailedInfoTest( command, info ):
|
||||
with patch( 'ycm.vimsupport.WriteToPreviewWindow' ) as write_to_preview:
|
||||
request = CommandRequest( [ command ] )
|
||||
request._response = { 'detailed_info': info }
|
||||
request.RunPostCommandActionsIfNeeded()
|
||||
write_to_preview.assert_called_with( info )
|
||||
|
||||
tests = [
|
||||
[ '___________', 'This is a message' ],
|
||||
[ '', 'this is also a message' ],
|
||||
[ 'GetDoc', 'std::string\netc\netc' ],
|
||||
]
|
||||
|
||||
for test in tests:
|
||||
yield DetailedInfoTest, test[ 0 ], test[ 1 ]
|
||||
|
||||
|
||||
def GoTo_Single_test( self ):
|
||||
# Ensures we handle any unknown type of response as a GoTo response
|
||||
|
||||
def GoToTest( command, response ):
|
||||
with patch( 'ycm.vimsupport.JumpToLocation' ) as jump_to_location:
|
||||
request = CommandRequest( [ command ] )
|
||||
request._response = response
|
||||
request.RunPostCommandActionsIfNeeded()
|
||||
jump_to_location.assert_called_with(
|
||||
response[ 'filepath' ],
|
||||
response[ 'line_num' ],
|
||||
response[ 'column_num' ] )
|
||||
|
||||
def GoToListTest( command, response ):
|
||||
# Note: the detail of these called are tested by
|
||||
# GoToResponse_QuickFix_test, so here we just check that the right call is
|
||||
# made
|
||||
with patch( 'ycm.vimsupport.SetQuickFixList' ) as set_qf_list:
|
||||
with patch( 'vim.eval' ) as vim_eval:
|
||||
request = CommandRequest( [ command ] )
|
||||
request._response = response
|
||||
request.RunPostCommandActionsIfNeeded()
|
||||
ok_( set_qf_list.called )
|
||||
ok_( vim_eval.called )
|
||||
|
||||
basic_goto = {
|
||||
'filepath': 'test',
|
||||
'line_num': 10,
|
||||
'column_num': 100,
|
||||
}
|
||||
|
||||
tests = [
|
||||
[ GoToTest, 'AnythingYouLike', basic_goto ],
|
||||
[ GoToTest, 'GoTo', basic_goto ],
|
||||
[ GoToTest, 'FindAThing', basic_goto ],
|
||||
[ GoToTest, 'FixItGoto', basic_goto ],
|
||||
[ GoToListTest, 'AnythingYouLike', [ basic_goto ] ],
|
||||
[ GoToListTest, 'GoTo', [] ],
|
||||
[ GoToListTest, 'FixItGoto', [ basic_goto, basic_goto ] ],
|
||||
]
|
||||
|
||||
for test in tests:
|
||||
yield test[ 0 ], test[ 1 ], test[ 2 ]
|
||||
|
@ -15,7 +15,7 @@
|
||||
# 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.test_utils import MockVimModule, MockVimCommand
|
||||
from ycm.test_utils import ExtendedMock, MockVimModule, MockVimCommand
|
||||
MockVimModule()
|
||||
|
||||
from ycm import vimsupport
|
||||
@ -23,6 +23,7 @@ from nose.tools import eq_
|
||||
from hamcrest import assert_that, calling, raises, none
|
||||
from mock import MagicMock, call, patch
|
||||
import os
|
||||
import json
|
||||
|
||||
|
||||
def ReplaceChunk_SingleLine_Repl_1_test():
|
||||
@ -343,7 +344,6 @@ def ReplaceChunk_SingleToMultipleLinesReplace_2_test():
|
||||
eq_( char_offset, 4 )
|
||||
|
||||
|
||||
|
||||
def ReplaceChunk_MultipleLinesToSingleLine_test():
|
||||
result_buffer = [ "aAa", "aBa", "aCaaaa" ]
|
||||
start, end = _BuildLocations( 2, 2, 3, 2 )
|
||||
@ -539,41 +539,517 @@ def _BuildLocations( start_line, start_column, end_line, end_column ):
|
||||
}
|
||||
|
||||
|
||||
def ReplaceChunksList_SortedChunks_test():
|
||||
def ReplaceChunksInBuffer_SortedChunks_test():
|
||||
chunks = [
|
||||
_BuildChunk( 1, 4, 1, 4, '('),
|
||||
_BuildChunk( 1, 11, 1, 11, ')' )
|
||||
]
|
||||
|
||||
result_buffer = [ "CT<10 >> 2> ct" ]
|
||||
vimsupport.ReplaceChunksList( chunks, result_buffer )
|
||||
vimsupport.ReplaceChunksInBuffer( chunks, result_buffer, None )
|
||||
|
||||
expected_buffer = [ "CT<(10 >> 2)> ct" ]
|
||||
eq_( expected_buffer, result_buffer )
|
||||
|
||||
|
||||
def ReplaceChunksList_UnsortedChunks_test():
|
||||
def ReplaceChunksInBuffer_UnsortedChunks_test():
|
||||
chunks = [
|
||||
_BuildChunk( 1, 11, 1, 11, ')'),
|
||||
_BuildChunk( 1, 4, 1, 4, '(' )
|
||||
]
|
||||
|
||||
result_buffer = [ "CT<10 >> 2> ct" ]
|
||||
vimsupport.ReplaceChunksList( chunks, result_buffer )
|
||||
vimsupport.ReplaceChunksInBuffer( chunks, result_buffer, None )
|
||||
|
||||
expected_buffer = [ "CT<(10 >> 2)> ct" ]
|
||||
eq_( expected_buffer, result_buffer )
|
||||
|
||||
|
||||
def _BuildChunk( start_line, start_column, end_line, end_column,
|
||||
replacement_text ):
|
||||
class MockBuffer( ):
|
||||
"""An object that looks like a vim.buffer object, enough for ReplaceChunk to
|
||||
generate a location list"""
|
||||
|
||||
def __init__( self, lines, name, number ):
|
||||
self.lines = lines
|
||||
self.name = name
|
||||
self.number = number
|
||||
|
||||
|
||||
def __getitem__( self, index ):
|
||||
return self.lines[ index ]
|
||||
|
||||
|
||||
def __len__( self ):
|
||||
return len( self.lines )
|
||||
|
||||
|
||||
def __setitem__( self, key, value ):
|
||||
return self.lines.__setitem__( key, value )
|
||||
|
||||
|
||||
@patch( 'ycm.vimsupport.GetBufferNumberForFilename',
|
||||
return_value=1,
|
||||
new_callable=ExtendedMock )
|
||||
@patch( 'ycm.vimsupport.BufferIsVisible',
|
||||
return_value=True,
|
||||
new_callable=ExtendedMock )
|
||||
@patch( 'ycm.vimsupport.OpenFilename' )
|
||||
@patch( 'ycm.vimsupport.EchoTextVimWidth', new_callable=ExtendedMock )
|
||||
@patch( 'vim.eval', new_callable=ExtendedMock )
|
||||
@patch( 'vim.command', new_callable=ExtendedMock )
|
||||
def ReplaceChunks_SingleFile_Open_test( vim_command,
|
||||
vim_eval,
|
||||
echo_text_vim_width,
|
||||
open_filename,
|
||||
buffer_is_visible,
|
||||
get_buffer_number_for_filename ):
|
||||
|
||||
chunks = [
|
||||
_BuildChunk( 1, 1, 2, 1, 'replacement', 'single_file' )
|
||||
]
|
||||
|
||||
result_buffer = MockBuffer( [
|
||||
'line1',
|
||||
'line2',
|
||||
'line3',
|
||||
], 'single_file', 1 )
|
||||
|
||||
with patch( 'vim.buffers', [ None, result_buffer, None ] ):
|
||||
vimsupport.ReplaceChunks( chunks )
|
||||
|
||||
# Ensure that we applied the replacement correctly
|
||||
eq_( result_buffer.lines, [
|
||||
'replacementline2',
|
||||
'line3',
|
||||
] )
|
||||
|
||||
# GetBufferNumberForFilename is called twice:
|
||||
# - once to the check if we would require opening the file (so that we can
|
||||
# raise a warning)
|
||||
# - once whilst applying the changes
|
||||
get_buffer_number_for_filename.assert_has_exact_calls( [
|
||||
call( 'single_file', False ),
|
||||
call( 'single_file', False ),
|
||||
] )
|
||||
|
||||
# BufferIsVisible is called twice for the same reasons as above
|
||||
buffer_is_visible.assert_has_exact_calls( [
|
||||
call( 1 ),
|
||||
call( 1 ),
|
||||
] )
|
||||
|
||||
# we don't attempt to open any files
|
||||
open_filename.assert_not_called()
|
||||
|
||||
# But we do set the quickfix list
|
||||
vim_eval.assert_has_exact_calls( [
|
||||
call( 'setqflist( {0} )'.format( json.dumps( [ {
|
||||
'bufnr': 1,
|
||||
'filename': 'single_file',
|
||||
'lnum': 1,
|
||||
'col': 1,
|
||||
'text': 'replacement',
|
||||
'type': 'F'
|
||||
} ] ) ) ),
|
||||
] )
|
||||
vim_command.assert_has_calls( [
|
||||
call( 'copen 1' )
|
||||
] )
|
||||
|
||||
# And it is ReplaceChunks that prints the message showing the number of
|
||||
# changes
|
||||
echo_text_vim_width.assert_has_exact_calls( [
|
||||
call( 'Applied 1 changes' ),
|
||||
] )
|
||||
|
||||
|
||||
@patch( 'ycm.vimsupport.GetBufferNumberForFilename',
|
||||
side_effect=[ -1, -1, 1 ],
|
||||
new_callable=ExtendedMock )
|
||||
@patch( 'ycm.vimsupport.BufferIsVisible',
|
||||
side_effect=[ False, False, True ],
|
||||
new_callable=ExtendedMock )
|
||||
@patch( 'ycm.vimsupport.OpenFilename',
|
||||
new_callable=ExtendedMock )
|
||||
@patch( 'ycm.vimsupport.EchoTextVimWidth', new_callable=ExtendedMock )
|
||||
@patch( 'ycm.vimsupport.Confirm',
|
||||
return_value=True,
|
||||
new_callable=ExtendedMock )
|
||||
@patch( 'vim.eval', return_value=10, new_callable=ExtendedMock )
|
||||
@patch( 'vim.command', new_callable=ExtendedMock )
|
||||
def ReplaceChunks_SingleFile_NotOpen_test( vim_command,
|
||||
vim_eval,
|
||||
confirm,
|
||||
echo_text_vim_width,
|
||||
open_filename,
|
||||
buffer_is_visible,
|
||||
get_buffer_number_for_filename ):
|
||||
|
||||
chunks = [
|
||||
_BuildChunk( 1, 1, 2, 1, 'replacement', 'single_file' )
|
||||
]
|
||||
|
||||
result_buffer = MockBuffer( [
|
||||
'line1',
|
||||
'line2',
|
||||
'line3',
|
||||
], 'single_file', 1 )
|
||||
|
||||
with patch( 'vim.buffers', [ None, result_buffer, None ] ):
|
||||
vimsupport.ReplaceChunks( chunks )
|
||||
|
||||
# We checked if it was OK to open the file
|
||||
confirm.assert_has_exact_calls( [
|
||||
call( vimsupport.FIXIT_OPENING_BUFFERS_MESSAGE_FORMAT.format( 1 ) )
|
||||
] )
|
||||
|
||||
# Ensure that we applied the replacement correctly
|
||||
eq_( result_buffer.lines, [
|
||||
'replacementline2',
|
||||
'line3',
|
||||
] )
|
||||
|
||||
# GetBufferNumberForFilename is called 3 times. The return values are set in
|
||||
# the @patch call above:
|
||||
# - once to the check if we would require opening the file (so that we can
|
||||
# raise a warning) (-1 return)
|
||||
# - once whilst applying the changes (-1 return)
|
||||
# - finally after calling OpenFilename (1 return)
|
||||
get_buffer_number_for_filename.assert_has_exact_calls( [
|
||||
call( 'single_file', False ),
|
||||
call( 'single_file', False ),
|
||||
call( 'single_file', False ),
|
||||
] )
|
||||
|
||||
# BufferIsVisible is called 3 times for the same reasons as above, with the
|
||||
# return of each one
|
||||
buffer_is_visible.assert_has_exact_calls( [
|
||||
call( -1 ),
|
||||
call( -1 ),
|
||||
call( 1 ),
|
||||
] )
|
||||
|
||||
# We open 'single_file' as expected.
|
||||
open_filename.assert_called_with( 'single_file', {
|
||||
'focus': True,
|
||||
'fix': True,
|
||||
'size': 10
|
||||
} )
|
||||
|
||||
# And close it again, then show the preview window (note, we don't check exact
|
||||
# calls because there are other calls which are checked elsewhere)
|
||||
vim_command.assert_has_calls( [
|
||||
call( 'lclose' ),
|
||||
call( 'hide' ),
|
||||
call( 'copen 1' ),
|
||||
] )
|
||||
|
||||
# And update the quickfix list
|
||||
vim_eval.assert_has_exact_calls( [
|
||||
call( '&previewheight' ),
|
||||
call( 'setqflist( {0} )'.format( json.dumps( [ {
|
||||
'bufnr': 1,
|
||||
'filename': 'single_file',
|
||||
'lnum': 1,
|
||||
'col': 1,
|
||||
'text': 'replacement',
|
||||
'type': 'F'
|
||||
} ] ) ) ),
|
||||
] )
|
||||
|
||||
# And it is ReplaceChunks that prints the message showing the number of
|
||||
# changes
|
||||
echo_text_vim_width.assert_has_exact_calls( [
|
||||
call( 'Applied 1 changes' ),
|
||||
] )
|
||||
|
||||
|
||||
@patch( 'ycm.vimsupport.GetBufferNumberForFilename',
|
||||
side_effect=[ -1, -1, 1 ],
|
||||
new_callable=ExtendedMock )
|
||||
@patch( 'ycm.vimsupport.BufferIsVisible',
|
||||
side_effect=[ False, False, True ],
|
||||
new_callable=ExtendedMock )
|
||||
@patch( 'ycm.vimsupport.OpenFilename',
|
||||
new_callable=ExtendedMock )
|
||||
@patch( 'ycm.vimsupport.EchoTextVimWidth',
|
||||
new_callable=ExtendedMock )
|
||||
@patch( 'ycm.vimsupport.Confirm',
|
||||
return_value=False,
|
||||
new_callable=ExtendedMock )
|
||||
@patch( 'vim.eval',
|
||||
return_value=10,
|
||||
new_callable=ExtendedMock )
|
||||
@patch( 'vim.command', new_callable=ExtendedMock )
|
||||
def ReplaceChunks_User_Declines_To_Open_File_test(
|
||||
vim_command,
|
||||
vim_eval,
|
||||
confirm,
|
||||
echo_text_vim_width,
|
||||
open_filename,
|
||||
buffer_is_visible,
|
||||
get_buffer_number_for_filename ):
|
||||
|
||||
# Same as above, except the user selects Cancel when asked if they should
|
||||
# allow us to open lots of (ahem, 1) file.
|
||||
|
||||
chunks = [
|
||||
_BuildChunk( 1, 1, 2, 1, 'replacement', 'single_file' )
|
||||
]
|
||||
|
||||
result_buffer = MockBuffer( [
|
||||
'line1',
|
||||
'line2',
|
||||
'line3',
|
||||
], 'single_file', 1 )
|
||||
|
||||
with patch( 'vim.buffers', [ None, result_buffer, None ] ):
|
||||
vimsupport.ReplaceChunks( chunks )
|
||||
|
||||
# We checked if it was OK to open the file
|
||||
confirm.assert_has_exact_calls( [
|
||||
call( vimsupport.FIXIT_OPENING_BUFFERS_MESSAGE_FORMAT.format( 1 ) )
|
||||
] )
|
||||
|
||||
# Ensure that buffer is not changed
|
||||
eq_( result_buffer.lines, [
|
||||
'line1',
|
||||
'line2',
|
||||
'line3',
|
||||
] )
|
||||
|
||||
# GetBufferNumberForFilename is called once. The return values are set in
|
||||
# the @patch call above:
|
||||
# - once to the check if we would require opening the file (so that we can
|
||||
# raise a warning) (-1 return)
|
||||
get_buffer_number_for_filename.assert_has_exact_calls( [
|
||||
call( 'single_file', False ),
|
||||
] )
|
||||
|
||||
# BufferIsVisible is called once for the above file, which wasn't visible.
|
||||
buffer_is_visible.assert_has_exact_calls( [
|
||||
call( -1 ),
|
||||
] )
|
||||
|
||||
# We don't attempt to open any files or update any quickfix list or anything
|
||||
# like that
|
||||
open_filename.assert_not_called()
|
||||
vim_eval.assert_not_called()
|
||||
vim_command.assert_not_called()
|
||||
echo_text_vim_width.assert_not_called()
|
||||
|
||||
|
||||
@patch( 'ycm.vimsupport.GetBufferNumberForFilename',
|
||||
side_effect=[ -1, -1, 1 ],
|
||||
new_callable=ExtendedMock )
|
||||
# Key difference is here: In the final check, BufferIsVisible returns False
|
||||
@patch( 'ycm.vimsupport.BufferIsVisible',
|
||||
side_effect=[ False, False, False ],
|
||||
new_callable=ExtendedMock )
|
||||
@patch( 'ycm.vimsupport.OpenFilename',
|
||||
new_callable=ExtendedMock )
|
||||
@patch( 'ycm.vimsupport.EchoTextVimWidth',
|
||||
new_callable=ExtendedMock )
|
||||
@patch( 'ycm.vimsupport.Confirm',
|
||||
return_value=True,
|
||||
new_callable=ExtendedMock )
|
||||
@patch( 'vim.eval',
|
||||
return_value=10,
|
||||
new_callable=ExtendedMock )
|
||||
@patch( 'vim.command',
|
||||
new_callable=ExtendedMock )
|
||||
def ReplaceChunks_User_Aborts_Opening_File_test(
|
||||
vim_command,
|
||||
vim_eval,
|
||||
confirm,
|
||||
echo_text_vim_width,
|
||||
open_filename,
|
||||
buffer_is_visible,
|
||||
get_buffer_number_for_filename ):
|
||||
|
||||
# Same as above, except the user selects Abort or Quick during the
|
||||
# "swap-file-found" dialog
|
||||
|
||||
chunks = [
|
||||
_BuildChunk( 1, 1, 2, 1, 'replacement', 'single_file' )
|
||||
]
|
||||
|
||||
result_buffer = MockBuffer( [
|
||||
'line1',
|
||||
'line2',
|
||||
'line3',
|
||||
], 'single_file', 1 )
|
||||
|
||||
with patch( 'vim.buffers', [ None, result_buffer, None ] ):
|
||||
assert_that( calling( vimsupport.ReplaceChunks ).with_args( chunks ),
|
||||
raises( RuntimeError,
|
||||
'Unable to open file: single_file\nFixIt/Refactor operation '
|
||||
'aborted prior to completion. Your files have not been '
|
||||
'fully updated. Please use undo commands to revert the '
|
||||
'applied changes.' ) )
|
||||
|
||||
# We checked if it was OK to open the file
|
||||
confirm.assert_has_exact_calls( [
|
||||
call( vimsupport.FIXIT_OPENING_BUFFERS_MESSAGE_FORMAT.format( 1 ) )
|
||||
] )
|
||||
|
||||
# Ensure that buffer is not changed
|
||||
eq_( result_buffer.lines, [
|
||||
'line1',
|
||||
'line2',
|
||||
'line3',
|
||||
] )
|
||||
|
||||
# We tried to open this file
|
||||
open_filename.assert_called_with( "single_file", {
|
||||
'focus': True,
|
||||
'fix': True,
|
||||
'size': 10
|
||||
} )
|
||||
vim_eval.assert_called_with( "&previewheight" )
|
||||
|
||||
# But raised an exception before issuing the message at the end
|
||||
echo_text_vim_width.assert_not_called()
|
||||
|
||||
|
||||
@patch( 'ycm.vimsupport.GetBufferNumberForFilename', side_effect=[
|
||||
22, # first_file (check)
|
||||
-1, # another_file (check)
|
||||
22, # first_file (apply)
|
||||
-1, # another_file (apply)
|
||||
19, # another_file (check after open)
|
||||
],
|
||||
new_callable=ExtendedMock )
|
||||
@patch( 'ycm.vimsupport.BufferIsVisible', side_effect=[
|
||||
True, # first_file (check)
|
||||
False, # second_file (check)
|
||||
True, # first_file (apply)
|
||||
False, # second_file (apply)
|
||||
True, # side_effect (check after open)
|
||||
],
|
||||
new_callable=ExtendedMock)
|
||||
@patch( 'ycm.vimsupport.OpenFilename',
|
||||
new_callable=ExtendedMock)
|
||||
@patch( 'ycm.vimsupport.EchoTextVimWidth',
|
||||
new_callable=ExtendedMock)
|
||||
@patch( 'ycm.vimsupport.Confirm', return_value=True,
|
||||
new_callable=ExtendedMock)
|
||||
@patch( 'vim.eval', return_value=10,
|
||||
new_callable=ExtendedMock)
|
||||
@patch( 'vim.command',
|
||||
new_callable=ExtendedMock)
|
||||
def ReplaceChunks_MultiFile_Open_test( vim_command,
|
||||
vim_eval,
|
||||
confirm,
|
||||
echo_text_vim_width,
|
||||
open_filename,
|
||||
buffer_is_visible,
|
||||
get_buffer_number_for_filename ):
|
||||
|
||||
# Chunks are split across 2 files, one is already open, one isn't
|
||||
|
||||
chunks = [
|
||||
_BuildChunk( 1, 1, 2, 1, 'first_file_replacement ', '1_first_file' ),
|
||||
_BuildChunk( 2, 1, 2, 1, 'second_file_replacement ', '2_another_file' ),
|
||||
]
|
||||
|
||||
first_file = MockBuffer( [
|
||||
'line1',
|
||||
'line2',
|
||||
'line3',
|
||||
], '1_first_file', 22 )
|
||||
another_file = MockBuffer( [
|
||||
'another line1',
|
||||
'ACME line2',
|
||||
], '2_another_file', 19 )
|
||||
|
||||
vim_buffers = [ None ] * 23
|
||||
vim_buffers[ 22 ] = first_file
|
||||
vim_buffers[ 19 ] = another_file
|
||||
|
||||
with patch( 'vim.buffers', vim_buffers ):
|
||||
vimsupport.ReplaceChunks( chunks )
|
||||
|
||||
# We checked for the right file names
|
||||
get_buffer_number_for_filename.assert_has_exact_calls( [
|
||||
call( '1_first_file', False ),
|
||||
call( '2_another_file', False ),
|
||||
call( '1_first_file', False ),
|
||||
call( '2_another_file', False ),
|
||||
call( '2_another_file', False ),
|
||||
] )
|
||||
|
||||
# We checked if it was OK to open the file
|
||||
confirm.assert_has_exact_calls( [
|
||||
call( vimsupport.FIXIT_OPENING_BUFFERS_MESSAGE_FORMAT.format( 1 ) )
|
||||
] )
|
||||
|
||||
# Ensure that buffers are updated
|
||||
eq_( another_file.lines, [
|
||||
'another line1',
|
||||
'second_file_replacement ACME line2',
|
||||
] )
|
||||
eq_( first_file.lines, [
|
||||
'first_file_replacement line2',
|
||||
'line3',
|
||||
] )
|
||||
|
||||
# We open '2_another_file' as expected.
|
||||
open_filename.assert_called_with( '2_another_file', {
|
||||
'focus': True,
|
||||
'fix': True,
|
||||
'size': 10
|
||||
} )
|
||||
|
||||
# And close it again, then show the preview window (note, we don't check exact
|
||||
# calls because there are other calls which are checked elsewhere)
|
||||
vim_command.assert_has_calls( [
|
||||
call( 'lclose' ),
|
||||
call( 'hide' ),
|
||||
call( 'copen 2' ),
|
||||
] )
|
||||
|
||||
# And update the quickfix list with each entry
|
||||
vim_eval.assert_has_exact_calls( [
|
||||
call( '&previewheight' ),
|
||||
call( 'setqflist( {0} )'.format( json.dumps( [ {
|
||||
'bufnr': 22,
|
||||
'filename': '1_first_file',
|
||||
'lnum': 1,
|
||||
'col': 1,
|
||||
'text': 'first_file_replacement ',
|
||||
'type': 'F'
|
||||
}, {
|
||||
'bufnr': 19,
|
||||
'filename': '2_another_file',
|
||||
'lnum': 2,
|
||||
'col': 1,
|
||||
'text': 'second_file_replacement ',
|
||||
'type': 'F'
|
||||
} ] ) ) ),
|
||||
] )
|
||||
|
||||
# And it is ReplaceChunks that prints the message showing the number of
|
||||
# changes
|
||||
echo_text_vim_width.assert_has_exact_calls( [
|
||||
call( 'Applied 2 changes' ),
|
||||
] )
|
||||
|
||||
|
||||
def _BuildChunk( start_line,
|
||||
start_column,
|
||||
end_line,
|
||||
end_column,
|
||||
replacement_text, filepath='test_file_name' ):
|
||||
return {
|
||||
'range': {
|
||||
'start': {
|
||||
'filepath': filepath,
|
||||
'line_num': start_line,
|
||||
'column_num': start_column,
|
||||
},
|
||||
'end': {
|
||||
'filepath': filepath,
|
||||
'line_num': end_line,
|
||||
'column_num': end_column,
|
||||
},
|
||||
@ -582,14 +1058,14 @@ def _BuildChunk( start_line, start_column, end_line, end_column,
|
||||
}
|
||||
|
||||
|
||||
@patch( 'vim.command' )
|
||||
@patch( 'vim.current' )
|
||||
@patch( 'vim.command', new_callable=ExtendedMock )
|
||||
@patch( 'vim.current', new_callable=ExtendedMock)
|
||||
def WriteToPreviewWindow_test( vim_current, vim_command ):
|
||||
vim_current.window.options.__getitem__ = MagicMock( return_value = True )
|
||||
|
||||
vimsupport.WriteToPreviewWindow( "test" )
|
||||
|
||||
vim_command.assert_has_calls( [
|
||||
vim_command.assert_has_exact_calls( [
|
||||
call( 'silent! pclose!' ),
|
||||
call( 'silent! pedit! _TEMP_FILE_' ),
|
||||
call( 'silent! wincmd P' ),
|
||||
@ -598,7 +1074,9 @@ def WriteToPreviewWindow_test( vim_current, vim_command ):
|
||||
vim_current.buffer.__setitem__.assert_called_with(
|
||||
slice( None, None, None ), [ 'test' ] )
|
||||
|
||||
vim_current.buffer.options.__setitem__.assert_has_calls( [
|
||||
vim_current.buffer.options.__setitem__.assert_has_exact_calls( [
|
||||
call( 'modifiable', True ),
|
||||
call( 'readonly', False ),
|
||||
call( 'buftype', 'nofile' ),
|
||||
call( 'swapfile', False ),
|
||||
call( 'modifiable', False ),
|
||||
@ -616,14 +1094,14 @@ def WriteToPreviewWindow_MultiLine_test( vim_current ):
|
||||
slice( None, None, None ), [ 'test', 'test2' ] )
|
||||
|
||||
|
||||
@patch( 'vim.command' )
|
||||
@patch( 'vim.current' )
|
||||
@patch( 'vim.command', new_callable=ExtendedMock )
|
||||
@patch( 'vim.current', new_callable=ExtendedMock )
|
||||
def WriteToPreviewWindow_JumpFail_test( vim_current, vim_command ):
|
||||
vim_current.window.options.__getitem__ = MagicMock( return_value = False )
|
||||
|
||||
vimsupport.WriteToPreviewWindow( "test" )
|
||||
|
||||
vim_command.assert_has_calls( [
|
||||
vim_command.assert_has_exact_calls( [
|
||||
call( 'silent! pclose!' ),
|
||||
call( 'silent! pedit! _TEMP_FILE_' ),
|
||||
call( 'silent! wincmd P' ),
|
||||
@ -634,15 +1112,15 @@ def WriteToPreviewWindow_JumpFail_test( vim_current, vim_command ):
|
||||
vim_current.buffer.options.__setitem__.assert_not_called()
|
||||
|
||||
|
||||
@patch( 'vim.command' )
|
||||
@patch( 'vim.current' )
|
||||
@patch( 'vim.command', new_callable=ExtendedMock )
|
||||
@patch( 'vim.current', new_callable=ExtendedMock )
|
||||
def WriteToPreviewWindow_JumpFail_MultiLine_test( vim_current, vim_command ):
|
||||
|
||||
vim_current.window.options.__getitem__ = MagicMock( return_value = False )
|
||||
|
||||
vimsupport.WriteToPreviewWindow( "test\ntest2" )
|
||||
|
||||
vim_command.assert_has_calls( [
|
||||
vim_command.assert_has_exact_calls( [
|
||||
call( 'silent! pclose!' ),
|
||||
call( 'silent! pedit! _TEMP_FILE_' ),
|
||||
call( 'silent! wincmd P' ),
|
||||
@ -689,7 +1167,9 @@ def BufferIsVisibleForFilename_test():
|
||||
eq_( vimsupport.BufferIsVisibleForFilename( 'another_filename' ), False )
|
||||
|
||||
|
||||
@patch( 'vim.command', side_effect = MockVimCommand )
|
||||
@patch( 'vim.command',
|
||||
side_effect = MockVimCommand,
|
||||
new_callable=ExtendedMock )
|
||||
def CloseBuffersForFilename_test( vim_command ):
|
||||
buffers = [
|
||||
{
|
||||
@ -709,14 +1189,14 @@ def CloseBuffersForFilename_test( vim_command ):
|
||||
with patch( 'vim.buffers', buffers ):
|
||||
vimsupport.CloseBuffersForFilename( 'some_filename' )
|
||||
|
||||
vim_command.assert_has_calls( [
|
||||
vim_command.assert_has_exact_calls( [
|
||||
call( 'silent! bwipeout! 2' ),
|
||||
call( 'silent! bwipeout! 5' )
|
||||
], any_order = True )
|
||||
|
||||
|
||||
@patch( 'vim.command' )
|
||||
@patch( 'vim.current' )
|
||||
@patch( 'vim.command', new_callable=ExtendedMock )
|
||||
@patch( 'vim.current', new_callable=ExtendedMock )
|
||||
def OpenFilename_test( vim_current, vim_command ):
|
||||
# Options used to open a logfile
|
||||
options = {
|
||||
@ -728,18 +1208,18 @@ def OpenFilename_test( vim_current, vim_command ):
|
||||
|
||||
vimsupport.OpenFilename( __file__, options )
|
||||
|
||||
vim_command.assert_has_calls( [
|
||||
call( 'silent! 12split {0}'.format( __file__ ) ),
|
||||
vim_command.assert_has_exact_calls( [
|
||||
call( '12split {0}'.format( __file__ ) ),
|
||||
call( "exec "
|
||||
"'au BufEnter <buffer> :silent! checktime {0}'".format( __file__ ) ),
|
||||
call( 'silent! normal G zz' ),
|
||||
call( 'silent! wincmd p' )
|
||||
] )
|
||||
|
||||
vim_current.buffer.options.__setitem__.assert_has_calls( [
|
||||
vim_current.buffer.options.__setitem__.assert_has_exact_calls( [
|
||||
call( 'autoread', True ),
|
||||
] )
|
||||
|
||||
vim_current.window.options.__setitem__.assert_has_calls( [
|
||||
vim_current.window.options.__setitem__.assert_has_exact_calls( [
|
||||
call( 'winfixheight', True )
|
||||
] )
|
||||
|
@ -20,6 +20,7 @@ import os
|
||||
import tempfile
|
||||
import json
|
||||
import re
|
||||
from collections import defaultdict
|
||||
from ycmd.utils import ToUtf8IfNeeded
|
||||
from ycmd import user_options_store
|
||||
|
||||
@ -28,6 +29,13 @@ BUFFER_COMMAND_MAP = { 'same-buffer' : 'edit',
|
||||
'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
|
||||
@ -236,6 +244,15 @@ def SetLocationList( diagnostics ):
|
||||
vim.eval( 'setloclist( 0, {0} )'.format( json.dumps( diagnostics ) ) )
|
||||
|
||||
|
||||
def SetQuickFixList( quickfix_list, display=False ):
|
||||
"""list should be in qflist format: see ":h setqflist" for details"""
|
||||
vim.eval( 'setqflist( {0} )'.format( json.dumps( quickfix_list ) ) )
|
||||
|
||||
if display:
|
||||
vim.command( 'copen {0}'.format( len( quickfix_list ) ) )
|
||||
JumpToPreviousWindow()
|
||||
|
||||
|
||||
def ConvertDiagnosticsToQfList( diagnostics ):
|
||||
def ConvertDiagnosticToQfFormat( diagnostic ):
|
||||
# See :h getqflist for a description of the dictionary fields.
|
||||
@ -428,6 +445,8 @@ def PresentDialog( message, choices, default_choice_index = 0 ):
|
||||
|
||||
|
||||
def Confirm( message ):
|
||||
"""Display |message| with Ok/Cancel operations. Returns True if the user
|
||||
selects Ok"""
|
||||
return bool( PresentDialog( message, [ "Ok", "Cancel" ] ) == 0 )
|
||||
|
||||
|
||||
@ -449,6 +468,7 @@ def EchoTextVimWidth( text ):
|
||||
old_ruler = GetIntValue( '&ruler' )
|
||||
old_showcmd = GetIntValue( '&showcmd' )
|
||||
vim.command( 'set noruler noshowcmd' )
|
||||
vim.command( 'redraw' )
|
||||
|
||||
EchoText( truncated_text, False )
|
||||
|
||||
@ -490,9 +510,145 @@ def GetIntValue( variable ):
|
||||
return int( vim.eval( variable ) )
|
||||
|
||||
|
||||
def ReplaceChunksList( chunks, vim_buffer = None ):
|
||||
if vim_buffer is None:
|
||||
vim_buffer = vim.current.buffer
|
||||
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( chunks_by_file.iterkeys() )
|
||||
|
||||
# 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, True )
|
||||
|
||||
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.
|
||||
@ -519,7 +675,8 @@ def ReplaceChunksList( chunks, vim_buffer = None ):
|
||||
chunk[ 'range' ][ 'end' ],
|
||||
chunk[ 'replacement_text' ],
|
||||
line_delta, char_delta,
|
||||
vim_buffer )
|
||||
vim_buffer,
|
||||
locations )
|
||||
line_delta += new_line_delta
|
||||
char_delta += new_char_delta
|
||||
|
||||
@ -534,11 +691,12 @@ def ReplaceChunksList( chunks, vim_buffer = None ):
|
||||
# returns the delta (in lines and characters) that any position after the end
|
||||
# needs to be adjusted by.
|
||||
def ReplaceChunk( start, end, replacement_text, line_delta, char_delta,
|
||||
vim_buffer ):
|
||||
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
|
||||
@ -563,6 +721,17 @@ def ReplaceChunk( start, end, replacement_text, line_delta, char_delta,
|
||||
|
||||
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 )
|
||||
|
||||
@ -710,30 +879,39 @@ def OpenFilename( filename, options = {} ):
|
||||
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' )
|
||||
|
||||
# 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 is 'tabedit':
|
||||
previous_tab = GetIntValue( 'tabpagenr()' )
|
||||
else:
|
||||
previous_tab = None
|
||||
|
||||
# Open the file
|
||||
CheckFilename( filename )
|
||||
vim.command( 'silent! {0}{1} {2}'.format( size, command, 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
|
||||
|
||||
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 )
|
||||
# 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
|
||||
|
||||
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' )
|
||||
_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
|
||||
@ -743,3 +921,22 @@ def OpenFilename( filename, options = {} ):
|
||||
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 is 'split':
|
||||
vim.current.window.options[ 'winfixheight' ] = fix
|
||||
if command is '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 is 'end':
|
||||
vim.command( 'silent! normal G zz' )
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user