├── .editorconfig ├── .gitattributes ├── .gitignore ├── CONFIG.md ├── CodeOfConduct.md ├── README.md ├── example └── .forcefeedbackprogramming ├── images ├── config_fig1.png ├── config_fig2.png ├── config_fig3.png └── example.png └── src ├── ForceFeedback.Adapters.VisualStudio ├── CodeBlockOccurrence.cs ├── Extensions │ ├── StringExtensions.cs │ └── SyntaxNodeExtension.cs ├── ForceFeedback.Adapters.VisualStudio.csproj ├── ForceFeedbackMethodTextAdornment.cs └── ForceFeedbackTextViewCreationListener.cs ├── ForceFeedback.Core.sln ├── ForceFeedback.Core ├── Feedbacks │ ├── DelayKeyboardInputsFeedback.cs │ ├── DrawColoredBackgroundFeedback.cs │ ├── InsertTextFeedback.cs │ └── PreventKeyboardInputsFeedback.cs ├── ForceFeedback.Core.csproj ├── ForceFeedbackMachine.cs └── IFeedback.cs ├── ForceFeedback.Setup.VisualStudio ├── ForceFeedback.Setup.VisualStudio.csproj ├── Properties │ └── AssemblyInfo.cs ├── VisualStudioSetupPackage.cs ├── index.html └── source.extension.vsixmanifest └── ForceFeedback.sln /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | # Default settings: 7 | # A newline ending every file 8 | # Use 4 spaces as indentation 9 | [*] 10 | insert_final_newline = true 11 | indent_style = space 12 | indent_size = 4 13 | 14 | [project.json] 15 | indent_size = 2 16 | 17 | # C# files 18 | [*.cs] 19 | # New line preferences 20 | csharp_new_line_before_open_brace = all 21 | csharp_new_line_before_else = true 22 | csharp_new_line_before_catch = true 23 | csharp_new_line_before_finally = true 24 | csharp_new_line_before_members_in_object_initializers = true 25 | csharp_new_line_before_members_in_anonymous_types = true 26 | csharp_new_line_between_query_expression_clauses = true 27 | 28 | # Indentation preferences 29 | csharp_indent_block_contents = true 30 | csharp_indent_braces = false 31 | csharp_indent_case_contents = true 32 | csharp_indent_switch_labels = true 33 | csharp_indent_labels = one_less_than_current 34 | 35 | # avoid this. unless absolutely necessary 36 | dotnet_style_qualification_for_field = false:suggestion 37 | dotnet_style_qualification_for_property = false:suggestion 38 | dotnet_style_qualification_for_method = false:suggestion 39 | dotnet_style_qualification_for_event = false:suggestion 40 | 41 | # only use var when it's obvious what the variable type is 42 | csharp_style_var_for_built_in_types = false:none 43 | csharp_style_var_when_type_is_apparent = false:none 44 | csharp_style_var_elsewhere = false:suggestion 45 | 46 | # use language keywords instead of BCL types 47 | dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion 48 | dotnet_style_predefined_type_for_member_access = true:suggestion 49 | 50 | # name all constant fields using PascalCase 51 | dotnet_naming_rule.constant_fields_should_be_pascal_case.severity = suggestion 52 | dotnet_naming_rule.constant_fields_should_be_pascal_case.symbols = constant_fields 53 | dotnet_naming_rule.constant_fields_should_be_pascal_case.style = pascal_case_style 54 | 55 | dotnet_naming_symbols.constant_fields.applicable_kinds = field 56 | dotnet_naming_symbols.constant_fields.required_modifiers = const 57 | 58 | dotnet_naming_style.pascal_case_style.capitalization = pascal_case 59 | 60 | # static fields should have s_ prefix 61 | dotnet_naming_rule.static_fields_should_have_prefix.severity = suggestion 62 | dotnet_naming_rule.static_fields_should_have_prefix.symbols = static_fields 63 | dotnet_naming_rule.static_fields_should_have_prefix.style = static_prefix_style 64 | 65 | dotnet_naming_symbols.static_fields.applicable_kinds = field 66 | dotnet_naming_symbols.static_fields.required_modifiers = static 67 | 68 | dotnet_naming_style.static_prefix_style.required_prefix = s_ 69 | dotnet_naming_style.static_prefix_style.capitalization = camel_case 70 | 71 | # internal and private fields should be _camelCase 72 | dotnet_naming_rule.camel_case_for_private_internal_fields.severity = suggestion 73 | dotnet_naming_rule.camel_case_for_private_internal_fields.symbols = private_internal_fields 74 | dotnet_naming_rule.camel_case_for_private_internal_fields.style = camel_case_underscore_style 75 | 76 | dotnet_naming_symbols.private_internal_fields.applicable_kinds = field 77 | dotnet_naming_symbols.private_internal_fields.applicable_accessibilities = private, internal 78 | 79 | dotnet_naming_style.camel_case_underscore_style.required_prefix = _ 80 | dotnet_naming_style.camel_case_underscore_style.capitalization = camel_case 81 | 82 | # Code style defaults 83 | dotnet_sort_system_directives_first = true 84 | csharp_preserve_single_line_blocks = true 85 | csharp_preserve_single_line_statements = false 86 | 87 | # Expression-level preferences 88 | dotnet_style_object_initializer = true:suggestion 89 | dotnet_style_collection_initializer = true:suggestion 90 | dotnet_style_explicit_tuple_names = true:suggestion 91 | dotnet_style_coalesce_expression = true:suggestion 92 | dotnet_style_null_propagation = true:suggestion 93 | 94 | # Expression-bodied members 95 | csharp_style_expression_bodied_methods = false:none 96 | csharp_style_expression_bodied_constructors = false:none 97 | csharp_style_expression_bodied_operators = false:none 98 | csharp_style_expression_bodied_properties = true:none 99 | csharp_style_expression_bodied_indexers = true:none 100 | csharp_style_expression_bodied_accessors = true:none 101 | 102 | # Pattern matching 103 | csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion 104 | csharp_style_pattern_matching_over_as_with_null_check = true:suggestion 105 | csharp_style_inlined_variable_declaration = true:suggestion 106 | 107 | # Null checking preferences 108 | csharp_style_throw_expression = true:suggestion 109 | csharp_style_conditional_delegate_call = true:suggestion 110 | 111 | # Space preferences 112 | csharp_space_after_cast = false 113 | csharp_space_after_colon_in_inheritance_clause = true 114 | csharp_space_after_comma = true 115 | csharp_space_after_dot = false 116 | csharp_space_after_keywords_in_control_flow_statements = true 117 | csharp_space_after_semicolon_in_for_statement = true 118 | csharp_space_around_binary_operators = before_and_after 119 | csharp_space_around_declaration_statements = do_not_ignore 120 | csharp_space_before_colon_in_inheritance_clause = true 121 | csharp_space_before_comma = false 122 | csharp_space_before_dot = false 123 | csharp_space_before_open_square_brackets = false 124 | csharp_space_before_semicolon_in_for_statement = false 125 | csharp_space_between_empty_square_brackets = false 126 | csharp_space_between_method_call_empty_parameter_list_parentheses = false 127 | csharp_space_between_method_call_name_and_opening_parenthesis = false 128 | csharp_space_between_method_call_parameter_list_parentheses = false 129 | csharp_space_between_method_declaration_empty_parameter_list_parentheses = false 130 | csharp_space_between_method_declaration_name_and_open_parenthesis = false 131 | csharp_space_between_method_declaration_parameter_list_parentheses = false 132 | csharp_space_between_parentheses = false 133 | csharp_space_between_square_brackets = false 134 | 135 | # C++ Files 136 | [*.{cpp,h,in}] 137 | curly_bracket_next_line = true 138 | indent_brace_style = Allman 139 | 140 | # Xml project files 141 | [*.{csproj,vcxproj,vcxproj.filters,proj,nativeproj,locproj}] 142 | indent_size = 2 143 | 144 | # Xml build files 145 | [*.builds] 146 | indent_size = 2 147 | 148 | # Xml files 149 | [*.{xml,stylecop,resx,ruleset}] 150 | indent_size = 2 151 | 152 | # Xml config files 153 | [*.{props,targets,config,nuspec}] 154 | indent_size = 2 155 | 156 | # Shell scripts 157 | [*.sh] 158 | end_of_line = lf 159 | [*.{cmd, bat}] 160 | end_of_line = crlf 161 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | [Xx]64/ 19 | [Xx]86/ 20 | [Bb]uild/ 21 | bld/ 22 | [Bb]in/ 23 | [Oo]bj/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | # Uncomment if you have tasks that create the project's static files in wwwroot 28 | #wwwroot/ 29 | 30 | # MSTest test Results 31 | [Tt]est[Rr]esult*/ 32 | [Bb]uild[Ll]og.* 33 | 34 | # NUNIT 35 | *.VisualState.xml 36 | TestResult.xml 37 | 38 | # Build Results of an ATL Project 39 | [Dd]ebugPS/ 40 | [Rr]eleasePS/ 41 | dlldata.c 42 | 43 | # DNX 44 | project.lock.json 45 | artifacts/ 46 | 47 | *_i.c 48 | *_p.c 49 | *_i.h 50 | *.ilk 51 | *.meta 52 | *.obj 53 | *.pch 54 | *.pdb 55 | *.pgc 56 | *.pgd 57 | *.rsp 58 | *.sbr 59 | *.tlb 60 | *.tli 61 | *.tlh 62 | *.tmp 63 | *.tmp_proj 64 | *.log 65 | *.vspscc 66 | *.vssscc 67 | .builds 68 | *.pidb 69 | *.svclog 70 | *.scc 71 | 72 | # Chutzpah Test files 73 | _Chutzpah* 74 | 75 | # Visual C++ cache files 76 | ipch/ 77 | *.aps 78 | *.ncb 79 | *.opendb 80 | *.opensdf 81 | *.sdf 82 | *.cachefile 83 | *.VC.db 84 | 85 | # Visual Studio profiler 86 | *.psess 87 | *.vsp 88 | *.vspx 89 | *.sap 90 | 91 | # TFS 2012 Local Workspace 92 | $tf/ 93 | 94 | # Guidance Automation Toolkit 95 | *.gpState 96 | 97 | # ReSharper is a .NET coding add-in 98 | _ReSharper*/ 99 | *.[Rr]e[Ss]harper 100 | *.DotSettings.user 101 | 102 | # JustCode is a .NET coding add-in 103 | .JustCode 104 | 105 | # TeamCity is a build add-in 106 | _TeamCity* 107 | 108 | # DotCover is a Code Coverage Tool 109 | *.dotCover 110 | 111 | # NCrunch 112 | _NCrunch_* 113 | .*crunch*.local.xml 114 | nCrunchTemp_* 115 | 116 | # MightyMoose 117 | *.mm.* 118 | AutoTest.Net/ 119 | 120 | # Web workbench (sass) 121 | .sass-cache/ 122 | 123 | # Installshield output folder 124 | [Ee]xpress/ 125 | 126 | # DocProject is a documentation generator add-in 127 | DocProject/buildhelp/ 128 | DocProject/Help/*.HxT 129 | DocProject/Help/*.HxC 130 | DocProject/Help/*.hhc 131 | DocProject/Help/*.hhk 132 | DocProject/Help/*.hhp 133 | DocProject/Help/Html2 134 | DocProject/Help/html 135 | 136 | # Click-Once directory 137 | publish/ 138 | 139 | # Publish Web Output 140 | *.[Pp]ublish.xml 141 | *.azurePubxml 142 | 143 | # TODO: Un-comment the next line if you do not want to checkin 144 | # your web deploy settings because they may include unencrypted 145 | # passwords 146 | #*.pubxml 147 | *.publishproj 148 | 149 | # NuGet Packages 150 | *.nupkg 151 | # The packages folder can be ignored because of Package Restore 152 | **/packages/* 153 | # except build/, which is used as an MSBuild target. 154 | !**/packages/build/ 155 | # Uncomment if necessary however generally it will be regenerated when needed 156 | #!**/packages/repositories.config 157 | # NuGet v3's project.json files produces more ignoreable files 158 | *.nuget.props 159 | *.nuget.targets 160 | 161 | # Microsoft Azure Build Output 162 | csx/ 163 | *.build.csdef 164 | 165 | # Microsoft Azure Emulator 166 | ecf/ 167 | rcf/ 168 | 169 | # Microsoft Azure ApplicationInsights config file 170 | ApplicationInsights.config 171 | 172 | # Windows Store app package directory 173 | AppPackages/ 174 | BundleArtifacts/ 175 | 176 | # Visual Studio cache files 177 | # files ending in .cache can be ignored 178 | *.[Cc]ache 179 | # but keep track of directories ending in .cache 180 | !*.[Cc]ache/ 181 | 182 | # Others 183 | ClientBin/ 184 | [Ss]tyle[Cc]op.* 185 | ~$* 186 | *~ 187 | *.dbmdl 188 | *.dbproj.schemaview 189 | *.pfx 190 | *.publishsettings 191 | node_modules/ 192 | orleans.codegen.cs 193 | 194 | # RIA/Silverlight projects 195 | Generated_Code/ 196 | 197 | # Backup & report files from converting an old project file 198 | # to a newer Visual Studio version. Backup files are not needed, 199 | # because we have git ;-) 200 | _UpgradeReport_Files/ 201 | Backup*/ 202 | UpgradeLog*.XML 203 | UpgradeLog*.htm 204 | 205 | # SQL Server files 206 | *.mdf 207 | *.ldf 208 | 209 | # Business Intelligence projects 210 | *.rdl.data 211 | *.bim.layout 212 | *.bim_*.settings 213 | 214 | # Microsoft Fakes 215 | FakesAssemblies/ 216 | 217 | # GhostDoc plugin setting file 218 | *.GhostDoc.xml 219 | 220 | # Node.js Tools for Visual Studio 221 | .ntvs_analysis.dat 222 | 223 | # Visual Studio 6 build log 224 | *.plg 225 | 226 | # Visual Studio 6 workspace options file 227 | *.opt 228 | 229 | # Visual Studio LightSwitch build output 230 | **/*.HTMLClient/GeneratedArtifacts 231 | **/*.DesktopClient/GeneratedArtifacts 232 | **/*.DesktopClient/ModelManifest.xml 233 | **/*.Server/GeneratedArtifacts 234 | **/*.Server/ModelManifest.xml 235 | _Pvt_Extensions 236 | 237 | # LightSwitch generated files 238 | GeneratedArtifacts/ 239 | ModelManifest.xml 240 | 241 | # Paket dependency manager 242 | .paket/paket.exe 243 | 244 | # FAKE - F# Make 245 | .fake/ 246 | /src/Precompiled 247 | src/.idea/.idea.ForceFeedback.Core/.idea/workspace.xml 248 | -------------------------------------------------------------------------------- /CONFIG.md: -------------------------------------------------------------------------------- 1 | # Configuring the VS Extension 2 | 3 | The Visual Studio extension for Force Feedback Programming provides visual plus tactile feedback on the evolvability of your code while typing. 4 | 5 | Since not all code is created equal and your team's coding style might differ from that of another team you can configure the extension's "sensitivity" and its "use of force". 6 | 7 | Currently the extension uses the *number of lines* in a function/methode as its only metric to determine how clean/dirty your code is. We know this might sound simplistic, but we've thought long and hard about it and believe that more sophisticated metrics would only deliver pseudo accuracy. 8 | 9 | The following method consists of 10 lines of code between the initial `{` and the final `}`. A background color is chosen accordingly: 10 | 11 | ![](images/config_fig1.png) 12 | 13 | Now, adding a line (by breaking the last statement apart) pushes it into the next "brownfield category", though, and the background color changes: 14 | 15 | ![](images/config_fig2.png) 16 | 17 | The visual feedback is immediate, as you see. So is the tactile feedback which consist of spurts of random characters inserted while you type and delaying the effect of your keystrokes. 18 | 19 | The metric is simple, as you see, but how is the feedback chosen? That's what you can control with FFP config files: 20 | 21 | ## Locating the config file 22 | The rules to assess methods and define the feedback to give are read from a config file with the name `.forcefeedbackprogramming`. 23 | 24 | The FFP extension will use the first config file it finds while searching for it in a number of places in this order: 25 | 26 | 1. directory of current source file 27 | 2. directory of project the source file belongs to 28 | 3. directory of solution the project belongs to 29 | 30 | If no config file is found, one is created from a default in the solution directory. 31 | 32 | ## Structure of config file 33 | A config file is a JSON file with a simple structure. Here's an excerpt from the default config: 34 | 35 | ``` 36 | { 37 | "Version": "1.0", 38 | 39 | "FeedbackRules": [ 40 | { 41 | "MinimumNumberOfLinesInMethod": 11, 42 | 43 | "BackgroundColor": "Beige", 44 | "BackgroundColorTransparency": 0.0, 45 | 46 | "NoiseDistance": 0, 47 | "NoiseLevel": 0, 48 | 49 | "Delay": 0 50 | }, 51 | { 52 | "MinimumNumberOfLinesInMethod": 26, 53 | 54 | "BackgroundColor": "Burlywood", 55 | "BackgroundColorTransparency": 0.0, 56 | 57 | "NoiseDistance": 50, 58 | "NoiseLevel": 3, 59 | 60 | "Delay": 0 61 | }, 62 | ... 63 | ] 64 | } 65 | ``` 66 | 67 | Currently it's just a list of rules defining **levels of what's considered clean/dirty**. 68 | 69 | * Levels relate to the number of lines in a method only (`MinimumNumberOfLinesInMethod`). The example states that less that 11 lines of code (LOC) is considered perfectly clean. No feedback is given. But from 11 LOC on you'll see/feel feedback. First a little - 11 to 25 LOC -, then more - 26 LOC and up. All methods with more LOC than the highest number in the rules state are covered by that rule. 70 | 71 | **Visual feedback** is given for each level based on two properties: 72 | 73 | * Visual feedback is given by coloring the backhground of a method's body (`BackgroundColor`). You can define the color by name, e.g. `Beige` or `Maroon` or any color from [this list](http://www.99colors.net/dot-net-colors). Or you can define it with a hex code, e.g. `#F0FFFF` instead of `Azure`; use a [tool like this](https://www.rapidtables.com/web/color/RGB_Color.html) to mix your color. 74 | * Some colors might lead to bad contrast for the source text when applied in their default "strength" or "thickness". To adjust for better readability you can change the transparency with which they are applied (`BackgroundColorTransparency`). The default is `0.0`, i.e. no transparency/"full strength"/maximum opacity. Choose a value between `0.0` and `1.0` to make the background color more light, more translucent. 75 | 76 | For **tactile feedback** you can tweak three properties: 77 | 78 | * If keystrokes/changes should be delayed (to simulate wading through a brownfield), then set `Delay` to a millisecond value of your choice, e.g. `100` to delay every keystroke by 0.1 seconds. 79 | * In addition you can add "noise" to the input. That means additional random characters will be inserted to hamper typing progress. `NoiseDistance` defines how many keystrokes/changes should pass without disturbance, e.g. `10` for "insert noise every 10 keystrokes". 80 | * How many noise characters are inserted is defined by `NoiseLevel`, e.g. `3` to insert a random string like "♥☺❉". 81 | -------------------------------------------------------------------------------- /CodeOfConduct.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 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, gender identity and expression, level of experience, 9 | nationality, personal appearance, race, religion, or sexual identity and 10 | orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at robin.sedlaczek@live.de. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at [http://contributor-covenant.org/version/1/4][version] 72 | 73 | [homepage]: http://contributor-covenant.org 74 | [version]: http://contributor-covenant.org/version/1/4/ 75 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Force Feedback Programming 2 | 3 | ## Overview 4 | 5 | Make evolvability really tangible while coding: that's what _Force Feedback Programming_ (FFP) is about. 6 | 7 | There are tools which might be able to tell you during CI if you wrote clean code. Or your colleagues give you feedback on this during a review. But that's pretty delayed feedback. It's hard to learn from it for next time. To write clean code in the first place still is not inevitable. 8 | 9 | That hopefully changes with FFP! Because FFP will tell you right away _while you're typing_ how you're doing on the evolvability front. 10 | 11 | And not only will FFP tell you how clean your code is, it will actively hinder you to pollute it. _FFP gives you visual feedback plus tactile._ You'll experience a real force right at your fingertips! Writing less clean code will become tangibly harder. 12 | 13 | And all this is made possible through a simple Visual Studio extension you can even [adapt to your coding style](CONFIG.md) and level of brownfield. 14 | 15 | Take as an example this screenshot from [a video by contributor Robin](https://vimeo.com/171889390) where he's explaining how FFP works: 16 | 17 | ![](images/example.png) 18 | 19 | As you see, methods in the source code are colored differently according to how clean/dirty FFP deems them. But this color is not static! It will change while you're editing your code because with each keystroke it might become more or less clean. 20 | 21 | That's not all, however. In addition to this kind of visual feedback the FFP extension gives tactile feedback. [Read Robin's article](https://robinsedlaczek.com/2016/06/23/introducing-force-feedback-programming/) and watch his video to see that in action. 22 | 23 | Enjoy! 24 | 25 | PS: If you like what you see and have time to spare, you can join us in moving the FFP extension forward. Please [check the wiki](https://github.com/robinsedlaczek/ForceFeedbackProgramming/wiki) for details. 26 | 27 | ## Binaries 28 | 29 | You can download the precompiled Visual Studio installer package from the [releases page](https://github.com/robinsedlaczek/ForceFeedbackProgramming/releases/ "Visual Studio Installer Package releases"). 30 | 31 | ## Supported IDEs 32 | 33 | ### Microsoft Visual Studio 34 | 35 | Force Feedback Programming is currently available for C# in Visual Studio only (VS 2015, 2017 and 2019). It's delivered as a Visual Studio extension that can be found in the VS marketplace [here](https://marketplace.visualstudio.com/items?itemName=RobinSedlaczek.ForceFeedback) and that can be installed via the main menu entry "Tools|Manage Extensions". We update the extension in the marketplace with every stable version. Stable versions on the [releases page](https://github.com/robinsedlaczek/ForceFeedbackProgramming/releases/ "Visual Studio Installer Package releases") are those versions, which are free of any additional version status info (e.g. -alpha, -beta etc.). 36 | 37 | ### ABAB 38 | 39 | To be honest, the phrase "Visual Studio only" might not really be correct anymore. It seems there are some guys who have taken the Force Feedback Programming idea to SAP's [ABAB](https://en.wikipedia.org/wiki/ABAP). [Here's a Tweet](https://twitter.com/ceedee666/status/1106887766221180929) hinting at that. A repo for their ABAB implementation [ABAB implementation](https://github.com/css-ch/abap-code-feedback) is on GitHub. 40 | 41 | *Please note: Their implementation is no fork of our repo. It's an independent project. Their ABAB AiE integration is not part of our effort here.* 42 | 43 | ### Roadmap 44 | 45 | We have the integration for Visual Studio Code and JetBrains Rider on our list! 46 | 47 | ## Health of master (Release|x86): 48 | 49 | [![Build status](https://ci.appveyor.com/api/projects/status/mrnvhtnf9k2xrs4g/branch/master?svg=true)](https://ci.appveyor.com/project/robinsedlaczek/forcefeedbackprogramming/branch/master) 50 | 51 | ## Wanna chat with us? 52 | 53 | You can meet us here: [![Gitter](https://badges.gitter.im/robinsedlaczek/ForceFeedbackProgramming.svg)](https://gitter.im/robinsedlaczek/ForceFeedbackProgramming?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) 54 | -------------------------------------------------------------------------------- /example/.forcefeedbackprogramming: -------------------------------------------------------------------------------- 1 | { 2 | "methodTooLongLimits": [ 3 | { 4 | "lines": 6, 5 | "color": "#ffffe5", 6 | "transparency": 0.25 7 | }, 8 | { 9 | "lines": 11, 10 | "color": "#fee391", 11 | "transparency": 0.25, 12 | "noiseDistance": 10 13 | }, 14 | { 15 | "lines": 16, 16 | "color": "#fec44f", 17 | "transparency": 0.50, 18 | "noiseDistance": 8 19 | }, 20 | { 21 | "lines": 21, 22 | "color": "#fe9929", 23 | "transparency": 0.80, 24 | "noiseDistance": 7 25 | }, 26 | { 27 | "lines": 26, 28 | "color": "#cc4c02", 29 | "transparency": 0.80, 30 | "noiseDistance": 6 31 | }, 32 | { 33 | "lines": 31, 34 | "color": "#993404", 35 | "transparency": 0.80, 36 | "noiseDistance": 5 37 | } 38 | ] 39 | } -------------------------------------------------------------------------------- /images/config_fig1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robinsedlaczek/ForceFeedbackProgramming/2b17ed993ca71e4f4290d87dc16088b378522e1d/images/config_fig1.png -------------------------------------------------------------------------------- /images/config_fig2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robinsedlaczek/ForceFeedbackProgramming/2b17ed993ca71e4f4290d87dc16088b378522e1d/images/config_fig2.png -------------------------------------------------------------------------------- /images/config_fig3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robinsedlaczek/ForceFeedbackProgramming/2b17ed993ca71e4f4290d87dc16088b378522e1d/images/config_fig3.png -------------------------------------------------------------------------------- /images/example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robinsedlaczek/ForceFeedbackProgramming/2b17ed993ca71e4f4290d87dc16088b378522e1d/images/example.png -------------------------------------------------------------------------------- /src/ForceFeedback.Adapters.VisualStudio/CodeBlockOccurrence.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.CodeAnalysis.CSharp.Syntax; 2 | using ForceFeedback.Core; 3 | using System.Collections.Generic; 4 | 5 | namespace ForceFeedback.Adapters.VisualStudio 6 | { 7 | internal class CodeBlockOccurrence 8 | { 9 | public CodeBlockOccurrence(BlockSyntax block, List feedbacks) 10 | { 11 | Block = block; 12 | Feedbacks = feedbacks; 13 | } 14 | 15 | public BlockSyntax Block { get; set; } 16 | 17 | public List Feedbacks { get; set; } 18 | } 19 | } -------------------------------------------------------------------------------- /src/ForceFeedback.Adapters.VisualStudio/Extensions/StringExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace ForceFeedback.Adapters.VisualStudio 4 | { 5 | public static class StringExtensions 6 | { 7 | private static readonly HashSet NewLineMarker = new HashSet 8 | { 9 | "\r\n", "\r", "\n" 10 | }; 11 | 12 | public static bool IsNewLineMarker(this string value) 13 | { 14 | return value != null && NewLineMarker.Contains(value); 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /src/ForceFeedback.Adapters.VisualStudio/Extensions/SyntaxNodeExtension.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.CodeAnalysis; 2 | using Microsoft.CodeAnalysis.CSharp; 3 | 4 | namespace ForceFeedback.Adapters.VisualStudio 5 | { 6 | public static class SyntaxNodeExtension 7 | { 8 | public static bool IsSyntaxBlock(this SyntaxNode node) 9 | { 10 | return node.Kind() == SyntaxKind.Block; 11 | } 12 | 13 | public static bool IsMethod(this SyntaxNode node) 14 | { 15 | return node.Kind() == SyntaxKind.MethodDeclaration; 16 | } 17 | 18 | public static bool IsConstructor(this SyntaxNode node) 19 | { 20 | return node.Kind() == SyntaxKind.ConstructorDeclaration; 21 | } 22 | 23 | public static bool IsSetter(this SyntaxNode node) 24 | { 25 | return node.Kind() == SyntaxKind.SetAccessorDeclaration; 26 | } 27 | 28 | public static bool IsGetter(this SyntaxNode node) 29 | { 30 | return node.Kind() == SyntaxKind.GetAccessorDeclaration; 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /src/ForceFeedback.Adapters.VisualStudio/ForceFeedback.Adapters.VisualStudio.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net472 5 | AnyCPU;x86 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/ForceFeedback.Adapters.VisualStudio/ForceFeedbackMethodTextAdornment.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // Copyright (c) Company. All rights reserved. 4 | // 5 | //------------------------------------------------------------------------------ 6 | 7 | using System; 8 | using System.Windows.Controls; 9 | using System.Windows.Media; 10 | using Microsoft.VisualStudio.Text; 11 | using Microsoft.VisualStudio.Text.Editor; 12 | using System.Collections.Generic; 13 | using Microsoft.CodeAnalysis.CSharp.Syntax; 14 | using Microsoft.CodeAnalysis.Text; 15 | using System.Linq; 16 | using System.Threading.Tasks; 17 | using Microsoft.CodeAnalysis; 18 | using System.Windows; 19 | using ForceFeedback.Core; 20 | using System.Threading; 21 | using ForceFeedback.Core.Feedbacks; 22 | 23 | namespace ForceFeedback.Adapters.VisualStudio 24 | { 25 | /// 26 | /// MethodTooLongTextAdornment places red boxes behind all the "a"s in the editor window 27 | /// 28 | internal sealed class ForceFeedbackMethodTextAdornment 29 | { 30 | #region Private Fields 31 | 32 | private readonly ITextDocumentFactoryService _textDocumentFactory; 33 | private readonly IAdornmentLayer _layer; 34 | private readonly IWpfTextView _view; 35 | private ITextDocument _textDocument; 36 | private IList _codeBlockOccurrences; 37 | private readonly ForceFeedbackMachine _feedbackMachine; 38 | 39 | private readonly string[] AllowedCharactersInChanges = new[] 40 | { 41 | "\r", "\n", "\r\n", 42 | " ", "\"", "'", ".", ",", "@", "$", "(", ")", "{", "}", "[", "]", "&", "|", "\\", "%", "+", "-", "*", "/", ";", ":", "_", "?", "!", 43 | "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", 44 | "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", 45 | "0", "1", "2", "3", "4", "5", "6", "7", "8", "9" 46 | }; 47 | 48 | #endregion 49 | 50 | #region Construction 51 | 52 | /// 53 | /// Initializes a new instance of the class. 54 | /// 55 | /// Text view to create the adornment for 56 | public ForceFeedbackMethodTextAdornment(IWpfTextView view, ITextDocumentFactoryService textDocumentFactory) 57 | { 58 | _view = view ?? throw new ArgumentNullException(nameof(view)); 59 | _textDocumentFactory = textDocumentFactory ?? throw new ArgumentNullException(nameof(textDocumentFactory)); 60 | 61 | var res = _textDocumentFactory.TryGetTextDocument(_view.TextBuffer, out _textDocument); 62 | 63 | _codeBlockOccurrences = new List(); 64 | 65 | _layer = view.GetAdornmentLayer("MethodTooLongTextAdornment"); 66 | 67 | _view.LayoutChanged += OnLayoutChanged; 68 | _view.TextBuffer.Changed += OnTextBufferChanged; 69 | _view.TextBuffer.Changing += OnTextBufferChanging; 70 | 71 | _textDocumentFactory = textDocumentFactory; 72 | 73 | var project = _textDocument?.TextBuffer?.CurrentSnapshot?.GetOpenDocumentInCurrentContextWithChanges()?.Project; 74 | 75 | _feedbackMachine = new ForceFeedbackMachine(solutionFilePath: project?.Solution?.FilePath, projectFilePath: project?.FilePath, sourceFilePath: _textDocument.FilePath); 76 | } 77 | 78 | private void OnTextBufferChanging(object sender, TextContentChangingEventArgs e) 79 | { 80 | var methodOccurence = _codeBlockOccurrences 81 | .Where(occurence => occurence.Block.FullSpan.IntersectsWith(_view.Caret.Position.BufferPosition.Position)) 82 | .Select(occurence => occurence) 83 | .FirstOrDefault(); 84 | 85 | string methodName = string.Empty; 86 | int linesOfCode = 0; 87 | 88 | GetMethodNameAndLineCount(methodOccurence.Block, out methodName, out linesOfCode); 89 | 90 | var feedbacks = _feedbackMachine.RequestFeedbackBeforeMethodCodeChange(methodName, linesOfCode); 91 | 92 | foreach (var feedback in feedbacks) 93 | { 94 | if (feedback is InsertTextFeedback) 95 | InsertText(feedback); 96 | else if (feedback is DelayKeyboardInputsFeedback) 97 | DelayKeyboardInput(feedback); 98 | else if (feedback is PreventKeyboardInputsFeedback) 99 | e.Cancel(); 100 | } 101 | } 102 | 103 | #endregion 104 | 105 | #region Event Handler 106 | 107 | private void OnTextBufferChanged(object sender, TextContentChangedEventArgs e) 108 | { 109 | if (!InteresstingChangeOccured(e)) 110 | return; 111 | 112 | var change = e.Changes[0]; 113 | 114 | var methodOccurence = _codeBlockOccurrences 115 | .Where(occurence => occurence.Block.FullSpan.IntersectsWith(change.NewSpan.Start)) 116 | .Select(occurence => occurence) 117 | .FirstOrDefault(); 118 | 119 | string methodName = string.Empty; 120 | int linesOfCode = 0; 121 | 122 | GetMethodNameAndLineCount(methodOccurence.Block, out methodName, out linesOfCode); 123 | 124 | var feedbacks = _feedbackMachine.RequestFeedbackAfterMethodCodeChange(methodName, linesOfCode); 125 | 126 | foreach (var feedback in feedbacks) 127 | { 128 | if (feedback is InsertTextFeedback) 129 | InsertText(feedback); 130 | else if (feedback is DelayKeyboardInputsFeedback) 131 | DelayKeyboardInput(feedback); 132 | } 133 | } 134 | 135 | private static void DelayKeyboardInput(IFeedback feedback) 136 | { 137 | var delayKeyboardInputsFeedback = feedback as DelayKeyboardInputsFeedback; 138 | 139 | Thread.Sleep(delayKeyboardInputsFeedback.Milliseconds); 140 | } 141 | 142 | private void InsertText(IFeedback feedback) 143 | { 144 | var insertTextFeedback = feedback as InsertTextFeedback; 145 | 146 | if (!_view.TextBuffer.CheckEditAccess()) 147 | throw new Exception("Cannot edit text buffer."); 148 | 149 | var edit = _view.TextBuffer.CreateEdit(EditOptions.None, null, "ForceFeedback"); 150 | var position = _view.Caret.Position.BufferPosition.Position; 151 | var inserted = edit.Insert(position, insertTextFeedback.Text); 152 | 153 | if (!inserted) 154 | throw new Exception($"Cannot insert '{insertTextFeedback.Text}' at position {position} in text buffer."); 155 | 156 | edit.Apply(); 157 | } 158 | 159 | private bool InteresstingChangeOccured(TextContentChangedEventArgs e) 160 | { 161 | if (WasChangeCausedByForceFeedback(e.EditTag) || e.Changes.Count == 0) 162 | return false; 163 | 164 | var change = e.Changes[0]; 165 | 166 | // [RS] We trim the new text when checking for allowed characters, if the text has more than one character. This is, e.g. 167 | // if the user inserted a linefeed and the IDE created whitespaces automatically for indention of the next line. 168 | // In this case, we want to ignore the generated leading whitespaces. 169 | // In the case the user entered a whitespace directly, we do not want to trim it away. So we check the new text length. 170 | return AllowedCharactersInChanges.Contains(change.NewText.Length == 1 ? change.NewText : change.NewText.Trim(' ')); 171 | } 172 | 173 | private static bool WasChangeCausedByForceFeedback(object editTag) 174 | { 175 | return editTag != null && editTag.ToString() == "ForceFeedback"; 176 | } 177 | 178 | /// 179 | /// Handles whenever the text displayed in the view changes by adding the adornment to any reformatted lines. 180 | /// 181 | /// This event is raised whenever the rendered text displayed in the changes. 182 | /// It is raised whenever the view does a layout (which happens when DisplayTextLineContainingBufferPosition is called or in response to text or classification changes). 183 | /// It is also raised whenever the view scrolls horizontally or when its size changes. 184 | /// 185 | /// The event sender. 186 | /// The event arguments. 187 | internal async void OnLayoutChanged(object sender, TextViewLayoutChangedEventArgs e) 188 | { 189 | try 190 | { 191 | var codeBlocks = await CollectBlockSyntaxNodesAsync(e.NewSnapshot); 192 | 193 | AnalyzeCodeBlockOccurrences(codeBlocks); 194 | CreateBackgroundVisualsForCodeBlocks(); 195 | } 196 | catch 197 | { 198 | // [RS] Maybe we should handle this exception a bit more faithfully. For now, we ignore the exceptions here 199 | // and wait for the next LayoutChanged event 200 | } 201 | } 202 | 203 | #endregion 204 | 205 | #region Private Methods 206 | 207 | /// 208 | /// This method checks if the given block syntaxes are too long based on the configured limits. If so, the block syntax 209 | /// and the corresponding limit configuration is put together in an instance of LongCodeBlockOccurrence. 210 | /// 211 | /// The list of block syntaxes that will be analyzed. 212 | private void AnalyzeCodeBlockOccurrences(IEnumerable codeBlocks) 213 | { 214 | if (codeBlocks == null) 215 | throw new ArgumentNullException(nameof(codeBlocks)); 216 | 217 | _codeBlockOccurrences.Clear(); 218 | 219 | foreach (var codeBlock in codeBlocks) 220 | { 221 | string methodName = string.Empty; 222 | int linesOfCode = 0; 223 | 224 | GetMethodNameAndLineCount(codeBlock, out methodName, out linesOfCode); 225 | 226 | var feedbacks = _feedbackMachine.RequestFeedbackForMethodCodeBlock(methodName, linesOfCode); 227 | var occurence = new CodeBlockOccurrence(codeBlock, feedbacks); 228 | 229 | _codeBlockOccurrences.Add(occurence); 230 | } 231 | } 232 | 233 | private void GetMethodNameAndLineCount(BlockSyntax codeBlock, out string methodName, out int linesOfCode) 234 | { 235 | linesOfCode = codeBlock 236 | .WithoutLeadingTrivia() 237 | .WithoutTrailingTrivia() 238 | .GetText() 239 | .Lines 240 | .Count; 241 | 242 | methodName = (codeBlock.Parent as MethodDeclarationSyntax)?.Identifier.ValueText; 243 | } 244 | 245 | /// 246 | /// This method collects syntax nodes of code blocks that have too many lines of code. 247 | /// 248 | /// The text snapshot containing the code to analyze. 249 | /// Returns a list with the code block syntax nodes. 250 | private async Task> CollectBlockSyntaxNodesAsync(ITextSnapshot newSnapshot) 251 | { 252 | if (newSnapshot == null) 253 | throw new ArgumentNullException(nameof(newSnapshot)); 254 | 255 | var currentDocument = newSnapshot.GetOpenDocumentInCurrentContextWithChanges(); 256 | 257 | var syntaxRoot = await currentDocument.GetSyntaxRootAsync(); 258 | 259 | var codeBlocks = syntaxRoot 260 | .DescendantNodes(node => true) 261 | .Where(node => node.IsSyntaxBlock() 262 | && ( node.Parent.IsMethod() 263 | || node.Parent.IsConstructor() 264 | || node.Parent.IsSetter() 265 | || node.Parent.IsGetter())) 266 | .Select(block => block as BlockSyntax); 267 | 268 | return codeBlocks; 269 | } 270 | 271 | /// 272 | /// Adds a background behind the code block that have too many lines. 273 | /// 274 | private void CreateBackgroundVisualsForCodeBlocks() 275 | { 276 | if (_codeBlockOccurrences == null) 277 | return; 278 | 279 | foreach (var occurrence in _codeBlockOccurrences) 280 | { 281 | if (occurrence.Feedbacks == null || !occurrence.Feedbacks.Any(feedback => feedback is DrawColoredBackgroundFeedback)) 282 | continue; 283 | 284 | var codeBlockParentSyntax = occurrence.Block.Parent; 285 | var snapshotSpan = new SnapshotSpan(_view.TextSnapshot, Span.FromBounds(codeBlockParentSyntax.Span.Start, codeBlockParentSyntax.Span.Start + codeBlockParentSyntax.Span.Length)); 286 | var adornmentBounds = CalculateBounds(codeBlockParentSyntax, snapshotSpan); 287 | 288 | if (adornmentBounds.IsEmpty) 289 | continue; 290 | 291 | var image = CreateAndPositionCodeBlockBackgroundVisual(adornmentBounds, occurrence); 292 | 293 | if (image == null) 294 | continue; 295 | 296 | _layer.RemoveAdornmentsByVisualSpan(snapshotSpan); 297 | _layer.AddAdornment(AdornmentPositioningBehavior.TextRelative, snapshotSpan, codeBlockParentSyntax, image, null); 298 | } 299 | } 300 | 301 | /// 302 | /// This method creates the visual for a code block background and moves it to the correct position. 303 | /// 304 | /// The bounds of the rectangular adornment. 305 | /// The occurence of the code block for which the visual will be created. 306 | /// Returns the image that is the visual adornment (code block background). 307 | private Image CreateAndPositionCodeBlockBackgroundVisual(Rect adornmentBounds, CodeBlockOccurrence codeBlockOccurence) 308 | { 309 | if (adornmentBounds == null) 310 | throw new ArgumentNullException(nameof(adornmentBounds)); 311 | 312 | if (codeBlockOccurence == null) 313 | throw new ArgumentNullException(nameof(codeBlockOccurence)); 314 | 315 | var backgroundGeometry = new RectangleGeometry(adornmentBounds); 316 | var feedback = codeBlockOccurence.Feedbacks.Where(f => f is DrawColoredBackgroundFeedback).FirstOrDefault() as DrawColoredBackgroundFeedback; 317 | var backgroundColor = Color.FromArgb(feedback.BackgroundColor.A, feedback.BackgroundColor.R, feedback.BackgroundColor.G, feedback.BackgroundColor.B); 318 | 319 | var backgroundBrush = new SolidColorBrush(backgroundColor); 320 | backgroundBrush.Freeze(); 321 | 322 | var outlineColor = Color.FromArgb(feedback.OutlineColor.A, feedback.OutlineColor.R, feedback.OutlineColor.G, feedback.OutlineColor.B); 323 | 324 | var outlinePenBrush = new SolidColorBrush(outlineColor); 325 | outlinePenBrush.Freeze(); 326 | 327 | var outlinePen = new Pen(outlinePenBrush, 0.5); 328 | outlinePen.Freeze(); 329 | 330 | var drawing = new GeometryDrawing(backgroundBrush, outlinePen, backgroundGeometry); 331 | drawing.Freeze(); 332 | 333 | var drawingImage = new DrawingImage(drawing); 334 | drawingImage.Freeze(); 335 | 336 | var image = new Image 337 | { 338 | Source = drawingImage 339 | }; 340 | 341 | Canvas.SetLeft(image, adornmentBounds.Left); 342 | Canvas.SetTop(image, adornmentBounds.Top); 343 | 344 | return image; 345 | } 346 | 347 | /// 348 | /// This method calculates the bounds of the syntax node background adornment. 349 | /// 350 | /// The syntax node that represents the block that has too many lines of code. 351 | /// The span of text that is associated with the background adornment. 352 | /// Returns the calculated bounds of the syntax node adornment. 353 | private Rect CalculateBounds(SyntaxNode syntaxNode, SnapshotSpan snapshotSpan) 354 | { 355 | if (syntaxNode == null) 356 | throw new ArgumentNullException(nameof(syntaxNode)); 357 | 358 | if (snapshotSpan == null) 359 | throw new ArgumentNullException(nameof(snapshotSpan)); 360 | 361 | var geometry = _view.TextViewLines.GetMarkerGeometry(snapshotSpan, false, new Thickness(0)); 362 | 363 | if (geometry == null) 364 | return Rect.Empty; 365 | 366 | var top = geometry.Bounds.Top; 367 | var height = geometry.Bounds.Bottom - geometry.Bounds.Top; 368 | 369 | return new Rect(_view.ViewportLeft, top, _view.ViewportWidth, height); 370 | } 371 | 372 | #endregion 373 | } 374 | } 375 | -------------------------------------------------------------------------------- /src/ForceFeedback.Adapters.VisualStudio/ForceFeedbackTextViewCreationListener.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // Copyright (c) Company. All rights reserved. 4 | // 5 | //------------------------------------------------------------------------------ 6 | 7 | using System.ComponentModel.Composition; 8 | using Microsoft.VisualStudio.Text; 9 | using Microsoft.VisualStudio.Text.Editor; 10 | using Microsoft.VisualStudio.Utilities; 11 | 12 | namespace ForceFeedback.Adapters.VisualStudio 13 | { 14 | /// 15 | /// Establishes an to place the adornment on and exports the 16 | /// that instantiates the adornment on the event of a 's creation 17 | /// 18 | [Export(typeof(IWpfTextViewCreationListener))] 19 | [ContentType("text")] 20 | [TextViewRole(PredefinedTextViewRoles.Document)] 21 | internal sealed class ForceFeedbackTextViewCreationListener : IWpfTextViewCreationListener 22 | { 23 | // Disable "Field is never assigned to..." and "Field is never used" compiler's warnings. Justification: the field is used by MEF. 24 | #pragma warning disable 649, 169 25 | 26 | /// 27 | /// Defines the adornment layer for the adornment. This layer is ordered 28 | /// after the selection layer in the Z-order 29 | /// 30 | [Export(typeof(AdornmentLayerDefinition))] 31 | [Name("ForceFeedbackMethodTextAdornment")] 32 | [Order(Before = PredefinedAdornmentLayers.Selection)] 33 | private AdornmentLayerDefinition EditorAdornmentLayer; 34 | 35 | [Import] 36 | public ITextDocumentFactoryService textDocumentFactory { get; set; } 37 | 38 | #pragma warning restore 649, 169 39 | 40 | #region IWpfTextViewCreationListener 41 | 42 | /// 43 | /// Called when a text view having matching roles is created over a text data model having a matching content type. 44 | /// Instantiates a MethodTooLongTextAdornment manager when the textView is created. 45 | /// 46 | /// The upon which the adornment should be placed 47 | public void TextViewCreated(IWpfTextView textView) 48 | { 49 | // The adornment will listen to any event that changes the layout (text changes, scrolling, etc) 50 | new ForceFeedbackMethodTextAdornment(textView, textDocumentFactory); 51 | } 52 | 53 | #endregion 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/ForceFeedback.Core.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.28606.126 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ForceFeedback.Core", "ForceFeedback.Core\ForceFeedback.Core.csproj", "{78799458-300B-43C3-9352-DDF2762437C3}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Debug|x86 = Debug|x86 12 | Release|Any CPU = Release|Any CPU 13 | Release|x86 = Release|x86 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {5905243F-9579-4582-B21D-B6B56839F130}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 17 | {5905243F-9579-4582-B21D-B6B56839F130}.Debug|Any CPU.Build.0 = Debug|Any CPU 18 | {5905243F-9579-4582-B21D-B6B56839F130}.Debug|x86.ActiveCfg = Debug|x86 19 | {5905243F-9579-4582-B21D-B6B56839F130}.Debug|x86.Build.0 = Debug|x86 20 | {5905243F-9579-4582-B21D-B6B56839F130}.Release|Any CPU.ActiveCfg = Release|Any CPU 21 | {5905243F-9579-4582-B21D-B6B56839F130}.Release|Any CPU.Build.0 = Release|Any CPU 22 | {5905243F-9579-4582-B21D-B6B56839F130}.Release|x86.ActiveCfg = Release|x86 23 | {5905243F-9579-4582-B21D-B6B56839F130}.Release|x86.Build.0 = Release|x86 24 | {920B1495-770A-439D-B93B-40FC872C8C68}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 25 | {920B1495-770A-439D-B93B-40FC872C8C68}.Debug|Any CPU.Build.0 = Debug|Any CPU 26 | {920B1495-770A-439D-B93B-40FC872C8C68}.Debug|x86.ActiveCfg = Debug|x86 27 | {920B1495-770A-439D-B93B-40FC872C8C68}.Debug|x86.Build.0 = Debug|x86 28 | {920B1495-770A-439D-B93B-40FC872C8C68}.Release|Any CPU.ActiveCfg = Release|Any CPU 29 | {920B1495-770A-439D-B93B-40FC872C8C68}.Release|Any CPU.Build.0 = Release|Any CPU 30 | {920B1495-770A-439D-B93B-40FC872C8C68}.Release|x86.ActiveCfg = Release|x86 31 | {920B1495-770A-439D-B93B-40FC872C8C68}.Release|x86.Build.0 = Release|x86 32 | {78799458-300B-43C3-9352-DDF2762437C3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 33 | {78799458-300B-43C3-9352-DDF2762437C3}.Debug|Any CPU.Build.0 = Debug|Any CPU 34 | {78799458-300B-43C3-9352-DDF2762437C3}.Debug|x86.ActiveCfg = Debug|x86 35 | {78799458-300B-43C3-9352-DDF2762437C3}.Debug|x86.Build.0 = Debug|x86 36 | {78799458-300B-43C3-9352-DDF2762437C3}.Release|Any CPU.ActiveCfg = Release|Any CPU 37 | {78799458-300B-43C3-9352-DDF2762437C3}.Release|Any CPU.Build.0 = Release|Any CPU 38 | {78799458-300B-43C3-9352-DDF2762437C3}.Release|x86.ActiveCfg = Release|x86 39 | {78799458-300B-43C3-9352-DDF2762437C3}.Release|x86.Build.0 = Release|x86 40 | EndGlobalSection 41 | GlobalSection(SolutionProperties) = preSolution 42 | HideSolutionNode = FALSE 43 | EndGlobalSection 44 | GlobalSection(ExtensibilityGlobals) = postSolution 45 | SolutionGuid = {649367F2-2819-4BBB-A272-76C1F4E160F2} 46 | EndGlobalSection 47 | EndGlobal 48 | -------------------------------------------------------------------------------- /src/ForceFeedback.Core/Feedbacks/DelayKeyboardInputsFeedback.cs: -------------------------------------------------------------------------------- 1 | namespace ForceFeedback.Core.Feedbacks 2 | { 3 | public class DelayKeyboardInputsFeedback : IFeedback 4 | { 5 | public DelayKeyboardInputsFeedback(int milliseconds) 6 | { 7 | Milliseconds = milliseconds; 8 | } 9 | 10 | public int Milliseconds { get; private set; } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/ForceFeedback.Core/Feedbacks/DrawColoredBackgroundFeedback.cs: -------------------------------------------------------------------------------- 1 | using System.Drawing; 2 | 3 | namespace ForceFeedback.Core.Feedbacks 4 | { 5 | public class DrawColoredBackgroundFeedback : IFeedback 6 | { 7 | public DrawColoredBackgroundFeedback(Color backgroundColor, Color outlineColor) 8 | { 9 | BackgroundColor = backgroundColor; 10 | OutlineColor = outlineColor; 11 | } 12 | 13 | public Color BackgroundColor { get; set; } 14 | 15 | public Color OutlineColor { get; } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/ForceFeedback.Core/Feedbacks/InsertTextFeedback.cs: -------------------------------------------------------------------------------- 1 | namespace ForceFeedback.Core.Feedbacks 2 | { 3 | public class InsertTextFeedback : IFeedback 4 | { 5 | public InsertTextFeedback(string text) 6 | { 7 | Text = text; 8 | } 9 | 10 | public string Text { get; } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/ForceFeedback.Core/Feedbacks/PreventKeyboardInputsFeedback.cs: -------------------------------------------------------------------------------- 1 | namespace ForceFeedback.Core.Feedbacks 2 | { 3 | public class PreventKeyboardInputsFeedback : IFeedback 4 | { 5 | public PreventKeyboardInputsFeedback() 6 | { 7 | 8 | } 9 | 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/ForceFeedback.Core/ForceFeedback.Core.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0 5 | AnyCPU;x86 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/ForceFeedback.Core/ForceFeedbackMachine.cs: -------------------------------------------------------------------------------- 1 | using ForceFeedback.Core.Feedbacks; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Drawing; 5 | 6 | namespace ForceFeedback.Core 7 | { 8 | public class ForceFeedbackMachine 9 | { 10 | private string _solutionFilePath; 11 | private string _projectFilePath; 12 | private string _sourceFilePath; 13 | 14 | public ForceFeedbackMachine(string solutionFilePath, string projectFilePath, string sourceFilePath) 15 | { 16 | _solutionFilePath = solutionFilePath; 17 | _projectFilePath = projectFilePath; 18 | _sourceFilePath = sourceFilePath; 19 | } 20 | 21 | public List RequestFeedbackForMethodCodeBlock(string methodName, int methodLineCount) 22 | { 23 | if (methodLineCount <= 3) 24 | return new List(); 25 | 26 | var backgroundColor = Color.Blue; 27 | var outlineColor = Color.White; 28 | 29 | if (methodLineCount > 15) 30 | { 31 | backgroundColor = Color.FromArgb(200, 0, 0, 255); 32 | outlineColor = Color.Red; 33 | } 34 | else if (methodLineCount > 10) 35 | backgroundColor = Color.FromArgb(150, 0, 0, 255); 36 | else if (methodLineCount > 5) 37 | backgroundColor = Color.FromArgb(100, 0, 0, 255); 38 | else if (methodLineCount > 3) 39 | backgroundColor = Color.FromArgb(50, 0, 0, 255); 40 | 41 | return new List 42 | { 43 | new DrawColoredBackgroundFeedback(backgroundColor, outlineColor) 44 | }; 45 | } 46 | 47 | public List RequestFeedbackAfterMethodCodeChange(string methodName, int methodLineCount) 48 | { 49 | var result = new List(); 50 | 51 | if (methodLineCount < 15) 52 | return result; 53 | 54 | const string noiseCharacters = "⌫♥♠♦◘○☺☻♀►♂↨◄↕"; 55 | var random = new Random(); 56 | var index = random.Next(0, noiseCharacters.Length); 57 | 58 | result.Add(new InsertTextFeedback($"{noiseCharacters[index]}")); 59 | 60 | // [RS] Add per line 100 ms delay. :) 61 | if (methodLineCount > 20) 62 | result.Add(new DelayKeyboardInputsFeedback((methodLineCount) - 20 * 100)); 63 | 64 | return result; 65 | } 66 | 67 | public List RequestFeedbackBeforeMethodCodeChange(string methodName, int methodLineCount) 68 | { 69 | var result = new List(); 70 | 71 | if (methodLineCount > 25) 72 | result.Add(new PreventKeyboardInputsFeedback()); 73 | 74 | return result; 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/ForceFeedback.Core/IFeedback.cs: -------------------------------------------------------------------------------- 1 | namespace ForceFeedback.Core 2 | { 3 | public interface IFeedback 4 | { 5 | 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/ForceFeedback.Setup.VisualStudio/ForceFeedback.Setup.VisualStudio.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 16.0 5 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 6 | 7 | 8 | true 9 | bin\x86\Debug\ 10 | DEBUG;TRACE 11 | full 12 | x86 13 | prompt 14 | MinimumRecommendedRules.ruleset 15 | 16 | 17 | ..\Precompiled\ 18 | TRACE 19 | true 20 | pdbonly 21 | x86 22 | prompt 23 | MinimumRecommendedRules.ruleset 24 | 25 | 26 | 27 | Debug 28 | AnyCPU 29 | 2.0 30 | {82b43b9b-a64c-4715-b499-d71e9ca2bd60};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 31 | {5905243F-9579-4582-B21D-B6B56839F130} 32 | Library 33 | Properties 34 | ForceFeedback.Setup.VisualStudio 35 | ForceFeedback.Setup.VisualStudio 36 | v4.7.2 37 | true 38 | true 39 | true 40 | false 41 | false 42 | true 43 | true 44 | Program 45 | $(DevEnvDir)devenv.exe 46 | /rootsuffix Exp 47 | 48 | 49 | true 50 | full 51 | false 52 | ..\Precompiled\ 53 | DEBUG;TRACE 54 | prompt 55 | 4 56 | True 57 | 58 | 59 | pdbonly 60 | true 61 | ..\Precompiled\ 62 | TRACE 63 | prompt 64 | 4 65 | AnyCPU 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | Designer 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | {920B1495-770A-439D-B93B-40FC872C8C68} 89 | ForceFeedback.Adapters.VisualStudio 90 | BuiltProjectOutputGroup%3bBuiltProjectOutputGroupDependencies%3bGetCopyToOutputDirectoryItems%3bSatelliteDllsProjectOutputGroup%3b 91 | DebugSymbolsProjectOutputGroup%3b 92 | 93 | 94 | {78799458-300B-43C3-9352-DDF2762437C3} 95 | ForceFeedback.Core 96 | BuiltProjectOutputGroup%3bBuiltProjectOutputGroupDependencies%3bGetCopyToOutputDirectoryItems%3bSatelliteDllsProjectOutputGroup%3b 97 | DebugSymbolsProjectOutputGroup%3b 98 | 99 | 100 | 101 | 102 | 109 | -------------------------------------------------------------------------------- /src/ForceFeedback.Setup.VisualStudio/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("ForceFeedback.VisualStudioSetup")] 8 | [assembly: AssemblyDescription("")] 9 | [assembly: AssemblyConfiguration("")] 10 | [assembly: AssemblyCompany("")] 11 | [assembly: AssemblyProduct("ForceFeedback.VisualStudioSetup")] 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 | -------------------------------------------------------------------------------- /src/ForceFeedback.Setup.VisualStudio/VisualStudioSetupPackage.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | using System.Threading; 4 | using Microsoft.VisualStudio.Shell; 5 | using Task = System.Threading.Tasks.Task; 6 | 7 | namespace ForceFeedback.Setup.VisualStudio 8 | { 9 | /// 10 | /// This is the class that implements the package exposed by this assembly. 11 | /// 12 | /// 13 | /// 14 | /// The minimum requirement for a class to be considered a valid package for Visual Studio 15 | /// is to implement the IVsPackage interface and register itself with the shell. 16 | /// This package uses the helper classes defined inside the Managed Package Framework (MPF) 17 | /// to do it: it derives from the Package class that provides the implementation of the 18 | /// IVsPackage interface and uses the registration attributes defined in the framework to 19 | /// register itself and its components with the shell. These attributes tell the pkgdef creation 20 | /// utility what data to put into .pkgdef file. 21 | /// 22 | /// 23 | /// To get loaded into VS, the package must be referred by <Asset Type="Microsoft.VisualStudio.VsPackage" ...> in .vsixmanifest file. 24 | /// 25 | /// 26 | [PackageRegistration(UseManagedResourcesOnly = true, AllowsBackgroundLoading = true)] 27 | [Guid(PackageGuidString)] 28 | public sealed class VisualStudioSetupPackage : AsyncPackage 29 | { 30 | /// 31 | /// ForceFeedback.VisualStudioSetupPackage GUID string. 32 | /// 33 | public const string PackageGuidString = "96087052-a8bc-4def-bb2e-222e084ba6d4"; 34 | 35 | #region Package Members 36 | 37 | /// 38 | /// Initialization of the package; this method is called right after the package is sited, so this is the place 39 | /// where you can put all the initialization code that rely on services provided by VisualStudio. 40 | /// 41 | /// A cancellation token to monitor for initialization cancellation, which can occur when VS is shutting down. 42 | /// A provider for progress updates. 43 | /// A task representing the async work of package initialization, or an already completed task if there is none. Do not return null from this method. 44 | protected override async Task InitializeAsync(CancellationToken cancellationToken, IProgress progress) 45 | { 46 | // When initialized asynchronously, the current thread may be a background thread at this point. 47 | // Do any initialization that requires the UI thread after switching to the UI thread. 48 | await this.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); 49 | 50 | await base.InitializeAsync(cancellationToken, progress); 51 | } 52 | 53 | #endregion 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/ForceFeedback.Setup.VisualStudio/index.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Getting Started 6 | 7 | 136 | 137 | 138 | 139 |
140 | 144 | 145 |
146 |
147 |

