├── .gitattributes ├── .gitignore ├── LICENSE ├── README.md ├── VSNvim.sln ├── VSNvim ├── NvimTextSelection.h ├── TextViewCreationListener.cpp ├── TextViewCreationListener.h ├── VSNvim.vcxproj ├── VSNvim.vcxproj.filters ├── VSNvimBridge.cpp ├── VSNvimBridge.h ├── VSNvimCaret.cpp ├── VSNvimCaret.h ├── VSNvimCaretBlinkState.h ├── VSNvimPackage.cpp ├── VSNvimPackage.h ├── VSNvimPackage.vsct ├── VSNvimTextView.cpp ├── VSNvimTextView.h ├── VSPackage.resx ├── nvim.h ├── packages.config └── source.extension.vsixmanifest └── appveyor.yml /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | bld/ 21 | [Bb]in/ 22 | [Oo]bj/ 23 | [Ll]og/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | # Uncomment if you have tasks that create the project's static files in wwwroot 28 | #wwwroot/ 29 | 30 | # MSTest test Results 31 | [Tt]est[Rr]esult*/ 32 | [Bb]uild[Ll]og.* 33 | 34 | # NUNIT 35 | *.VisualState.xml 36 | TestResult.xml 37 | 38 | # Build Results of an ATL Project 39 | [Dd]ebugPS/ 40 | [Rr]eleasePS/ 41 | dlldata.c 42 | 43 | # DNX 44 | project.lock.json 45 | project.fragment.lock.json 46 | artifacts/ 47 | 48 | *_i.c 49 | *_p.c 50 | *_i.h 51 | *.ilk 52 | *.meta 53 | *.obj 54 | *.pch 55 | *.pdb 56 | *.pgc 57 | *.pgd 58 | *.rsp 59 | *.sbr 60 | *.tlb 61 | *.tli 62 | *.tlh 63 | *.tmp 64 | *.tmp_proj 65 | *.log 66 | *.vspscc 67 | *.vssscc 68 | .builds 69 | *.pidb 70 | *.svclog 71 | *.scc 72 | 73 | # Chutzpah Test files 74 | _Chutzpah* 75 | 76 | # Visual C++ cache files 77 | ipch/ 78 | *.aps 79 | *.ncb 80 | *.opendb 81 | *.opensdf 82 | *.sdf 83 | *.cachefile 84 | *.VC.db 85 | *.VC.VC.opendb 86 | 87 | # Visual Studio profiler 88 | *.psess 89 | *.vsp 90 | *.vspx 91 | *.sap 92 | 93 | # TFS 2012 Local Workspace 94 | $tf/ 95 | 96 | # Guidance Automation Toolkit 97 | *.gpState 98 | 99 | # ReSharper is a .NET coding add-in 100 | _ReSharper*/ 101 | *.[Rr]e[Ss]harper 102 | *.DotSettings.user 103 | 104 | # JustCode is a .NET coding add-in 105 | .JustCode 106 | 107 | # TeamCity is a build add-in 108 | _TeamCity* 109 | 110 | # DotCover is a Code Coverage Tool 111 | *.dotCover 112 | 113 | # NCrunch 114 | _NCrunch_* 115 | .*crunch*.local.xml 116 | nCrunchTemp_* 117 | 118 | # MightyMoose 119 | *.mm.* 120 | AutoTest.Net/ 121 | 122 | # Web workbench (sass) 123 | .sass-cache/ 124 | 125 | # Installshield output folder 126 | [Ee]xpress/ 127 | 128 | # DocProject is a documentation generator add-in 129 | DocProject/buildhelp/ 130 | DocProject/Help/*.HxT 131 | DocProject/Help/*.HxC 132 | DocProject/Help/*.hhc 133 | DocProject/Help/*.hhk 134 | DocProject/Help/*.hhp 135 | DocProject/Help/Html2 136 | DocProject/Help/html 137 | 138 | # Click-Once directory 139 | publish/ 140 | 141 | # Publish Web Output 142 | *.[Pp]ublish.xml 143 | *.azurePubxml 144 | # TODO: Comment the next line if you want to checkin your web deploy settings 145 | # but database connection strings (with potential passwords) will be unencrypted 146 | #*.pubxml 147 | *.publishproj 148 | 149 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 150 | # checkin your Azure Web App publish settings, but sensitive information contained 151 | # in these scripts will be unencrypted 152 | PublishScripts/ 153 | 154 | # NuGet Packages 155 | *.nupkg 156 | # The packages folder can be ignored because of Package Restore 157 | **/packages/* 158 | # except build/, which is used as an MSBuild target. 159 | !**/packages/build/ 160 | # Uncomment if necessary however generally it will be regenerated when needed 161 | #!**/packages/repositories.config 162 | # NuGet v3's project.json files produces more ignoreable files 163 | *.nuget.props 164 | *.nuget.targets 165 | 166 | # Microsoft Azure Build Output 167 | csx/ 168 | *.build.csdef 169 | 170 | # Microsoft Azure Emulator 171 | ecf/ 172 | rcf/ 173 | 174 | # Windows Store app package directories and files 175 | AppPackages/ 176 | BundleArtifacts/ 177 | Package.StoreAssociation.xml 178 | _pkginfo.txt 179 | 180 | # Visual Studio cache files 181 | # files ending in .cache can be ignored 182 | *.[Cc]ache 183 | # but keep track of directories ending in .cache 184 | !*.[Cc]ache/ 185 | 186 | # Others 187 | ClientBin/ 188 | ~$* 189 | *~ 190 | *.dbmdl 191 | *.dbproj.schemaview 192 | *.jfm 193 | *.pfx 194 | *.publishsettings 195 | node_modules/ 196 | orleans.codegen.cs 197 | 198 | # Since there are multiple workflows, uncomment next line to ignore bower_components 199 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 200 | #bower_components/ 201 | 202 | # RIA/Silverlight projects 203 | Generated_Code/ 204 | 205 | # Backup & report files from converting an old project file 206 | # to a newer Visual Studio version. Backup files are not needed, 207 | # because we have git ;-) 208 | _UpgradeReport_Files/ 209 | Backup*/ 210 | UpgradeLog*.XML 211 | UpgradeLog*.htm 212 | 213 | # SQL Server files 214 | *.mdf 215 | *.ldf 216 | 217 | # Business Intelligence projects 218 | *.rdl.data 219 | *.bim.layout 220 | *.bim_*.settings 221 | 222 | # Microsoft Fakes 223 | FakesAssemblies/ 224 | 225 | # GhostDoc plugin setting file 226 | *.GhostDoc.xml 227 | 228 | # Node.js Tools for Visual Studio 229 | .ntvs_analysis.dat 230 | 231 | # Visual Studio 6 build log 232 | *.plg 233 | 234 | # Visual Studio 6 workspace options file 235 | *.opt 236 | 237 | # Visual Studio LightSwitch build output 238 | **/*.HTMLClient/GeneratedArtifacts 239 | **/*.DesktopClient/GeneratedArtifacts 240 | **/*.DesktopClient/ModelManifest.xml 241 | **/*.Server/GeneratedArtifacts 242 | **/*.Server/ModelManifest.xml 243 | _Pvt_Extensions 244 | 245 | # Paket dependency manager 246 | .paket/paket.exe 247 | paket-files/ 248 | 249 | # FAKE - F# Make 250 | .fake/ 251 | 252 | # JetBrains Rider 253 | .idea/ 254 | *.sln.iml 255 | 256 | # CodeRush 257 | .cr/ 258 | 259 | # Python Tools for Visual Studio (PTVS) 260 | __pycache__/ 261 | *.pyc 262 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | VSNvim 2 | ====== 3 | 4 | [![AppVeyor Build Status](https://ci.appveyor.com/api/projects/status/github/neovim/VSNvim?branch=master&svg=true)](https://ci.appveyor.com/project/neovim/VSNvim/branch/master) 5 | 6 | [Neovim](https://github.com/neovim/neovim) extension for Visual Studio 2017 7 | 8 | Status 9 | ------ 10 | 11 | This extension is still in the early stages of development. Some 12 | Neovim features have not been fully integrated into Visual Studio 13 | including: 14 | - Highlights 15 | - Completion menus for insert mode and command line mode 16 | - Output from command line mode 17 | - Opening buffers 18 | - Line numbers and signs in the margin 19 | - Status lines 20 | - Window size and layout 21 | 22 | Pre-releases can be downloaded from the AppVeyor build artifacts. 23 | 24 | Build 25 | ----- 26 | 27 | 1. Install Visual Studio 2017 with the extension development workload. 28 | 1. Clone and build the Neovim fork for VSNvim. 29 | ``` 30 | git clone -b vsnvim https://github.com/b-r-o-c-k/neovim.git 31 | ``` 32 | 1. Change the `NvimSrcDir`, `NvimDepsDir`, and `NvimBuildDir` properties in 33 | the `VSNvim\VSNvim.vcxproj` to the correct paths of the fork. 34 | 1. Open `VSNvim.sln`, restore NuGet packages, and build. 35 | -------------------------------------------------------------------------------- /VSNvim.sln: -------------------------------------------------------------------------------- 1 | Microsoft Visual Studio Solution File, Format Version 12.00 2 | # Visual Studio 15 3 | VisualStudioVersion = 15.0.27703.2018 4 | MinimumVisualStudioVersion = 10.0.40219.1 5 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "VSNvim", "VSNvim\VSNvim.vcxproj", "{80AC6B61-9659-4F47-919D-22327F0953E4}" 6 | EndProject 7 | Global 8 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 9 | Debug|x86 = Debug|x86 10 | Release|x86 = Release|x86 11 | EndGlobalSection 12 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 13 | {80AC6B61-9659-4F47-919D-22327F0953E4}.Debug|x86.ActiveCfg = Debug|Win32 14 | {80AC6B61-9659-4F47-919D-22327F0953E4}.Debug|x86.Build.0 = Debug|Win32 15 | {80AC6B61-9659-4F47-919D-22327F0953E4}.Release|x86.ActiveCfg = Release|Win32 16 | {80AC6B61-9659-4F47-919D-22327F0953E4}.Release|x86.Build.0 = Release|Win32 17 | EndGlobalSection 18 | GlobalSection(SolutionProperties) = preSolution 19 | HideSolutionNode = FALSE 20 | EndGlobalSection 21 | GlobalSection(ExtensibilityGlobals) = postSolution 22 | SolutionGuid = {F3C1B5D3-7B4E-4421-9DF5-C5CE4477AA0F} 23 | EndGlobalSection 24 | EndGlobal 25 | -------------------------------------------------------------------------------- /VSNvim/NvimTextSelection.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace VSNvim 4 | { 5 | public enum class NvimTextSelection 6 | { 7 | Normal, // v 8 | Line, // V 9 | Block // CTRL-V 10 | }; 11 | } 12 | -------------------------------------------------------------------------------- /VSNvim/TextViewCreationListener.cpp: -------------------------------------------------------------------------------- 1 | #include "TextViewCreationListener.h" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "nvim.h" 8 | #include "VSNvimBridge.h" 9 | #include "VSNvimTextView.h" 10 | #include "VSNvimPackage.h" 11 | 12 | using namespace System::Threading; 13 | using namespace Microsoft::VisualStudio::Text::Editor; 14 | using namespace Microsoft::VisualStudio::TextManager::Interop; 15 | 16 | namespace VSNvim 17 | { 18 | static std::unordered_map special_keys_ 19 | { 20 | {VK_BACK, ""}, 21 | {VK_TAB, ""}, 22 | {VK_RETURN, ""}, 23 | {VK_ESCAPE, ""}, 24 | {VK_SPACE, ""}, 25 | {VK_UP, ""}, 26 | {VK_DOWN, ""}, 27 | {VK_LEFT, ""}, 28 | {VK_RIGHT, ""}, 29 | {VK_F1, ""}, 30 | {VK_F2, ""}, 31 | {VK_F3, ""}, 32 | {VK_F4, ""}, 33 | {VK_F5, ""}, 34 | {VK_F6, ""}, 35 | {VK_F7, ""}, 36 | {VK_F8, ""}, 37 | {VK_F9, ""}, 38 | {VK_F10, ""}, 39 | {VK_F11, ""}, 40 | {VK_F12, ""}, 41 | {VK_F13, ""}, 42 | {VK_F14, ""}, 43 | {VK_F15, ""}, 44 | {VK_F16, ""}, 45 | {VK_F17, ""}, 46 | {VK_F18, ""}, 47 | {VK_F19, ""}, 48 | {VK_F20, ""}, 49 | {VK_F21, ""}, 50 | {VK_F22, ""}, 51 | {VK_F23, ""}, 52 | {VK_F24, ""}, 53 | {VK_HELP, ""}, 54 | {VK_INSERT, ""}, 55 | {VK_HOME, ""}, 56 | {VK_END, ""}, 57 | {VK_PRIOR, ""}, 58 | {VK_NEXT, ""}, 59 | {VK_ADD, ""}, 60 | {VK_SUBTRACT, ""}, 61 | {VK_MULTIPLY, ""}, 62 | {VK_DIVIDE, ""}, 63 | {VK_DECIMAL, ""}, 64 | {VK_NUMPAD0, ""}, 65 | {VK_NUMPAD1, ""}, 66 | {VK_NUMPAD2, ""}, 67 | {VK_NUMPAD3, ""}, 68 | {VK_NUMPAD4, ""}, 69 | {VK_NUMPAD5, ""}, 70 | {VK_NUMPAD6, ""}, 71 | {VK_NUMPAD7, ""}, 72 | {VK_NUMPAD8, ""}, 73 | {VK_NUMPAD9, ""}, 74 | }; 75 | 76 | HHOOK keyboard_hook_; 77 | bool is_text_view_focused_ = false; 78 | bool is_nvim_running = false; 79 | 80 | static LRESULT CALLBACK KeyboardHookHandler( 81 | int code, WPARAM w_param, LPARAM l_param) 82 | { 83 | struct KeyFlags 84 | { 85 | unsigned int RepCnt : 16; 86 | unsigned int ScanCode : 8; 87 | unsigned int ExtKey : 1; 88 | unsigned int Reserved : 4; 89 | unsigned int Context : 1; 90 | unsigned int PrevState : 1; 91 | unsigned int Released : 1; 92 | }; 93 | 94 | const auto key_flags = reinterpret_cast(&l_param); 95 | if (!VSNvimPackage::IsEnabled || key_flags->Released || !is_text_view_focused_) 96 | { 97 | return CallNextHookEx(keyboard_hook_, code, w_param, l_param); 98 | } 99 | if (const auto special_key = special_keys_.find(w_param); 100 | special_key != special_keys_.end()) 101 | { 102 | VSNvim::SendInput(std::make_unique(special_key->second)); 103 | return 1; 104 | } 105 | BYTE keyboard_state_[256]; 106 | GetKeyboardState(keyboard_state_); 107 | WCHAR awAnsiCode[2]; 108 | const auto utf16_chars = 109 | ToUnicode(w_param, key_flags->ScanCode, keyboard_state_, awAnsiCode, 110 | sizeof(awAnsiCode), 0); 111 | if (utf16_chars) 112 | { 113 | char utf8_chars[10]; 114 | const auto utf8_len 115 | = WideCharToMultiByte(CP_UTF8, 0, awAnsiCode, utf16_chars, utf8_chars, 116 | sizeof(utf8_chars), NULL, NULL); 117 | if (!utf8_len) 118 | { 119 | return CallNextHookEx(keyboard_hook_, code, w_param, l_param); 120 | } 121 | VSNvim::SendInput(std::make_unique(utf8_chars)); 122 | return 1; 123 | } 124 | return CallNextHookEx(keyboard_hook_, code, w_param, l_param); 125 | } 126 | 127 | void StartNvim() 128 | { 129 | char* argv[] = {"nvim.exe", "--headless", nullptr}; 130 | const auto argc = sizeof(argv) / sizeof(*argv) - 1; 131 | nvim::nvim_main(argc, argv); 132 | } 133 | 134 | TextViewCreationListener::TextViewCreationListener() 135 | { 136 | text_view_creation_listener_ = this; 137 | } 138 | 139 | static void OnKeyboardFocusedChanged(System::Object^ sender, 140 | System::Windows::DependencyPropertyChangedEventArgs e) 141 | { 142 | is_text_view_focused_ = static_cast(e.NewValue); 143 | } 144 | 145 | void TextViewCreationListener::TextViewCreated(IWpfTextView^ text_view) 146 | { 147 | text_view->VisualElement->IsKeyboardFocusedChanged += 148 | gcnew System::Windows::DependencyPropertyChangedEventHandler( 149 | &OnKeyboardFocusedChanged); 150 | 151 | if (is_nvim_running) 152 | { 153 | VSNvim::CreateBuffer(std::make_unique>(text_view)); 154 | return; 155 | } 156 | 157 | keyboard_hook_ = SetWindowsHookEx(WH_KEYBOARD, &KeyboardHookHandler, NULL, 158 | GetCurrentThreadId()); 159 | 160 | auto thread = gcnew Thread(gcnew ThreadStart(&StartNvim)); 161 | thread->Start(); 162 | is_nvim_running = true; 163 | } 164 | } // namespace VSNvim 165 | -------------------------------------------------------------------------------- /VSNvim/TextViewCreationListener.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace VSNvim 4 | { 5 | [System::ComponentModel::Composition::Export( 6 | Microsoft::VisualStudio::Text::Editor::IWpfTextViewCreationListener::typeid)] 7 | [Microsoft::VisualStudio::Utilities::ContentType("any")] 8 | [Microsoft::VisualStudio::Text::Editor::TextViewRole( 9 | Microsoft::VisualStudio::Text::Editor::PredefinedTextViewRoles::Editable)] 10 | public ref class TextViewCreationListener 11 | : Microsoft::VisualStudio::Text::Editor::IWpfTextViewCreationListener 12 | { 13 | public: 14 | [System::ComponentModel::Composition::Import] 15 | Microsoft::VisualStudio::Text::Classification:: 16 | IEditorFormatMapService^ format_map_service_; 17 | 18 | [System::ComponentModel::Composition::Import] 19 | Microsoft::VisualStudio::Shell::SVsServiceProvider^ service_provider_; 20 | 21 | [System::ComponentModel::Composition::Import] 22 | Microsoft::VisualStudio::Editor 23 | ::IVsEditorAdaptersFactoryService^ editor_adaptor_; 24 | 25 | static TextViewCreationListener^ text_view_creation_listener_; 26 | 27 | Microsoft::VisualStudio::Shell::SVsServiceProvider^ 28 | GetServiceProvider() 29 | { 30 | return service_provider_; 31 | } 32 | 33 | TextViewCreationListener(); 34 | 35 | virtual void TextViewCreated( 36 | Microsoft::VisualStudio::Text::Editor::IWpfTextView^ text_view); 37 | 38 | literal System::String^ caret_adornment_layer_name_ = "VSNvimCaret"; 39 | 40 | [System::ComponentModel::Composition:: 41 | Export(Microsoft::VisualStudio::Text::Editor:: 42 | AdornmentLayerDefinition::typeid)] 43 | [Microsoft::VisualStudio::Utilities::Name(caret_adornment_layer_name_)] 44 | [Microsoft::VisualStudio::Utilities::Order( 45 | After = Microsoft::VisualStudio::Text::Editor:: 46 | PredefinedAdornmentLayers::Selection)] 47 | Microsoft::VisualStudio::Text::Editor::AdornmentLayerDefinition 48 | caret_adornment_layer_; 49 | }; 50 | } 51 | -------------------------------------------------------------------------------- /VSNvim/VSNvim.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | C:\projects\neovim\ 5 | C:\projects\neovim\.deps\usr\ 6 | C:\projects\neovim\build\ 7 | 8 | 9 | 10 | 15.0 11 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 12 | 13 | 14 | 15 | 16 | 17 | Debug 18 | Win32 19 | 20 | 21 | Release 22 | Win32 23 | 24 | 25 | 26 | 15.0 27 | {82b43b9b-a64c-4715-b499-d71e9ca2bd60};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 28 | {80AC6B61-9659-4F47-919D-22327F0953E4} 29 | v4.6 30 | ManagedCProj 31 | VSNvim 32 | 10.0.17134.0 33 | VSNvim 34 | true 35 | true 36 | true 37 | true 38 | true 39 | false 40 | false 41 | 42 | 43 | 44 | DynamicLibrary 45 | true 46 | v141 47 | true 48 | Unicode 49 | 50 | 51 | DynamicLibrary 52 | false 53 | v141 54 | true 55 | Unicode 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | true 71 | Debug\ 72 | MultiThreadedDebugDLL 73 | 74 | 75 | false 76 | Release\ 77 | MultiThreadedDLL 78 | 79 | 80 | 81 | stdcpp17 82 | $([System.IO.Path]::Combine($(NvimSrcDir), src));$([System.IO.Path]::Combine($(NvimBuildDir), include));$([System.IO.Path]::Combine($(NvimBuildDir), config));$([System.IO.Path]::Combine($(NvimBuildDir), src\nvim\auto));$([System.IO.Path]::Combine($(NvimDepsDir), include));%(AdditionalIncludeDirectories) 83 | 84 | 85 | nvim.lib;libintl.lib;libiconv.lib;winpty.lib;uv.lib;msgpackc_import.lib;vterm.lib;termkey.lib;unibilium.lib;lua51.lib;Ws2_32.lib;Comctl32.lib;%(AdditionalDependencies) 86 | $([System.IO.Path]::Combine($(NvimBuildDir), lib));$([System.IO.Path]::Combine($(NvimDepsDir), lib));%(AdditionalLibraryDirectories) 87 | 88 | 89 | 90 | 91 | Level3 92 | Disabled 93 | WIN32;_DEBUG;%(PreprocessorDefinitions) 94 | 95 | 96 | 97 | 98 | Level3 99 | WIN32;NDEBUG;%(PreprocessorDefinitions) 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | PreserveNewest 113 | true 114 | . 115 | 116 | 117 | true 118 | 119 | 120 | Designer 121 | 122 | 123 | Designer 124 | 125 | 126 | Menus.ctmenu 127 | 128 | 129 | 130 | 131 | False 132 | 133 | 134 | False 135 | 136 | 137 | False 138 | 139 | 140 | False 141 | 142 | 143 | 144 | False 145 | 146 | 147 | ..\packages\Microsoft.VisualStudio.CoreUtility.15.6.27740\lib\net46\Microsoft.VisualStudio.CoreUtility.dll 148 | 149 | 150 | ..\packages\Microsoft.VisualStudio.Editor.15.6.27740\lib\net46\Microsoft.VisualStudio.Editor.dll 151 | 152 | 153 | ..\packages\Microsoft.VisualStudio.ImageCatalog.15.7.27703\lib\net45\Microsoft.VisualStudio.ImageCatalog.dll 154 | 155 | 156 | ..\packages\Microsoft.VisualStudio.Imaging.15.7.27703\lib\net45\Microsoft.VisualStudio.Imaging.dll 157 | 158 | 159 | ..\packages\Microsoft.VisualStudio.OLE.Interop.7.10.6071\lib\Microsoft.VisualStudio.OLE.Interop.dll 160 | 161 | 162 | ..\packages\Microsoft.VisualStudio.Shell.15.0.15.7.27703\lib\net45\Microsoft.VisualStudio.Shell.15.0.dll 163 | 164 | 165 | ..\packages\Microsoft.VisualStudio.Shell.Framework.15.7.27703\lib\net45\Microsoft.VisualStudio.Shell.Framework.dll 166 | 167 | 168 | ..\packages\Microsoft.VisualStudio.Shell.Interop.7.10.6072\lib\net11\Microsoft.VisualStudio.Shell.Interop.dll 169 | 170 | 171 | ..\packages\Microsoft.VisualStudio.Shell.Interop.8.0.8.0.50728\lib\net11\Microsoft.VisualStudio.Shell.Interop.8.0.dll 172 | 173 | 174 | ..\packages\Microsoft.VisualStudio.Shell.Interop.9.0.9.0.30730\lib\net11\Microsoft.VisualStudio.Shell.Interop.9.0.dll 175 | 176 | 177 | ..\packages\Microsoft.VisualStudio.Text.Data.15.6.27740\lib\net46\Microsoft.VisualStudio.Text.Data.dll 178 | 179 | 180 | ..\packages\Microsoft.VisualStudio.Text.Logic.15.6.27740\lib\net46\Microsoft.VisualStudio.Text.Logic.dll 181 | 182 | 183 | ..\packages\Microsoft.VisualStudio.Text.UI.15.6.27740\lib\net46\Microsoft.VisualStudio.Text.UI.dll 184 | 185 | 186 | ..\packages\Microsoft.VisualStudio.Text.UI.Wpf.15.6.27740\lib\net46\Microsoft.VisualStudio.Text.UI.Wpf.dll 187 | 188 | 189 | ..\packages\Microsoft.VisualStudio.TextManager.Interop.7.10.6071\lib\net11\Microsoft.VisualStudio.TextManager.Interop.dll 190 | 191 | 192 | ..\packages\Microsoft.VisualStudio.TextManager.Interop.8.0.8.0.50728\lib\net11\Microsoft.VisualStudio.TextManager.Interop.8.0.dll 193 | 194 | 195 | ..\packages\Microsoft.VisualStudio.Threading.15.8.132\lib\net46\Microsoft.VisualStudio.Threading.dll 196 | 197 | 198 | ..\packages\Microsoft.VisualStudio.Utilities.15.7.27703\lib\net46\Microsoft.VisualStudio.Utilities.dll 199 | 200 | 201 | ..\packages\Microsoft.VisualStudio.Validation.15.3.58\lib\net45\Microsoft.VisualStudio.Validation.dll 202 | 203 | 204 | ..\packages\Newtonsoft.Json.11.0.2\lib\net45\Newtonsoft.Json.dll 205 | 206 | 207 | 208 | 209 | False 210 | 211 | 212 | ..\packages\StreamJsonRpc.1.3.23\lib\net45\StreamJsonRpc.dll 213 | 214 | 215 | 216 | ..\packages\System.Collections.Immutable.1.5.0\lib\netstandard1.3\System.Collections.Immutable.dll 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | true 241 | VSPackage 242 | 243 | 244 | 245 | 246 | 247 | 248 | This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | -------------------------------------------------------------------------------- /VSNvim/VSNvim.vcxproj.filters: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 6 | cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx 7 | 8 | 9 | {93995380-89BD-4b04-88EB-625FBE52EBFB} 10 | h;hh;hpp;hxx;hm;inl;inc;ipp;xsd 11 | 12 | 13 | {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} 14 | rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms 15 | 16 | 17 | 18 | 19 | Source Files 20 | 21 | 22 | Source Files 23 | 24 | 25 | Source Files 26 | 27 | 28 | Header Files 29 | 30 | 31 | Source Files 32 | 33 | 34 | Source Files 35 | 36 | 37 | 38 | 39 | Header Files 40 | 41 | 42 | Header Files 43 | 44 | 45 | Header Files 46 | 47 | 48 | Header Files 49 | 50 | 51 | Header Files 52 | 53 | 54 | Header Files 55 | 56 | 57 | Header Files 58 | 59 | 60 | 61 | 62 | Resource Files 63 | 64 | 65 | 66 | 67 | Resource Files 68 | 69 | 70 | Resource Files 71 | 72 | 73 | -------------------------------------------------------------------------------- /VSNvim/VSNvimBridge.cpp: -------------------------------------------------------------------------------- 1 | #include "VSNvimBridge.h" 2 | 3 | #include 4 | 5 | #include "VSNvimTextView.h" 6 | #include "TextViewCreationListener.h" 7 | 8 | using namespace Microsoft::VisualStudio::Text::Editor; 9 | using namespace Microsoft::VisualStudio::TextManager::Interop; 10 | 11 | nvim::UI* ui; 12 | 13 | namespace VSNvim 14 | { 15 | template 16 | static void QueueNvimAction(TCallback callback) 17 | { 18 | static_assert(sizeof(TCallback) <= sizeof(nvim::Event::argv), 19 | "The callback is too big."); 20 | 21 | nvim::Event event; 22 | event.handler = [](void** argv) 23 | { 24 | (*reinterpret_cast(argv))(); 25 | }; 26 | new (reinterpret_cast(&event.argv)) 27 | TCallback(std::move(callback)); 28 | nvim::loop_schedule(&nvim::main_loop, event); 29 | } 30 | 31 | void ResizeWindow(nvim::win_T* nvim_window, int top_line, 32 | int bottom_line, int window_height) 33 | { 34 | QueueNvimAction([nvim_window, top_line, bottom_line, window_height]() 35 | { 36 | nvim_window->w_topline = top_line; 37 | nvim_window->w_botline = bottom_line; 38 | 39 | if (ui->height != window_height) 40 | { 41 | ui->height = window_height; 42 | nvim::ui_refresh(); 43 | } 44 | }); 45 | } 46 | 47 | void SendInput(std::unique_ptr&& input) 48 | { 49 | QueueNvimAction([input = std::move(input)]() 50 | { 51 | nvim::nvim_input(nvim::CreateString(*input)); 52 | }); 53 | } 54 | 55 | static VSNvimTextView^ CreateVSNvimTextViewAction( 56 | IWpfTextView^ text_view, System::IntPtr nvim_window) 57 | { 58 | return gcnew VSNvimTextView(text_view, 59 | static_cast(nvim_window.ToPointer())); 60 | } 61 | 62 | void SwitchToBuffer(nvim::buf_T* buffer) 63 | { 64 | QueueNvimAction([buffer]() 65 | { 66 | nvim::Error error; 67 | nvim::nvim_set_current_buf(buffer->handle, &error); 68 | }); 69 | } 70 | 71 | void WipeBuffer(nvim::buf_T* buffer) 72 | { 73 | QueueNvimAction([buffer]() 74 | { 75 | const auto vsnvim_text_view_ptr_ = buffer->vsnvim_data; 76 | auto command = 77 | std::string("bw! ") + std::to_string(buffer->handle); 78 | nvim::Error error; 79 | nvim::nvim_command(nvim::CreateString(command), &error); 80 | delete vsnvim_text_view_ptr_; 81 | buffer->vsnvim_data = nullptr; 82 | }); 83 | } 84 | 85 | ref struct TextViewClosedHandler 86 | { 87 | nvim::buf_T* nvim_buffer_; 88 | 89 | TextViewClosedHandler( 90 | VSNvim::VSNvimTextView^ vsnvim_text_view, 91 | nvim::buf_T* nvim_buffer) 92 | : nvim_buffer_(nvim_buffer) 93 | { 94 | auto vsnvim_text_view_ptr = 95 | new gcroot(vsnvim_text_view); 96 | nvim_buffer->vsnvim_data = reinterpret_cast(vsnvim_text_view_ptr); 97 | } 98 | 99 | void OnTextViewClosed(System::Object^ sender, System::EventArgs^ e) 100 | { 101 | if (!nvim_buffer_) 102 | { 103 | return; 104 | } 105 | WipeBuffer(nvim_buffer_); 106 | nvim_buffer_ = nullptr; 107 | } 108 | }; 109 | 110 | void InitBuffer(IWpfTextView^ text_view) 111 | { 112 | auto vsnvim_text_view = static_cast( 113 | System::Windows::Application::Current->Dispatcher->Invoke( 114 | gcnew System::Func( 115 | &CreateVSNvimTextViewAction), 116 | text_view, 117 | System::IntPtr(nvim::curwin))); 118 | text_view->Closed += gcnew System::EventHandler( 119 | gcnew TextViewClosedHandler(vsnvim_text_view, nvim::curbuf), 120 | &TextViewClosedHandler::OnTextViewClosed); 121 | } 122 | 123 | void InitFirstBuffer() 124 | { 125 | const auto service_provider = 126 | TextViewCreationListener::text_view_creation_listener_->service_provider_; 127 | const auto text_manager = static_cast( 128 | service_provider->GetService(SVsTextManager::typeid)); 129 | IVsTextView^ active_text_view; 130 | if (text_manager->GetActiveView(false, nullptr, active_text_view) != S_OK) 131 | { 132 | return; 133 | } 134 | const auto editor_adapter = 135 | TextViewCreationListener::text_view_creation_listener_->editor_adaptor_; 136 | const auto active_wpf_text_view = static_cast( 137 | editor_adapter->GetWpfTextView(active_text_view)); 138 | 139 | InitBuffer(active_wpf_text_view); 140 | } 141 | 142 | void CreateBuffer(std::unique_ptr>&& text_view) 143 | { 144 | QueueNvimAction([text_view = std::move(text_view)]() 145 | { 146 | auto command = std::string("set hidden | enew"); 147 | nvim::Error error; 148 | nvim::nvim_command(nvim::CreateString(command), &error); 149 | InitBuffer(*text_view); 150 | }); 151 | } 152 | } // namespace VSNvim 153 | 154 | extern "C" 155 | { 156 | static VSNvim::VSNvimTextView^ GetTextView(void* vsnvim_data) 157 | { 158 | return *reinterpret_cast*>(vsnvim_data); 159 | } 160 | 161 | const nvim::char_u* vsnvim_get_line(void* vsnvim_data, nvim::linenr_T lnum) 162 | { 163 | return GetTextView(vsnvim_data)->GetLine(lnum); 164 | } 165 | 166 | int vsnvim_append_line( 167 | void* vsnvim_data, nvim::linenr_T lnum, nvim::char_u* line, nvim::colnr_T len) 168 | { 169 | GetTextView(vsnvim_data)->AppendLine(lnum, line, len); 170 | return true; 171 | } 172 | 173 | int vsnvim_delete_line(void* vsnvim_data, nvim::linenr_T lnum) 174 | { 175 | GetTextView(vsnvim_data)->DeleteLine(lnum); 176 | return true; 177 | } 178 | 179 | int vsnvim_delete_char(void* vsnvim_data, nvim::linenr_T lnum, 180 | nvim::colnr_T col) 181 | { 182 | GetTextView(vsnvim_data)->DeleteChar(lnum, col); 183 | return true; 184 | } 185 | 186 | int vsnvim_replace_line(void* vsnvim_data, nvim::linenr_T lnum, 187 | nvim::char_u* line) 188 | { 189 | GetTextView(vsnvim_data)->ReplaceLine(lnum, line); 190 | return true; 191 | } 192 | 193 | int vsnvim_replace_char(void* vsnvim_data, nvim::linenr_T lnum, 194 | nvim::colnr_T col, nvim::char_u chr) 195 | { 196 | GetTextView(vsnvim_data)->ReplaceChar(lnum, col, chr); 197 | return true; 198 | } 199 | 200 | void vsnvim_init_buffers() 201 | { 202 | VSNvim::InitFirstBuffer(); 203 | } 204 | 205 | int vs_plines_win_nofold(void* vs_data, nvim::linenr_T lnum) 206 | { 207 | return GetTextView(vs_data)->GetPhysicalLinesCount(lnum); 208 | } 209 | 210 | void vsnvim_execute_command(const nvim::char_u* command) 211 | { 212 | const auto chr_ptr = reinterpret_cast(command); 213 | const auto command_str = gcnew System::String(chr_ptr, 0, 214 | std::strlen(chr_ptr), System::Text::Encoding::UTF8); 215 | const auto split = command_str->Split(gcnew array{ " " }, 2, 216 | System::StringSplitOptions::RemoveEmptyEntries); 217 | if (!split->Length) 218 | { 219 | return; 220 | } 221 | const auto command_name = split[0]; 222 | const auto command_args = split->Length == 2 223 | ? split[1] 224 | : System::String::Empty; 225 | const auto service_provider = 226 | VSNvim::TextViewCreationListener::text_view_creation_listener_-> 227 | GetServiceProvider(); 228 | const auto dte = safe_cast( 229 | service_provider->GetService( 230 | Microsoft::VisualStudio::Shell::Interop::SDTE::typeid)); 231 | try 232 | { 233 | dte->ExecuteCommand(command_name, command_args); 234 | } 235 | catch (...) 236 | { 237 | // No information useful is provided in the exception. 238 | nvim::emsg(reinterpret_cast( 239 | "Failed to execute Visual Studio command")); 240 | } 241 | } 242 | 243 | static VSNvim::NvimTextSelection GetSelectionType() 244 | { 245 | switch (nvim::VIsual_mode) 246 | { 247 | default: 248 | case 'v': 249 | return VSNvim::NvimTextSelection::Normal; 250 | case 'V': 251 | return VSNvim::NvimTextSelection::Line; 252 | case Ctrl_V: 253 | return VSNvim::NvimTextSelection::Block; 254 | } 255 | } 256 | 257 | static bool cursor_enabled_; 258 | static nvim::Array cursor_styles_; 259 | 260 | static void SetCaretOptions( 261 | VSNvim::VSNvimCaret^ caret, 262 | bool enabled, 263 | System::Int64 horizontal, 264 | System::Int64 vertical, 265 | System::Int64 blink_wait, 266 | System::Int64 blink_on, 267 | System::Int64 blink_off) 268 | { 269 | caret->SetOptions( 270 | enabled, 271 | horizontal, 272 | vertical, 273 | blink_wait, 274 | blink_on, 275 | blink_off); 276 | } 277 | 278 | static void NvimModeChange( 279 | nvim::UI* ui, nvim::String mode, nvim::Integer mode_index) 280 | { 281 | const auto text_view = 282 | *reinterpret_cast*>( 283 | nvim::curbuf->vsnvim_data); 284 | const auto current_mode = cursor_styles_.items[mode_index]; 285 | std::string_view cursor_shape; 286 | auto cell_percentage = 100ll; 287 | auto blink_wait = 0ll; 288 | auto blink_on = 0ll; 289 | auto blink_off = 0ll; 290 | const auto dictionary = current_mode.data.dictionary; 291 | for (auto i = 0; i < dictionary.size; i++) 292 | { 293 | const auto item = dictionary.items[i]; 294 | const auto key = std::string_view(item.key.data, item.key.size); 295 | if (key == "cursor_shape") 296 | { 297 | cursor_shape = std::string_view( 298 | item.value.data.string.data, item.value.data.string.size); 299 | } 300 | else if (key == "cell_percentage") 301 | { 302 | cell_percentage = static_cast(item.value.data.integer); 303 | } 304 | else if (key == "blinkwait") 305 | { 306 | blink_wait = item.value.data.integer; 307 | } 308 | else if (key == "blinkon") 309 | { 310 | blink_on = item.value.data.integer; 311 | } 312 | else if (key == "blinkoff") 313 | { 314 | blink_off = item.value.data.integer; 315 | } 316 | } 317 | 318 | System::Windows::Application::Current->Dispatcher->Invoke( 319 | gcnew System::Action 326 | (&SetCaretOptions), 327 | text_view->GetCaret(), 328 | cursor_enabled_, 329 | cursor_shape == "horizontal" ? cell_percentage : 100, 330 | cursor_shape == "vertical" ? cell_percentage : 100, 331 | blink_wait, 332 | blink_on, 333 | blink_off); 334 | } 335 | 336 | Microsoft::VisualStudio::Shell::Interop::IVsStatusbar^ GetVSStatusBar() 337 | { 338 | const auto service_provider = VSNvim::TextViewCreationListener:: 339 | text_view_creation_listener_->service_provider_; 340 | return static_cast( 341 | service_provider->GetService( 342 | Microsoft::VisualStudio::Shell::Interop::SVsStatusbar::typeid)); 343 | } 344 | 345 | void vsnvim_ui_start() 346 | { 347 | ui = new nvim::UI(); 348 | ui->width = 1; 349 | ui->height = 1; 350 | ui->stop = [](nvim::UI* ui) 351 | { 352 | nvim::ui_detach_impl(ui); 353 | delete ui; 354 | }; 355 | // This callback must be defined because there are no NULL checks for it. 356 | ui->hl_attr_define = [](nvim::UI* ui, nvim::Integer id, nvim::HlAttrs attrs, 357 | nvim::HlAttrs cterm_attrs, nvim::Array info) 358 | { 359 | }; 360 | 361 | ui->mode_change = NvimModeChange; 362 | ui->mode_info_set = [](nvim::UI* ui, nvim::Boolean enabled, 363 | nvim::Array cursor_styles) 364 | { 365 | cursor_enabled_ = enabled; 366 | if (cursor_styles_.items) 367 | { 368 | nvim::api_free_array(cursor_styles_); 369 | cursor_styles_.items = nullptr; 370 | } 371 | cursor_styles_ = nvim::copy_array(cursor_styles); 372 | }; 373 | 374 | ui->event = [](nvim::UI* ui, char* name, 375 | nvim::Array args, bool* args_consumed) 376 | { 377 | if (std::string_view(name) == "cmdline_show") 378 | { 379 | const auto text = args.items[0].data.array 380 | .items[0].data.array 381 | .items[1].data.string; 382 | const auto first_char = args.items[2].data.string; 383 | const auto cmdline = 384 | gcnew System::String( 385 | first_char.data, 0, first_char.size, System::Text::Encoding::UTF8) 386 | + gcnew System::String( 387 | text.data, 0, text.size, System::Text::Encoding::UTF8); 388 | GetVSStatusBar()->SetText(cmdline); 389 | } 390 | else if (std::string_view(name) == "cmdline_hide") 391 | { 392 | GetVSStatusBar()->Clear(); 393 | } 394 | }; 395 | 396 | ui->flush = [](nvim::UI* ui) 397 | { 398 | const auto text_view = 399 | *reinterpret_cast*>( 400 | nvim::curbuf->vsnvim_data); 401 | const auto cursor = &nvim::curwin->w_cursor; 402 | text_view->CursorGoto(cursor->lnum, cursor->col); 403 | text_view->Scroll(nvim::curwin->w_topline); 404 | if (nvim::VIsual_active) 405 | { 406 | text_view->SelectText(nvim::VIsual, GetSelectionType()); 407 | } 408 | else 409 | { 410 | text_view->ClearTextSelection(); 411 | } 412 | }; 413 | 414 | memset(ui->ui_ext, 0, sizeof(ui->ui_ext)); 415 | ui->ui_ext[nvim::kUICmdline] = true; 416 | 417 | nvim::ui_attach_impl(ui); 418 | } 419 | } // extern "C" 420 | -------------------------------------------------------------------------------- /VSNvim/VSNvimBridge.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "nvim.h" 4 | #include 5 | #include 6 | #include "nvim.h" 7 | #include // gcroot 8 | 9 | namespace VSNvim 10 | { 11 | void ResizeWindow(nvim::win_T* nvim_window, int top_line, 12 | int bottom_line, int window_height); 13 | 14 | void SendInput(std::unique_ptr&& input); 15 | 16 | void CreateBuffer( 17 | std::unique_ptr>&& text_view); 19 | 20 | void SwitchToBuffer(nvim::buf_T* buffer); 21 | } 22 | -------------------------------------------------------------------------------- /VSNvim/VSNvimCaret.cpp: -------------------------------------------------------------------------------- 1 | #include "VSNvimCaret.h" 2 | 3 | using namespace System; 4 | using namespace System::Windows::Media; 5 | using namespace Microsoft::VisualStudio::Text::Editor; 6 | using namespace Microsoft::VisualStudio::Shell::Interop; 7 | using Microsoft::VisualStudio::Text::Classification::EditorFormatDefinition; 8 | using Microsoft::VisualStudio::Text::Formatting::VisibilityState; 9 | using Microsoft::VisualStudio::Text::SnapshotSpan; 10 | 11 | namespace VSNvim 12 | { 13 | VSNvimCaret::VSNvimCaret(ITextCaret^ caret, 14 | System::Windows::Media::Color color, 15 | IAdornmentLayer^ adornment_layer) 16 | : caret_(caret), 17 | adornment_layer_(adornment_layer), 18 | blink_timer_( 19 | TimeSpan::Zero, 20 | System::Windows::Threading::DispatcherPriority::Normal, 21 | gcnew System::EventHandler(this, &VSNvimCaret::OnBlinkTimerTick), 22 | System::Windows::Application::Current->Dispatcher) 23 | { 24 | blink_timer_.IsEnabled = false; 25 | caret_->PositionChanged += 26 | gcnew System::EventHandler( 27 | this, &VSNvim::VSNvimCaret::OnPositionChanged); 28 | 29 | const auto brush = gcnew SolidColorBrush(color); 30 | if (brush->CanFreeze) 31 | { 32 | brush->Freeze(); 33 | } 34 | rectangle_.Fill = brush; 35 | 36 | CreateCaretAdornment(); 37 | } 38 | 39 | void VSNvimCaret::SetState(VSNvimCaretBlinkState state) 40 | { 41 | blink_state_ = state; 42 | blink_timer_.Interval = TimeSpan::FromMilliseconds(blink_wait_); 43 | blink_timer_.IsEnabled = state != VSNvimCaretBlinkState::Inactive 44 | && blink_wait_; 45 | caret_->IsHidden = state != VSNvimCaretBlinkState::Inactive; 46 | rectangle_.Visibility = 47 | state == VSNvimCaretBlinkState::Wait || 48 | state == VSNvimCaretBlinkState::On 49 | ? System::Windows::Visibility::Visible 50 | : System::Windows::Visibility::Collapsed; 51 | CreateCaretAdornment(); 52 | } 53 | 54 | void VSNvimCaret::SetOptions( 55 | bool enabled, 56 | Int64 horizontal, 57 | Int64 vertical, 58 | Int64 blink_wait, 59 | Int64 blink_on, 60 | Int64 blink_off) 61 | { 62 | horizontal_percentage_ = horizontal / 100.; 63 | vertical_percentage_ = vertical / 100.; 64 | blink_wait_ = blink_wait; 65 | blink_on_ = blink_on; 66 | blink_off_ = blink_off; 67 | SetState(enabled 68 | ? VSNvimCaretBlinkState::Wait 69 | : VSNvimCaretBlinkState::Inactive); 70 | } 71 | 72 | void VSNvimCaret::OnPositionChanged(Object^ sender, 73 | CaretPositionChangedEventArgs^ e) 74 | { 75 | if (blink_state_ == VSNvimCaretBlinkState::Inactive) 76 | { 77 | return; 78 | } 79 | 80 | CreateCaretAdornment(); 81 | SetState(VSNvimCaretBlinkState::Wait); 82 | } 83 | 84 | void VSNvimCaret::OnBlinkTimerTick( 85 | System::Object^ sender, System::EventArgs^ e) 86 | { 87 | VSNvimCaretBlinkState next_state; 88 | switch (blink_state_) 89 | { 90 | case VSNvimCaretBlinkState::On: 91 | case VSNvimCaretBlinkState::Wait: 92 | next_state = VSNvimCaretBlinkState::Off; 93 | break; 94 | case VSNvimCaretBlinkState::Off: 95 | next_state = VSNvimCaretBlinkState::On; 96 | break; 97 | default: 98 | next_state = VSNvimCaretBlinkState::Inactive; 99 | } 100 | SetState(next_state); 101 | } 102 | 103 | void VSNvimCaret::CreateCaretAdornment() 104 | { 105 | if (is_adornment_active_) 106 | { 107 | adornment_layer_->RemoveAdornment(%rectangle_); 108 | } 109 | 110 | adornment_layer_->AddAdornment( 111 | AdornmentPositioningBehavior::TextRelative, 112 | SnapshotSpan(caret_->Position.BufferPosition, 0), nullptr, %rectangle_, 113 | gcnew AdornmentRemovedCallback(this, &VSNvimCaret::OnAdornmentRemoved)); 114 | is_adornment_active_ = true; 115 | 116 | const auto caret_position = caret_->Position.BufferPosition; 117 | auto char_bounds = caret_->ContainingTextViewLine->GetExtendedCharacterBounds(caret_position); 118 | const auto width = char_bounds.Width; 119 | const auto height = char_bounds.Height; 120 | const auto top_margin = height * (1. - horizontal_percentage_); 121 | 122 | rectangle_.Width = width * vertical_percentage_; 123 | rectangle_.Height = height * horizontal_percentage_; 124 | rectangle_.Margin = System::Windows::Thickness(0, top_margin, 0, 0); 125 | 126 | if (caret_->ContainingTextViewLine->VisibilityState 127 | == VisibilityState::Unattached) 128 | { 129 | return; 130 | } 131 | 132 | System::Windows::Controls::Canvas::SetLeft(%rectangle_, caret_->Left); 133 | System::Windows::Controls::Canvas::SetTop(%rectangle_, caret_->Top); 134 | } 135 | 136 | void VSNvimCaret::Enable() 137 | { 138 | SetState(VSNvimCaretBlinkState::Wait); 139 | } 140 | 141 | void VSNvimCaret::Disable() 142 | { 143 | SetState(VSNvimCaretBlinkState::Inactive); 144 | } 145 | 146 | void VSNvimCaret::OnAdornmentRemoved( 147 | Object^ tag, System::Windows::UIElement^ element) 148 | { 149 | is_adornment_active_ = false; 150 | } 151 | } // namespace VSNvim 152 | -------------------------------------------------------------------------------- /VSNvim/VSNvimCaret.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "VSNvimCaretBlinkState.h" 4 | 5 | namespace VSNvim 6 | { 7 | public ref class VSNvimCaret 8 | { 9 | System::Windows::Threading::DispatcherTimer blink_timer_; 10 | System::Windows::Shapes::Rectangle rectangle_; 11 | Microsoft::VisualStudio::Text::Editor::IAdornmentLayer^ adornment_layer_; 12 | Microsoft::VisualStudio::Text::Editor::ITextCaret^ caret_; 13 | double horizontal_percentage_; 14 | double vertical_percentage_; 15 | VSNvimCaretBlinkState blink_state_; 16 | System::Int64 blink_wait_; 17 | System::Int64 blink_on_; 18 | System::Int64 blink_off_; 19 | bool is_adornment_active_; 20 | 21 | void SetState(VSNvimCaretBlinkState state); 22 | 23 | void OnPositionChanged(System::Object^ sender, 24 | Microsoft::VisualStudio::Text::Editor::CaretPositionChangedEventArgs^ e); 25 | 26 | void OnBlinkTimerTick(System::Object^ sender, System::EventArgs^ e); 27 | 28 | void OnAdornmentRemoved( 29 | System::Object^ tag, System::Windows::UIElement^ element); 30 | 31 | public: 32 | VSNvimCaret(Microsoft::VisualStudio::Text::Editor::ITextCaret^ caret, 33 | System::Windows::Media::Color color, 34 | Microsoft::VisualStudio::Text::Editor::IAdornmentLayer^ adornment_layer); 35 | 36 | void CreateCaretAdornment(); 37 | 38 | void Enable(); 39 | 40 | void Disable(); 41 | 42 | void SetOptions( 43 | bool enabled, 44 | System::Int64 horizontal, 45 | System::Int64 vertical, 46 | System::Int64 blink_wait, 47 | System::Int64 blink_on, 48 | System::Int64 blink_off); 49 | }; 50 | } 51 | -------------------------------------------------------------------------------- /VSNvim/VSNvimCaretBlinkState.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | public enum class VSNvimCaretBlinkState 4 | { 5 | Inactive, 6 | Wait, 7 | On, 8 | Off 9 | }; 10 | -------------------------------------------------------------------------------- /VSNvim/VSNvimPackage.cpp: -------------------------------------------------------------------------------- 1 | #include "VSNvimPackage.h" 2 | 3 | using namespace Microsoft::VisualStudio::Shell; 4 | using namespace System::ComponentModel::Design; 5 | 6 | namespace VSNvim 7 | { 8 | void VSNvimPackage::Initialize() 9 | { 10 | const auto command_service = static_cast( 11 | GetService(IMenuCommandService::typeid)); 12 | const auto menu_group_guid = 13 | System::Guid("a691cbd7-8fb4-4b12-bc3e-9835a4f45705"); 14 | command_service->AddCommand(gcnew MenuCommand( 15 | gcnew System::EventHandler(this, &VSNvimPackage::SetEnabled), 16 | gcnew CommandID(menu_group_guid, 0x0100))); 17 | command_service->AddCommand(gcnew MenuCommand( 18 | gcnew System::EventHandler(this, &VSNvimPackage::SetDisabled), 19 | gcnew CommandID(menu_group_guid, 0x0101))); 20 | command_service->AddCommand(gcnew MenuCommand( 21 | gcnew System::EventHandler(this, &VSNvimPackage::ToggledEnabled), 22 | gcnew CommandID(menu_group_guid, 0x0102))); 23 | } 24 | 25 | void VSNvimPackage::SetEnabled(System::Object^ sender, System::EventArgs^ e) 26 | { 27 | IsEnabled = true; 28 | Enabled(this, gcnew System::EventArgs()); 29 | } 30 | 31 | void VSNvimPackage::SetDisabled(System::Object^ sender, System::EventArgs^ e) 32 | { 33 | IsEnabled = false; 34 | Disabled(this, gcnew System::EventArgs()); 35 | } 36 | 37 | void VSNvimPackage::ToggledEnabled(System::Object^ sender, System::EventArgs^ e) 38 | { 39 | IsEnabled = !IsEnabled; 40 | if (IsEnabled) 41 | { 42 | Enabled(this, gcnew System::EventArgs()); 43 | } 44 | else 45 | { 46 | Disabled(this, gcnew System::EventArgs()); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /VSNvim/VSNvimPackage.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace VSNvim 4 | { 5 | [Microsoft::VisualStudio::Shell::PackageRegistration( 6 | UseManagedResourcesOnly = true)] 7 | [System::Runtime::InteropServices::Guid(VSNvimPackage::PackageGuid)] 8 | [Microsoft::VisualStudio::Shell::ProvideMenuResource("Menus.ctmenu", 1)] 9 | public ref class VSNvimPackage : Microsoft::VisualStudio::Shell::Package 10 | { 11 | private: 12 | void VSNvimPackage::SetEnabled(System::Object^ sender, 13 | System::EventArgs^ e); 14 | 15 | void VSNvimPackage::SetDisabled(System::Object^ sender, 16 | System::EventArgs^ e); 17 | 18 | void VSNvimPackage::ToggledEnabled(System::Object^ sender, 19 | System::EventArgs^ e); 20 | public: 21 | void Initialize() override; 22 | 23 | literal System::String^ PackageGuid = "aacd9f2c-b95b-49b3-836c-c9bde586e38f"; 24 | 25 | static bool IsEnabled = true; 26 | 27 | static event System::EventHandler^ Enabled; 28 | 29 | static event System::EventHandler^ Disabled; 30 | }; 31 | } 32 | -------------------------------------------------------------------------------- /VSNvim/VSNvimPackage.vsct: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 25 | 26 | 33 | 34 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 47 | 48 | 55 | 64 | 73 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | -------------------------------------------------------------------------------- /VSNvim/VSNvimTextView.cpp: -------------------------------------------------------------------------------- 1 | #include "VSNvimTextView.h" 2 | 3 | // This #define is a workaround for a regression with the cliext headers. 4 | // https://developercommunity.visualstudio.com/content/problem/316251/vs-158-broken-stlclr-iterator-debug-level2-error-c.html 5 | #define _STLCLRDB_REPORT 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include "TextViewCreationListener.h" 13 | #include "VSNvimPackage.h" 14 | #include "VSNvimBridge.h" 15 | 16 | using namespace System; 17 | using namespace System::ComponentModel::Composition; 18 | using namespace System::Linq; 19 | using namespace System::Text; 20 | using namespace System::Threading; 21 | using namespace System::Runtime::InteropServices; 22 | using namespace Microsoft::VisualStudio::Utilities; 23 | using namespace Microsoft::VisualStudio::Text; 24 | using namespace Microsoft::VisualStudio::Text::Editor; 25 | using namespace Microsoft::VisualStudio::Text::Formatting; 26 | 27 | namespace VSNvim 28 | { 29 | ITextSnapshotLine^ VSNvimTextView::GetLineFromNumber(nvim::linenr_T lnum) 30 | { 31 | // Line numbers start at one for Nvim and zero for Visual Studio 32 | return text_view_->TextBuffer->CurrentSnapshot-> 33 | GetLineFromLineNumber(lnum - 1); 34 | } 35 | 36 | static System::Windows::Media::Color GetSelectedTextColor(ITextView^ text_view) 37 | { 38 | const auto format_map_service = 39 | TextViewCreationListener::text_view_creation_listener_->format_map_service_; 40 | const auto selected_text = format_map_service-> 41 | GetEditorFormatMap(text_view)->GetProperties("Selected Text"); 42 | const auto background_color_id = 43 | Microsoft::VisualStudio::Text::Classification::EditorFormatDefinition:: 44 | BackgroundColorId; 45 | return static_cast( 46 | selected_text[background_color_id]); 47 | } 48 | 49 | VSNvimTextView::VSNvimTextView( 50 | IWpfTextView^ text_view, nvim::win_T* nvim_window) 51 | : text_view_(text_view), 52 | nvim_buffer_(nvim_window->w_buffer), 53 | nvim_window_(nvim_window), 54 | caret_(text_view->Caret, 55 | GetSelectedTextColor(text_view), 56 | text_view->GetAdornmentLayer( 57 | TextViewCreationListener::caret_adornment_layer_name_)) 58 | { 59 | text_view->GotAggregateFocus += 60 | gcnew System::EventHandler(this, &VSNvimTextView::OnGotAggregateFocus); 61 | text_view->LayoutChanged += 62 | gcnew System::EventHandler( 63 | this, &VSNvim::VSNvimTextView::OnLayoutChanged); 64 | VSNvimPackage::Enabled += 65 | gcnew System::EventHandler(this, &VSNvimTextView::OnEnabled); 66 | VSNvimPackage::Disabled += 67 | gcnew System::EventHandler(this, &VSNvimTextView::OnDisabled); 68 | 69 | nvim_buffer_->b_ml.ml_line_count = 70 | text_view->TextSnapshot->LineCount; 71 | SetBufferFlags(); 72 | } 73 | 74 | void VSNvimTextView::AppendLine(nvim::linenr_T lnum, nvim::char_u* line, 75 | nvim::colnr_T len) 76 | { 77 | const auto utf16_line = 78 | Encoding::UTF8->GetString(line, len == 0 79 | ? strlen(reinterpret_cast(line)) 80 | : len) + Environment::NewLine; 81 | System::Windows::Application::Current->Dispatcher->Invoke( 82 | gcnew Action( 83 | this, &VSNvimTextView::AppendLineAction), lnum, utf16_line); 84 | } 85 | 86 | void VSNvimTextView::AppendLineAction( 87 | nvim::linenr_T lnum, System::String^ line) 88 | { 89 | const auto text_buffer = text_view_->TextBuffer; 90 | const auto buffer_pos = 91 | lnum == 0 ? 0 92 | : GetLineFromNumber(lnum)->EndIncludingLineBreak.Position; 93 | text_buffer->Insert(buffer_pos, line); 94 | SetBufferFlags(); 95 | } 96 | 97 | void VSNvimTextView::ReplaceLine(nvim::linenr_T lnum, nvim::char_u* line) 98 | { 99 | const auto utf16_line = Encoding::UTF8->GetString(line, 100 | strlen(reinterpret_cast(line))) + Environment::NewLine; 101 | System::Windows::Application::Current->Dispatcher->Invoke( 102 | gcnew Action( 103 | this, &VSNvimTextView::ReplaceLineAction), lnum, utf16_line); 104 | } 105 | 106 | void VSNvimTextView::ReplaceLineAction( 107 | nvim::linenr_T lnum, System::String^ line) 108 | { 109 | const auto text_line = GetLineFromNumber(lnum); 110 | const auto line_span = Span(text_line->Start.Position, 111 | text_line->LengthIncludingLineBreak); 112 | text_view_->TextBuffer->Replace(line_span, line); 113 | SetBufferFlags(); 114 | } 115 | 116 | void VSNvimTextView::ReplaceChar(nvim::linenr_T lnum, 117 | nvim::colnr_T col, nvim::char_u chr) 118 | { 119 | const auto utf16_char = Encoding::UTF8->GetString(&chr, 1); 120 | System::Windows::Application::Current->Dispatcher->Invoke( 121 | gcnew Action( 122 | this, &VSNvimTextView::ReplaceCharAction), lnum, col, chr); 123 | } 124 | 125 | void VSNvimTextView::ReplaceCharAction(nvim::linenr_T lnum, 126 | nvim::colnr_T col, System::String^ chr) 127 | { 128 | const auto text_line = GetLineFromNumber(lnum); 129 | const auto line_span = Span(text_line->Start.Position + col, 1); 130 | text_view_->TextBuffer->Replace(line_span, chr); 131 | SetBufferFlags(); 132 | } 133 | 134 | void VSNvimTextView::DeleteLine(nvim::linenr_T lnum) 135 | { 136 | System::Windows::Application::Current->Dispatcher->Invoke( 137 | gcnew Action( 138 | this, &VSNvimTextView::DeleteLineAction), lnum); 139 | } 140 | 141 | void VSNvimTextView::DeleteLineAction(nvim::linenr_T lnum) 142 | { 143 | const auto text_line = GetLineFromNumber(lnum); 144 | const auto line_span = Span(text_line->Start.Position, 145 | text_line->LengthIncludingLineBreak); 146 | text_view_->TextBuffer->Delete(line_span); 147 | SetBufferFlags(); 148 | } 149 | 150 | void VSNvimTextView::DeleteChar(nvim::linenr_T lnum, nvim::colnr_T col) 151 | { 152 | System::Windows::Application::Current->Dispatcher->Invoke( 153 | gcnew Action( 154 | this, &VSNvimTextView::DeleteCharAction), lnum, col); 155 | } 156 | 157 | void VSNvimTextView::DeleteCharAction(nvim::linenr_T lnum, nvim::colnr_T col) 158 | { 159 | const auto text_line = GetLineFromNumber(lnum); 160 | const auto char_span = Span(text_line->Start.Position + col, 1); 161 | text_view_->TextBuffer->Delete(char_span); 162 | SetBufferFlags(); 163 | } 164 | 165 | void VSNvimTextView::SetBufferFlags() 166 | { 167 | const auto buffer_empty = text_view_->TextSnapshot->Length == 0; 168 | if (buffer_empty) 169 | { 170 | nvim::curbuf->b_ml.ml_flags |= ML_EMPTY; 171 | } 172 | else 173 | { 174 | nvim::curbuf->b_ml.ml_flags &= ~ML_EMPTY; 175 | } 176 | } 177 | 178 | VSNvimCaret^ VSNvimTextView::GetCaret() 179 | { 180 | return %caret_; 181 | } 182 | 183 | const nvim::char_u* VSNvimTextView::GetLine(nvim::linenr_T lnum) 184 | { 185 | if (last_line_ != nullptr) 186 | { 187 | last_line_->Free(); 188 | } 189 | 190 | const auto utf16_line = GetLineFromNumber(lnum)->GetText(); 191 | const auto utf8_line = Encoding::UTF8->GetBytes(utf16_line); 192 | last_line_ = GCHandle::Alloc(utf8_line, GCHandleType::Pinned); 193 | return static_cast( 194 | last_line_->AddrOfPinnedObject().ToPointer()); 195 | } 196 | 197 | void VSNvimTextView::CursorGoto(nvim::linenr_T lnum, nvim::colnr_T col) 198 | { 199 | System::Windows::Application::Current->Dispatcher->Invoke( 200 | gcnew Action( 201 | this, &VSNvimTextView::CursorGotoAction), 202 | lnum, col); 203 | } 204 | 205 | void VSNvimTextView::CursorGotoAction(nvim::linenr_T lnum, nvim::colnr_T col) 206 | { 207 | if (text_view_->IsClosed || text_view_->InLayout) 208 | { 209 | return; 210 | } 211 | 212 | auto line_start = GetLineFromNumber(lnum)->Start; 213 | text_view_->Caret->MoveTo(line_start.Add(col)); 214 | } 215 | 216 | static bool IsLineFullyVisible(ITextViewLine^ line) 217 | { 218 | return line->VisibilityState.Equals(VisibilityState::FullyVisible); 219 | } 220 | 221 | static bool IsLineNotFullyVisible(ITextViewLine^ line) 222 | { 223 | return !IsLineFullyVisible(line); 224 | } 225 | 226 | void VSNvimTextView::OnEnabled(System::Object ^ sender, System::EventArgs^ e) 227 | { 228 | caret_.Enable(); 229 | } 230 | 231 | void VSNvimTextView::OnDisabled(System::Object ^ sender, System::EventArgs^ e) 232 | { 233 | caret_.Disable(); 234 | } 235 | 236 | void VSNvimTextView::OnGotAggregateFocus( 237 | System::Object^ sender, System::EventArgs^ e) 238 | { 239 | VSNvim::SwitchToBuffer(nvim_buffer_); 240 | } 241 | 242 | void VSNvimTextView::OnLayoutChanged( 243 | Object^ sender, TextViewLayoutChangedEventArgs^ e) 244 | { 245 | caret_.CreateCaretAdornment(); 246 | 247 | const auto lines = text_view_->TextViewLines; 248 | using LineList = System::Collections::Generic::IList; 249 | auto lines_adapter = 250 | cliext::collection_adapter(static_cast(lines)); 251 | 252 | const auto first_visible_line_index = 253 | lines->GetIndexOfTextLine(lines->FirstVisibleLine); 254 | const auto first_visible_line = 255 | cliext::find_if(lines_adapter.begin() + first_visible_line_index, 256 | lines_adapter.end() - 1, &IsLineFullyVisible).get_cref(); 257 | const auto top_line_number = 258 | first_visible_line->Start.GetContainingLine()->LineNumber + 1; 259 | if (top_line_ == top_line_number) 260 | { 261 | return; 262 | } 263 | top_line_ = top_line_number; 264 | 265 | const auto last_visible_line_index = 266 | lines->GetIndexOfTextLine(lines->LastVisibleLine); 267 | const auto first_hidden_line = 268 | cliext::find_if(lines_adapter.begin() + last_visible_line_index, 269 | lines_adapter.end() - 1, &IsLineNotFullyVisible).get_cref(); 270 | const auto bottom_line_number = 271 | first_hidden_line->Start.GetContainingLine()->LineNumber + 1; 272 | 273 | const auto window_height = 274 | static_cast(text_view_->ViewportHeight) / text_view_->LineHeight; 275 | VSNvim::ResizeWindow(nvim_window_, top_line_number, 276 | bottom_line_number, window_height); 277 | } 278 | 279 | int VSNvimTextView::GetPhysicalLinesCount(nvim::linenr_T lnum) 280 | { 281 | const auto line = GetLineFromNumber(lnum); 282 | const auto line_span = SnapshotSpan(line->Start, line->End); 283 | const auto physical_lines = text_view_->TextViewLines-> 284 | GetTextViewLinesIntersectingSpan(line_span)->Count; 285 | return (cliext::max)(physical_lines, 1); 286 | } 287 | 288 | void VSNvimTextView::Scroll(nvim::linenr_T lnum) 289 | { 290 | System::Windows::Application::Current->Dispatcher-> 291 | Invoke(gcnew Action( 292 | this, &VSNvimTextView::ScrollAction), lnum); 293 | } 294 | 295 | void VSNvimTextView::ScrollAction(nvim::linenr_T lnum) 296 | { 297 | const auto line_start = GetLineFromNumber(lnum)->Start; 298 | text_view_->DisplayTextLineContainingBufferPosition( 299 | line_start, 0, ViewRelativePosition::Top); 300 | } 301 | 302 | void VSNvimTextView::SelectText(nvim::pos_T position, NvimTextSelection mode) 303 | { 304 | System::Windows::Application::Current->Dispatcher-> 305 | Invoke(gcnew Action( 306 | this, &VSNvimTextView::SelectTextAction), 307 | position.lnum, position.col, mode); 308 | } 309 | 310 | void VSNvimTextView::SelectTextAction( 311 | nvim::linenr_T line, nvim::colnr_T col, NvimTextSelection mode) 312 | { 313 | SnapshotPoint position1; 314 | SnapshotPoint position2; 315 | if (mode == NvimTextSelection::Line) 316 | { 317 | position1 = text_view_->Caret->Position 318 | .BufferPosition.GetContainingLine()->End; 319 | position2 = GetLineFromNumber(line)->Start; 320 | } 321 | else 322 | { 323 | // Add one to include the character at the position of the caret. 324 | position1 = text_view_->Caret->Position.BufferPosition + 1; 325 | position2 = GetLineFromNumber(line)->Start.Add(col); 326 | } 327 | const auto reversed = position1 > position2; 328 | text_view_->Selection->Select( 329 | reversed 330 | ? SnapshotSpan(position2, position1) 331 | : SnapshotSpan(position1, position2), 332 | reversed); 333 | text_view_->Selection->Mode = 334 | mode == NvimTextSelection::Block 335 | ? TextSelectionMode::Box 336 | : TextSelectionMode::Stream; 337 | } 338 | 339 | void VSNvimTextView::ClearTextSelection() 340 | { 341 | System::Windows::Application::Current->Dispatcher-> 342 | Invoke(gcnew Action(this, &VSNvimTextView::ClearTextSelectionAction)); 343 | } 344 | 345 | void VSNvimTextView::ClearTextSelectionAction() 346 | { 347 | text_view_->Selection->Clear(); 348 | } 349 | } // namespace VSNvim 350 | -------------------------------------------------------------------------------- /VSNvim/VSNvimTextView.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "nvim.h" 4 | #include "NvimTextSelection.h" 5 | #include "VSNvimCaret.h" 6 | 7 | namespace VSNvim 8 | { 9 | public ref class VSNvimTextView 10 | { 11 | private: 12 | VSNvimCaret caret_; 13 | Microsoft::VisualStudio::Text::Editor::ITextView^ text_view_; 14 | nvim::buf_T* nvim_buffer_; 15 | nvim::win_T* nvim_window_; 16 | int top_line_; 17 | 18 | // Holds a reference to the last accessed line 19 | // to prevent it from being garbage collected 20 | System::Runtime::InteropServices::GCHandle^ last_line_; 21 | 22 | Microsoft::VisualStudio::Text::ITextSnapshotLine^ 23 | GetLineFromNumber(nvim::linenr_T lnum); 24 | 25 | void AppendLineAction(nvim::linenr_T lnum, System::String^ line); 26 | 27 | void DeleteLineAction(nvim::linenr_T lnum); 28 | 29 | void DeleteCharAction(nvim::linenr_T lnum, nvim::colnr_T col); 30 | 31 | void ReplaceLineAction(nvim::linenr_T lnum, System::String^ line); 32 | 33 | void ReplaceCharAction(nvim::linenr_T lnum, nvim::colnr_T col, 34 | System::String^ chr); 35 | 36 | void CursorGotoAction(nvim::linenr_T lnum, nvim::colnr_T col); 37 | 38 | void ScrollAction(nvim::linenr_T lnum); 39 | 40 | void SelectTextAction( 41 | nvim::linenr_T line, nvim::colnr_T col, NvimTextSelection mode); 42 | 43 | void ClearTextSelectionAction(); 44 | 45 | void OnEnabled(System::Object^ sender, System::EventArgs^ e); 46 | 47 | void OnDisabled(System::Object^ sender, System::EventArgs^ e); 48 | 49 | void OnGotAggregateFocus( 50 | System::Object^ sender, System::EventArgs^ e); 51 | 52 | void OnLayoutChanged(System::Object^ sender, 53 | Microsoft::VisualStudio::Text::Editor::TextViewLayoutChangedEventArgs^ e); 54 | 55 | public: 56 | VSNvimTextView( 57 | Microsoft::VisualStudio::Text::Editor::IWpfTextView^ text_view, 58 | nvim::win_T* nvim_window); 59 | 60 | VSNvimCaret^ GetCaret(); 61 | 62 | const nvim::char_u* GetLine(nvim::linenr_T lnum); 63 | 64 | void AppendLine(nvim::linenr_T lnum, nvim::char_u* line, nvim::colnr_T len); 65 | 66 | void DeleteLine(nvim::linenr_T lnum); 67 | 68 | void DeleteChar(nvim::linenr_T lnum, nvim::colnr_T col); 69 | 70 | void ReplaceLine(nvim::linenr_T lnum, nvim::char_u* line); 71 | 72 | void ReplaceChar(nvim::linenr_T lnum, nvim::colnr_T col, nvim::char_u chr); 73 | 74 | void CursorGoto(nvim::linenr_T lnum, nvim::colnr_T col); 75 | 76 | void Scroll(nvim::linenr_T lnum); 77 | 78 | void SelectText(nvim::pos_T position, NvimTextSelection mode); 79 | 80 | void ClearTextSelection(); 81 | 82 | int GetPhysicalLinesCount(nvim::linenr_T lnum); 83 | 84 | void SetBufferFlags(); 85 | }; 86 | } // namespace VSNvim 87 | -------------------------------------------------------------------------------- /VSNvim/VSPackage.resx: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | text/microsoft-resx 120 | 121 | 122 | 2.0 123 | 124 | 125 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 126 | 127 | 128 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 129 | 130 | 131 | 132 | VSNvim 133 | 134 | 135 | Neovim for Visual Studio 136 | 137 | 138 | -------------------------------------------------------------------------------- /VSNvim/nvim.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // The following headers are dependents of Nvim. They are included 4 | // here so their contents get declared inside the global namespace 5 | // and not the nvim namespace. 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | namespace nvim 16 | { 17 | extern "C" { 18 | // This is is normally defined with a compiler argument when Nvim is built. 19 | #define INCLUDE_GENERATED_DECLARATIONS 20 | // Avoid compiler errors caused by names that are C++ keywords. 21 | #define _Bool bool 22 | #define this this_ 23 | 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | 39 | // Undefine macros with names of keywords so they may be used again. 40 | #undef _Bool 41 | #undef this 42 | // Undefine the "inline" macro that was defined in os/win_defs.h. 43 | #undef inline 44 | 45 | // nvim/main.h does not have a definition for nvim_main. 46 | int nvim_main(int argc, char** argv); 47 | } 48 | 49 | inline nvim::String CreateString(std::string& str) 50 | { 51 | nvim::String nvim_str; 52 | nvim_str.data = str.data(); 53 | nvim_str.size = str.size(); 54 | return nvim_str; 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /VSNvim/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /VSNvim/source.extension.vsixmanifest: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | VSNvim 6 | Neovim for Visual Studio 7 | https://github.com/b-r-o-c-k/VSNvim 8 | LICENSE 9 | neovim;nvim;vim;vsnvim;vsvim 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | image: Visual Studio 2017 2 | platform: x86 3 | configuration: Release 4 | shallow_clone: true 5 | cache: 6 | - packages -> **\*.vcxproj 7 | install: 8 | - git clone --branch vsnvim --depth 1 --quiet 9 | https://github.com/b-r-o-c-k/neovim.git C:\projects\neovim 10 | - cd C:\projects\neovim 11 | - mkdir .deps 12 | - cd .deps 13 | - cmake -G "Visual Studio 15 2017" --config RelWithDebInfo ..\third-party 14 | - cmake --build . --config RelWithDebInfo 15 | - cd .. 16 | - mkdir build 17 | - cd build 18 | - cmake -G "Visual Studio 15 2017" --config RelWithDebInfo .. 19 | - cmake --build . --config RelWithDebInfo --target libnvim 20 | - cd %APPVEYOR_BUILD_FOLDER% 21 | before_build: 22 | - nuget restore 23 | artifacts: 24 | - path: '**\*.vsix' 25 | --------------------------------------------------------------------------------