├── .gitattributes ├── .gitignore ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── ISSUE_TEMPLATE.md ├── LICENSE ├── License.txt ├── Readme.tt ├── References ├── v14 │ ├── Microsoft.Alm.Shared.dll │ ├── Microsoft.TeamFoundation.CodeSense.Client.Common.dll │ ├── Microsoft.VisualStudio.Alm.Shared.CodeAnalysisClient.dll │ ├── Microsoft.VisualStudio.Alm.Shared.dll │ ├── Microsoft.VisualStudio.CodeSense.Client.Common.dll │ ├── Microsoft.VisualStudio.CodeSense.Roslyn.dll │ ├── Microsoft.VisualStudio.CodeSense.dll │ └── Microsoft.VisualStudio.Text.Internal.dll └── v15 │ ├── Microsoft.Alm.Shared.dll │ ├── Microsoft.TeamFoundation.CodeSense.Client.Common.dll │ ├── Microsoft.VisualStudio.Alm.Shared.dll │ ├── Microsoft.VisualStudio.CodeSense.Client.Common.dll │ ├── Microsoft.VisualStudio.CodeSense.Roslyn.dll │ ├── Microsoft.VisualStudio.CodeSense.dll │ └── Microsoft.VisualStudio.Text.Internal.dll ├── Settings.md ├── TeamCoding.Shared ├── Documents │ ├── CaretAdornmentData.cs │ ├── DocumentPaths.cs │ ├── DocumentRepoMetaData.cs │ ├── ICaretAdornmentDataProvider.cs │ ├── ICaretInfoProvider.cs │ ├── IRemotelyAccessedDocumentData.cs │ └── SourceControlRepositories │ │ ├── FileNumberBasis.cs │ │ └── ISourceControlRepository.cs ├── Extensions │ ├── DictionaryExtensions.cs │ ├── EnumerableExtensions.cs │ ├── ITextBufferExtensions.cs │ ├── StreamExtensions.cs │ └── StringExtensions.cs ├── ITeamCodingPackageProvider.cs ├── IdentityManagement │ └── IUserIdentity.cs ├── IntArrayEqualityComparer.cs ├── Key.snk ├── Logging │ └── ILogger.cs ├── Properties │ └── AssemblyInfo.cs ├── TeamCoding.Shared.csproj ├── TeamCodingProjectTypeProvider.cs ├── VisualStudio │ └── Models │ │ └── ChangePersisters │ │ └── IRemoteModelPersister.cs └── packages.config ├── TeamCoding.WindowsService ├── Install.ps1 ├── Multicaster.cs ├── Program.cs ├── Properties │ └── AssemblyInfo.cs ├── RunAsConsole.bat ├── TeamCoding.WindowsService.csproj ├── TeamCodingSyncServer.Designer.cs ├── TeamCodingSyncServer.cs ├── TeamCodingSyncServer.resx ├── TeamCodingSyncServerInstaller.cs ├── Uninstall.ps1 └── packages.config ├── TeamCoding.sln ├── TeamCoding.v14 ├── Documents │ ├── CaretAdornmentDataProvider.cs │ ├── CaretInfoProvider.cs │ ├── LineNumberTranslator.cs │ └── SourceControlRepositories │ │ └── TeamFoundationServiceRepository.cs ├── Extensions │ ├── ParameterListSyntaxExtensions.cs │ ├── SyntaxNodeExtensions.SyntaxNodeHashCache.cs │ └── SyntaxNodeExtensions.cs ├── Key.snk ├── Properties │ └── AssemblyInfo.cs ├── TeamCoding.v14.csproj ├── VisualStudio │ └── CodeLens │ │ ├── CurrentUsersDataPoint.cs │ │ ├── CurrentUsersDataPointProvider.cs │ │ ├── CurrentUsersDataPointUpdater.cs │ │ ├── CurrentUsersDataPointViewModel.cs │ │ └── CurrentUsersDataPointViewModelProvider.cs ├── app.config └── packages.config ├── TeamCoding.v15 ├── Key.snk ├── Properties │ └── AssemblyInfo.cs ├── TeamCoding.v15.csproj ├── VisualStudio │ └── CodeLens │ │ ├── CurrentUsersDataPoint.cs │ │ ├── CurrentUsersDataPoint.tt │ │ ├── CurrentUsersDataPointProvider.cs │ │ ├── CurrentUsersDataPointProvider.tt │ │ ├── CurrentUsersDataPointUpdater.cs │ │ ├── CurrentUsersDataPointUpdater.tt │ │ ├── CurrentUsersDataPointViewModel.cs │ │ ├── CurrentUsersDataPointViewModel.tt │ │ ├── CurrentUsersDataPointViewModelProvider.cs │ │ ├── CurrentUsersDataPointViewModelProvider.tt │ │ └── VisualStudioIntegrationServiceExtensions.cs ├── app.config └── packages.config ├── TeamCoding ├── CredentialManagement │ ├── NativeMethods.cs │ └── WindowsCredential.cs ├── Documents │ ├── RemotelyAccessedDocumentData.cs │ └── SourceControlRepositories │ │ ├── CachedSourceControlRepository.cs │ │ ├── GitRepository.cs │ │ └── SolutionNameBasedRepository.cs ├── Events │ ├── CombinedEvent.cs │ └── DelayedEvent.cs ├── Extensions │ ├── DTEExtensions.cs │ ├── DependencyObjectExtensions.cs │ ├── DocumentViewExtensions.cs │ ├── FrameworkElementExtensions.cs │ ├── IDBConnectionExtensions.cs │ ├── StreamExtensions.cs │ ├── TaskExtensions.cs │ ├── TextBlockExtensions.cs │ ├── TextViewExtensions.cs │ └── WaitHandleExtensions.cs ├── Guids.cs ├── IdentityManagement │ ├── CachedFailoverIdentityProvider.cs │ ├── CredentialManagerIdentityProvider.cs │ ├── IIdentityProvider.cs │ ├── MachineIdentityProvider.cs │ ├── UserIdentity.cs │ ├── VSIdentityProvider.cs │ └── VSOptionsIdentityProvider.cs ├── Key.snk ├── Logging │ └── Logger.cs ├── Options │ ├── OptionPageGrid.cs │ ├── OptionsPage.xaml │ ├── OptionsPage.xaml.cs │ ├── SettingProperties │ │ └── SettingProperty.cs │ ├── Settings.cs │ ├── SharedSettings.cs │ ├── TeamCodingExample.xaml │ ├── TeamCodingExample.xaml.cs │ └── UserSettings.cs ├── Properties │ └── AssemblyInfo.cs ├── Resources │ ├── GettingStarted.tt │ ├── OverviewCommand.png │ ├── OverviewCommandIcon.png │ ├── PreviewImage.png │ ├── SqlServerPersisterCreateScript.sql │ └── TeamCodingPackage.ico ├── Settings.StyleCop ├── TeamCoding.csproj ├── TeamCodingPackage.cs ├── TeamCodingPackage.vsct ├── TeamCodingPackageProvider.cs ├── VisualStudio │ ├── Controls │ │ ├── TextBlockToLetterMarginConverter.cs │ │ ├── UserAvatar.xaml │ │ ├── UserAvatar.xaml.cs │ │ └── UserAvatarModel.cs │ ├── IDEWrapper.cs │ ├── Models │ │ ├── ChangePersisters │ │ │ ├── CombinedPersister │ │ │ │ ├── CombinedLocalModelPersister.cs │ │ │ │ └── CombinedRemoteModelPersister.cs │ │ │ ├── DebugPersister │ │ │ │ ├── DebugLocalModelPersister.cs │ │ │ │ └── DebugRemoteModelPersister.cs │ │ │ ├── FileBasedPersister │ │ │ │ ├── FileBasedLocalModelPersisterBase.cs │ │ │ │ ├── FileBasedRemoteModelPersisterBase.cs │ │ │ │ ├── SharedFolderLocalModelPersister.cs │ │ │ │ └── SharedFolderRemoteModelPersister.cs │ │ │ ├── ILocalModelPerisister.cs │ │ │ ├── LocalModelPersisterBase.cs │ │ │ ├── RedisPersister │ │ │ │ ├── RedisLocalModelPersister.cs │ │ │ │ ├── RedisRemoteModelPersister.cs │ │ │ │ └── RedisWrapper.cs │ │ │ ├── RemoteModelPersisterBase.cs │ │ │ ├── SlackPersister │ │ │ │ ├── ObjectSlackMessageConverter.cs │ │ │ │ ├── SlackLocalModelPersister.cs │ │ │ │ ├── SlackRemoteModelPersister.cs │ │ │ │ └── SlackWrapper.cs │ │ │ ├── SqlServerPersister │ │ │ │ ├── SqlConnectionWrapper.cs │ │ │ │ ├── SqlServerLocalModelPersister.cs │ │ │ │ ├── SqlServerRemoteModelPersister.cs │ │ │ │ └── SqlWatcher.cs │ │ │ └── WindowsServicePersister │ │ │ │ ├── WinServiceClient.cs │ │ │ │ ├── WinServiceLocalModelPersister.cs │ │ │ │ └── WinServiceRemoteModelPersister.cs │ │ ├── LocalIDEModel.cs │ │ └── RemoteIDEModel.cs │ ├── SolutionEventsHandler.cs │ ├── TeamCodingTextViewConnectionListener.cs │ ├── TextAdornment │ │ ├── TextAdornment.cs │ │ └── TextAdornmentTextViewCreationListener.cs │ ├── ToolWindows │ │ └── OverviewWindow │ │ │ ├── Overview.cs │ │ │ ├── OverviewCommand.cs │ │ │ ├── OverviewControl.xaml │ │ │ └── OverviewControl.xaml.cs │ ├── UserColours.cs │ ├── UserImageCache.cs │ └── VisuallyDistinctColours.cs ├── app.config ├── packages.config ├── source.extension.cs ├── source.extension.ico ├── source.extension.resx └── source.extension.vsixmanifest ├── appveyor.yml └── readme.md /.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 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Roadmap 2 | 3 | - [x] A feature that has been completed 4 | - [ ] A feature that has NOT yet been completed 5 | 6 | Features that have a checkmark are complete and available for 7 | download in the 8 | [CI build](http://vsixgallery.com/extension/TeamCoding.George Duckett.307fe055-6f5e-40d7-bb11-13abd012e528/). 9 | 10 | # Changelog 11 | 12 | These are the changes to each version that has been released 13 | on the official Visual Studio extension gallery. 14 | 15 | ## 1.1 16 | 17 | **2016-09-03** 18 | 19 | - [x] Feature 3 20 | - [x] Feature 4 21 | 22 | ## 1.0 23 | 24 | **2016-09-02** 25 | 26 | - [x] Initial release 27 | - [x] Feature 1 28 | - [x] Feature 2 29 | - [x] Sub feature -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | ## Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | * Using welcoming and inclusive language 12 | * Being respectful of differing viewpoints and experiences 13 | * Gracefully accepting constructive criticism 14 | * Focusing on what is best for the community 15 | * Showing empathy towards other community members 16 | 17 | Examples of unacceptable behavior by participants include: 18 | 19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances 20 | * Trolling, insulting/derogatory comments, and personal or political attacks 21 | * Public or private harassment 22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission 23 | * Other conduct which could reasonably be considered inappropriate in a professional setting 24 | 25 | ## Our Responsibilities 26 | 27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 28 | 29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 30 | 31 | ## Scope 32 | 33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 34 | 35 | ## Enforcement 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at georgeduckett@gmail.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 38 | 39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. 40 | 41 | ## Attribution 42 | 43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] 44 | 45 | [homepage]: http://contributor-covenant.org 46 | [version]: http://contributor-covenant.org/version/1/4/ 47 | -------------------------------------------------------------------------------- /ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### Installed product versions 2 | - Visual Studio: [example 2015 Professional] 3 | - This extension: [example 1.1.21] 4 | 5 | ### Description 6 | Replace this text with a short description 7 | 8 | ### Steps to recreate 9 | 1. Replace this 10 | 2. text with 11 | 3. the steps 12 | 4. to recreate 13 | 14 | ### Current behavior 15 | Explain what it's doing and why it's wrong 16 | 17 | ### Expected behavior 18 | Explain what it should be doing after it's fixed. 19 | 20 | ### Team Coding Output 21 | Copy-Paste the log from the output window titled Team Coding (if relevent). -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2016 George Duckett 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. -------------------------------------------------------------------------------- /License.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 George Alan Duckett 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /Readme.tt: -------------------------------------------------------------------------------- 1 | <#@ template language="C#" debug="false" hostspecific="true" #> 2 | <#@ output extension=".md" #> 3 | <#@ assembly name="$(SolutionDir)\TeamCoding\bin\Debug\TeamCoding.dll" #> 4 | <#@ assembly name="$(SolutionDir)\TeamCoding\bin\Debug\TeamCoding.v14.dll" #> 5 | Team Coding 6 | -- 7 | 8 | **About:** <#= typeof(TeamCoding.TeamCodingPackage).Assembly.GetType("TeamCoding.Vsix").GetField("Description").GetRawConstantValue() #> 9 | 10 | **Quickstart:** <#=System.IO.File.ReadAllText(Host.ResolveAssemblyReference("$(ProjectDir)") + "\\Resources\\GettingStarted.txt") #> 11 | 12 | **Settings:** The settings are located at Tools->Options-><#= TeamCoding.Options.OptionPageGrid.OptionsName #>. 13 | There are configuration options for your user identity (how you appear to others) and how others appear to you, as well as configuration options for how to share open document information with others. 14 | Multiple methods can be used at once, you can also add a `<#= TeamCoding.Options.Settings.TeamCodingConfigFileName #>` file anywhere within the solution folder for it to override those settings. 15 | If this file is in place then for that solution settings don't need to be set in each developer's IDE. 16 | You can enable/disable the CodeLens addon in Tools->Options->Text Editor->All Language->Code Lens-><#= TeamCoding.VisualStudio.CodeLens.CurrentUsersDataPointProvider.CodeLensName #>. 17 | For more details regarding the different user and shared options, see [Settings.md](https://github.com/georgeduckett/TeamCoding/blob/master/Settings.md) 18 | 19 | **User Identity:** It tries to get your email address as a user identity from various sources (saved Windows Credentials for GitHub, your logged in identity from Visual Studio, your machine name). 20 | This will be made public to your team. It uses Gravatar to get a user image from the email address. 21 | 22 | **Overview window** There is a tool window that allows you to see every user and what documents they've got open in a treeview. To access it use the View->Other Windows->TeamCoding Overview command. 23 | 24 | **Roadmap:** Still very much alpha, please report bugs as [GitHub issues](https://github.com/georgeduckett/TeamCoding/issues). 25 | Currently sharing options are a shared folder, Redis, Slack, an SQL Server table or via a server application as a windows service or console application. If you want others (along with any other feature requests) please raise them as issues. Supports Git repos and Team Foundation Services / Visual Studio Online for determining what to share as well as a fall-back to sharing purely based on matching solution filenames. In the pipeline a shared-coding experience (multiple users editing the same document(s)). 26 | 27 | **Reporting Bugs**: When reporting bugs ([here](https://github.com/georgeduckett/TeamCoding/issues)) please include information from the `<#= TeamCoding.Logging.Logger.OutputWindowCategory #>` tab in the output window (if relevent). If there were any exceptions they should be visible there, which can help track down the cause. 28 | 29 | **Links:** [GitHub Repo](https://github.com/georgeduckett/TeamCoding/) 30 | 31 | [![Build status](https://ci.appveyor.com/api/projects/status/vqgmu9893sxn3p7m?svg=true)](https://ci.appveyor.com/project/georgeduckett/teamcoding) 32 | 33 | --- 34 | 35 | ![Demo Gif](http://i.giphy.com/3oz8xNb3MTsqzn67za.gif) -------------------------------------------------------------------------------- /References/v14/Microsoft.Alm.Shared.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/georgeduckett/TeamCoding/5ad8f1d221730348662c8c11f3fb57a1ce79e642/References/v14/Microsoft.Alm.Shared.dll -------------------------------------------------------------------------------- /References/v14/Microsoft.TeamFoundation.CodeSense.Client.Common.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/georgeduckett/TeamCoding/5ad8f1d221730348662c8c11f3fb57a1ce79e642/References/v14/Microsoft.TeamFoundation.CodeSense.Client.Common.dll -------------------------------------------------------------------------------- /References/v14/Microsoft.VisualStudio.Alm.Shared.CodeAnalysisClient.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/georgeduckett/TeamCoding/5ad8f1d221730348662c8c11f3fb57a1ce79e642/References/v14/Microsoft.VisualStudio.Alm.Shared.CodeAnalysisClient.dll -------------------------------------------------------------------------------- /References/v14/Microsoft.VisualStudio.Alm.Shared.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/georgeduckett/TeamCoding/5ad8f1d221730348662c8c11f3fb57a1ce79e642/References/v14/Microsoft.VisualStudio.Alm.Shared.dll -------------------------------------------------------------------------------- /References/v14/Microsoft.VisualStudio.CodeSense.Client.Common.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/georgeduckett/TeamCoding/5ad8f1d221730348662c8c11f3fb57a1ce79e642/References/v14/Microsoft.VisualStudio.CodeSense.Client.Common.dll -------------------------------------------------------------------------------- /References/v14/Microsoft.VisualStudio.CodeSense.Roslyn.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/georgeduckett/TeamCoding/5ad8f1d221730348662c8c11f3fb57a1ce79e642/References/v14/Microsoft.VisualStudio.CodeSense.Roslyn.dll -------------------------------------------------------------------------------- /References/v14/Microsoft.VisualStudio.CodeSense.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/georgeduckett/TeamCoding/5ad8f1d221730348662c8c11f3fb57a1ce79e642/References/v14/Microsoft.VisualStudio.CodeSense.dll -------------------------------------------------------------------------------- /References/v14/Microsoft.VisualStudio.Text.Internal.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/georgeduckett/TeamCoding/5ad8f1d221730348662c8c11f3fb57a1ce79e642/References/v14/Microsoft.VisualStudio.Text.Internal.dll -------------------------------------------------------------------------------- /References/v15/Microsoft.Alm.Shared.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/georgeduckett/TeamCoding/5ad8f1d221730348662c8c11f3fb57a1ce79e642/References/v15/Microsoft.Alm.Shared.dll -------------------------------------------------------------------------------- /References/v15/Microsoft.TeamFoundation.CodeSense.Client.Common.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/georgeduckett/TeamCoding/5ad8f1d221730348662c8c11f3fb57a1ce79e642/References/v15/Microsoft.TeamFoundation.CodeSense.Client.Common.dll -------------------------------------------------------------------------------- /References/v15/Microsoft.VisualStudio.Alm.Shared.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/georgeduckett/TeamCoding/5ad8f1d221730348662c8c11f3fb57a1ce79e642/References/v15/Microsoft.VisualStudio.Alm.Shared.dll -------------------------------------------------------------------------------- /References/v15/Microsoft.VisualStudio.CodeSense.Client.Common.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/georgeduckett/TeamCoding/5ad8f1d221730348662c8c11f3fb57a1ce79e642/References/v15/Microsoft.VisualStudio.CodeSense.Client.Common.dll -------------------------------------------------------------------------------- /References/v15/Microsoft.VisualStudio.CodeSense.Roslyn.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/georgeduckett/TeamCoding/5ad8f1d221730348662c8c11f3fb57a1ce79e642/References/v15/Microsoft.VisualStudio.CodeSense.Roslyn.dll -------------------------------------------------------------------------------- /References/v15/Microsoft.VisualStudio.CodeSense.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/georgeduckett/TeamCoding/5ad8f1d221730348662c8c11f3fb57a1ce79e642/References/v15/Microsoft.VisualStudio.CodeSense.dll -------------------------------------------------------------------------------- /References/v15/Microsoft.VisualStudio.Text.Internal.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/georgeduckett/TeamCoding/5ad8f1d221730348662c8c11f3fb57a1ce79e642/References/v15/Microsoft.VisualStudio.Text.Internal.dll -------------------------------------------------------------------------------- /TeamCoding.Shared/Documents/CaretAdornmentData.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace TeamCoding.Interfaces.Documents 8 | { 9 | public struct CaretAdornmentData 10 | { 11 | public readonly bool RelativeToServerSource; 12 | public readonly int NonWhiteSpaceStart; 13 | public readonly int SpanStart; 14 | public readonly int SpanEnd; 15 | public CaretAdornmentData(bool relativeToServerSource, int nonWhiteSpaceStart, int spanStart, int spanEnd) { RelativeToServerSource = relativeToServerSource; NonWhiteSpaceStart = nonWhiteSpaceStart; SpanStart = spanStart; SpanEnd = spanEnd; } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /TeamCoding.Shared/Documents/DocumentPaths.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace TeamCoding.Documents 9 | { 10 | public static class DocumentPaths 11 | { 12 | private readonly static string PathSeperator = Path.DirectorySeparatorChar.ToString(); 13 | private readonly static Dictionary _GetFullPathCache = new Dictionary(); 14 | private readonly static Dictionary _GetCorrectCaseCache = new Dictionary(); 15 | public static string GetFullPath(string partialPath) 16 | { 17 | if (_GetFullPathCache.ContainsKey(partialPath)) 18 | { 19 | return _GetFullPathCache[partialPath]; 20 | } 21 | else 22 | { 23 | var fullPath = Path.GetFullPath(partialPath); 24 | _GetFullPathCache.Add(partialPath, fullPath); 25 | 26 | return fullPath; 27 | } 28 | } 29 | public static string GetCorrectCase(string fileOrFolder) 30 | { 31 | if (_GetCorrectCaseCache.ContainsKey(fileOrFolder)) 32 | { 33 | return _GetCorrectCaseCache[fileOrFolder]; 34 | } 35 | 36 | var result = GetCorrectCaseInternal(fileOrFolder); 37 | _GetCorrectCaseCache.Add(fileOrFolder, result); 38 | return result; 39 | } 40 | private static string GetCorrectCaseInternal(string fileOrFolder) 41 | { // http://stackoverflow.com/a/29751774 42 | if (string.IsNullOrEmpty(fileOrFolder)) 43 | { 44 | return fileOrFolder; 45 | } 46 | string myParentFolder = Path.GetDirectoryName(fileOrFolder); 47 | string myChildName = Path.GetFileName(fileOrFolder); 48 | if (ReferenceEquals(myParentFolder, null)) 49 | { 50 | return fileOrFolder.TrimEnd(new char[] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar }).ToUpperInvariant(); 51 | } 52 | 53 | if (Directory.Exists(myParentFolder)) 54 | { 55 | string myFileOrFolder = Directory.GetFileSystemEntries(myParentFolder, myChildName).FirstOrDefault(); 56 | if (!ReferenceEquals(myFileOrFolder, null)) 57 | { 58 | myChildName = Path.GetFileName(myFileOrFolder); 59 | } 60 | } 61 | return GetCorrectCase(myParentFolder) + PathSeperator + myChildName; // Don't use path.combine as it mucks up the casing 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /TeamCoding.Shared/Documents/DocumentRepoMetaData.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | 4 | namespace TeamCoding.Documents 5 | { 6 | /// 7 | /// Represents data about a document 8 | /// 9 | [ProtoBuf.ProtoContract] 10 | public class DocumentRepoMetaData 11 | { 12 | [ProtoBuf.ProtoIgnore] 13 | public string RepoProvider { get; set; } 14 | [ProtoBuf.ProtoContract] 15 | public class CaretInfo 16 | { 17 | [ProtoBuf.ProtoMember(1)] 18 | public int[] SyntaxNodeIds { get; set; } = new int[0]; 19 | [ProtoBuf.ProtoMember(2)] 20 | public int LeafMemberCaretOffset { get; set; } 21 | [ProtoBuf.ProtoMember(3)] 22 | public int LeafMemberLineOffset { get; set; } 23 | [ProtoBuf.ProtoMember(4)] 24 | public int LeafMemberLineBasis { get; set; } 25 | } 26 | [ProtoBuf.ProtoMember(1)] 27 | public string RepoUrl { get; set; } 28 | [ProtoBuf.ProtoMember(2)] 29 | public string RepoBranch { get; set; } 30 | [ProtoBuf.ProtoMember(3)] 31 | public string RelativePath { get; set; } 32 | [ProtoBuf.ProtoMember(4)] 33 | public bool BeingEdited { get; set; } 34 | [ProtoBuf.ProtoMember(5)] 35 | public DateTime LastActioned { get; set; } 36 | [ProtoBuf.ProtoMember(6)] 37 | public CaretInfo CaretPositionInfo { get; set; } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /TeamCoding.Shared/Documents/ICaretAdornmentDataProvider.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.Text; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace TeamCoding.Interfaces.Documents 9 | { 10 | public interface ICaretAdornmentDataProvider 11 | { 12 | Task> GetCaretAdornmentDataAsync(ITextSnapshot textSnapshot, int[] caretMemberHashCodes); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /TeamCoding.Shared/Documents/ICaretInfoProvider.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.Text; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using TeamCoding.Documents; 8 | 9 | namespace TeamCoding.Interfaces.Documents 10 | { 11 | public interface ICaretInfoProvider 12 | { 13 | /// 14 | /// 15 | /// 16 | /// Must be a Microsoft.VisualStudio.Text.SnapshotPoint 17 | /// 18 | Task GetCaretInfoAsync(SnapshotPoint snapshotPoint); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /TeamCoding.Shared/Documents/IRemotelyAccessedDocumentData.cs: -------------------------------------------------------------------------------- 1 | using TeamCoding.IdentityManagement; 2 | 3 | namespace TeamCoding.Documents 4 | { 5 | public interface IRemotelyAccessedDocumentData 6 | { 7 | bool BeingEdited { get; set; } 8 | DocumentRepoMetaData.CaretInfo CaretPositionInfo { get; set; } 9 | bool HasFocus { get; set; } 10 | IUserIdentity IdeUserIdentity { get; set; } 11 | string RelativePath { get; set; } 12 | string Repository { get; set; } 13 | string RepositoryBranch { get; set; } 14 | 15 | bool Equals(IRemotelyAccessedDocumentData other); 16 | bool Equals(object obj); 17 | int GetHashCode(); 18 | } 19 | } -------------------------------------------------------------------------------- /TeamCoding.Shared/Documents/SourceControlRepositories/FileNumberBasis.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace TeamCoding.Documents.SourceControlRepositories 8 | { 9 | public enum FileNumberBasis { Server, Local } 10 | } 11 | -------------------------------------------------------------------------------- /TeamCoding.Shared/Documents/SourceControlRepositories/ISourceControlRepository.cs: -------------------------------------------------------------------------------- 1 | namespace TeamCoding.Documents.SourceControlRepositories 2 | { 3 | public interface ISourceControlRepository 4 | { 5 | DocumentRepoMetaData GetRepoDocInfo(string fullFilePath); 6 | string[] GetRemoteFileLines(string fullFilePath); 7 | } 8 | } -------------------------------------------------------------------------------- /TeamCoding.Shared/Extensions/DictionaryExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace TeamCoding.Extensions 8 | { 9 | public static class DictionaryExtensions 10 | { 11 | public static bool DictionaryEqual(this IDictionary first, IDictionary second, IEqualityComparer valueComparer = null) 12 | { // http://stackoverflow.com/a/3928856 13 | if (first == second) 14 | return true; 15 | if (first == null || second == null) 16 | return false; 17 | if (first.Count != second.Count) 18 | return false; 19 | 20 | valueComparer = valueComparer ?? EqualityComparer.Default; 21 | 22 | foreach (var kvp in first) 23 | { 24 | if (!second.TryGetValue(kvp.Key, out var secondValue)) 25 | return false; 26 | if (!valueComparer.Equals(kvp.Value, secondValue)) 27 | return false; 28 | } 29 | return true; 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /TeamCoding.Shared/Extensions/EnumerableExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace TeamCoding.Extensions 8 | { 9 | public static class EnumerableExtensions 10 | { 11 | public static IEnumerable TakeWhileInclusive(this IEnumerable source, Func predicate) 12 | { // http://stackoverflow.com/a/3098714 13 | var foundMatch = false; 14 | foreach (T item in source) 15 | { 16 | if (predicate(item)) 17 | { 18 | foundMatch = true; 19 | yield return item; 20 | } 21 | else 22 | { 23 | if (foundMatch) 24 | { 25 | yield return item; 26 | } 27 | yield break; 28 | } 29 | } 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /TeamCoding.Shared/Extensions/ITextBufferExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.Text; 2 | using Microsoft.VisualStudio.Text.Projection; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using System.Linq; 6 | using TeamCoding.Documents; 7 | 8 | namespace TeamCoding.Extensions 9 | { 10 | public static class TextBufferExtensions 11 | { 12 | public static string GetTextDocumentFilePath(this ITextBuffer textBuffer) 13 | { 14 | return GetTextDocumentFilePaths(textBuffer).FirstOrDefault(); 15 | } 16 | public static IEnumerable GetTextDocumentFilePaths(this ITextBuffer textBuffer) 17 | { 18 | if (textBuffer.Properties.TryGetProperty(typeof(ITextDocument), out ITextDocument textDoc)) 19 | { 20 | return new[] { DocumentPaths.GetFullPath(textDoc.FilePath) }; 21 | } 22 | else if (textBuffer is IProjectionBufferBase ProjBuffer && ProjBuffer.SourceBuffers.Count != 0) 23 | { 24 | return GetTextDocumentFilePaths(ProjBuffer.SourceBuffers); 25 | } 26 | else 27 | { 28 | return Enumerable.Empty(); 29 | } 30 | } 31 | public static IEnumerable GetTextDocumentFilePaths(IEnumerable textBuffers) 32 | { 33 | return textBuffers.Select(b => GetTextDocumentFilePath(b)).Distinct(); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /TeamCoding.Shared/Extensions/StreamExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace TeamCoding.Extensions 9 | { 10 | public static class StreamExtensions 11 | { 12 | public static string ReadAlltext(this Stream stream) 13 | { 14 | using(var sr = new StreamReader(stream)) 15 | { 16 | return sr.ReadToEnd(); 17 | } 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /TeamCoding.Shared/Extensions/StringExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Security.Cryptography; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace TeamCoding.Interfaces.Extensions 9 | { 10 | public static class StringExtensions 11 | { 12 | public static int ToIntegerCode(this string str, bool ignoreCase = false) 13 | { 14 | using (var md5provider = new MD5CryptoServiceProvider()) 15 | { 16 | if (ignoreCase) 17 | str = str.ToLowerInvariant(); 18 | 19 | var bytes = md5provider.ComputeHash(new UTF8Encoding().GetBytes(str)); 20 | var integer = BitConverter.ToInt32(bytes, 0); 21 | return integer; 22 | } 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /TeamCoding.Shared/ITeamCodingPackageProvider.cs: -------------------------------------------------------------------------------- 1 | using System.Net.Http; 2 | using TeamCoding.Documents.SourceControlRepositories; 3 | using TeamCoding.IdentityManagement; 4 | using TeamCoding.Interfaces.Documents; 5 | using TeamCoding.Logging; 6 | using TeamCoding.VisualStudio; 7 | using TeamCoding.VisualStudio.Models; 8 | using TeamCoding.VisualStudio.Models.ChangePersisters; 9 | 10 | namespace TeamCoding 11 | { 12 | public interface ITeamCodingPackageProvider 13 | { 14 | ICaretAdornmentDataProvider CaretAdornmentDataProvider { get; } 15 | ICaretInfoProvider CaretInfoProvider { get; } 16 | ISourceControlRepository SourceControlRepository { get; } 17 | HttpClient HttpClient { get; } 18 | IRemoteModelPersister RemoteModelChangeManager { get; } 19 | ILogger Logger { get; } 20 | } 21 | } -------------------------------------------------------------------------------- /TeamCoding.Shared/IdentityManagement/IUserIdentity.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using System.Windows.Media; 7 | 8 | namespace TeamCoding.IdentityManagement 9 | { 10 | public interface IUserIdentity 11 | { 12 | string Id { get; set; } 13 | string ImageUrl { get; set; } 14 | string DisplayName { get; set; } 15 | byte[] ImageBytes { get; set; } 16 | Color GetUserColour(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /TeamCoding.Shared/IntArrayEqualityComparer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace TeamCoding 8 | { 9 | public class IntArrayEqualityComparer : IEqualityComparer 10 | { 11 | public bool Equals(int[] x, int[] y) 12 | { 13 | if (x.Length != y.Length) 14 | { 15 | return false; 16 | } 17 | for (int i = 0; i < x.Length; i++) 18 | { 19 | if (x[i] != y[i]) 20 | { 21 | return false; 22 | } 23 | } 24 | return true; 25 | } 26 | 27 | public int GetHashCode(int[] obj) 28 | { 29 | int result = 17; 30 | for (int i = 0; i < obj.Length; i++) 31 | { 32 | unchecked 33 | { 34 | result = result * 23 + obj[i]; 35 | } 36 | } 37 | return result; 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /TeamCoding.Shared/Key.snk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/georgeduckett/TeamCoding/5ad8f1d221730348662c8c11f3fb57a1ce79e642/TeamCoding.Shared/Key.snk -------------------------------------------------------------------------------- /TeamCoding.Shared/Logging/ILogger.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.CompilerServices; 3 | 4 | namespace TeamCoding.Logging 5 | { 6 | public interface ILogger 7 | { 8 | void WriteError(string error, Exception ex = null, [CallerMemberName] string filePath = null, [CallerMemberName] string memberName = null); 9 | void WriteError(Exception ex, [CallerMemberName] string filePath = null, [CallerMemberName] string memberName = null); 10 | void WriteInformation(string info, [CallerMemberName] string filePath = null, [CallerMemberName] string memberName = null); 11 | } 12 | } -------------------------------------------------------------------------------- /TeamCoding.Shared/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("TeamCoding.Interfaces")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("TeamCoding.Interfaces")] 13 | [assembly: AssemblyCopyright("Copyright © 2016")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("a72eb923-18eb-42a9-938e-533cfc5b15ef")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /TeamCoding.Shared/TeamCodingProjectTypeProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace TeamCoding 8 | { 9 | /// 10 | /// Provides singleton instances of concrete types from the TeamCoding project implementing specified interfaces. 11 | /// If the instance was created using MEF or through something else then it won't re-use that. 12 | /// 13 | public static class TeamCodingProjectTypeProvider 14 | { 15 | private static readonly Dictionary CachedObjectInstances = new Dictionary(); 16 | public static T Get() 17 | { 18 | if (CachedObjectInstances.TryGetValue(typeof(T), out object o)) 19 | { 20 | return (T)o; 21 | } 22 | 23 | o = Activator.CreateInstance(AppDomain.CurrentDomain.GetAssemblies() 24 | .Single(a => a.GetName().Name == "TeamCoding") 25 | .ExportedTypes.Single(t => typeof(T).IsAssignableFrom(t))); 26 | 27 | CachedObjectInstances.Add(typeof(T), o); 28 | 29 | return (T)o; 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /TeamCoding.Shared/VisualStudio/Models/ChangePersisters/IRemoteModelPersister.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using TeamCoding.Documents; 7 | 8 | namespace TeamCoding.VisualStudio.Models.ChangePersisters 9 | { 10 | /// 11 | /// Manages receiving remote IDE model changes. 12 | /// 13 | public interface IRemoteModelPersister : IDisposable 14 | { 15 | event EventHandler RemoteModelReceived; 16 | IEnumerable GetOpenFiles(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /TeamCoding.Shared/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /TeamCoding.WindowsService/Install.ps1: -------------------------------------------------------------------------------- 1 | If (-NOT ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")) 2 | 3 | { 4 | $arguments = "& '" + $myinvocation.mycommand.definition + "'" 5 | Start-Process powershell -Verb runAs -ArgumentList $arguments 6 | Break 7 | } 8 | 9 | function is64bit() { 10 | return ([IntPtr]::Size -eq 8) 11 | } 12 | function get-programfilesdir() { 13 | if (is64bit -eq $true) { 14 | (Get-Item "Env:ProgramFiles(x86)").Value 15 | } 16 | else { 17 | (Get-Item "Env:ProgramFiles").Value 18 | } 19 | } 20 | Try 21 | { 22 | $dir = "$(get-programfilesdir)\TeamCoding Sync\" 23 | $scriptPath = split-path -parent $MyInvocation.MyCommand.Definition 24 | New-Item $dir -type directory -Force 25 | Copy-Item "$scriptPath\*" $dir -Force 26 | Start-Process "$($dir)TeamCoding.WindowsService.exe" -ArgumentList "\i" -Verb runAs -wait 27 | } 28 | Catch 29 | { 30 | echo $_.Exception|format-list -force 31 | Write-Host -NoNewLine 'An error occured (press a key to exit)'; 32 | $null = $Host.UI.RawUI.ReadKey('NoEcho,IncludeKeyDown'); 33 | } -------------------------------------------------------------------------------- /TeamCoding.WindowsService/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("TeamCoding.WindowsService")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("TeamCoding.WindowsService")] 13 | [assembly: AssemblyCopyright("Copyright © 2016")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("7773a028-f409-4611-88a3-2cca4d57e8db")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /TeamCoding.WindowsService/RunAsConsole.bat: -------------------------------------------------------------------------------- 1 | start TeamCoding.WindowsService "\c" -------------------------------------------------------------------------------- /TeamCoding.WindowsService/TeamCodingSyncServer.Designer.cs: -------------------------------------------------------------------------------- 1 | namespace TeamCoding.WindowsService 2 | { 3 | partial class TeamCodingSyncServer 4 | { 5 | /// 6 | /// Required designer variable. 7 | /// 8 | private System.ComponentModel.IContainer components = null; 9 | 10 | /// 11 | /// Clean up any resources being used. 12 | /// 13 | /// true if managed resources should be disposed; otherwise, false. 14 | protected override void Dispose(bool disposing) 15 | { 16 | if (disposing && (components != null)) 17 | { 18 | components.Dispose(); 19 | } 20 | base.Dispose(disposing); 21 | } 22 | 23 | #region Component Designer generated code 24 | 25 | /// 26 | /// Required method for Designer support - do not modify 27 | /// the contents of this method with the code editor. 28 | /// 29 | private void InitializeComponent() 30 | { 31 | // 32 | // TeamCodingSyncServer 33 | // 34 | this.ServiceName = "TeamCoding Sync"; 35 | 36 | } 37 | 38 | #endregion 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /TeamCoding.WindowsService/TeamCodingSyncServer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using System.Data; 5 | using System.Diagnostics; 6 | using System.Linq; 7 | using System.ServiceProcess; 8 | using System.Text; 9 | using System.Threading.Tasks; 10 | 11 | namespace TeamCoding.WindowsService 12 | { 13 | public partial class TeamCodingSyncServer : ServiceBase 14 | { 15 | public const string SyncServerServiceName = "TeamCoding Sync"; 16 | private const int DefaultPort = 23023; 17 | private Multicaster Multicaster; 18 | private int Port; 19 | public TeamCodingSyncServer() 20 | { 21 | ServiceName = SyncServerServiceName; 22 | CanPauseAndContinue = CanShutdown = CanHandlePowerEvent = CanStop = true; 23 | InitializeComponent(); 24 | } 25 | protected override void OnStart(string[] args) 26 | { 27 | if (args.Length == 0 || !int.TryParse(args[0], out Port)) 28 | { 29 | // TODO: Also read from a config file 30 | Port = DefaultPort; 31 | } 32 | 33 | CreateMulticaster(); 34 | } 35 | protected override bool OnPowerEvent(PowerBroadcastStatus powerStatus) 36 | { 37 | switch (powerStatus) 38 | { 39 | case PowerBroadcastStatus.Suspend: 40 | case PowerBroadcastStatus.QuerySuspend: DisposeMulticaster();break; 41 | case PowerBroadcastStatus.QuerySuspendFailed: 42 | case PowerBroadcastStatus.ResumeAutomatic: 43 | case PowerBroadcastStatus.ResumeCritical: 44 | case PowerBroadcastStatus.ResumeSuspend: CreateMulticaster();break; 45 | } 46 | return true; 47 | } 48 | protected override void OnShutdown() 49 | { 50 | DisposeMulticaster(); 51 | } 52 | protected override void OnContinue() 53 | { 54 | CreateMulticaster(); 55 | } 56 | protected override void OnPause() 57 | { 58 | DisposeMulticaster(); 59 | } 60 | protected override void OnStop() 61 | { 62 | DisposeMulticaster(); 63 | } 64 | private void CreateMulticaster() 65 | { 66 | if (Multicaster == null) 67 | { 68 | Multicaster = new Multicaster(Port); 69 | } 70 | } 71 | private void DisposeMulticaster() 72 | { 73 | if (Multicaster != null) 74 | { 75 | Multicaster.Dispose(); 76 | Multicaster = null; 77 | } 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /TeamCoding.WindowsService/TeamCodingSyncServerInstaller.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using System.Configuration.Install; 5 | using System.Linq; 6 | using System.ServiceProcess; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | 10 | namespace TeamCoding.WindowsService 11 | { 12 | [RunInstaller(true)] 13 | public class TeamCodingSyncServerInstaller : Installer 14 | { 15 | public TeamCodingSyncServerInstaller() 16 | { 17 | var processInstaller = new ServiceProcessInstaller(); 18 | var serviceInstaller = new ServiceInstaller(); 19 | serviceInstaller.AfterInstall += ServiceInstaller_AfterInstall; 20 | 21 | processInstaller.Account = ServiceAccount.NetworkService; 22 | 23 | serviceInstaller.StartType = ServiceStartMode.Automatic; 24 | serviceInstaller.Description = "TeamCoding Sync is the optional companion service to the TeamCoding Visual Studio extension."; 25 | 26 | serviceInstaller.DisplayName = TeamCodingSyncServer.SyncServerServiceName; 27 | serviceInstaller.ServiceName = TeamCodingSyncServer.SyncServerServiceName; 28 | Installers.Add(processInstaller); 29 | Installers.Add(serviceInstaller); 30 | } 31 | 32 | private void ServiceInstaller_AfterInstall(object sender, InstallEventArgs e) 33 | { 34 | using (ServiceController sc = new ServiceController((sender as ServiceInstaller).ServiceName)) 35 | { 36 | sc.Start(); 37 | } 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /TeamCoding.WindowsService/Uninstall.ps1: -------------------------------------------------------------------------------- 1 | If (-NOT ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")) 2 | 3 | { 4 | $arguments = "& '" + $myinvocation.mycommand.definition + "'" 5 | Start-Process powershell -Verb runAs -ArgumentList $arguments 6 | Break 7 | } 8 | 9 | function is64bit() { 10 | return ([IntPtr]::Size -eq 8) 11 | } 12 | function get-programfilesdir() { 13 | if (is64bit -eq $true) { 14 | (Get-Item "Env:ProgramFiles(x86)").Value 15 | } 16 | else { 17 | (Get-Item "Env:ProgramFiles").Value 18 | } 19 | } 20 | Try 21 | { 22 | $dir = "$(get-programfilesdir)\TeamCoding Sync\" 23 | Start-Process "$($dir)TeamCoding.WindowsService.exe" -ArgumentList "\u" -Verb runAs -wait 24 | Remove-Item $dir -Force -Recurse 25 | } 26 | Catch 27 | { 28 | echo $_.Exception|format-list -force 29 | Write-Host -NoNewLine 'An error occured (press a key to exit)'; 30 | $null = $Host.UI.RawUI.ReadKey('NoEcho,IncludeKeyDown'); 31 | } 32 | -------------------------------------------------------------------------------- /TeamCoding.WindowsService/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /TeamCoding.v14/Documents/CaretAdornmentDataProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Microsoft.VisualStudio.Text; 7 | using TeamCoding.Interfaces.Documents; 8 | using Microsoft.CodeAnalysis.Text; 9 | using TeamCoding.Extensions; 10 | using TeamCoding.Interfaces.Extensions; 11 | using Microsoft.CodeAnalysis; 12 | 13 | namespace TeamCoding.Documents 14 | { 15 | public class CaretAdornmentDataProvider : ICaretAdornmentDataProvider 16 | { 17 | public async Task> GetCaretAdornmentDataAsync(ITextSnapshot textSnapshot, int[] caretMemberHashcodes) 18 | { 19 | if (caretMemberHashcodes == null || caretMemberHashcodes.Length == 0) 20 | { 21 | return Enumerable.Empty(); 22 | } 23 | 24 | var document = textSnapshot.GetOpenDocumentInCurrentContextWithChanges(); 25 | if (document == null) 26 | { 27 | return GetTextCaretAdornmentData(textSnapshot, caretMemberHashcodes); 28 | } 29 | 30 | var syntaxTree = await document.GetSyntaxTreeAsync(); 31 | if (syntaxTree == null) 32 | { 33 | return GetTextCaretAdornmentData(textSnapshot, caretMemberHashcodes); 34 | } 35 | 36 | var rootNode = await syntaxTree.GetRootAsync(); 37 | return GetRoslynCaretAdornmentData(caretMemberHashcodes, rootNode); 38 | } 39 | private IEnumerable GetTextCaretAdornmentData(ITextSnapshot textSnapshot, int[] caretMemberHashcodes) 40 | { 41 | string snapshotText = textSnapshot.GetText(); 42 | if (caretMemberHashcodes.Length == 1) 43 | { 44 | return new[] { new CaretAdornmentData(true, 0, 0, snapshotText.Length) }; 45 | } 46 | else 47 | { 48 | return Enumerable.Empty(); 49 | } 50 | } 51 | private static IEnumerable GetRoslynCaretAdornmentData(int[] caretMemberHashcodes, SyntaxNode rootNode) 52 | { 53 | if (rootNode.GetValueBasedHashCode() != caretMemberHashcodes[0]) 54 | { 55 | return Enumerable.Empty(); 56 | } 57 | var nodes = new List() { rootNode }; 58 | var i = 1; 59 | while (nodes.Count != 0 && i < caretMemberHashcodes.Length) 60 | { 61 | var oldNodesCount = nodes.Count; 62 | for (int nodeIndex = 0; nodeIndex < oldNodesCount; nodeIndex++) 63 | { 64 | foreach (var childTokenOrNode in nodes[nodeIndex].ChildNodesAndTokens()) 65 | { 66 | if (childTokenOrNode.IsNode) 67 | { 68 | var childNode = childTokenOrNode.AsNode(); 69 | if (childNode.GetValueBasedHashCode() == caretMemberHashcodes[i]) 70 | { 71 | nodes.Add(childNode); 72 | } 73 | } 74 | } 75 | } 76 | nodes.RemoveRange(0, oldNodesCount); 77 | i++; 78 | } 79 | 80 | return nodes.Select(n => new CaretAdornmentData(false, n.FullSpan.Start + (n.HasLeadingTrivia ? n.GetLeadingTrivia().SelectMany(t => t.ToFullString()).TakeWhile(c => char.IsWhiteSpace(c)).Count() : 0), n.SpanStart, n.Span.End)); 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /TeamCoding.v14/Documents/CaretInfoProvider.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.CodeAnalysis; 2 | using Microsoft.CodeAnalysis.Text; 3 | using Microsoft.VisualStudio.Text; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | using TeamCoding.Documents; 10 | using TeamCoding.Documents.SourceControlRepositories; 11 | using TeamCoding.Extensions; 12 | using TeamCoding.Interfaces.Documents; 13 | using TeamCoding.Interfaces.Extensions; 14 | 15 | namespace TeamCoding.Documents 16 | { 17 | public class CaretInfoProvider : ICaretInfoProvider 18 | { 19 | private readonly ISourceControlRepository SourceControlRepository = TeamCodingProjectTypeProvider.Get().SourceControlRepository; 20 | public async Task GetCaretInfoAsync(SnapshotPoint snapshotPoint) 21 | { 22 | var document = snapshotPoint.Snapshot.GetOpenDocumentInCurrentContextWithChanges(); 23 | if (document == null) 24 | { 25 | return GetTextCaretInfo(snapshotPoint); 26 | } 27 | 28 | var syntaxRoot = await document.GetSyntaxRootAsync(); 29 | if (syntaxRoot == null) 30 | { 31 | return GetTextCaretInfo(snapshotPoint); 32 | } 33 | 34 | return GetRoslynCaretInfo(syntaxRoot, snapshotPoint); 35 | } 36 | private DocumentRepoMetaData.CaretInfo GetRoslynCaretInfo(SyntaxNode syntaxRoot, SnapshotPoint snapshotPoint) 37 | { 38 | var caretToken = syntaxRoot.FindToken(snapshotPoint); 39 | int[] memberHashCodes = null; 40 | IEnumerable memberNodes = null; 41 | 42 | var desiredLeafNode = caretToken.Parent.AncestorsAndSelf().FirstOrDefault(n => n.IsUniquelyIdentifiedNode()); 43 | 44 | switch (caretToken.Language) 45 | { 46 | case "C#": 47 | case "Visual Basic": 48 | memberNodes = caretToken.Parent.AncestorsAndSelf().Reverse().TakeWhileInclusive(n => n != desiredLeafNode).ToArray(); 49 | memberHashCodes = memberNodes.Select(n => n.GetValueBasedHashCode()).ToArray(); 50 | break; 51 | default: 52 | TeamCodingProjectTypeProvider.Get().Logger.WriteInformation($"Document with unsupported language found: {caretToken.Language}"); return null; 53 | } 54 | 55 | var lastNode = memberNodes.Last(); 56 | 57 | var caretLine = snapshotPoint.GetContainingLine(); 58 | var lastNodeLine = snapshotPoint.Snapshot.GetLineFromPosition(lastNode.Span.Start); 59 | 60 | int leafMemberLineOffset = caretLine.LineNumber - lastNodeLine.LineNumber; 61 | 62 | return new DocumentRepoMetaData.CaretInfo() 63 | { 64 | SyntaxNodeIds = memberHashCodes, 65 | LeafMemberLineOffset = leafMemberLineOffset, 66 | LeafMemberCaretOffset = snapshotPoint.Position - caretLine.Start 67 | }; 68 | } 69 | private DocumentRepoMetaData.CaretInfo GetTextCaretInfo(SnapshotPoint snapshotPoint) 70 | { 71 | ITextSnapshotLine textSnapshotLine = snapshotPoint.GetContainingLine(); 72 | var caretColumn = snapshotPoint.Position - textSnapshotLine.Start; 73 | 74 | var textSnapshotLineNumber = textSnapshotLine.LineNumber; 75 | 76 | var filePath = snapshotPoint.Snapshot.TextBuffer.GetTextDocumentFilePath(); 77 | 78 | if (filePath != null && SourceControlRepository.GetRepoDocInfo(filePath) != null) 79 | { 80 | var remoteFileText = SourceControlRepository.GetRemoteFileLines(filePath); 81 | 82 | if (remoteFileText != null) 83 | { 84 | var localFileText = snapshotPoint.Snapshot.GetText().Split(new[] { "\r\n" }, StringSplitOptions.None); 85 | 86 | textSnapshotLineNumber = LineNumberTranslator.GetLineNumber(localFileText, remoteFileText, textSnapshotLineNumber, FileNumberBasis.Server); 87 | } 88 | } 89 | 90 | return new DocumentRepoMetaData.CaretInfo() 91 | { 92 | SyntaxNodeIds = new int[1], // Dummy value 93 | LeafMemberLineOffset = textSnapshotLineNumber, 94 | LeafMemberCaretOffset = caretColumn 95 | }; 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /TeamCoding.v14/Documents/LineNumberTranslator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using TeamCoding.Documents.SourceControlRepositories; 7 | 8 | namespace TeamCoding.Documents 9 | { 10 | public static class LineNumberTranslator 11 | { 12 | public static int GetLineNumber(string[] local, string[] remote, int lineNumber, FileNumberBasis targetBasis) 13 | { 14 | var changes = Microsoft.TeamFoundation.Diff.DiffFinder.LcsDiff.Diff(remote, 15 | local, 16 | EqualityComparer.Default); 17 | 18 | var localLineNumber = 0; 19 | var serverLineNumber = 0; 20 | 21 | 22 | if (targetBasis == FileNumberBasis.Server) 23 | { 24 | foreach (var change in changes) 25 | { 26 | if (lineNumber < change.ModifiedStart) 27 | { 28 | break; 29 | } 30 | else if (lineNumber > change.ModifiedEnd) 31 | { 32 | localLineNumber = change.ModifiedEnd; 33 | serverLineNumber = change.OriginalEnd; 34 | } 35 | else 36 | { 37 | return change.OriginalStart; 38 | } 39 | } 40 | 41 | return lineNumber + (serverLineNumber - localLineNumber); 42 | } 43 | else 44 | { 45 | foreach (var change in changes) 46 | { 47 | if (lineNumber < change.OriginalStart) 48 | { 49 | break; 50 | } 51 | else if (lineNumber > change.OriginalEnd) 52 | { 53 | localLineNumber = change.ModifiedEnd; 54 | serverLineNumber = change.OriginalEnd; 55 | } 56 | else 57 | { 58 | return change.ModifiedStart; 59 | } 60 | } 61 | 62 | return lineNumber + (localLineNumber - serverLineNumber); 63 | } 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /TeamCoding.v14/Extensions/ParameterListSyntaxExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace TeamCoding.Extensions 8 | { 9 | public static class ParameterListSyntaxExtensions 10 | { 11 | internal static string GetParameterTypesString(this Microsoft.CodeAnalysis.VisualBasic.Syntax.ParameterListSyntax typeParameters) 12 | { 13 | if (typeParameters?.Parameters.Any() ?? false) 14 | { 15 | return "(" + string.Join(", ", typeParameters?.Parameters.Select(p => p.AsClause?.Type?.ToString() ?? "Nothing")) + ")"; 16 | } 17 | return string.Empty; 18 | } 19 | internal static string GetParameterTypesString(this Microsoft.CodeAnalysis.CSharp.Syntax.ParameterListSyntax typeParameters) 20 | { 21 | if (typeParameters?.Parameters.Any() ?? false) 22 | { 23 | return "(" + string.Join(", ", typeParameters?.Parameters.Select(p => p.Type.ToString())) + ")"; 24 | } 25 | return string.Empty; 26 | } 27 | internal static string GetGenericParametersString(this Microsoft.CodeAnalysis.VisualBasic.Syntax.TypeParameterListSyntax typeParameters) 28 | { 29 | if (typeParameters?.Parameters.Any() ?? false) 30 | { 31 | return "<" + typeParameters?.Parameters.Count().ToString() + ">"; 32 | } 33 | return string.Empty; 34 | } 35 | internal static string GetGenericParametersString(this Microsoft.CodeAnalysis.CSharp.Syntax.TypeParameterListSyntax typeParameters) 36 | { 37 | if (typeParameters?.Parameters.Any() ?? false) 38 | { 39 | return "<" + typeParameters?.Parameters.Count().ToString() + ">"; 40 | } 41 | return string.Empty; 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /TeamCoding.v14/Extensions/SyntaxNodeExtensions.SyntaxNodeHashCache.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.CodeAnalysis; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace TeamCoding.Extensions 9 | { 10 | public static partial class SyntaxNodeExtensions 11 | { 12 | /// 13 | /// Caches syntax node hashes. Handles removing old syntax nodes with a file path of a compilation unit we're asked for the hash of 14 | /// 15 | private static class SyntaxNodeHashCache 16 | { 17 | private static readonly Dictionary _SyntaxNodeHashes = new Dictionary(); 18 | private static readonly MultiValueDictionary _SyntaxNodesFromFilePath = new MultiValueDictionary(); 19 | 20 | public static bool TryGetHash(SyntaxNode node, out int hash) => _SyntaxNodeHashes.TryGetValue(node, out hash); 21 | 22 | public static void Add(SyntaxNode syntaxNode, int identityHash) 23 | { 24 | var filePath = syntaxNode.SyntaxTree?.FilePath; 25 | if (syntaxNode is ICompilationUnitSyntax && filePath != null && _SyntaxNodesFromFilePath.ContainsKey(filePath)) 26 | { 27 | // If we've got a new compilation unit syntax then any syntax nodes from the same file won't get used so remove them. 28 | foreach (var node in _SyntaxNodesFromFilePath[filePath]) 29 | { 30 | _SyntaxNodeHashes.Remove(node); 31 | } 32 | _SyntaxNodesFromFilePath.Remove(filePath); 33 | } 34 | 35 | if (filePath != null) 36 | { 37 | _SyntaxNodesFromFilePath.Add(filePath, syntaxNode); 38 | } 39 | _SyntaxNodeHashes.Add(syntaxNode, identityHash); 40 | } 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /TeamCoding.v14/Key.snk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/georgeduckett/TeamCoding/5ad8f1d221730348662c8c11f3fb57a1ce79e642/TeamCoding.v14/Key.snk -------------------------------------------------------------------------------- /TeamCoding.v14/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("TeamCoding.v14")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("TeamCoding.v14")] 13 | [assembly: AssemblyCopyright("Copyright © 2016")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("1bc98758-87ae-488e-9b2f-4e06acc83646")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /TeamCoding.v14/VisualStudio/CodeLens/CurrentUsersDataPoint.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.Alm.Roslyn.Client.Features.WorkspaceUpdateManager; 2 | using Microsoft.VisualStudio.CodeSense; 3 | using Microsoft.VisualStudio.CodeSense.Roslyn; 4 | using Microsoft.VisualStudio.Language.Intellisense; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Linq; 8 | using System.Text; 9 | using System.Threading.Tasks; 10 | using TeamCoding.Extensions; 11 | 12 | namespace TeamCoding.VisualStudio.CodeLens 13 | { 14 | public class CurrentUsersDataPoint : DataPoint 15 | { 16 | private readonly IWorkspaceUpdateManager WorkspaceUpdateManager; 17 | private readonly Task WorkspaceChangedTask; 18 | private readonly CurrentUsersDataPointUpdater DataPointUpdater; 19 | private bool Disposed; 20 | private object DisposedLock = new object(); 21 | public readonly ICodeElementDescriptor CodeElementDescriptor; 22 | public CurrentUsersDataPoint(CurrentUsersDataPointUpdater dataPointUpdater, IWorkspaceUpdateManager workspaceUpdateManager, ICodeElementDescriptor codeElementDescriptor) 23 | { 24 | DataPointUpdater = dataPointUpdater; 25 | CodeElementDescriptor = codeElementDescriptor; 26 | WorkspaceUpdateManager = workspaceUpdateManager; 27 | WorkspaceChangedTask = WorkspaceUpdateManager?.AddWorkspaceChangedAsync(OnWorkspaceChanged); 28 | } 29 | public override Task GetDataAsync() 30 | { 31 | return DataPointUpdater.GetTextForDataPoint(CodeElementDescriptor); 32 | } 33 | private void OnWorkspaceChanged(object sender, WorkspaceChangesEventArgs e) 34 | { 35 | Task.Run(() => 36 | { 37 | object obj = DisposedLock; 38 | lock (obj) 39 | { 40 | if (!Disposed) 41 | { 42 | System.Threading.Thread.Sleep(2000); 43 | Invalidate(); 44 | } 45 | } 46 | }).FireAndForget(); 47 | } 48 | protected override void Dispose(bool disposing) 49 | { 50 | object obj = DisposedLock; 51 | if (!Disposed) 52 | { 53 | lock (obj) 54 | { 55 | if (!Disposed) 56 | { 57 | if (disposing) 58 | { 59 | WorkspaceUpdateManager?.RemoveWorkspaceChangedAsync(new EventHandler(OnWorkspaceChanged)).FireAndForget(); 60 | } 61 | Disposed = true; 62 | } 63 | } 64 | } 65 | base.Dispose(disposing); 66 | } 67 | } 68 | 69 | } -------------------------------------------------------------------------------- /TeamCoding.v14/VisualStudio/CodeLens/CurrentUsersDataPointProvider.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.Alm.Roslyn.Client.Features.WorkspaceUpdateManager; 2 | using Microsoft.VisualStudio.CodeSense.Roslyn; 3 | using Microsoft.VisualStudio.Language.Intellisense; 4 | using Microsoft.VisualStudio.Utilities; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.ComponentModel.Composition; 8 | using System.Linq; 9 | using System.Text; 10 | using System.Threading.Tasks; 11 | 12 | namespace TeamCoding.VisualStudio.CodeLens 13 | { 14 | [Export(typeof(ICodeLensDataPointProvider)), Name(CodeLensName)] 15 | public class CurrentUsersDataPointProvider : ICodeLensDataPointProvider 16 | { 17 | public const string CodeLensName = "Team Coding"; 18 | [Import] 19 | private readonly CurrentUsersDataPointUpdater DataPointUpdater = null; 20 | [Import] 21 | private readonly IWorkspaceUpdateManager WorkspaceUpdateManager = null; 22 | public bool CanCreateDataPoint(ICodeLensDescriptor descriptor) 23 | { 24 | return descriptor is ICodeElementDescriptor; 25 | } 26 | public ICodeLensDataPoint CreateDataPoint(ICodeLensDescriptor codeLensDescriptor) 27 | { 28 | return new CurrentUsersDataPoint(DataPointUpdater, WorkspaceUpdateManager, (ICodeElementDescriptor)codeLensDescriptor); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /TeamCoding.v14/VisualStudio/CodeLens/CurrentUsersDataPointViewModel.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.CodeSense.Editor; 2 | using Microsoft.VisualStudio.Language.Intellisense; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.ComponentModel; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | 10 | namespace TeamCoding.VisualStudio.CodeLens 11 | { 12 | public class CurrentUsersDataPointViewModel : GlyphDataPointViewModel 13 | { 14 | private readonly CurrentUsersDataPointUpdater DataPointUpdater; 15 | public override string AdditionalInformation => "Current users in this area"; 16 | public CurrentUsersDataPointViewModel(CurrentUsersDataPointUpdater dataPointUpdater, ICodeLensDataPoint dataPoint) : base(dataPoint) 17 | { 18 | DataPointUpdater = dataPointUpdater; 19 | dataPointUpdater.AddDataPointModel(this); 20 | HasDetails = false; 21 | PropertyChanged += CurrentUsersDataPointViewModel_PropertyChanged; 22 | } 23 | private void CurrentUsersDataPointViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e) 24 | { 25 | if (e.PropertyName == nameof(Data)) 26 | { 27 | Descriptor = (string)Data; 28 | } 29 | } 30 | protected override bool? HasDataCore(object dataValue) => (base.HasDataCore(dataValue) ?? false) && !string.IsNullOrEmpty(dataValue as string); 31 | protected override void Dispose(bool disposing) 32 | { 33 | if (disposing) 34 | { 35 | DataPointUpdater.RemoveDataPointModel(this); 36 | PropertyChanged -= CurrentUsersDataPointViewModel_PropertyChanged; 37 | } 38 | base.Dispose(disposing); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /TeamCoding.v14/VisualStudio/CodeLens/CurrentUsersDataPointViewModelProvider.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.CodeSense.Editor; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using Microsoft.VisualStudio.Language.Intellisense; 8 | using System.ComponentModel.Composition; 9 | 10 | namespace TeamCoding.VisualStudio.CodeLens 11 | { 12 | [DataPointViewModelProvider(typeof(CurrentUsersDataPoint))] 13 | public class CurrentUsersDataPointViewModelProvider : DataPointViewModelProvider 14 | { 15 | [Import] 16 | private readonly CurrentUsersDataPointUpdater DataPointUpdater = null; 17 | protected override CurrentUsersDataPointViewModel GetViewModel(ICodeLensDataPoint dataPoint) 18 | { 19 | var dataPointModel = new CurrentUsersDataPointViewModel(DataPointUpdater, dataPoint); 20 | return dataPointModel; 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /TeamCoding.v14/app.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 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /TeamCoding.v15/Key.snk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/georgeduckett/TeamCoding/5ad8f1d221730348662c8c11f3fb57a1ce79e642/TeamCoding.v15/Key.snk -------------------------------------------------------------------------------- /TeamCoding.v15/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("TeamCoding.v15")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("TeamCoding.v15")] 13 | [assembly: AssemblyCopyright("Copyright © 2016")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("5bcc781b-0c52-4e96-bc43-8b1e02bc442b")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /TeamCoding.v15/VisualStudio/CodeLens/CurrentUsersDataPoint.cs: -------------------------------------------------------------------------------- 1 | using IWorkspaceUpdateManager = Microsoft.VisualStudio.Alm.Roslyn.Client.IVisualStudioIntegrationService; 2 | using WorkspaceChangesEventArgs = Microsoft.CodeAnalysis.WorkspaceChangeEventArgs; 3 | using Microsoft.VisualStudio.CodeSense; 4 | using Microsoft.VisualStudio.CodeSense.Roslyn; 5 | using Microsoft.VisualStudio.Language.Intellisense; 6 | using System; 7 | using System.Collections.Generic; 8 | using System.Linq; 9 | using System.Text; 10 | using System.Threading.Tasks; 11 | using TeamCoding.Extensions; 12 | 13 | namespace TeamCoding.VisualStudio.CodeLens 14 | { 15 | public class CurrentUsersDataPointV15 : DataPoint 16 | { 17 | private readonly IWorkspaceUpdateManager WorkspaceUpdateManager; 18 | private readonly Task WorkspaceChangedTask; 19 | private readonly CurrentUsersDataPointV15Updater DataPointUpdater; 20 | private bool Disposed; 21 | private object DisposedLock = new object(); 22 | public readonly ICodeElementDescriptor CodeElementDescriptor; 23 | public CurrentUsersDataPointV15(CurrentUsersDataPointV15Updater dataPointUpdater, IWorkspaceUpdateManager workspaceUpdateManager, ICodeElementDescriptor codeElementDescriptor) 24 | { 25 | DataPointUpdater = dataPointUpdater; 26 | CodeElementDescriptor = codeElementDescriptor; 27 | WorkspaceUpdateManager = workspaceUpdateManager; 28 | WorkspaceChangedTask = WorkspaceUpdateManager?.AddWorkspaceChangedAsync(OnWorkspaceChanged); 29 | } 30 | public override Task GetDataAsync() 31 | { 32 | return DataPointUpdater.GetTextForDataPoint(CodeElementDescriptor); 33 | } 34 | private void OnWorkspaceChanged(object sender, WorkspaceChangesEventArgs e) 35 | { 36 | Task.Run(async () => 37 | { 38 | await Task.Delay(2000); 39 | lock (DisposedLock) 40 | { 41 | if (!Disposed) 42 | { 43 | Invalidate(); 44 | } 45 | } 46 | }).FireAndForget(); 47 | } 48 | protected override void Dispose(bool disposing) 49 | { 50 | if (!Disposed) 51 | { 52 | lock (DisposedLock) 53 | { 54 | if (!Disposed) 55 | { 56 | if (disposing) 57 | { 58 | WorkspaceUpdateManager?.RemoveWorkspaceChangedAsync(new EventHandler(OnWorkspaceChanged)).FireAndForget(); 59 | } 60 | Disposed = true; 61 | } 62 | } 63 | } 64 | base.Dispose(disposing); 65 | } 66 | } 67 | 68 | } -------------------------------------------------------------------------------- /TeamCoding.v15/VisualStudio/CodeLens/CurrentUsersDataPoint.tt: -------------------------------------------------------------------------------- 1 | <#@ template debug="false" hostspecific="true" language="C#" #> 2 | <#@ assembly name="System.Core" #> 3 | <#@ import namespace="System.Linq" #> 4 | <#@ import namespace="System.Text" #> 5 | <#@ import namespace="System.Collections.Generic" #> 6 | <#@ output extension=".cs" #> 7 | <#@ import namespace="System.IO" #> 8 | using IWorkspaceUpdateManager = Microsoft.VisualStudio.Alm.Roslyn.Client.IVisualStudioIntegrationService; 9 | using WorkspaceChangesEventArgs = Microsoft.CodeAnalysis.WorkspaceChangeEventArgs; 10 | <# 11 | string absolutePath = Host.ResolvePath("..\\..\\..\\TeamCoding.v14\\VisualStudio\\CodeLens\\" + Path.GetFileNameWithoutExtension(Host.TemplateFile) + ".cs"); 12 | 13 | string contents = File.ReadAllText(absolutePath).Replace("CurrentUsersDataPoint", "CurrentUsersDataPointV15") 14 | .Replace("CurrentUsersTemplateProvider", "CurrentUsersTemplateProviderV15") 15 | .Replace(@"using Microsoft.VisualStudio.Alm.Roslyn.Client.Features.WorkspaceUpdateManager; 16 | ", ""); 17 | // process file contents here, we'll simply dump it to the output here 18 | this.Write(contents); 19 | #> -------------------------------------------------------------------------------- /TeamCoding.v15/VisualStudio/CodeLens/CurrentUsersDataPointProvider.cs: -------------------------------------------------------------------------------- 1 | using IWorkspaceUpdateManager = Microsoft.VisualStudio.Alm.Roslyn.Client.IVisualStudioIntegrationService; 2 | using Microsoft.VisualStudio.CodeSense.Roslyn; 3 | using Microsoft.VisualStudio.Language.Intellisense; 4 | using Microsoft.VisualStudio.Utilities; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.ComponentModel.Composition; 8 | using System.Linq; 9 | using System.Text; 10 | using System.Threading.Tasks; 11 | 12 | namespace TeamCoding.VisualStudio.CodeLens 13 | { 14 | [Export(typeof(ICodeLensDataPointProvider)), Name(CodeLensName)] 15 | public class CurrentUsersDataPointV15Provider : ICodeLensDataPointProvider 16 | { 17 | public const string CodeLensName = "Team Coding"; 18 | [Import] 19 | private readonly CurrentUsersDataPointV15Updater DataPointUpdater = null; 20 | [Import] 21 | private readonly IWorkspaceUpdateManager WorkspaceUpdateManager = null; 22 | public bool CanCreateDataPoint(ICodeLensDescriptor descriptor) 23 | { 24 | return descriptor is ICodeElementDescriptor; 25 | } 26 | public ICodeLensDataPoint CreateDataPoint(ICodeLensDescriptor codeLensDescriptor) 27 | { 28 | return new CurrentUsersDataPointV15(DataPointUpdater, WorkspaceUpdateManager, (ICodeElementDescriptor)codeLensDescriptor); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /TeamCoding.v15/VisualStudio/CodeLens/CurrentUsersDataPointProvider.tt: -------------------------------------------------------------------------------- 1 | <#@ template debug="false" hostspecific="true" language="C#" #> 2 | <#@ assembly name="System.Core" #> 3 | <#@ import namespace="System.Linq" #> 4 | <#@ import namespace="System.Text" #> 5 | <#@ import namespace="System.Collections.Generic" #> 6 | <#@ output extension=".cs" #> 7 | <#@ import namespace="System.IO" #> 8 | using IWorkspaceUpdateManager = Microsoft.VisualStudio.Alm.Roslyn.Client.IVisualStudioIntegrationService; 9 | <# 10 | string absolutePath = Host.ResolvePath("..\\..\\..\\TeamCoding.v14\\VisualStudio\\CodeLens\\" + Path.GetFileNameWithoutExtension(Host.TemplateFile) + ".cs"); 11 | string contents = File.ReadAllText(absolutePath).Replace("CurrentUsersDataPoint", "CurrentUsersDataPointV15") 12 | .Replace("CurrentUsersTemplateProvider", "CurrentUsersTemplateProviderV15") 13 | .Replace(@"using Microsoft.VisualStudio.Alm.Roslyn.Client.Features.WorkspaceUpdateManager; 14 | ", ""); 15 | // process file contents here, we'll simply dump it to the output here 16 | this.Write(contents); 17 | #> -------------------------------------------------------------------------------- /TeamCoding.v15/VisualStudio/CodeLens/CurrentUsersDataPointUpdater.tt: -------------------------------------------------------------------------------- 1 | <#@ template debug="false" hostspecific="true" language="C#" #> 2 | <#@ assembly name="System.Core" #> 3 | <#@ import namespace="System.Linq" #> 4 | <#@ import namespace="System.Text" #> 5 | <#@ import namespace="System.Collections.Generic" #> 6 | <#@ output extension=".cs" #> 7 | <#@ import namespace="System.IO" #> 8 | <# 9 | string absolutePath = Host.ResolvePath("..\\..\\..\\TeamCoding.v14\\VisualStudio\\CodeLens\\" + Path.GetFileNameWithoutExtension(Host.TemplateFile) + ".cs"); 10 | string contents = File.ReadAllText(absolutePath).Replace("CurrentUsersDataPoint", "CurrentUsersDataPointV15") 11 | .Replace("CurrentUsersTemplateProvider", "CurrentUsersTemplateProviderV15"); 12 | // process file contents here, we'll simply dump it to the output here 13 | this.Write(contents); 14 | #> -------------------------------------------------------------------------------- /TeamCoding.v15/VisualStudio/CodeLens/CurrentUsersDataPointViewModel.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.CodeSense.Editor; 2 | using Microsoft.VisualStudio.Language.Intellisense; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.ComponentModel; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | 10 | namespace TeamCoding.VisualStudio.CodeLens 11 | { 12 | public class CurrentUsersDataPointV15ViewModel : GlyphDataPointViewModel 13 | { 14 | private readonly CurrentUsersDataPointV15Updater DataPointUpdater; 15 | public override string AdditionalInformation => "Current users in this area"; 16 | public CurrentUsersDataPointV15ViewModel(CurrentUsersDataPointV15Updater dataPointUpdater, ICodeLensDataPoint dataPoint) : base(dataPoint) 17 | { 18 | DataPointUpdater = dataPointUpdater; 19 | dataPointUpdater.AddDataPointModel(this); 20 | HasDetails = false; 21 | PropertyChanged += CurrentUsersDataPointV15ViewModel_PropertyChanged; 22 | } 23 | private void CurrentUsersDataPointV15ViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e) 24 | { 25 | if (e.PropertyName == nameof(Data)) 26 | { 27 | Descriptor = (string)Data; 28 | } 29 | } 30 | protected override bool? HasDataCore(object dataValue) => (base.HasDataCore(dataValue) ?? false) && !string.IsNullOrEmpty(dataValue as string); 31 | protected override void Dispose(bool disposing) 32 | { 33 | if (disposing) 34 | { 35 | DataPointUpdater.RemoveDataPointModel(this); 36 | PropertyChanged -= CurrentUsersDataPointV15ViewModel_PropertyChanged; 37 | } 38 | base.Dispose(disposing); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /TeamCoding.v15/VisualStudio/CodeLens/CurrentUsersDataPointViewModel.tt: -------------------------------------------------------------------------------- 1 | <#@ template debug="false" hostspecific="true" language="C#" #> 2 | <#@ assembly name="System.Core" #> 3 | <#@ import namespace="System.Linq" #> 4 | <#@ import namespace="System.Text" #> 5 | <#@ import namespace="System.Collections.Generic" #> 6 | <#@ output extension=".cs" #> 7 | <#@ import namespace="System.IO" #> 8 | <# 9 | string absolutePath = Host.ResolvePath("..\\..\\..\\TeamCoding.v14\\VisualStudio\\CodeLens\\" + Path.GetFileNameWithoutExtension(Host.TemplateFile) + ".cs"); 10 | string contents = File.ReadAllText(absolutePath).Replace("CurrentUsersDataPoint", "CurrentUsersDataPointV15") 11 | .Replace("CurrentUsersTemplateProvider", "CurrentUsersTemplateProviderV15"); 12 | // process file contents here, we'll simply dump it to the output here 13 | this.Write(contents); 14 | #> -------------------------------------------------------------------------------- /TeamCoding.v15/VisualStudio/CodeLens/CurrentUsersDataPointViewModelProvider.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.CodeSense.Editor; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using Microsoft.VisualStudio.Language.Intellisense; 8 | using System.ComponentModel.Composition; 9 | 10 | namespace TeamCoding.VisualStudio.CodeLens 11 | { 12 | [DataPointViewModelProvider(typeof(CurrentUsersDataPointV15))] 13 | public class CurrentUsersDataPointV15ViewModelProvider : DataPointViewModelProvider 14 | { 15 | [Import] 16 | private readonly CurrentUsersDataPointV15Updater DataPointUpdater = null; 17 | protected override CurrentUsersDataPointV15ViewModel GetViewModel(ICodeLensDataPoint dataPoint) 18 | { 19 | var dataPointModel = new CurrentUsersDataPointV15ViewModel(DataPointUpdater, dataPoint); 20 | return dataPointModel; 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /TeamCoding.v15/VisualStudio/CodeLens/CurrentUsersDataPointViewModelProvider.tt: -------------------------------------------------------------------------------- 1 | <#@ template debug="false" hostspecific="true" language="C#" #> 2 | <#@ assembly name="System.Core" #> 3 | <#@ import namespace="System.Linq" #> 4 | <#@ import namespace="System.Text" #> 5 | <#@ import namespace="System.Collections.Generic" #> 6 | <#@ output extension=".cs" #> 7 | <#@ import namespace="System.IO" #> 8 | <# 9 | string absolutePath = Host.ResolvePath("..\\..\\..\\TeamCoding.v14\\VisualStudio\\CodeLens\\" + Path.GetFileNameWithoutExtension(Host.TemplateFile) + ".cs"); 10 | string contents = File.ReadAllText(absolutePath).Replace("CurrentUsersDataPoint", "CurrentUsersDataPointV15") 11 | .Replace("CurrentUsersTemplateProvider", "CurrentUsersTemplateProviderV15"); 12 | // process file contents here, we'll simply dump it to the output here 13 | this.Write(contents); 14 | #> -------------------------------------------------------------------------------- /TeamCoding.v15/VisualStudio/CodeLens/VisualStudioIntegrationServiceExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.CodeAnalysis; 2 | using Microsoft.VisualStudio.Alm.Roslyn.Client; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace TeamCoding.VisualStudio.CodeLens 10 | { 11 | public static class VisualStudioIntegrationServiceExtensions 12 | { 13 | public static Task AddWorkspaceChangedAsync(this IVisualStudioIntegrationService service, EventHandler eventHandler) 14 | { 15 | return Task.Run(() => service.AddWorkspaceChanged(eventHandler)); 16 | } 17 | public static Task RemoveWorkspaceChangedAsync(this IVisualStudioIntegrationService service, EventHandler eventHandler) 18 | { 19 | return Task.Run(() => service.RemoveWorkspaceChanged(eventHandler)); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /TeamCoding/CredentialManagement/WindowsCredential.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Security.Permissions; 3 | 4 | namespace TeamCoding.CredentialManagement 5 | { 6 | public class Credential 7 | { 8 | public enum CredentialType : uint 9 | { 10 | None = 0, 11 | Generic = 1, 12 | DomainPassword = 2, 13 | DomainCertificate = 3, 14 | DomainVisiblePassword = 4 15 | } 16 | private static object LockObject = new object(); 17 | private static SecurityPermission UnmanagedCodePermission; 18 | public string Target { get; set; } 19 | public string Username { get; set; } 20 | public string Description { get; set; } 21 | static Credential() 22 | { 23 | lock (LockObject) 24 | { 25 | UnmanagedCodePermission = new SecurityPermission(SecurityPermissionFlag.UnmanagedCode); 26 | } 27 | } 28 | public bool Load() 29 | { 30 | UnmanagedCodePermission.Demand(); 31 | bool result = NativeMethods.CredRead(Target, CredentialType.Generic, 0, out var credPointer); 32 | if (!result) 33 | { 34 | return false; 35 | } 36 | using (NativeMethods.CriticalCredentialHandle credentialHandle = new NativeMethods.CriticalCredentialHandle(credPointer)) 37 | { 38 | LoadInternal(credentialHandle.GetCredential()); 39 | } 40 | return true; 41 | } 42 | internal void LoadInternal(NativeMethods.CREDENTIAL credential) 43 | { 44 | Username = credential.UserName; 45 | Target = credential.TargetName; 46 | Description = credential.Comment; 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /TeamCoding/Documents/RemotelyAccessedDocumentData.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using TeamCoding.IdentityManagement; 3 | 4 | namespace TeamCoding.Documents 5 | { 6 | /// 7 | /// Contains data about a document belonging to source control that's open by a user 8 | /// 9 | public class RemotelyAccessedDocumentData : IEquatable, IRemotelyAccessedDocumentData 10 | { 11 | public string Repository { get; set; } 12 | public string RepositoryBranch { get; set; } 13 | public string RelativePath { get; set; } 14 | public IUserIdentity IdeUserIdentity { get; set; } 15 | public bool BeingEdited { get; set; } 16 | public bool HasFocus { get; set; } 17 | public DocumentRepoMetaData.CaretInfo CaretPositionInfo { get; set; } 18 | public override int GetHashCode() 19 | { 20 | var hash = 17; 21 | hash = hash * 31 + Repository.GetHashCode(); 22 | 23 | if (!string.IsNullOrEmpty(RepositoryBranch)) 24 | hash = hash * 31 + RepositoryBranch.GetHashCode(); 25 | 26 | hash = hash * 31 + IdeUserIdentity.Id.GetHashCode(); 27 | hash = hash * 31 + BeingEdited.GetHashCode(); 28 | hash = hash * 31 + HasFocus.GetHashCode(); 29 | hash = hash * 31 + (CaretPositionInfo?.LeafMemberCaretOffset.GetHashCode() ?? 0); 30 | hash = hash * 31 + (CaretPositionInfo?.LeafMemberLineOffset.GetHashCode() ?? 0); 31 | hash = hash * 31 + (CaretPositionInfo?.SyntaxNodeIds.GetHashCode() ?? 0); 32 | 33 | return hash; 34 | } 35 | public bool Equals(IRemotelyAccessedDocumentData other) 36 | { 37 | if (other == null) 38 | return false; 39 | 40 | return Repository == other.Repository && 41 | RepositoryBranch == other.RepositoryBranch && 42 | RelativePath == other.RelativePath && 43 | IdeUserIdentity.Id == other.IdeUserIdentity.Id && 44 | BeingEdited == other.BeingEdited && 45 | HasFocus == other.HasFocus && 46 | CaretPositionInfo?.LeafMemberCaretOffset == other.CaretPositionInfo?.LeafMemberCaretOffset && 47 | CaretPositionInfo?.LeafMemberLineOffset == other.CaretPositionInfo?.LeafMemberLineOffset && 48 | CaretPositionInfo?.SyntaxNodeIds == other.CaretPositionInfo?.SyntaxNodeIds; 49 | } 50 | public override bool Equals(object obj) 51 | { 52 | var typedObj = obj as IRemotelyAccessedDocumentData; 53 | return Equals(typedObj); 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /TeamCoding/Documents/SourceControlRepositories/CachedSourceControlRepository.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace TeamCoding.Documents.SourceControlRepositories 8 | { 9 | public class CachedSourceControlRepository : ISourceControlRepository // TODO: Catch source-control events like undoing pending changes 10 | { 11 | private readonly ISourceControlRepository[] Repositories; 12 | public CachedSourceControlRepository(params ISourceControlRepository[] repositories) 13 | { 14 | Repositories = repositories; 15 | } 16 | protected readonly Dictionary RepoData = new Dictionary(); 17 | public void RemoveCachedRepoData(string docFilePath) => RepoData.Remove(docFilePath); 18 | public DocumentRepoMetaData GetRepoDocInfo(string fullFilePath) 19 | { 20 | if (string.IsNullOrEmpty(fullFilePath)) 21 | return null; 22 | 23 | if (RepoData.ContainsKey(fullFilePath)) 24 | { 25 | var fileRepoData = RepoData[fullFilePath]; 26 | 27 | fileRepoData.LastActioned = DateTime.UtcNow; 28 | 29 | return fileRepoData; 30 | } 31 | 32 | foreach(var repository in Repositories) 33 | { 34 | var data = repository.GetRepoDocInfo(fullFilePath); 35 | if(data != null) 36 | { 37 | RepoData.Add(fullFilePath, data); 38 | return data; 39 | } 40 | } 41 | 42 | return null; 43 | } 44 | public string[] GetRemoteFileLines(string fullFilePath) 45 | { 46 | if (string.IsNullOrEmpty(fullFilePath)) 47 | return null; 48 | 49 | foreach (var repository in Repositories) 50 | { 51 | var data = repository.GetRemoteFileLines(fullFilePath); 52 | if (data != null) 53 | { 54 | return data; 55 | } 56 | } 57 | 58 | return null; 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /TeamCoding/Documents/SourceControlRepositories/GitRepository.cs: -------------------------------------------------------------------------------- 1 | using LibGit2Sharp; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using System.Linq; 6 | using TeamCoding.VisualStudio; 7 | 8 | namespace TeamCoding.Documents.SourceControlRepositories 9 | { 10 | /// 11 | /// Provides methods to get information about a file in a Git repository 12 | /// 13 | public class GitRepository : ISourceControlRepository 14 | { 15 | private string GetRepoPath(string fullFilePath) 16 | { 17 | var repoPath = Repository.Discover(fullFilePath); 18 | 19 | if (repoPath == null) return null; // No repository for file 20 | 21 | return fullFilePath.Substring(new DirectoryInfo(repoPath).Parent.FullName.Length).TrimStart('\\'); 22 | } 23 | public DocumentRepoMetaData GetRepoDocInfo(string fullFilePath) 24 | { 25 | var relativePath = GetRepoPath(fullFilePath); 26 | 27 | // It's ok to return null here since calling methods will handle it and it allows us to not have some global "is this a repository setting" 28 | // Another reason it's better to do it this way is there's no "before loading a solution" event, meaning lots of listeners get the IsEnabled setting change too late (after docs are loaded) 29 | if (relativePath == null) return null; 30 | 31 | var repo = new Repository(Repository.Discover(fullFilePath)); 32 | 33 | if (repo.Ignore.IsPathIgnored(relativePath)) return null; 34 | 35 | var repoHeadTree = repo.Head.Tip.Tree; 36 | var remoteMasterTree = repo.Head.TrackedBranch?.Tip?.Tree; 37 | 38 | // Check for local changes, then server changes. 39 | // It's possible there is a local change that actually makes it the same as the remote, but I think that's ok to say the user is editing anyway 40 | var isEdited = repo.Diff.Compare(new[] { fullFilePath }).Any() || 41 | (remoteMasterTree != null && repo.Diff.Compare(remoteMasterTree, repoHeadTree, new[] { fullFilePath }).Any()); 42 | 43 | return new DocumentRepoMetaData() 44 | { 45 | RepoProvider = nameof(GitRepository), 46 | RepoUrl = repo.Head.TrackedBranch?.Remote?.Url ?? repo.Branches.Single(b => b.IsTracking).TrackedBranch.Remote.Url, 47 | RepoBranch = repo.Head.TrackedBranch?.CanonicalName ?? repo.Head.CanonicalName, 48 | RelativePath = relativePath, 49 | BeingEdited = isEdited, 50 | LastActioned = DateTime.UtcNow 51 | }; 52 | } 53 | public string[] GetRemoteFileLines(string fullFilePath) 54 | { 55 | var relativePath = GetRepoPath(fullFilePath); 56 | 57 | if (relativePath == null) return null; 58 | 59 | var repo = new Repository(Repository.Discover(fullFilePath)); 60 | 61 | if (repo.Ignore.IsPathIgnored(relativePath)) return null; 62 | 63 | if (repo.Head.TrackedBranch == null) return null; 64 | 65 | var remoteFileBlob = repo.Head.TrackedBranch.Tip[relativePath].Target; 66 | return ((Blob)remoteFileBlob).GetContentText().Split('\n'); 67 | } 68 | } 69 | } -------------------------------------------------------------------------------- /TeamCoding/Documents/SourceControlRepositories/SolutionNameBasedRepository.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using TeamCoding.VisualStudio; 6 | 7 | namespace TeamCoding.Documents.SourceControlRepositories 8 | { 9 | /// 10 | /// Provides methods to get information about a file based on just the solution name, treating each unique solution name as a repository. 11 | /// This could link unrelated solutions, but only 2 users were using the same sync method and working on different identically-named solutions. 12 | /// This generally won't be a problem unless I create some kind of global sync option that's enabled by default. 13 | /// This must be added last in a list of repositories as it could match anything. 14 | /// 15 | public class SolutionNameBasedRepository : ISourceControlRepository 16 | { 17 | private string GetRepoPath(string fullFilePath) 18 | { 19 | var repoPath = Path.GetDirectoryName(TeamCodingPackage.Current.IDEWrapper.SolutionFilePath); 20 | 21 | return fullFilePath.Substring(new DirectoryInfo(repoPath).Parent.FullName.Length).TrimStart('\\'); 22 | } 23 | public DocumentRepoMetaData GetRepoDocInfo(string fullFilePath) 24 | { 25 | //Ensure it's not another repo that's providing information for this solution's sln file (if it is then don't fall back to this as ignored files etc. would get covered by this 26 | if(fullFilePath != TeamCodingPackage.Current.IDEWrapper.SolutionFilePath && 27 | TeamCodingPackage.Current.SourceControlRepo.GetRepoDocInfo(TeamCodingPackage.Current.IDEWrapper.SolutionFilePath).RepoProvider != nameof(SolutionNameBasedRepository)) 28 | { 29 | return null; 30 | } 31 | 32 | var relativePath = GetRepoPath(fullFilePath); 33 | 34 | if (relativePath == null) return null; 35 | 36 | return new DocumentRepoMetaData() 37 | { 38 | RepoProvider = nameof(SolutionNameBasedRepository), 39 | RepoUrl = Path.GetFileNameWithoutExtension(TeamCodingPackage.Current.IDEWrapper.SolutionFilePath), 40 | RelativePath = relativePath, 41 | LastActioned = DateTime.UtcNow 42 | }; 43 | } 44 | public bool CanProvideDataForSolution(string solutionFilePath) 45 | { 46 | return true; 47 | } 48 | 49 | public int? GetLineNumber(string fullFilePath, int fileLineNumber, FileNumberBasis targetBasis) 50 | { 51 | if (fullFilePath == null) 52 | { 53 | return null; 54 | } 55 | else 56 | { 57 | return fileLineNumber; 58 | } 59 | } 60 | public string[] GetRemoteFileLines(string fullFilePath) 61 | { 62 | return File.ReadAllText(fullFilePath).Split(new[] { Environment.NewLine }, StringSplitOptions.None); 63 | } 64 | } 65 | } -------------------------------------------------------------------------------- /TeamCoding/Events/CombinedEvent.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace TeamCoding.Events 8 | { 9 | public class CombinedEvent 10 | { 11 | public event EventHandler Event; 12 | public CombinedEvent(params EventHandler[] events) 13 | { 14 | for(int i=0;i 21 | { 22 | public CombinedEvent(EventHandler event1, EventHandler event2) 23 | { 24 | 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /TeamCoding/Events/DelayedEvent.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using System.Timers; 7 | 8 | namespace TeamCoding.Events 9 | { 10 | public class DelayedEvent 11 | { 12 | private readonly Timer Timer; 13 | private object LatestSender; 14 | private readonly object EventArgsLock = new object(); 15 | public event EventHandler Event; 16 | public event EventHandler PassthroughEvent; 17 | public DelayedEvent(int millisecondsDelay) 18 | { 19 | Timer = new Timer(millisecondsDelay) { Enabled = false, AutoReset = false }; 20 | Timer.Elapsed += Timer_Elapsed; 21 | } 22 | private void Timer_Elapsed(object sender, ElapsedEventArgs e) 23 | { 24 | lock (EventArgsLock) 25 | { 26 | Event?.Invoke(LatestSender, EventArgs.Empty); 27 | }; 28 | } 29 | public void Invoke(object sender, EventArgs e) 30 | { 31 | PassthroughEvent?.Invoke(sender, e); 32 | Timer.Stop(); 33 | lock (EventArgsLock) 34 | { 35 | LatestSender = sender; 36 | } 37 | Timer.Start(); 38 | } 39 | } 40 | public class DelayedEvent where TEventArgs : EventArgs 41 | { 42 | private readonly Timer Timer; 43 | private TEventArgs LatestEventArgs; 44 | private object LatestSender; 45 | private readonly object EventArgsLock = new object(); 46 | public event EventHandler Event; 47 | public event EventHandler PassthroughEvent; 48 | public DelayedEvent(int millisecondsDelay) 49 | { 50 | Timer = new Timer(millisecondsDelay) { Enabled = false, AutoReset = false }; 51 | Timer.Elapsed += Timer_Elapsed; 52 | } 53 | private void Timer_Elapsed(object sender, ElapsedEventArgs e) 54 | { 55 | lock (EventArgsLock) 56 | { 57 | Event?.Invoke(LatestSender, LatestEventArgs); 58 | }; 59 | } 60 | public void Invoke(object sender, TEventArgs e) 61 | { 62 | PassthroughEvent?.Invoke(sender, e); 63 | Timer.Stop(); 64 | lock (EventArgsLock) 65 | { 66 | LatestSender = sender; 67 | LatestEventArgs = e; 68 | } 69 | Timer.Start(); 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /TeamCoding/Extensions/DTEExtensions.cs: -------------------------------------------------------------------------------- 1 | using EnvDTE; 2 | using TeamCoding.Documents; 3 | 4 | namespace TeamCoding.Extensions 5 | { 6 | public static class DTEExtensions 7 | { 8 | public static string GetWindowsFilePath(this Window window) 9 | { 10 | return window?.Document?.GetWindowsFilePath(); 11 | } 12 | public static string GetWindowsFilePath(this Document document) 13 | { 14 | return DocumentPaths.GetCorrectCase(document.FullName); 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /TeamCoding/Extensions/DocumentViewExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.Platform.WindowManagement; 2 | using TeamCoding.Documents; 3 | 4 | namespace TeamCoding.Extensions 5 | { 6 | public static class DocumentViewExtensions 7 | { 8 | public static string GetRelatedFilePath(this DocumentView documentView) 9 | { 10 | var firstPipe = documentView.Name.IndexOf('|'); 11 | var secondPipe = documentView.Name.IndexOf('|', firstPipe + 1); 12 | var thirdPipe = documentView.Name.IndexOf('|', secondPipe + 1); 13 | var fileName = documentView.Name.Substring(secondPipe + 1, thirdPipe - secondPipe - 1); 14 | return DocumentPaths.GetCorrectCase(fileName); 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /TeamCoding/Extensions/FrameworkElementExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Windows; 2 | 3 | namespace TeamCoding.Extensions 4 | { 5 | public static class FrameworkElementExtensions 6 | { 7 | public static void Remove(this FrameworkElement element) 8 | { 9 | element.Parent.RemoveChild(element); 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /TeamCoding/Extensions/IDBConnectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Data; 4 | using System.Linq; 5 | using System.Runtime.CompilerServices; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace TeamCoding.Extensions 10 | { 11 | public static class IDBConnectionExtensions 12 | { 13 | public static int ExecuteWithLogging(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = default(int?), CommandType? commandType = default(CommandType?), [CallerFilePath] string filePath = null, [CallerMemberName] string memberName = null) 14 | { 15 | if(cnn == null) 16 | { 17 | TeamCodingPackage.Current.Logger.WriteError("Tried to run sql with no connection", null, filePath, memberName); 18 | return -1; 19 | } 20 | try 21 | { 22 | return Dapper.SqlMapper.Execute(cnn, sql, param, transaction, commandTimeout, commandType); 23 | } 24 | catch(Exception ex) 25 | { 26 | TeamCodingPackage.Current.Logger.WriteError(ex, filePath, memberName); 27 | return -1; 28 | } 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /TeamCoding/Extensions/StreamExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace TeamCoding.Extensions 9 | { 10 | public static class StreamExtensions 11 | { 12 | public static string ReadAlltext(this Stream stream) 13 | { 14 | using(var sr = new StreamReader(stream)) 15 | { 16 | return sr.ReadToEnd(); 17 | } 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /TeamCoding/Extensions/TaskExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace TeamCoding.Extensions 8 | { 9 | public static class TaskExtensions 10 | { 11 | private static void WriteTaskException(Task t) 12 | { 13 | if (t != null) 14 | { 15 | TeamCodingPackage.Current.Logger.WriteError(t.Exception); 16 | } 17 | } 18 | /// 19 | /// Handles any exception caused by the task 20 | /// 21 | /// The task to handle exceptions for 22 | /// What to do if there is an exception 23 | /// A task that upon completion will have waited for the task, and handled any exception 24 | public static async Task HandleExceptionAsync(this Task task, Action handleException) 25 | { 26 | try 27 | { 28 | await task; 29 | } 30 | catch (Exception ex) 31 | { 32 | handleException(ex); 33 | } 34 | } 35 | /// 36 | /// Handles any exception caused by the task, and returns default() 37 | /// 38 | /// The type of the result of the task 39 | /// The task to handle exceptions for 40 | /// What to do if there is an exception 41 | /// The result of the task if there's no exception, otherwise default() 42 | public static async Task HandleExceptionAsync(this Task task, Action handleException) 43 | { 44 | TResult result = default(TResult); 45 | try 46 | { 47 | result = await task; 48 | } 49 | catch(Exception ex) 50 | { 51 | handleException(ex); 52 | } 53 | return result; 54 | } 55 | public static Task HandleException(this Task task) 56 | { 57 | task.ContinueWith(WriteTaskException, TaskContinuationOptions.OnlyOnFaulted); 58 | return task; 59 | } 60 | public static Task HandleException(this Task task) 61 | { 62 | task.ContinueWith(WriteTaskException, TaskContinuationOptions.OnlyOnFaulted); 63 | return task; 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /TeamCoding/Extensions/TextBlockExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using System.Windows; 7 | using System.Windows.Controls; 8 | using System.Windows.Media; 9 | 10 | namespace TeamCoding.Extensions 11 | { 12 | public static class TextBlockExtensions 13 | { 14 | public static FormattedText GetFormattedText(this TextBlock textBlock) 15 | { 16 | return new FormattedText(textBlock.Text, 17 | System.Globalization.CultureInfo.CurrentUICulture, 18 | FlowDirection.LeftToRight, 19 | new Typeface(textBlock.FontFamily, textBlock.FontStyle, textBlock.FontWeight, textBlock.FontStretch), 20 | textBlock.FontSize, textBlock.Foreground); 21 | } 22 | public static Rect GetBoundingRect(this TextBlock textBlock) 23 | { 24 | var text = textBlock.GetFormattedText(); 25 | return text.BuildGeometry(new Point()).Bounds; 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /TeamCoding/Extensions/TextViewExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.Text.Editor; 2 | using System.Collections.Generic; 3 | 4 | namespace TeamCoding.Extensions 5 | { 6 | public static class TextViewExtensions 7 | { 8 | public static string GetTextDocumentFilePath(this IWpfTextView textView) 9 | { 10 | return textView.TextBuffer.GetTextDocumentFilePath(); 11 | } 12 | public static IEnumerable GetTextDocumentFilePaths(this IWpfTextView textView) 13 | { 14 | return textView.TextBuffer.GetTextDocumentFilePaths(); 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /TeamCoding/Extensions/WaitHandleExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | 8 | namespace TeamCoding.Extensions 9 | { 10 | public static class WaitHandleExtensions 11 | { // http://stackoverflow.com/a/18766131 12 | public static Task AsTask(this WaitHandle handle) => AsTask(handle, Timeout.InfiniteTimeSpan); 13 | public static Task AsTask(this WaitHandle handle, TimeSpan timeout) 14 | { 15 | var tcs = new TaskCompletionSource(); 16 | var registration = ThreadPool.RegisterWaitForSingleObject(handle, (state, timedOut) => 17 | { 18 | var localTcs = (TaskCompletionSource)state; 19 | localTcs.TrySetResult(!timedOut); 20 | }, tcs, timeout, executeOnlyOnce: true); 21 | tcs.Task.ContinueWith((_, state) => ((RegisteredWaitHandle)state).Unregister(null), registration, TaskScheduler.Default); 22 | return tcs.Task; 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /TeamCoding/Guids.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace TeamCoding 8 | { 9 | public static class Guids 10 | { 11 | public const string OutputWindowCategoryGuidString = "3C4076F2-E4E4-4B36-9643-FF48585BD80C"; 12 | public const string PackageGuidString = "ac66efb2-fad5-442d-87e2-b9b4a206f14d"; 13 | public const string OptionPageGridGuidString = "BEC8E8F5-B7B8-422E-A586-12E7AA7E8DF8"; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /TeamCoding/IdentityManagement/CachedFailoverIdentityProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | 4 | namespace TeamCoding.IdentityManagement 5 | { 6 | /// 7 | /// An identity provider that tries child identity providers in turn until we get a valid identity 8 | /// 9 | public class CachedFailoverIdentityProvider : IIdentityProvider 10 | { 11 | private UserIdentity Identity; 12 | private readonly IIdentityProvider[] IdentityProviders; 13 | public bool ShouldCache => IdentityProviders.All(ip => ip.ShouldCache); 14 | public UserIdentity GetIdentity() 15 | { 16 | foreach (var identityProvider in IdentityProviders) 17 | { 18 | if (identityProvider.ShouldCache && Identity != null) 19 | { 20 | return Identity; 21 | } 22 | else 23 | { 24 | var newIdentity = identityProvider.GetIdentity(); 25 | 26 | if(newIdentity != null) 27 | { 28 | Identity = newIdentity; 29 | TeamCodingPackage.Current.Logger.WriteInformation($"{nameof(CachedFailoverIdentityProvider)}: Got identity from {identityProvider.GetType().Name}"); 30 | return Identity; 31 | } 32 | } 33 | } 34 | 35 | throw new InvalidOperationException("Failed to get a user identity after trying all providers: " + string.Join(", ", IdentityProviders.Select(ip => ip.GetType().Name))); 36 | } 37 | public CachedFailoverIdentityProvider(params IIdentityProvider[] identityProviders) 38 | { 39 | TeamCodingPackage.Current.Logger.WriteInformation( 40 | $"{nameof(CachedFailoverIdentityProvider)}: Using identity providers; {string.Join(", ", identityProviders.Select(ip => ip.GetType().Name))}"); 41 | 42 | IdentityProviders = identityProviders; 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /TeamCoding/IdentityManagement/CredentialManagerIdentityProvider.cs: -------------------------------------------------------------------------------- 1 | using TeamCoding.CredentialManagement; 2 | using TeamCoding.Extensions; 3 | 4 | namespace TeamCoding.IdentityManagement 5 | { 6 | /// 7 | /// Gets crendials from the windows credential manager for a given set of credential targets (tried in turn) 8 | /// 9 | public class CredentialManagerIdentityProvider : IIdentityProvider 10 | { 11 | private readonly UserIdentity Identity; 12 | public bool ShouldCache => true; 13 | public UserIdentity GetIdentity() => Identity; 14 | public CredentialManagerIdentityProvider(string[] credentialTargets) 15 | { 16 | Credential credential = null; 17 | foreach (var credentialTarget in credentialTargets) 18 | { 19 | credential = new Credential { Target = credentialTarget }; 20 | if(credential.Load() && credential.Username != null) 21 | { 22 | break; 23 | } 24 | } 25 | 26 | if(credential?.Username == null) 27 | { 28 | Identity = null; 29 | return; 30 | } 31 | 32 | Identity = new UserIdentity() 33 | { 34 | Id = credential.Username, 35 | DisplayName = credential.Username, 36 | ImageUrl = UserIdentity.GetGravatarUrlFromEmail(credential.Username) 37 | }; 38 | 39 | TeamCodingPackage.Current.IDEWrapper.InvokeAsync(async () => 40 | { 41 | var oldDisplayName = Identity.DisplayName; 42 | try 43 | { 44 | Identity.DisplayName = await UserIdentity.GetGravatarDisplayNameFromEmailAsync(credential.Username).HandleException(); 45 | } 46 | catch { } // Swallow failures here since they're dealt with above 47 | if (oldDisplayName != Identity.DisplayName) 48 | { 49 | TeamCodingPackage.Current.LocalIdeModel.OnUserIdentityChanged(); 50 | } 51 | }); 52 | } 53 | 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /TeamCoding/IdentityManagement/IIdentityProvider.cs: -------------------------------------------------------------------------------- 1 | namespace TeamCoding.IdentityManagement 2 | { 3 | /// 4 | /// An interface that provides a user identity 5 | /// 6 | public interface IIdentityProvider 7 | { 8 | UserIdentity GetIdentity(); 9 | bool ShouldCache { get; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /TeamCoding/IdentityManagement/MachineIdentityProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace TeamCoding.IdentityManagement 4 | { 5 | /// 6 | /// Gets the machine names as the identity 7 | /// 8 | public class MachineIdentityProvider : IIdentityProvider 9 | { 10 | public bool ShouldCache => true; 11 | public UserIdentity GetIdentity() 12 | { 13 | return new UserIdentity() { Id = Environment.UserName, DisplayName = Environment.UserName }; 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /TeamCoding/IdentityManagement/UserIdentity.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using Newtonsoft.Json.Linq; 3 | using System.Collections.Generic; 4 | using System.Collections.ObjectModel; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using System.Windows.Media; 8 | using TeamCoding.Extensions; 9 | using TeamCoding.VisualStudio; 10 | 11 | namespace TeamCoding.IdentityManagement 12 | { 13 | /// 14 | /// A user's identity 15 | /// 16 | [ProtoBuf.ProtoContract] 17 | public class UserIdentity : IUserIdentity 18 | { 19 | [ProtoBuf.ProtoMember(1)] 20 | public string Id { get; set; } 21 | [ProtoBuf.ProtoMember(2)] 22 | public string ImageUrl { get; set; } 23 | [ProtoBuf.ProtoMember(3)] 24 | public string DisplayName { get; set; } 25 | [ProtoBuf.ProtoMember(4)] 26 | public byte[] ImageBytes { get; set; } 27 | public static string GetGravatarUrlFromEmail(string email) 28 | { 29 | return $"https://www.gravatar.com/avatar/{CalculateMD5Hash(email).ToLower()}?d=404"; 30 | } 31 | public override string ToString() 32 | { 33 | return $"Id: {Id}, DisplayName: {DisplayName}, ImageUrl: {ImageUrl}"; 34 | } 35 | public Color GetUserColour() 36 | { 37 | return UserColours.GetUserColour(this); 38 | } 39 | public static async Task GetGravatarDisplayNameFromEmailAsync(string email) 40 | { 41 | var result = await TeamCodingPackage.Current.HttpClient.GetAsync($"https://www.gravatar.com/{CalculateMD5Hash(email).ToLower()}.json?d=404").HandleException(); 42 | if (!result.IsSuccessStatusCode) return null; 43 | 44 | var gravatarProfileJsonString = await result.Content.ReadAsStringAsync(); 45 | var gravatarProfileJsonObject = (JObject)JsonConvert.DeserializeObject(gravatarProfileJsonString); 46 | return gravatarProfileJsonObject?["entry"]?[0]?["displayName"]?.Value(); 47 | } 48 | private static string CalculateMD5Hash(string input) 49 | { 50 | var md5 = System.Security.Cryptography.MD5.Create(); 51 | byte[] inputBytes = Encoding.ASCII.GetBytes(input); 52 | byte[] hash = md5.ComputeHash(inputBytes); 53 | StringBuilder sb = new StringBuilder(); 54 | for (int i = 0; i < hash.Length; i++) 55 | { 56 | sb.Append(hash[i].ToString("X2")); 57 | } 58 | return sb.ToString(); 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /TeamCoding/IdentityManagement/VSIdentityProvider.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Win32; 2 | using System; 3 | using System.Drawing; 4 | using System.Linq; 5 | 6 | namespace TeamCoding.IdentityManagement 7 | { 8 | /// 9 | /// Gets a user identity from the visual studio registry 10 | /// 11 | public class VSIdentityProvider : IIdentityProvider 12 | { 13 | private const string SubKey = "Software\\Microsoft\\VSCommon\\ConnectedUser\\IdeUser\\Cache"; 14 | private const string EmailAddressKeyName = "EmailAddress"; 15 | private const string UserNameKeyName = "DisplayName"; 16 | private const string ImageKeyname = "Avatar.Small"; 17 | private readonly UserIdentity Identity; 18 | public bool ShouldCache => true; 19 | public VSIdentityProvider() 20 | { 21 | try 22 | { 23 | RegistryKey root = Registry.CurrentUser; 24 | using (var sk = root.OpenSubKey(SubKey)) 25 | { 26 | if (sk != null) 27 | { 28 | var userName = (string)sk.GetValue(UserNameKeyName); 29 | var email = (string)sk.GetValue(EmailAddressKeyName); 30 | var bytes = ((byte[])sk.GetValue(ImageKeyname)).Skip(16).ToArray(); 31 | Identity = new UserIdentity() 32 | { 33 | Id = userName, 34 | DisplayName = userName, 35 | ImageUrl = UserIdentity.GetGravatarUrlFromEmail(email), 36 | ImageBytes = IsVSPlaceholderImage(bytes) ? null : bytes, // Only use the image if it's not a default one 37 | }; 38 | } 39 | } 40 | } 41 | catch (Exception ex) when (!System.Diagnostics.Debugger.IsAttached) 42 | { 43 | TeamCodingPackage.Current.Logger.WriteError(ex); 44 | Identity = null; 45 | } 46 | } 47 | /// 48 | /// Indicates whether the image defined by the given byte array is likely to be a placeolder one generate by visual studio based on the user's initials 49 | /// 50 | /// 51 | /// 52 | private bool IsVSPlaceholderImage(byte[] imageBytes) 53 | { 54 | var image = Image.FromStream(new System.IO.MemoryStream(imageBytes)) as Bitmap; 55 | 56 | var Colour = image.GetPixel(0, 0); 57 | 58 | if (Colour == image.GetPixel(1, 1) && 59 | Colour == image.GetPixel(0, image.Height - 1) && 60 | Colour == image.GetPixel(1, image.Height - 2) && 61 | Colour == image.GetPixel(image.Width - 1, 0) && 62 | Colour == image.GetPixel(image.Width - 2, 1) && 63 | Colour == image.GetPixel(image.Width - 1, image.Height - 1) && 64 | Colour == image.GetPixel(image.Width - 2, image.Height - 2)) 65 | { 66 | // Corner pixels, plus one pixel in from the corner are the same colour 67 | return true; 68 | } 69 | 70 | return false; 71 | } 72 | 73 | public UserIdentity GetIdentity() => Identity; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /TeamCoding/IdentityManagement/VSOptionsIdentityProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace TeamCoding.IdentityManagement 8 | { 9 | public class VSOptionsIdentityProvider : IIdentityProvider 10 | { 11 | public bool ShouldCache => false; 12 | public UserIdentity GetIdentity() 13 | { 14 | if (string.IsNullOrWhiteSpace(TeamCodingPackage.Current.Settings.UserSettings.Username)) return null; 15 | 16 | var ImageUrl = string.IsNullOrWhiteSpace(TeamCodingPackage.Current.Settings.UserSettings.UserImageUrl) ? null : new Uri(TeamCodingPackage.Current.Settings.UserSettings.UserImageUrl); 17 | if(ImageUrl != null &&(!ImageUrl.IsAbsoluteUri || ImageUrl.IsFile || ImageUrl.IsUnc)) 18 | { 19 | ImageUrl = null; 20 | } 21 | return new UserIdentity() 22 | { 23 | Id = TeamCodingPackage.Current.Settings.UserSettings.Username, 24 | DisplayName = TeamCodingPackage.Current.Settings.UserSettings.Username, 25 | ImageUrl = ImageUrl?.ToString() 26 | }; 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /TeamCoding/Key.snk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/georgeduckett/TeamCoding/5ad8f1d221730348662c8c11f3fb57a1ce79e642/TeamCoding/Key.snk -------------------------------------------------------------------------------- /TeamCoding/Logging/Logger.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.Shell; 2 | using Microsoft.VisualStudio.Shell.Interop; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.IO; 6 | using System.Linq; 7 | using System.Runtime.CompilerServices; 8 | using System.Text; 9 | using System.Threading.Tasks; 10 | 11 | namespace TeamCoding.Logging 12 | { 13 | public class Logger : ILogger 14 | { 15 | public const string OutputWindowCategory = "Team Coding"; 16 | private IVsOutputWindowPane TeamCodingPane; 17 | private EnvDTE.OutputWindow OutputWindow; 18 | public Logger() 19 | { 20 | var dte = (EnvDTE.DTE)Package.GetGlobalService(typeof(EnvDTE.DTE)); 21 | OutputWindow = (EnvDTE.OutputWindow)dte.Windows.Item(EnvDTE.Constants.vsWindowKindOutput).Object; 22 | } 23 | private void EnsureOutputPane() 24 | { 25 | if (TeamCodingPane == null) 26 | { 27 | var OldPane = OutputWindow.ActivePane; 28 | var outputWindow = (IVsOutputWindow)Package.GetGlobalService(typeof(SVsOutputWindow)); 29 | var outputWindowCategoryGuid = new Guid(Guids.OutputWindowCategoryGuidString); 30 | outputWindow.CreatePane(ref outputWindowCategoryGuid, OutputWindowCategory, 0, 0); 31 | outputWindow.GetPane(ref outputWindowCategoryGuid, out TeamCodingPane); 32 | TeamCodingPane.Activate(); // Activate it so it's visible 33 | try 34 | { 35 | OldPane?.Activate(); // Then activate the old one again 36 | } 37 | catch(Exception ex) 38 | { 39 | WriteError(ex); 40 | } 41 | } 42 | } 43 | public void WriteInformation(string info, [CallerFilePath] string filePath = null, [CallerMemberName] string memberName = null) 44 | { 45 | LogText(info, filePath, memberName); 46 | } 47 | private void LogText(string text, string filePath, string methodName) 48 | { 49 | try 50 | { 51 | EnsureOutputPane(); 52 | } 53 | catch { } 54 | ActivityLog.TryLogInformation(OutputWindowCategory, $"{Path.GetFileNameWithoutExtension(filePath)}.{methodName}:{text}"); 55 | try 56 | { 57 | TeamCodingPane.OutputStringThreadSafe($"{DateTime.Now} {Path.GetFileNameWithoutExtension(filePath)}.{methodName}:{text}{Environment.NewLine}"); 58 | } 59 | catch { } 60 | } 61 | public void WriteError(string error, Exception ex = null, [CallerFilePath] string filePath = null, [CallerMemberName] string memberName = null) 62 | { 63 | if(ex != null) 64 | { 65 | WriteError(ex, filePath, memberName); 66 | } 67 | LogText(error, filePath, memberName); 68 | TeamCodingPane.Activate(); 69 | } 70 | public void WriteError(Exception ex, [CallerFilePath] string filePath = null, [CallerMemberName] string memberName = null) => WriteError(ex.ToString(), null, filePath, memberName); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /TeamCoding/Options/OptionPageGrid.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.Shell; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.ComponentModel; 5 | using System.Linq; 6 | using System.Runtime.InteropServices; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | using System.Windows; 10 | 11 | namespace TeamCoding.Options 12 | { // Test out some way of getting (some) settings from a local file that could be synced from a repository. 13 | [Guid(Guids.OptionPageGridGuidString)] 14 | public class OptionPageGrid : UIElementDialogPage 15 | { 16 | public const string OptionsName = "Team Coding"; 17 | public string Username { get; set; } = UserSettings.DefaultUsername; 18 | public string UserImageUrl { get; set; } = UserSettings.DefaultImageUrl; 19 | public UserSettings.UserDisplaySetting UserCodeDisplay { get; set; } = UserSettings.DefaultUserCodeDisplay; 20 | public UserSettings.UserDisplaySetting UserTabDisplay { get; set; } = UserSettings.DefaultUserTabDisplay; 21 | public bool ShowSelf { get; set; } = UserSettings.DefaultShowSelf; 22 | public bool ShowAllBranches { get; set; } = UserSettings.DefaultShowAllBranches; 23 | public string FileBasedPersisterPath { get; set; } = SharedSettings.DefaultFileBasedPersisterPath; 24 | public string RedisServer { get; set; } = SharedSettings.DefaultRedisServer; 25 | public string SlackToken { get; set; } = SharedSettings.DefaultSlackToken; 26 | public string SlackChannel { get; set; } = SharedSettings.DefaultSlackChannel; 27 | public string SqlServerConnectionString { get; set; } = SharedSettings.DefaultSqlServerConnectionString; 28 | public string WinServiceIPAddress { get; set; } = SharedSettings.DefaultWinServiceIPAddress; 29 | private OptionsPage OptionsPage; 30 | protected override UIElement Child { get { return OptionsPage ?? (OptionsPage = new OptionsPage(this)); } } 31 | public IEnumerable UserDisplaySettings => 32 | Enum.GetValues(typeof(UserSettings.UserDisplaySetting)).Cast(); 33 | protected override void OnApply(PageApplyEventArgs e) 34 | { 35 | base.OnApply(e); 36 | TeamCodingPackage.Current.Settings.UpdateFromGrid(this); 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /TeamCoding/Options/SettingProperties/SettingProperty.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace TeamCoding.Options 8 | { 9 | public class SettingProperty 10 | { 11 | private readonly object Owner; 12 | private readonly Func> InvalidReasonFunc; 13 | private bool? IsValid = null; 14 | public SettingProperty(object owner, Func> invalidReasonFunc = null) { Owner = owner; InvalidReasonFunc = invalidReasonFunc; } 15 | private TProperty _Value; 16 | public TProperty Value 17 | { 18 | get { return _Value; } 19 | set 20 | { 21 | if (!EqualityComparer.Default.Equals(_Value, value)) 22 | { 23 | Changing?.Invoke(Owner, EventArgs.Empty); 24 | IsValid = null; 25 | _Value = value; 26 | Changed?.Invoke(Owner, EventArgs.Empty); 27 | } 28 | } 29 | } 30 | public async Task IsValidAsync() => IsValid ?? (bool)(IsValid = await GetNewValueInvalidReasonAsync(Value).ContinueWith(t => !t.IsFaulted && t.Result == null)); 31 | public Task GetNewValueInvalidReasonAsync(TProperty newValue) => InvalidReasonFunc == null ? Task.FromResult(null) : InvalidReasonFunc(newValue); 32 | public event EventHandler Changing; 33 | public event EventHandler Changed; 34 | } 35 | } -------------------------------------------------------------------------------- /TeamCoding/Options/TeamCodingExample.xaml: -------------------------------------------------------------------------------- 1 |  9 | 10 | { 11 | "FileBasedPersisterPath": "\\\\FileServer\\TeamCodingPersistence", 12 | "RedisServer": "localhost", 13 | "SlackToken": "xxxx-00000000000-xxxxxxxxxxxxxxxxxxxxxxxx", 14 | "SlackChannel": "#teamcodingextensionsync", 15 | "SqlServerConnectionString": "Server=myServerAddress;Database=myDataBase;Trusted_Connection=True;" 16 | } 17 | 18 | 19 | -------------------------------------------------------------------------------- /TeamCoding/Options/TeamCodingExample.xaml.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.PlatformUI; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using System.Windows; 8 | using System.Windows.Controls; 9 | using System.Windows.Data; 10 | using System.Windows.Documents; 11 | using System.Windows.Input; 12 | using System.Windows.Media; 13 | using System.Windows.Media.Imaging; 14 | using System.Windows.Navigation; 15 | using System.Windows.Shapes; 16 | 17 | namespace TeamCoding.Options 18 | { 19 | /// 20 | /// Interaction logic for TeamCodingExample.xaml 21 | /// 22 | public partial class TeamCodingExample : DialogWindow 23 | { 24 | public TeamCodingExample() 25 | { 26 | InitializeComponent(); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /TeamCoding/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | 4 | // General Information about an assembly is controlled through the following 5 | // set of attributes. Change these attribute values to modify the information 6 | // associated with an assembly. 7 | [assembly: AssemblyTitle("TeamCoding")] 8 | [assembly: AssemblyDescription("")] 9 | [assembly: AssemblyConfiguration("")] 10 | [assembly: AssemblyCompany("")] 11 | [assembly: AssemblyProduct("TeamCoding")] 12 | [assembly: AssemblyCopyright("")] 13 | [assembly: AssemblyTrademark("")] 14 | [assembly: AssemblyCulture("")] 15 | 16 | // Setting ComVisible to false makes the types in this assembly not visible 17 | // to COM components. If you need to access a type in this assembly from 18 | // COM, set the ComVisible attribute to true on that type. 19 | [assembly: ComVisible(false)] 20 | 21 | // Version information for an assembly consists of the following four values: 22 | // 23 | // Major Version 24 | // Minor Version 25 | // Build Number 26 | // Revision 27 | // 28 | // You can specify all the values or you can default the Build and Revision Numbers 29 | // by using the '*' as shown below: 30 | // [assembly: AssemblyVersion("1.0.*")] 31 | [assembly: AssemblyVersion("1.0.0.0")] 32 | [assembly: AssemblyFileVersion("1.0.0.0")] 33 | -------------------------------------------------------------------------------- /TeamCoding/Resources/GettingStarted.tt: -------------------------------------------------------------------------------- 1 | <#@ template language="C#" debug="false" hostspecific="true" #> 2 | <#@ assembly name="Microsoft.VisualStudio.Shell.14.0, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" #> 3 | <#@ output extension=".txt" #> 4 | <#@ assembly name="$(SolutionDir)\TeamCoding\bin\Debug\TeamCoding.dll" #> 5 | There are 2 ways Team Coding can be minimally configured. 6 | In the options menu (under <#= TeamCoding.Options.OptionPageGrid.OptionsName #>) where you can also set user settings and/or 7 | by opening a repository with a `<#= TeamCoding.Options.Settings.TeamCodingConfigFileName #>` file in it (shared settings will be taken from there). 8 | An example of a valid `<#= TeamCoding.Options.Settings.TeamCodingConfigFileName #>` file can be found in the options menu. 9 | The borders around the user image at the top of open tabs are coloured white for the user's selected tab and grey for an edited document tab. 10 | You can test the extension on your own, or test that you've got a sharing method correctly set up by ticking the "<#= TeamCoding.Options.OptionsPage.chkShowSelfCaption #>" checkbox. -------------------------------------------------------------------------------- /TeamCoding/Resources/OverviewCommand.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/georgeduckett/TeamCoding/5ad8f1d221730348662c8c11f3fb57a1ce79e642/TeamCoding/Resources/OverviewCommand.png -------------------------------------------------------------------------------- /TeamCoding/Resources/OverviewCommandIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/georgeduckett/TeamCoding/5ad8f1d221730348662c8c11f3fb57a1ce79e642/TeamCoding/Resources/OverviewCommandIcon.png -------------------------------------------------------------------------------- /TeamCoding/Resources/PreviewImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/georgeduckett/TeamCoding/5ad8f1d221730348662c8c11f3fb57a1ce79e642/TeamCoding/Resources/PreviewImage.png -------------------------------------------------------------------------------- /TeamCoding/Resources/SqlServerPersisterCreateScript.sql: -------------------------------------------------------------------------------- 1 | /****** Object: Table [dbo].[TeamCodingSync] ******/ 2 | SET ANSI_NULLS ON 3 | GO 4 | 5 | SET QUOTED_IDENTIFIER ON 6 | GO 7 | 8 | CREATE TABLE [dbo].[TeamCodingSync]( 9 | [Id] [varchar](512) NOT NULL, 10 | [Model] [varbinary](max) NULL, 11 | [LastUpdated] [datetime] NOT NULL, 12 | CONSTRAINT [PK_TeamCodingSync] PRIMARY KEY CLUSTERED 13 | ( 14 | [Id] ASC 15 | )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] 16 | ) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY] 17 | 18 | GO 19 | 20 | EXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=N'Table used to sync models in the Visual Studio Team Coding extension' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'TeamCodingSync' 21 | GO 22 | 23 | 24 | -- You will also need to ensure the Service Broker is enabled for the database, you can use the below query. 25 | -- ALTER DATABASE CURRENT SET ENABLE_BROKER -------------------------------------------------------------------------------- /TeamCoding/Resources/TeamCodingPackage.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/georgeduckett/TeamCoding/5ad8f1d221730348662c8c11f3fb57a1ce79e642/TeamCoding/Resources/TeamCodingPackage.ico -------------------------------------------------------------------------------- /TeamCoding/Settings.StyleCop: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | False 8 | 9 | 10 | 11 | 12 | False 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | False 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /TeamCoding/TeamCodingPackageProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel.Composition; 4 | using System.Linq; 5 | using System.Net.Http; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | using TeamCoding.Documents.SourceControlRepositories; 9 | using TeamCoding.Interfaces.Documents; 10 | using TeamCoding.Logging; 11 | using TeamCoding.VisualStudio.Models.ChangePersisters; 12 | 13 | namespace TeamCoding 14 | { 15 | [Export(typeof(ITeamCodingPackageProvider))] 16 | public class TeamCodingPackageProvider : ITeamCodingPackageProvider 17 | { 18 | public ICaretAdornmentDataProvider CaretAdornmentDataProvider => TeamCodingPackage.Current.CaretAdornmentDataProvider; 19 | public ICaretInfoProvider CaretInfoProvider => TeamCodingPackage.Current.CaretInfoProvider; 20 | public ISourceControlRepository SourceControlRepository => TeamCodingPackage.Current.SourceControlRepo; 21 | public HttpClient HttpClient => TeamCodingPackage.Current.HttpClient; 22 | public IRemoteModelPersister RemoteModelChangeManager => TeamCodingPackage.Current.RemoteModelChangeManager; 23 | public ILogger Logger => TeamCodingPackage.Current.Logger; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /TeamCoding/VisualStudio/Controls/TextBlockToLetterMarginConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Globalization; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using System.Windows; 8 | using System.Windows.Controls; 9 | using System.Windows.Data; 10 | using TeamCoding.Extensions; 11 | 12 | namespace TeamCoding.VisualStudio.Controls 13 | { 14 | public class TextBlockToLetterMarginConverter : IMultiValueConverter 15 | { // This is a multi-value converter so we can add the text property as a binding so changing it triggers a re-calculation 16 | public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) 17 | { 18 | var rect = ((TextBlock)values[0]).GetBoundingRect(); 19 | if (rect.Top >= 5) 20 | { // If we have a lot of blank space at the top of the up-most pixel of the rendered character (for lower case letters for example), move the text up 21 | return new Thickness(0, (-rect.Top) / 2, 0, 0); 22 | } 23 | else 24 | { 25 | return new Thickness(0); 26 | } 27 | } 28 | public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) 29 | { 30 | throw new NotSupportedException(); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /TeamCoding/VisualStudio/Controls/UserAvatar.xaml: -------------------------------------------------------------------------------- 1 |  12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /TeamCoding/VisualStudio/Controls/UserAvatar.xaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using System.Windows; 7 | using System.Windows.Controls; 8 | using System.Windows.Data; 9 | using System.Windows.Documents; 10 | using System.Windows.Input; 11 | using System.Windows.Media; 12 | using System.Windows.Media.Imaging; 13 | using System.Windows.Navigation; 14 | using System.Windows.Shapes; 15 | 16 | namespace TeamCoding.VisualStudio.Controls 17 | { 18 | /// 19 | /// Interaction logic for UserAvatar.xaml 20 | /// 21 | public partial class UserAvatar : UserControl 22 | { 23 | public UserAvatarModel Model => (UserAvatarModel)DataContext; 24 | public UserAvatar() 25 | { 26 | InitializeComponent(); 27 | } 28 | public Visibility UserBorderVisibility 29 | { 30 | get 31 | { 32 | return bdrOuterBorder.Visibility; 33 | } 34 | set 35 | { 36 | BindingOperations.ClearBinding(bdrOuterBorder, VisibilityProperty); 37 | bdrOuterBorder.Visibility = value; 38 | } 39 | } 40 | 41 | public Brush UserBorderBrush 42 | { 43 | get 44 | { 45 | return bdrOuterBorder.BorderBrush; 46 | } 47 | set 48 | { 49 | BindingOperations.ClearBinding(bdrOuterBorder, BorderBrushProperty); 50 | bdrOuterBorder.BorderBrush = value; 51 | } 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /TeamCoding/VisualStudio/Controls/UserAvatarModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using System.Windows; 8 | using System.Windows.Media; 9 | 10 | namespace TeamCoding.VisualStudio.Controls 11 | { 12 | public class UserAvatarModel : INotifyPropertyChanged 13 | { 14 | private ImageSource _AvatarImageSource; 15 | public ImageSource AvatarImageSource 16 | { 17 | get { return _AvatarImageSource; } 18 | set 19 | { 20 | _AvatarImageSource = value; 21 | OnPropertyChanged(nameof(AvatarImageSource)); 22 | } 23 | } 24 | private Brush _BorderBrush; 25 | public Brush BorderBrush 26 | { 27 | get { return _BorderBrush; } 28 | set 29 | { 30 | _BorderBrush = value; 31 | OnPropertyChanged(nameof(BorderBrush)); 32 | } 33 | } 34 | private Visibility _BorderVisibility; 35 | public Visibility BorderVisibility 36 | { 37 | get { return _BorderVisibility; } 38 | set 39 | { 40 | _BorderVisibility = value; 41 | OnPropertyChanged(nameof(BorderVisibility)); 42 | } 43 | } 44 | private string _Tag; 45 | public string Tag 46 | { 47 | get { return _Tag; } 48 | set 49 | { 50 | _Tag = value; 51 | OnPropertyChanged(nameof(Tag)); 52 | } 53 | } 54 | private string _ToolTip; 55 | public string ToolTip 56 | { 57 | get { return _ToolTip; } 58 | set 59 | { 60 | _ToolTip = value; 61 | OnPropertyChanged(nameof(ToolTip)); 62 | } 63 | } 64 | private Brush _BackgroundBrush; 65 | public Brush BackgroundBrush 66 | { 67 | get { return _BackgroundBrush; } 68 | set 69 | { 70 | _BackgroundBrush = value; 71 | OnPropertyChanged(nameof(BackgroundBrush)); 72 | } 73 | } 74 | private char? _Letter; 75 | public char? Letter 76 | { 77 | get { return _Letter; } 78 | set 79 | { 80 | _Letter = value; 81 | OnPropertyChanged(nameof(Letter)); 82 | } 83 | } 84 | private Brush _LetterBrush; 85 | public Brush LetterBrush 86 | { 87 | get { return _LetterBrush; } 88 | set 89 | { 90 | _LetterBrush = value; 91 | OnPropertyChanged(nameof(LetterBrush)); 92 | } 93 | } 94 | private Thickness _LetterMargin; 95 | public Thickness LetterMargin 96 | { 97 | get { return _LetterMargin; } 98 | set 99 | { 100 | _LetterMargin = value; 101 | OnPropertyChanged(nameof(LetterMargin)); 102 | } 103 | } 104 | 105 | private void OnPropertyChanged(string propName) 106 | { 107 | PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName)); 108 | } 109 | public event PropertyChangedEventHandler PropertyChanged; 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /TeamCoding/VisualStudio/Models/ChangePersisters/CombinedPersister/CombinedLocalModelPersister.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace TeamCoding.VisualStudio.Models.ChangePersisters.CombinedPersister 8 | { 9 | public class CombinedLocalModelPersister : ILocalModelPerisister 10 | { 11 | private readonly ILocalModelPerisister[] LocalModelPersisters; 12 | public CombinedLocalModelPersister(params ILocalModelPerisister[] localModelPersisters) 13 | { 14 | LocalModelPersisters = localModelPersisters; 15 | } 16 | public async Task SendUpdateAsync() 17 | { 18 | await Task.WhenAll(LocalModelPersisters.Select(lmp => lmp.SendUpdateAsync())); 19 | } 20 | public void Dispose() 21 | { 22 | foreach(var localModelPersister in LocalModelPersisters) 23 | { 24 | localModelPersister.Dispose(); 25 | } 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /TeamCoding/VisualStudio/Models/ChangePersisters/CombinedPersister/CombinedRemoteModelPersister.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using TeamCoding.Documents; 7 | 8 | namespace TeamCoding.VisualStudio.Models.ChangePersisters.CombinedPersister 9 | { 10 | public class CombinedRemoteModelPersister : IRemoteModelPersister 11 | { 12 | private readonly IRemoteModelPersister[] RemoteModelPersisters; 13 | private IRemotelyAccessedDocumentData[] CachedOpenFiles = null; 14 | public event EventHandler RemoteModelReceived 15 | { 16 | add 17 | { 18 | foreach (var remoteModelPersister in RemoteModelPersisters) 19 | { 20 | remoteModelPersister.RemoteModelReceived += value; 21 | } 22 | } 23 | remove 24 | { 25 | foreach (var remoteModelPersister in RemoteModelPersisters) 26 | { 27 | remoteModelPersister.RemoteModelReceived -= value; 28 | } 29 | } 30 | } 31 | public IEnumerable GetOpenFiles() 32 | { 33 | if (CachedOpenFiles != null) 34 | { 35 | return CachedOpenFiles; 36 | } 37 | else 38 | { 39 | return CachedOpenFiles = RemoteModelPersisters.SelectMany(rmp => rmp.GetOpenFiles().ToArray()).GroupBy(scdd => new 40 | { 41 | scdd.Repository, 42 | scdd.RepositoryBranch, 43 | scdd.RelativePath, 44 | scdd.IdeUserIdentity.Id 45 | }).Select(g => new RemotelyAccessedDocumentData() 46 | { 47 | Repository = g.Key.Repository, 48 | RepositoryBranch = g.Key.RepositoryBranch, 49 | RelativePath = g.Key.RelativePath, 50 | IdeUserIdentity = g.First().IdeUserIdentity, 51 | HasFocus = g.Any(scdd => scdd.HasFocus), 52 | BeingEdited = g.Any(scdd => scdd.BeingEdited), 53 | CaretPositionInfo = g.FirstOrDefault(scdd => scdd.CaretPositionInfo != null)?.CaretPositionInfo 54 | }).ToArray(); 55 | } 56 | } 57 | public CombinedRemoteModelPersister(params IRemoteModelPersister[] remoteModelPersisters) 58 | { 59 | RemoteModelPersisters = remoteModelPersisters; 60 | foreach(var remoteModelPersister in RemoteModelPersisters) 61 | { 62 | remoteModelPersister.RemoteModelReceived += RemoteModelPersister_RemoteModelReceived; 63 | } 64 | } 65 | private void RemoteModelPersister_RemoteModelReceived(object sender, EventArgs e) 66 | { 67 | CachedOpenFiles = null; 68 | } 69 | public void Dispose() 70 | { 71 | foreach(var remoteModelPersister in RemoteModelPersisters) 72 | { 73 | remoteModelPersister.RemoteModelReceived -= RemoteModelPersister_RemoteModelReceived; 74 | remoteModelPersister.Dispose(); 75 | } 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /TeamCoding/VisualStudio/Models/ChangePersisters/DebugPersister/DebugLocalModelPersister.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using TeamCoding.VisualStudio.Models.ChangePersisters.FileBasedPersister; 8 | 9 | namespace TeamCoding.VisualStudio.Models.ChangePersisters.DebugPersister 10 | { 11 | public class DebugLocalModelPersister : FileBasedLocalModelPersisterBase 12 | { 13 | protected override string PersistenceFolderPath => Directory.GetCurrentDirectory(); 14 | 15 | public DebugLocalModelPersister(LocalIDEModel model) : base(model) { } 16 | protected override void SendModel(RemoteIDEModel model) 17 | { 18 | if (PersistenceFolderPath != null && Directory.Exists(PersistenceFolderPath)) 19 | { 20 | // Delete any temporary persistence files 21 | foreach (var file in Directory.EnumerateFiles(PersistenceFolderPath, PersistenceFileSearchFormat)) 22 | { 23 | if (File.Exists(file) && file != PersistenceFilePath) 24 | { 25 | File.Delete(file); 26 | } 27 | } 28 | } 29 | base.SendModel(model); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /TeamCoding/VisualStudio/Models/ChangePersisters/DebugPersister/DebugRemoteModelPersister.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using TeamCoding.VisualStudio.Models.ChangePersisters.FileBasedPersister; 8 | 9 | namespace TeamCoding.VisualStudio.Models.ChangePersisters.DebugPersister 10 | { 11 | public class DebugRemoteModelPersister : FileBasedRemoteModelPersisterBase 12 | { 13 | protected override string PersistenceFolderPath => Directory.GetCurrentDirectory(); 14 | 15 | public DebugRemoteModelPersister() : base() { } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /TeamCoding/VisualStudio/Models/ChangePersisters/FileBasedPersister/SharedFolderLocalModelPersister.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace TeamCoding.VisualStudio.Models.ChangePersisters.FileBasedPersister 9 | { 10 | public class SharedFolderLocalModelPersister : FileBasedLocalModelPersisterBase 11 | { 12 | protected override string PersistenceFolderPath => TeamCodingPackage.Current.Settings.SharedSettings.FileBasedPersisterPath; 13 | public SharedFolderLocalModelPersister(LocalIDEModel model) : base(model) { } 14 | public static Task FolderPathIsValid(string folderPath) 15 | { 16 | return Task.Run(() => 17 | { 18 | if (!Directory.Exists(folderPath)) 19 | { 20 | return "Directory not found"; 21 | } 22 | 23 | try 24 | { 25 | File.Create(Path.Combine(folderPath, "test.tmp")).Dispose(); 26 | } 27 | catch(Exception ex) 28 | { 29 | return "Failed to create test file" + Environment.NewLine + Environment.NewLine + ex.ToString(); 30 | } 31 | try 32 | { 33 | File.Delete(Path.Combine(folderPath, "test.tmp")); 34 | } 35 | catch (Exception ex) 36 | { 37 | return "Failed to delete test file" + Environment.NewLine + Environment.NewLine + ex.ToString(); 38 | } 39 | 40 | return null; 41 | }); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /TeamCoding/VisualStudio/Models/ChangePersisters/FileBasedPersister/SharedFolderRemoteModelPersister.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace TeamCoding.VisualStudio.Models.ChangePersisters.FileBasedPersister 8 | { 9 | public class SharedFolderRemoteModelPersister : FileBasedRemoteModelPersisterBase 10 | { 11 | protected override string PersistenceFolderPath => TeamCodingPackage.Current.Settings.SharedSettings.FileBasedPersisterPath; 12 | public SharedFolderRemoteModelPersister() : base() { } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /TeamCoding/VisualStudio/Models/ChangePersisters/ILocalModelPerisister.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace TeamCoding.VisualStudio.Models.ChangePersisters 8 | { 9 | /// 10 | /// Handles sending local IDE model changes to other clients. 11 | /// 12 | public interface ILocalModelPerisister : IDisposable 13 | { 14 | Task SendUpdateAsync(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /TeamCoding/VisualStudio/Models/ChangePersisters/LocalModelPersisterBase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using TeamCoding.Documents; 7 | using TeamCoding.Options; 8 | 9 | namespace TeamCoding.VisualStudio.Models.ChangePersisters 10 | { 11 | public abstract class LocalModelPersisterBase : ILocalModelPerisister 12 | { 13 | private readonly UserSettings UserSettings; 14 | private readonly LocalIDEModel IdeModel; 15 | private readonly SettingProperty[] SettingProperties; 16 | public LocalModelPersisterBase(LocalIDEModel model, params SettingProperty[] settingProperties) 17 | { 18 | SettingProperties = settingProperties; 19 | IdeModel = model; 20 | 21 | IdeModel.ModelChanged += IdeModel_ModelChangedAsync; 22 | foreach (var property in SettingProperties) 23 | { 24 | property.Changing += SettingProperty_ChangingAsync; 25 | property.Changed += SettingProperty_ChangedAsync; 26 | } 27 | UserSettings = TeamCodingPackage.Current.Settings.UserSettings; 28 | UserSettings.ShowSelfChanged += UserSettings_ShowSelfChangedAsync; 29 | } 30 | 31 | private async void UserSettings_ShowSelfChangedAsync(object sender, EventArgs e) 32 | { 33 | if (UserSettings.ShowSelf) 34 | { 35 | await TeamCodingPackage.Current.LocalModelChangeManager.SendUpdateAsync(); 36 | } 37 | else 38 | { 39 | SendModel(new RemoteIDEModel(new LocalIDEModel())); 40 | } 41 | } 42 | private async void SettingProperty_ChangingAsync(object sender, EventArgs e) 43 | { 44 | if (await RequiredPropertiesAreSetAsync()) 45 | { 46 | SendModel(new RemoteIDEModel(new LocalIDEModel())); 47 | } 48 | } 49 | protected virtual async Task RequiredPropertiesAreSetAsync() 50 | { 51 | if (SettingProperties.Any(p => string.IsNullOrEmpty(p.Value))) return false; 52 | 53 | var propertyTasks = SettingProperties.Select(p => p.IsValidAsync()).ToArray(); 54 | 55 | await Task.WhenAll(propertyTasks); 56 | 57 | return propertyTasks.All(pt => pt.Result); 58 | } 59 | private async void IdeModel_ModelChangedAsync(object sender, EventArgs e) 60 | { 61 | await SendChangesAsync(); 62 | } 63 | private async void SettingProperty_ChangedAsync(object sender, EventArgs e) 64 | { 65 | await SendChangesAsync(); 66 | } 67 | public Task SendUpdateAsync() 68 | { 69 | return SendChangesAsync(); 70 | } 71 | private async Task SendChangesAsync() 72 | { 73 | if (await RequiredPropertiesAreSetAsync()) 74 | { 75 | SendModel(new RemoteIDEModel(IdeModel)); 76 | } 77 | } 78 | protected abstract void SendModel(RemoteIDEModel remoteModel); 79 | public virtual void Dispose() 80 | { 81 | foreach (var property in SettingProperties) 82 | { 83 | property.Changing += SettingProperty_ChangingAsync; 84 | property.Changed += SettingProperty_ChangedAsync; 85 | } 86 | UserSettings.ShowSelfChanged -= UserSettings_ShowSelfChangedAsync; 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /TeamCoding/VisualStudio/Models/ChangePersisters/RedisPersister/RedisLocalModelPersister.cs: -------------------------------------------------------------------------------- 1 | using StackExchange.Redis; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | using TeamCoding.Events; 9 | using TeamCoding.Extensions; 10 | 11 | namespace TeamCoding.VisualStudio.Models.ChangePersisters.RedisPersister 12 | { 13 | public class RedisLocalModelPersister : LocalModelPersisterBase 14 | { 15 | public RedisLocalModelPersister(LocalIDEModel model) : base(model, TeamCodingPackage.Current.Settings.SharedSettings.RedisServerProperty) { } 16 | protected override void SendModel(RemoteIDEModel remoteModel) 17 | { 18 | using (var ms = new MemoryStream()) 19 | { 20 | ProtoBuf.Serializer.Serialize(ms, remoteModel); 21 | TeamCodingPackage.Current.Redis.PublishAsync(RedisRemoteModelPersister.ModelPersisterChannel, ms.ToArray()).HandleException(); 22 | } 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /TeamCoding/VisualStudio/Models/ChangePersisters/RedisPersister/RedisRemoteModelPersister.cs: -------------------------------------------------------------------------------- 1 | using StackExchange.Redis; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | using TeamCoding.Documents; 9 | using TeamCoding.Extensions; 10 | 11 | namespace TeamCoding.VisualStudio.Models.ChangePersisters.RedisPersister 12 | { 13 | public class RedisRemoteModelPersister : RemoteModelPersisterBase 14 | { 15 | public const string ModelPersisterChannel = "TeamCoding.ModelPersister"; 16 | private readonly Task SubscribeTask; 17 | public RedisRemoteModelPersister() 18 | { 19 | SubscribeTask = TeamCodingPackage.Current.Redis.SubscribeAsync(ModelPersisterChannel, Redis_RemoteModelReceived).HandleException(); 20 | } 21 | private void Redis_RemoteModelReceived(RedisChannel channel, RedisValue value) 22 | { 23 | using (var ms = new MemoryStream(value)) 24 | { 25 | OnRemoteModelReceived(ProtoBuf.Serializer.Deserialize(ms)); 26 | } 27 | } 28 | public override void Dispose() 29 | { 30 | Task.WaitAll(SubscribeTask); 31 | base.Dispose(); 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /TeamCoding/VisualStudio/Models/ChangePersisters/RemoteModelPersisterBase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using TeamCoding.Documents; 7 | using TeamCoding.Options; 8 | 9 | namespace TeamCoding.VisualStudio.Models.ChangePersisters 10 | { 11 | public abstract class RemoteModelPersisterBase : IRemoteModelPersister 12 | { 13 | // TODO: Cache all the different lookups we want to do 14 | public event EventHandler RemoteModelReceived; 15 | private readonly Dictionary RemoteModels = new Dictionary(); 16 | private IRemotelyAccessedDocumentData[] CachedOpenFiles = null; 17 | 18 | public IEnumerable GetOpenFiles() 19 | { 20 | if (CachedOpenFiles != null) 21 | { 22 | return CachedOpenFiles; 23 | } 24 | else 25 | { 26 | return CachedOpenFiles = RemoteModels.Values.SelectMany(model => model.OpenFiles.Select(of => new RemotelyAccessedDocumentData() 27 | { 28 | Repository = of.RepoUrl, 29 | RepositoryBranch = of.RepoBranch, 30 | IdeUserIdentity = model.IDEUserIdentity, 31 | RelativePath = of.RelativePath, 32 | BeingEdited = of.BeingEdited, 33 | HasFocus = of == model.OpenFiles.OrderByDescending(oof => oof.LastActioned).FirstOrDefault(), 34 | CaretPositionInfo = of.CaretPositionInfo 35 | })).ToArray(); 36 | } 37 | } 38 | 39 | public void ClearRemoteModels() 40 | { 41 | RemoteModels.Clear(); 42 | CachedOpenFiles = null; 43 | } 44 | public void OnRemoteModelReceived(RemoteIDEModel remoteModel) 45 | { 46 | TeamCodingPackage.Current.IDEWrapper.InvokeAsync(() => 47 | { 48 | CachedOpenFiles = null; 49 | if (remoteModel == null) 50 | { 51 | ClearRemoteModels(); 52 | RemoteModelReceived?.Invoke(this, EventArgs.Empty); 53 | } 54 | else if (remoteModel.Id == LocalIDEModel.Id.Value && !TeamCodingPackage.Current.Settings.UserSettings.ShowSelf) 55 | { 56 | // If the remote model is the same as the local model, then remove it if it's there already 57 | if (RemoteModels.ContainsKey(remoteModel.Id)) 58 | { 59 | RemoteModels.Remove(remoteModel.Id); 60 | RemoteModelReceived?.Invoke(this, EventArgs.Empty); 61 | } 62 | } 63 | else if (remoteModel.OpenFiles.Count == 0) 64 | { 65 | if (RemoteModels.ContainsKey(remoteModel.Id)) 66 | { 67 | RemoteModels.Remove(remoteModel.Id); 68 | RemoteModelReceived?.Invoke(this, EventArgs.Empty); 69 | } 70 | } 71 | else 72 | { 73 | RemoteModels[remoteModel.Id] = remoteModel; 74 | RemoteModelReceived?.Invoke(this, EventArgs.Empty); 75 | } 76 | }); 77 | } 78 | public virtual void Dispose() 79 | { 80 | 81 | } 82 | 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /TeamCoding/VisualStudio/Models/ChangePersisters/SlackPersister/SlackLocalModelPersister.cs: -------------------------------------------------------------------------------- 1 | using SlackConnector.Models; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Reflection; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | using TeamCoding.Extensions; 10 | using TeamCoding.VisualStudio.Models.ChangePersisters; 11 | 12 | namespace TeamCoding.VisualStudio.Models.ChangePersisters.SlackPersister 13 | { 14 | public class SlackLocalModelPersister : LocalModelPersisterBase 15 | { 16 | public SlackLocalModelPersister(LocalIDEModel model) 17 | :base(model, 18 | TeamCodingPackage.Current.Settings.SharedSettings.SlackTokenProperty, 19 | TeamCodingPackage.Current.Settings.SharedSettings.SlackChannelProperty) 20 | { 21 | 22 | } 23 | protected override void SendModel(RemoteIDEModel remoteModel) 24 | { 25 | TeamCodingPackage.Current.Slack.PublishAsync(TeamCodingPackage.Current.ObjectSlackMessageConverter.ToBotMessage(remoteModel)).HandleException(); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /TeamCoding/VisualStudio/Models/ChangePersisters/SlackPersister/SlackRemoteModelPersister.cs: -------------------------------------------------------------------------------- 1 | using SlackConnector.Models; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Reflection; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | using TeamCoding.Extensions; 10 | 11 | namespace TeamCoding.VisualStudio.Models.ChangePersisters.SlackPersister 12 | { 13 | public class SlackRemoteModelPersister : RemoteModelPersisterBase 14 | { 15 | private readonly Task SubscribeTask; 16 | public SlackRemoteModelPersister() 17 | { 18 | SubscribeTask = TeamCodingPackage.Current.Slack.Subscribe(Slack_RemoteModelReceived).HandleException(); 19 | } 20 | private void Slack_RemoteModelReceived(SlackMessage message) 21 | { 22 | try 23 | { 24 | var receivedMessage = Newtonsoft.Json.JsonConvert.DeserializeObject(message.RawData); 25 | 26 | var receivedModel = TeamCodingPackage.Current.ObjectSlackMessageConverter.ToIdeModel(receivedMessage); 27 | 28 | receivedModel.IDEUserIdentity.ImageUrl = receivedModel.IDEUserIdentity.ImageUrl?.TrimStart('<')?.TrimEnd('>'); 29 | if (receivedModel.IDEUserIdentity.DisplayName?.Contains("|") ?? false) 30 | { 31 | receivedModel.IDEUserIdentity.DisplayName = receivedModel.IDEUserIdentity.DisplayName?.Substring(receivedModel.IDEUserIdentity.DisplayName.IndexOf('|') + 1).TrimEnd('>'); 32 | } 33 | if (receivedModel.IDEUserIdentity.Id.Contains("|")) 34 | { 35 | receivedModel.IDEUserIdentity.Id = receivedModel.IDEUserIdentity.Id.Substring(receivedModel.IDEUserIdentity.Id.IndexOf('|') + 1).TrimEnd('>'); 36 | } 37 | 38 | foreach (var openFile in receivedModel.OpenFiles) 39 | { 40 | openFile.RepoUrl = openFile.RepoUrl?.TrimStart('<')?.TrimEnd('>'); 41 | } 42 | 43 | OnRemoteModelReceived(receivedModel); 44 | } 45 | catch(Exception ex) 46 | { 47 | TeamCodingPackage.Current.Logger.WriteError("Error parsing Slack Message", ex); 48 | } 49 | } 50 | 51 | 52 | public override void Dispose() 53 | { 54 | Task.WaitAll(SubscribeTask); 55 | base.Dispose(); 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /TeamCoding/VisualStudio/Models/ChangePersisters/SqlServerPersister/SqlServerLocalModelPersister.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | using TeamCoding.Documents; 8 | using Dapper; 9 | using TeamCoding.VisualStudio.Models.ChangePersisters; 10 | 11 | namespace TeamCoding.VisualStudio.Models.ChangePersisters.SqlServerPersister 12 | { 13 | public class SqlServerLocalModelPersister : LocalModelPersisterBase 14 | { 15 | private readonly SqlConnectionWrapper ConnectionWrapper; 16 | public SqlServerLocalModelPersister(SqlConnectionWrapper connectionWrapper, LocalIDEModel model) 17 | :base(model, TeamCodingPackage.Current.Settings.SharedSettings.SqlServerConnectionStringProperty) 18 | { 19 | ConnectionWrapper = connectionWrapper; 20 | } 21 | protected override void SendModel(RemoteIDEModel remoteModel) => ConnectionWrapper.UpdateModel(remoteModel); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /TeamCoding/VisualStudio/Models/ChangePersisters/SqlServerPersister/SqlServerRemoteModelPersister.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Data.SqlClient; 4 | using System.IO; 5 | using System.Linq; 6 | using TeamCoding.Documents; 7 | using Dapper; 8 | 9 | namespace TeamCoding.VisualStudio.Models.ChangePersisters.SqlServerPersister 10 | { 11 | public class SqlServerRemoteModelPersister : RemoteModelPersisterBase 12 | { 13 | private readonly SqlConnectionWrapper ConnectionWrapper; 14 | public SqlServerRemoteModelPersister(SqlConnectionWrapper connectionWrapper) 15 | { 16 | ConnectionWrapper = connectionWrapper; 17 | ConnectionWrapper.DataChanged += ConnectionWrapper_DataChanged; 18 | } 19 | private void ConnectionWrapper_DataChanged(object sender, EventArgs e) 20 | { 21 | ClearRemoteModels(); 22 | foreach(var queryData in ConnectionWrapper.GetData()) 23 | { 24 | using (var ms = new MemoryStream(queryData.Model)) 25 | { 26 | OnRemoteModelReceived(ProtoBuf.Serializer.Deserialize(ms)); 27 | } 28 | } 29 | } 30 | public override void Dispose() 31 | { 32 | ConnectionWrapper.DataChanged -= ConnectionWrapper_DataChanged; 33 | base.Dispose(); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /TeamCoding/VisualStudio/Models/ChangePersisters/SqlServerPersister/SqlWatcher.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Data.SqlClient; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace TeamCoding.VisualStudio.Models.ChangePersisters.SqlServerPersister 9 | { 10 | public class SqlWatcher 11 | { // http://stackoverflow.com/questions/20503286/using-sqldependency-with-named-queues 12 | private readonly string ConnectionString; 13 | private readonly string ListenerQuery; 14 | private SqlDependency Dependency; 15 | public event EventHandler DataChanged; 16 | public SqlWatcher(string connectionString, string listenerQuery) 17 | { 18 | ConnectionString = connectionString; 19 | ListenerQuery = listenerQuery; 20 | } 21 | public void Start() 22 | { 23 | SqlDependency.Start(ConnectionString); 24 | ListenForChanges(); 25 | } 26 | public void Stop() 27 | { 28 | SqlDependency.Stop(ConnectionString); 29 | } 30 | private void ListenForChanges() 31 | { 32 | //Remove existing dependency, if necessary 33 | if (Dependency != null) 34 | { 35 | Dependency.OnChange -= Dependency_OnChange; 36 | Dependency = null; 37 | } 38 | 39 | SqlConnection connection = new SqlConnection(ConnectionString); 40 | connection.Open(); 41 | 42 | SqlCommand command = new SqlCommand(ListenerQuery, connection); 43 | 44 | Dependency = new SqlDependency(command); 45 | 46 | // Subscribe to the SqlDependency event. 47 | Dependency.OnChange += Dependency_OnChange; 48 | 49 | SqlDependency.Start(ConnectionString); 50 | 51 | command.ExecuteReader(); 52 | 53 | //Perform this action when SQL notifies of a change 54 | DataChanged?.Invoke(this, EventArgs.Empty); 55 | 56 | connection.Close(); 57 | } 58 | 59 | private void Dependency_OnChange(object sender, SqlNotificationEventArgs e) 60 | { 61 | if (e.Source == SqlNotificationSource.Data || e.Source == SqlNotificationSource.Timeout) 62 | { 63 | ListenForChanges(); 64 | } 65 | else 66 | { 67 | TeamCodingPackage.Current.Logger.WriteError($"Data not refreshed due to unexpected SqlNotificationEventArgs: Source={e.Source}, Info={e.Info}, Type={e.Type}"); 68 | } 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /TeamCoding/VisualStudio/Models/ChangePersisters/WindowsServicePersister/WinServiceLocalModelPersister.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using TeamCoding.Options; 7 | 8 | namespace TeamCoding.VisualStudio.Models.ChangePersisters.WindowsServicePersister 9 | { 10 | public class WinServiceLocalModelPersister : LocalModelPersisterBase 11 | { 12 | private readonly WinServiceClient Client; 13 | public WinServiceLocalModelPersister(WinServiceClient client, LocalIDEModel model) : base(model, TeamCodingPackage.Current.Settings.SharedSettings.WinServiceIPAddressProperty) 14 | { 15 | Client = client; 16 | } 17 | protected override void SendModel(RemoteIDEModel remoteModel) 18 | { 19 | Client.SendModel(remoteModel); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /TeamCoding/VisualStudio/Models/ChangePersisters/WindowsServicePersister/WinServiceRemoteModelPersister.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace TeamCoding.VisualStudio.Models.ChangePersisters.WindowsServicePersister 9 | { 10 | public class WinServiceRemoteModelPersister : RemoteModelPersisterBase 11 | { 12 | private readonly WinServiceClient Client; 13 | public WinServiceRemoteModelPersister(WinServiceClient client) 14 | { 15 | Client = client; 16 | Client.MessageReceived += Client_MessageReceived; 17 | } 18 | private void Client_MessageReceived(object sender, WinServiceClient.MessageReceivedEventArgs e) 19 | { 20 | using (var ms = new MemoryStream(e.Message)) 21 | { 22 | OnRemoteModelReceived(ProtoBuf.Serializer.Deserialize(ms)); 23 | } 24 | } 25 | public override void Dispose() 26 | { 27 | Client.MessageReceived -= Client_MessageReceived; 28 | base.Dispose(); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /TeamCoding/VisualStudio/Models/RemoteIDEModel.cs: -------------------------------------------------------------------------------- 1 | using ProtoBuf; 2 | using System; 3 | using System.Collections.Generic; 4 | using TeamCoding.Documents; 5 | using TeamCoding.IdentityManagement; 6 | 7 | namespace TeamCoding.VisualStudio.Models 8 | { 9 | /// 10 | /// Represents an IDE being used remotely 11 | /// 12 | [ProtoContract] 13 | public class RemoteIDEModel 14 | { 15 | [ProtoMember(1)] 16 | public string Id; 17 | [ProtoMember(2)] 18 | public UserIdentity IDEUserIdentity; 19 | [ProtoIgnore] 20 | private List _OpenFiles; 21 | [ProtoMember(3)] 22 | public List OpenFiles 23 | { 24 | get { return _OpenFiles ?? (_OpenFiles = new List()); } 25 | private set { _OpenFiles = value; } 26 | } 27 | 28 | public RemoteIDEModel() { } // For protobuf 29 | public RemoteIDEModel(LocalIDEModel localModel) 30 | { 31 | Id = LocalIDEModel.Id.Value; 32 | IDEUserIdentity = TeamCodingPackage.Current.IdentityProvider.GetIdentity(); 33 | OpenFiles = new List(localModel.OpenDocs()); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /TeamCoding/VisualStudio/SolutionEventsHandler.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.Shell; 2 | using Microsoft.VisualStudio.Shell.Interop; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace TeamCoding.VisualStudio 10 | { 11 | public class SolutionEventsHandler : IVsSolutionEvents 12 | { 13 | public int OnAfterOpenSolution(object pUnkReserved, int fNewSolution) 14 | { 15 | if (fNewSolution == 0) 16 | { 17 | try 18 | { 19 | TeamCodingPackage.Current.Settings.LoadFromJsonFile(); 20 | } 21 | catch (Exception ex) when (!System.Diagnostics.Debugger.IsAttached) 22 | { 23 | TeamCodingPackage.Current.Logger.WriteError(ex); 24 | } 25 | } 26 | 27 | return Microsoft.VisualStudio.VSConstants.S_OK; 28 | } 29 | public int OnQueryBackgroundLoadProjectBatch(out bool pfShouldDelayLoadToNextIdle) 30 | { 31 | pfShouldDelayLoadToNextIdle = false; 32 | return Microsoft.VisualStudio.VSConstants.S_OK; 33 | } 34 | public int OnAfterBackgroundSolutionLoadComplete() => Microsoft.VisualStudio.VSConstants.S_OK; 35 | public int OnAfterLoadProjectBatch(bool fIsBackgroundIdleBatch) => Microsoft.VisualStudio.VSConstants.S_OK; 36 | public int OnBeforeBackgroundSolutionLoadBegins() => Microsoft.VisualStudio.VSConstants.S_OK; 37 | public int OnBeforeLoadProjectBatch(bool fIsBackgroundIdleBatch) => Microsoft.VisualStudio.VSConstants.S_OK; 38 | public void OnBeforeOpenProject(ref Guid guidProjectID, ref Guid guidProjectType, string pszFileName) { } 39 | public int OnAfterOpenProject(IVsHierarchy pHierarchy, int fAdded) => Microsoft.VisualStudio.VSConstants.S_OK; 40 | public int OnQueryCloseProject(IVsHierarchy pHierarchy, int fRemoving, ref int pfCancel) => Microsoft.VisualStudio.VSConstants.S_OK; 41 | public int OnBeforeCloseProject(IVsHierarchy pHierarchy, int fRemoved) => Microsoft.VisualStudio.VSConstants.S_OK; 42 | public int OnAfterLoadProject(IVsHierarchy pStubHierarchy, IVsHierarchy pRealHierarchy) => Microsoft.VisualStudio.VSConstants.S_OK; 43 | public int OnQueryUnloadProject(IVsHierarchy pRealHierarchy, ref int pfCancel) => Microsoft.VisualStudio.VSConstants.S_OK; 44 | public int OnBeforeUnloadProject(IVsHierarchy pRealHierarchy, IVsHierarchy pStubHierarchy) => Microsoft.VisualStudio.VSConstants.S_OK; 45 | public int OnQueryCloseSolution(object pUnkReserved, ref int pfCancel) => Microsoft.VisualStudio.VSConstants.S_OK; 46 | public int OnBeforeCloseSolution(object pUnkReserved) => Microsoft.VisualStudio.VSConstants.S_OK; 47 | public int OnAfterCloseSolution(object pUnkReserved) => Microsoft.VisualStudio.VSConstants.S_OK; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /TeamCoding/VisualStudio/TextAdornment/TextAdornmentTextViewCreationListener.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // Copyright (c) Company. All rights reserved. 4 | // 5 | //------------------------------------------------------------------------------ 6 | 7 | using System.ComponentModel.Composition; 8 | using Microsoft.VisualStudio.Text.Editor; 9 | using Microsoft.VisualStudio.Utilities; 10 | using TeamCoding.Extensions; 11 | using System; 12 | 13 | namespace TeamCoding.VisualStudio.TextAdornment 14 | { 15 | /// 16 | /// Establishes an to place the adornment on and exports the 17 | /// that instantiates the adornment on the event of a 's creation 18 | /// 19 | [Export(typeof(IWpfTextViewCreationListener))] 20 | [ContentType("text")] 21 | [TextViewRole(PredefinedTextViewRoles.Document)] 22 | internal sealed class TextAdornmentTextViewCreationListener : IWpfTextViewCreationListener 23 | { 24 | // Disable "Field is never assigned to..." and "Field is never used" compiler's warnings. Justification: the field is used by MEF. 25 | #pragma warning disable 649, 169 26 | 27 | /// 28 | /// Defines the adornment layer for the adornment. This layer is ordered 29 | /// after the selection layer in the Z-order 30 | /// 31 | [Export(typeof(AdornmentLayerDefinition))] 32 | [Name("TextAdornment")] 33 | [Order(After = PredefinedAdornmentLayers.Text)] 34 | private AdornmentLayerDefinition editorAdornmentLayer; 35 | 36 | #pragma warning restore 649, 169 37 | 38 | #region IWpfTextViewCreationListener 39 | 40 | /// 41 | /// Called when a text view having matching roles is created over a text data model having a matching content type. 42 | /// Instantiates a TextAdornment manager when the textView is created. 43 | /// 44 | /// The upon which the adornment should be placed 45 | public void TextViewCreated(IWpfTextView textView) 46 | { 47 | try 48 | { 49 | if (TeamCodingPackage.Current.SourceControlRepo.GetRepoDocInfo(textView.GetTextDocumentFilePath()) == null) return; 50 | 51 | new TextAdornment(textView); 52 | } 53 | catch (Exception ex) when (!System.Diagnostics.Debugger.IsAttached) 54 | { 55 | TeamCodingPackage.Current.Logger.WriteError(ex); 56 | } 57 | } 58 | 59 | #endregion 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /TeamCoding/VisualStudio/ToolWindows/OverviewWindow/Overview.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | using Microsoft.VisualStudio.Shell; 4 | namespace TeamCoding.VisualStudio.ToolWindows.OverviewWindow 5 | { 6 | 7 | /// 8 | /// This class implements the tool window exposed by this package and hosts a user control. 9 | /// 10 | /// 11 | /// In Visual Studio tool windows are composed of a frame (implemented by the shell) and a pane, 12 | /// usually implemented by the package implementer. 13 | /// 14 | /// This class derives from the ToolWindowPane class provided from the MPF in order to use its 15 | /// implementation of the IVsUIElementPane interface. 16 | /// 17 | /// 18 | [Guid("042eee56-d447-4dbd-b5ae-6009269f1e54")] 19 | public class Overview : ToolWindowPane 20 | { 21 | /// 22 | /// Initializes a new instance of the class. 23 | /// 24 | public Overview() : base(null) 25 | { 26 | this.Caption = "TeamCoding Overview"; 27 | 28 | // This is the user control hosted by the tool window; Note that, even if this class implements IDisposable, 29 | // we are not calling Dispose on this object. This is because ToolWindowPane calls Dispose on 30 | // the object returned by the Content property. 31 | this.Content = new OverviewControl(); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /TeamCoding/VisualStudio/ToolWindows/OverviewWindow/OverviewCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel.Design; 3 | using System.Globalization; 4 | using Microsoft.VisualStudio.Shell; 5 | using Microsoft.VisualStudio.Shell.Interop; 6 | 7 | namespace TeamCoding.VisualStudio.ToolWindows.OverviewWindow 8 | { 9 | /// 10 | /// Command handler 11 | /// 12 | internal sealed class OverviewCommand 13 | { 14 | /// 15 | /// Command ID. 16 | /// 17 | public const int CommandId = 0x0100; 18 | 19 | /// 20 | /// Command menu group (command set GUID). 21 | /// 22 | public static readonly Guid CommandSet = new Guid("76d6d986-dfad-4322-a2d8-5bec8525339f"); 23 | 24 | /// 25 | /// VS Package that provides this command, not null. 26 | /// 27 | private readonly Package Package; 28 | 29 | /// 30 | /// Initializes a new instance of the class. 31 | /// Adds our command handlers for menu (commands must exist in the command table file) 32 | /// 33 | /// Owner package, not null. 34 | private OverviewCommand(Package package) 35 | { 36 | Package = package ?? throw new ArgumentNullException(nameof(package)); 37 | 38 | OleMenuCommandService commandService = ServiceProvider.GetService(typeof(IMenuCommandService)) as OleMenuCommandService; 39 | if (commandService != null) 40 | { 41 | var menuCommandID = new CommandID(CommandSet, CommandId); 42 | var menuItem = new MenuCommand(ShowToolWindow, menuCommandID); 43 | commandService.AddCommand(menuItem); 44 | } 45 | } 46 | 47 | /// 48 | /// Gets the instance of the command. 49 | /// 50 | public static OverviewCommand Instance 51 | { 52 | get; 53 | private set; 54 | } 55 | 56 | /// 57 | /// Gets the service provider from the owner package. 58 | /// 59 | private IServiceProvider ServiceProvider 60 | { 61 | get 62 | { 63 | return this.Package; 64 | } 65 | } 66 | 67 | /// 68 | /// Initializes the singleton instance of the command. 69 | /// 70 | /// Owner package, not null. 71 | public static void Initialize(Package package) 72 | { 73 | Instance = new OverviewCommand(package); 74 | } 75 | 76 | /// 77 | /// Shows the tool window when the menu item is clicked. 78 | /// 79 | /// The event sender. 80 | /// The event args. 81 | private void ShowToolWindow(object sender, EventArgs e) 82 | { 83 | // Get the instance number 0 of this tool window. This window is single instance so this instance 84 | // is actually the only one. 85 | // The last flag is set to true so that if the tool window does not exists it will be created. 86 | ToolWindowPane window = this.Package.FindToolWindow(typeof(Overview), 0, true); 87 | if ((null == window) || (null == window.Frame)) 88 | { 89 | throw new NotSupportedException("Cannot create tool window"); 90 | } 91 | 92 | IVsWindowFrame windowFrame = (IVsWindowFrame)window.Frame; 93 | Microsoft.VisualStudio.ErrorHandler.ThrowOnFailure(windowFrame.Show()); 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /TeamCoding/VisualStudio/ToolWindows/OverviewWindow/OverviewControl.xaml: -------------------------------------------------------------------------------- 1 |  10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /TeamCoding/VisualStudio/ToolWindows/OverviewWindow/OverviewControl.xaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics.CodeAnalysis; 4 | using System.Linq; 5 | using System.Windows; 6 | using System.Windows.Controls; 7 | using TeamCoding.Documents; 8 | using TeamCoding.Extensions; 9 | 10 | namespace TeamCoding.VisualStudio.ToolWindows.OverviewWindow 11 | { 12 | 13 | /// 14 | /// Interaction logic for OverviewControl. 15 | /// 16 | public partial class OverviewControl : UserControl 17 | { 18 | private HashSet ExpandedItems = new HashSet(); 19 | 20 | private HashSet Documents; 21 | 22 | /// 23 | /// Initializes a new instance of the class. 24 | /// 25 | public OverviewControl() 26 | { 27 | InitializeComponent(); 28 | 29 | TeamCodingPackage.Current.RemoteModelChangeManager.RemoteModelReceived += RemoteModelChangeManager_RemoteModelReceived; 30 | } 31 | 32 | private void RemoteModelChangeManager_RemoteModelReceived(object sender, EventArgs e) 33 | { 34 | var newDocuments = new HashSet(TeamCodingPackage.Current.RemoteModelChangeManager.GetOpenFiles(), OverviewWindowEqualityComparer.Instance); 35 | 36 | if (Documents == null || !(Documents.IsSubsetOf(newDocuments) && Documents.IsSupersetOf(newDocuments))) 37 | { 38 | Documents = newDocuments; 39 | 40 | tvUserDocs.DataContext = (from of in Documents 41 | group of by of.IdeUserIdentity into ofg 42 | select new 43 | { 44 | Identity = ofg.Key, 45 | UserAvatarModel = TeamCodingPackage.Current.UserImages.CreateUserAvatarModel(ofg.Key), 46 | Documents = ofg.ToArray() 47 | }).ToArray(); 48 | 49 | var treeItems = tvUserDocs.FindChildren(); 50 | 51 | foreach (var node in treeItems) 52 | { 53 | if (ExpandedItems.Contains((string)node.Tag)) 54 | { 55 | node.IsExpanded = true; 56 | } 57 | } 58 | } 59 | } 60 | 61 | private void TreeViewItem_Expanded(object sender, RoutedEventArgs e) 62 | { 63 | ExpandedItems.Add((string)((TreeViewItem)sender).Tag); 64 | } 65 | 66 | private void TreeViewItem_Collapsed(object sender, RoutedEventArgs e) 67 | { 68 | ExpandedItems.Remove((string)((TreeViewItem)sender).Tag); 69 | } 70 | 71 | class OverviewWindowEqualityComparer : IEqualityComparer 72 | { 73 | public static OverviewWindowEqualityComparer Instance { get; } = new OverviewWindowEqualityComparer(); 74 | 75 | public bool Equals(IRemotelyAccessedDocumentData x, IRemotelyAccessedDocumentData y) 76 | { 77 | if (x == null ^ y == null) 78 | return false; 79 | 80 | return x.Repository == y.Repository && 81 | x.RepositoryBranch == y.RepositoryBranch && 82 | x.RelativePath == y.RelativePath && 83 | x.IdeUserIdentity.Id == y.IdeUserIdentity.Id && 84 | x.BeingEdited == y.BeingEdited && 85 | x.HasFocus == y.HasFocus 86 | ; 87 | } 88 | 89 | public int GetHashCode(IRemotelyAccessedDocumentData obj) 90 | { 91 | var hash = 17; 92 | hash = hash * 31 + obj.Repository.GetHashCode(); 93 | 94 | if (!string.IsNullOrEmpty(obj.RepositoryBranch)) 95 | hash = hash * 31 + obj.RepositoryBranch.GetHashCode(); 96 | 97 | hash = hash * 31 + obj.IdeUserIdentity.Id.GetHashCode(); 98 | hash = hash * 31 + obj.BeingEdited.GetHashCode(); 99 | hash = hash * 31 + obj.HasFocus.GetHashCode(); 100 | 101 | return hash; 102 | } 103 | } 104 | } 105 | } -------------------------------------------------------------------------------- /TeamCoding/VisualStudio/UserColours.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using System.Windows.Media; 7 | using TeamCoding.IdentityManagement; 8 | 9 | namespace TeamCoding.VisualStudio 10 | { 11 | public static class UserColours 12 | { 13 | private static readonly Dictionary UserToBrush = new Dictionary(); 14 | private static readonly Dictionary UserToPen = new Dictionary(); 15 | public static Color GetUserColour(IUserIdentity user) 16 | { 17 | return VisuallyDistinctColours.GetColourFromSeed(user.Id.GetHashCode()); 18 | } 19 | public static SolidColorBrush GetUserBrush(IUserIdentity user) 20 | { 21 | var hash = user.Id.GetHashCode(); 22 | if (!UserToBrush.TryGetValue(hash, out var brush)) 23 | { 24 | brush = new SolidColorBrush(VisuallyDistinctColours.GetColourFromSeed(hash)); 25 | brush.Freeze(); 26 | } 27 | return brush; 28 | } 29 | public static Pen GetUserPen(IUserIdentity user) 30 | { 31 | var hash = user.Id.GetHashCode(); 32 | if (!UserToPen.TryGetValue(hash, out var pen)) 33 | { 34 | pen = new Pen(GetUserBrush(user), 1); 35 | pen.Freeze(); 36 | } 37 | return pen; 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /TeamCoding/VisualStudio/VisuallyDistinctColours.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using System.Windows.Media; 7 | 8 | namespace TeamCoding.VisualStudio 9 | { 10 | public class VisuallyDistinctColours 11 | { 12 | private static readonly List Colours = new List 13 | { 14 | UIntToColor(0xFFFFB300), //Vivid Yellow 15 | UIntToColor(0xFF803E75), //Strong Purple 16 | UIntToColor(0xFFFF6800), //Vivid Orange 17 | UIntToColor(0xFFA6BDD7), //Very Light Blue 18 | UIntToColor(0xFFC10020), //Vivid Red 19 | UIntToColor(0xFFCEA262), //Grayish Yellow 20 | UIntToColor(0xFF817066), //Medium Gray 21 | UIntToColor(0xFF007D34), //Vivid Green 22 | UIntToColor(0xFFF6768E), //Strong Purplish Pink 23 | UIntToColor(0xFF00538A), //Strong Blue 24 | UIntToColor(0xFFFF7A5C), //Strong Yellowish Pink 25 | UIntToColor(0xFF53377A), //Strong Violet 26 | UIntToColor(0xFFFF8E00), //Vivid Orange Yellow 27 | UIntToColor(0xFFB32851), //Strong Purplish Red 28 | UIntToColor(0xFFF4C800), //Vivid Greenish Yellow 29 | UIntToColor(0xFF7F180D), //Strong Reddish Brown 30 | UIntToColor(0xFF93AA00), //Vivid Yellowish Green 31 | UIntToColor(0xFF593315), //Deep Yellowish Brown 32 | UIntToColor(0xFFF13A13), //Vivid Reddish Orange 33 | UIntToColor(0xFF232C16), //Dark Olive Green 34 | }; 35 | private static Color UIntToColor(uint color) 36 | { 37 | var a = (byte)(color >> 24); 38 | var r = (byte)(color >> 16); 39 | var g = (byte)(color >> 8); 40 | var b = (byte)(color >> 0); 41 | return Color.FromArgb(a, r, g, b); 42 | } 43 | public static Color GetColourFromSeed(int seed) 44 | { 45 | return Colours[Math.Abs(seed % Colours.Count)]; 46 | } 47 | 48 | public static Brush GetTextBrushFromBackgroundColour(Color backgroundColour) 49 | { 50 | var r = F(backgroundColour.R / 255.0); 51 | var g = F(backgroundColour.G / 255.0); 52 | var b = F(backgroundColour.B / 255.0); 53 | 54 | var l = 0.2126 * r + 0.7152 * g + 0.0722 * b; 55 | 56 | if (l > 0.179) 57 | return Brushes.Black; 58 | else 59 | return Brushes.White; 60 | } 61 | 62 | private static double F(double c) 63 | { 64 | if (c <= 0.03928) 65 | { 66 | c = c / 12.92; 67 | } 68 | else 69 | { 70 | c = Math.Pow((c + 0.055) / 1.055, 2.4f); 71 | } 72 | 73 | return c; 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /TeamCoding/source.extension.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------------------ 2 | // 3 | // This file was generated by Extensibility Tools v1.10.184 4 | // 5 | // ------------------------------------------------------------------------------ 6 | namespace TeamCoding 7 | { 8 | static class Vsix 9 | { 10 | public const string Id = "TeamCoding.George Duckett.307fe055-6f5e-40d7-bb11-13abd012e528"; 11 | public const string Name = "TeamCoding"; 12 | public const string Description = @"The Team Coding Visual Studio extension helps discover team members who are working in the same area of a source controlled solution as you. It displays icons on tabs, text in CodeLens is as well as showing a coloured caret where they are working for C# and visual basic code."; 13 | public const string Language = "en-US"; 14 | public const string Version = "0.8.0"; 15 | public const string Author = "George Duckett"; 16 | public const string Tags = "Collaboration, Teams, Source Control, Git, Pair Programming, Code Review"; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /TeamCoding/source.extension.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/georgeduckett/TeamCoding/5ad8f1d221730348662c8c11f3fb57a1ce79e642/TeamCoding/source.extension.ico -------------------------------------------------------------------------------- /TeamCoding/source.extension.vsixmanifest: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | TeamCoding 6 | The Team Coding Visual Studio extension helps discover team members who are working in the same area of a source controlled solution as you. It displays icons on tabs, text in CodeLens is as well as showing a coloured caret where they are working. 7 | https://github.com/georgeduckett/TeamCoding 8 | Resources\License.txt 9 | Resources\GettingStarted.txt 10 | Resources\TeamCodingPackage.ico 11 | Resources\PreviewImage.png 12 | Collaboration, Teams, Source Control, Git, Pair Programming, Code Review 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 | 3 | install: 4 | - ps: >- 5 | (New-Object Net.WebClient).DownloadString("https://raw.github.com/madskristensen/ExtensionScripts/master/AppVeyor/vsix.ps1") | iex; 6 | 7 | before_build: 8 | - ps: Vsix-IncrementVsixVersion | Vsix-UpdateBuildVersion 9 | - ps: Vsix-TokenReplacement TeamCoding\source.extension.cs 'Version = "([0-9\\.]+)"' 'Version = "{version}"' 10 | 11 | build_script: 12 | - nuget restore -Verbosity quiet 13 | - msbuild /p:configuration=Release /p:DeployExtension=false /p:ZipPackageCompressionLevel=normal /v:m 14 | 15 | after_test: 16 | - 7z a TeamCoding.WindowsService.zip %APPVEYOR_BUILD_FOLDER%\\TeamCoding.WindowsService\\bin\\Release\\*.* 17 | - ps: Get-ChildItem .\*.zip | % { Push-AppveyorArtifact $_.FullName -FileName $_.Name } 18 | - ps: Vsix-PushArtifacts 19 | # | Vsix-PublishToGallery 20 | 21 | #on_finish: 22 | # - ps: $blockRdp = $true; iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1')) 23 | 24 | 25 | deploy: 26 | - provider: Environment 27 | name: github 28 | on: 29 | appveyor_repo_tag: true -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | Team Coding 2 | -- 3 | 4 | **About:** The Team Coding Visual Studio extension helps discover team members who are working in the same area of a source controlled solution as you. It displays icons on tabs, text in CodeLens is as well as showing a coloured caret where they are working for C# and visual basic code. 5 | 6 | **Quickstart:** There are 2 ways Team Coding can be minimally configured. 7 | In the options menu (under Team Coding) where you can also set user settings and/or 8 | by opening a repository with a `teamcoding.json` file in it (shared settings will be taken from there). 9 | An example of a valid `teamcoding.json` file can be found in the options menu. 10 | The borders around the user image at the top of open tabs are coloured white for the user's selected tab and grey for an edited document tab. 11 | You can test the extension on your own, or test that you've got a sharing method correctly set up by ticking the "Show yourself" checkbox. 12 | 13 | **Settings:** The settings are located at Tools->Options->Team Coding. 14 | There are configuration options for your user identity (how you appear to others) and how others appear to you, as well as configuration options for how to share open document information with others. 15 | Multiple methods can be used at once, you can also add a `teamcoding.json` file anywhere within the solution folder for it to override those settings. 16 | If this file is in place then for that solution settings don't need to be set in each developer's IDE. 17 | You can enable/disable the CodeLens addon in Tools->Options->Text Editor->All Language->Code Lens->Team Coding. 18 | For more details regarding the different user and shared options, see [Settings.md](https://github.com/georgeduckett/TeamCoding/blob/master/Settings.md) 19 | 20 | **User Identity:** It tries to get your email address as a user identity from various sources (saved Windows Credentials for GitHub, your logged in identity from Visual Studio, your machine name). 21 | This will be made public to your team. It uses Gravatar to get a user image from the email address. 22 | 23 | **Overview window** There is a tool window that allows you to see every user and what documents they've got open in a treeview. To access it use the View->Other Windows->TeamCoding Overview command. 24 | 25 | **Roadmap:** Please report bugs as [GitHub issues](https://github.com/georgeduckett/TeamCoding/issues). 26 | Currently sharing options are a shared folder, Redis, Slack, an SQL Server table or via a server application as a windows service or console application. If you want others (along with any other feature requests) please raise them as issues. Supports Git repos and Team Foundation Services / Visual Studio Online for determining what to share as well as a fall-back to sharing purely based on matching solution filenames. In the pipeline a shared-coding experience (multiple users editing the same document(s)). 27 | 28 | **Reporting Bugs**: When reporting bugs ([here](https://github.com/georgeduckett/TeamCoding/issues)) please include information from the `Team Coding` tab in the output window (if relevent). If there were any exceptions they should be visible there, which can help track down the cause. 29 | 30 | **Links:** [GitHub Repo](https://github.com/georgeduckett/TeamCoding/) 31 | 32 | [![Build status](https://ci.appveyor.com/api/projects/status/vqgmu9893sxn3p7m?svg=true)](https://ci.appveyor.com/project/georgeduckett/teamcoding) 33 | 34 | --- 35 | 36 | ![Demo Gif](http://i.giphy.com/3oz8xNb3MTsqzn67za.gif) --------------------------------------------------------------------------------