Creating a Visual Studio Extension

148 | 149 |

This project enables developers to create an extension for Visual Studio. The solution contains a VSIX project that packages the extension into a VSIX file. This file is used to install an extension for Visual Studio.

150 |

Add new features

151 | 152 |
    153 |
  1. Right-click the project node in Solution Explorer and select Add>New Item.
  2. 154 |
  3. In the Add New Item dialog box, expand the Extensibility node under Visual C# or Visual Basic.
  4. 155 |
  5. Choose from the available item templates: Visual Studio Package, Editor Items (Classifier, Margin, Text Adornment, Viewport Adornment), Command, Tool Window, Toolbox Control, and then click Add.
  6. 156 |
157 | 158 |

The files for the template that you selected are added to the project. You can start adding functionality to your item template, press F5 to run the project, or add additional item templates.

159 | 160 |

Run and debug

161 |

To run the project, press F5. Visual Studio will:

162 | 163 |
    164 |
  • Build the extension from the VSIX project.
  • 165 |
  • Create a VSIX package from the VSIX project.
  • 166 |
  • When debugging, start an experimental instance of Visual Studio with the VSIX package installed.
  • 167 |
168 | 169 |

In the experimental instance of Visual Studio you can test out the functionality of your extension without affecting your Visual Studio installation.

170 | 171 |
172 |
173 |
174 |

Visual Studio Extensibility Resources

175 | 176 |
    177 |
  1. Visual Studio documentation
    Detailed documentation and API reference material for building extensions.
  2. 178 |
  3. Extension samples on GitHub
    Use a sample project to kickstart your development.
  4. 179 |
  5. Extensibility chat room on Gitter
    Meet other extension developers and exchange tips and tricks for extension development.
  6. 180 |
  7. Channel 9 videos on extensibility
    Watch videos from the product team on Visual Studio extensibility.
  8. 181 |
  9. Extensibility Tools
    Install an optional helper tool that adds extra IDE support for extension authors.
  10. 182 |
183 |

Give us feedback

184 | 187 |
188 |
189 |
190 |
191 | 192 | 193 | -------------------------------------------------------------------------------- /src/ForceFeedback.Setup.VisualStudio/source.extension.vsixmanifest: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Force Feedback Programming 6 | Make evolvability really tangible while coding: that's what Force Feedback Programming (FFP) is about. 7 | https://github.com/robinsedlaczek/ForceFeedbackProgramming 8 | Clean Code, Force Feedback, CI, FFP 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /src/ForceFeedback.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.28606.126 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ForceFeedback.Setup.VisualStudio", "ForceFeedback.Setup.VisualStudio\ForceFeedback.Setup.VisualStudio.csproj", "{5905243F-9579-4582-B21D-B6B56839F130}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ForceFeedback.Adapters.VisualStudio", "ForceFeedback.Adapters.VisualStudio\ForceFeedback.Adapters.VisualStudio.csproj", "{920B1495-770A-439D-B93B-40FC872C8C68}" 9 | EndProject 10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ForceFeedback.Core", "ForceFeedback.Core\ForceFeedback.Core.csproj", "{78799458-300B-43C3-9352-DDF2762437C3}" 11 | EndProject 12 | Global 13 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 14 | Debug|Any CPU = Debug|Any CPU 15 | Debug|x86 = Debug|x86 16 | Release|Any CPU = Release|Any CPU 17 | Release|x86 = Release|x86 18 | EndGlobalSection 19 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 20 | {5905243F-9579-4582-B21D-B6B56839F130}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {5905243F-9579-4582-B21D-B6B56839F130}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {5905243F-9579-4582-B21D-B6B56839F130}.Debug|x86.ActiveCfg = Debug|x86 23 | {5905243F-9579-4582-B21D-B6B56839F130}.Debug|x86.Build.0 = Debug|x86 24 | {5905243F-9579-4582-B21D-B6B56839F130}.Release|Any CPU.ActiveCfg = Release|Any CPU 25 | {5905243F-9579-4582-B21D-B6B56839F130}.Release|Any CPU.Build.0 = Release|Any CPU 26 | {5905243F-9579-4582-B21D-B6B56839F130}.Release|x86.ActiveCfg = Release|x86 27 | {5905243F-9579-4582-B21D-B6B56839F130}.Release|x86.Build.0 = Release|x86 28 | {920B1495-770A-439D-B93B-40FC872C8C68}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 29 | {920B1495-770A-439D-B93B-40FC872C8C68}.Debug|Any CPU.Build.0 = Debug|Any CPU 30 | {920B1495-770A-439D-B93B-40FC872C8C68}.Debug|x86.ActiveCfg = Debug|x86 31 | {920B1495-770A-439D-B93B-40FC872C8C68}.Debug|x86.Build.0 = Debug|x86 32 | {920B1495-770A-439D-B93B-40FC872C8C68}.Release|Any CPU.ActiveCfg = Release|Any CPU 33 | {920B1495-770A-439D-B93B-40FC872C8C68}.Release|Any CPU.Build.0 = Release|Any CPU 34 | {920B1495-770A-439D-B93B-40FC872C8C68}.Release|x86.ActiveCfg = Release|x86 35 | {920B1495-770A-439D-B93B-40FC872C8C68}.Release|x86.Build.0 = Release|x86 36 | {78799458-300B-43C3-9352-DDF2762437C3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 37 | {78799458-300B-43C3-9352-DDF2762437C3}.Debug|Any CPU.Build.0 = Debug|Any CPU 38 | {78799458-300B-43C3-9352-DDF2762437C3}.Debug|x86.ActiveCfg = Debug|x86 39 | {78799458-300B-43C3-9352-DDF2762437C3}.Debug|x86.Build.0 = Debug|x86 40 | {78799458-300B-43C3-9352-DDF2762437C3}.Release|Any CPU.ActiveCfg = Release|Any CPU 41 | {78799458-300B-43C3-9352-DDF2762437C3}.Release|Any CPU.Build.0 = Release|Any CPU 42 | {78799458-300B-43C3-9352-DDF2762437C3}.Release|x86.ActiveCfg = Release|x86 43 | {78799458-300B-43C3-9352-DDF2762437C3}.Release|x86.Build.0 = Release|x86 44 | EndGlobalSection 45 | GlobalSection(SolutionProperties) = preSolution 46 | HideSolutionNode = FALSE 47 | EndGlobalSection 48 | GlobalSection(ExtensibilityGlobals) = postSolution 49 | SolutionGuid = {649367F2-2819-4BBB-A272-76C1F4E160F2} 50 | EndGlobalSection 51 | EndGlobal 52 | --------------------------------------------------------------------------------