PageRenderTime 3653ms CodeModel.GetById 24ms RepoModel.GetById 0ms app.codeStats 0ms

/VsVimShared/KeyBinding.cs

http://github.com/jaredpar/VsVim
C# | 369 lines | 302 code | 45 blank | 22 comment | 41 complexity | 272b40df8f7bbfdbf5a38439e86319a1 MD5 | raw file
Possible License(s): Apache-2.0, GPL-2.0
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using Vim;
  6. namespace VsVim
  7. {
  8. /// <summary>
  9. /// KeyBinding in Visual Studio as set through the Key board part of the Environment options
  10. /// panel
  11. /// </summary>
  12. public sealed class KeyBinding : IEquatable<KeyBinding>
  13. {
  14. private readonly Lazy<string> _commandString;
  15. public readonly string Scope;
  16. public readonly IEnumerable<KeyStroke> KeyStrokes;
  17. public KeyStroke FirstKeyStroke
  18. {
  19. get { return KeyStrokes.First(); }
  20. }
  21. /// <summary>
  22. /// Visual Studio string which is the equivalent of this KeyBinding instance
  23. /// </summary>
  24. public string CommandString
  25. {
  26. get { return _commandString.Value; }
  27. }
  28. public KeyBinding(string scope, KeyStroke stroke)
  29. {
  30. Scope = scope;
  31. KeyStrokes = Enumerable.Repeat(stroke, 1);
  32. _commandString = new Lazy<string>(CreateCommandString);
  33. }
  34. public KeyBinding(string scope, IEnumerable<KeyStroke> strokes)
  35. {
  36. Scope = scope;
  37. KeyStrokes = strokes.ToList();
  38. _commandString = new Lazy<string>(CreateCommandString);
  39. }
  40. #region Equality
  41. public override int GetHashCode()
  42. {
  43. return Scope.GetHashCode() ^ CommandString.GetHashCode();
  44. }
  45. public override bool Equals(object obj)
  46. {
  47. var other = obj as KeyBinding;
  48. return Equals(other);
  49. }
  50. public bool Equals(KeyBinding other)
  51. {
  52. if (ReferenceEquals(other, null))
  53. {
  54. return false;
  55. }
  56. var comp = StringComparer.OrdinalIgnoreCase;
  57. return
  58. comp.Equals(Scope, other.Scope)
  59. && comp.Equals(CommandString, other.CommandString);
  60. }
  61. public static bool operator ==(KeyBinding left, KeyBinding right)
  62. {
  63. return EqualityComparer<KeyBinding>.Default.Equals(left, right);
  64. }
  65. public static bool operator !=(KeyBinding left, KeyBinding right)
  66. {
  67. return !EqualityComparer<KeyBinding>.Default.Equals(left, right);
  68. }
  69. #endregion
  70. private string CreateCommandString()
  71. {
  72. var builder = new StringBuilder();
  73. builder.Append(Scope);
  74. builder.Append("::");
  75. var isFirst = true;
  76. foreach (var stroke in KeyStrokes)
  77. {
  78. if (!isFirst)
  79. {
  80. builder.Append(", ");
  81. }
  82. isFirst = false;
  83. AppendCommandForSingle(stroke, builder);
  84. }
  85. return builder.ToString();
  86. }
  87. private static void AppendCommandForSingle(KeyStroke stroke, StringBuilder builder)
  88. {
  89. if (0 != (stroke.KeyModifiers & KeyModifiers.Control))
  90. {
  91. builder.Append("Ctrl+");
  92. }
  93. if (0 != (stroke.KeyModifiers & KeyModifiers.Shift))
  94. {
  95. builder.Append("Shift+");
  96. }
  97. if (0 != (stroke.KeyModifiers & KeyModifiers.Alt))
  98. {
  99. builder.Append("Alt+");
  100. }
  101. EnsureVsMap();
  102. var input = stroke.KeyInput;
  103. var query = _vsMap.Where(x => x.Value == input);
  104. if (query.Any())
  105. {
  106. builder.Append(query.First().Key);
  107. }
  108. else if (Char.IsLetter(input.Char))
  109. {
  110. builder.Append(Char.ToUpper(input.Char));
  111. }
  112. else if (input.Char == ' ')
  113. {
  114. builder.Append("Space");
  115. }
  116. else
  117. {
  118. builder.Append(input.Char);
  119. }
  120. }
  121. public override string ToString()
  122. {
  123. return CommandString;
  124. }
  125. public static string CreateKeyBindingStringForSingleKeyStroke(KeyStroke stroke)
  126. {
  127. var builder = new StringBuilder();
  128. AppendCommandForSingle(stroke, builder);
  129. return builder.ToString();
  130. }
  131. #region Parsing Methods
  132. private static readonly string[] ModifierPrefix = new[] { "Shift", "Alt", "Ctrl" };
  133. private static Dictionary<string, KeyInput> _vsMap;
  134. private static void BuildVsMap()
  135. {
  136. var map = new Dictionary<string, KeyInput>(StringComparer.OrdinalIgnoreCase);
  137. map.Add("Down Arrow", KeyInputUtil.VimKeyToKeyInput(VimKey.Down));
  138. map.Add("Up Arrow", KeyInputUtil.VimKeyToKeyInput(VimKey.Up));
  139. map.Add("Left Arrow", KeyInputUtil.VimKeyToKeyInput(VimKey.Left));
  140. map.Add("Right Arrow", KeyInputUtil.VimKeyToKeyInput(VimKey.Right));
  141. map.Add("Bkspce", KeyInputUtil.VimKeyToKeyInput(VimKey.Back));
  142. map.Add("PgDn", KeyInputUtil.VimKeyToKeyInput(VimKey.PageDown));
  143. map.Add("PgUp", KeyInputUtil.VimKeyToKeyInput(VimKey.PageUp));
  144. map.Add("Ins", KeyInputUtil.VimKeyToKeyInput(VimKey.Insert));
  145. map.Add("Del", KeyInputUtil.VimKeyToKeyInput(VimKey.Delete));
  146. map.Add("Esc", KeyInputUtil.EscapeKey);
  147. map.Add("Break", KeyInputUtil.CharWithControlToKeyInput('c'));
  148. map.Add("Num +", KeyInputUtil.VimKeyToKeyInput(VimKey.KeypadPlus));
  149. map.Add("Num -", KeyInputUtil.VimKeyToKeyInput(VimKey.KeypadMinus));
  150. map.Add("Num /", KeyInputUtil.VimKeyToKeyInput(VimKey.KeypadDivide));
  151. map.Add("Num *", KeyInputUtil.VimKeyToKeyInput(VimKey.KeypadMultiply));
  152. map.Add("Enter", KeyInputUtil.EnterKey);
  153. map.Add("Tab", KeyInputUtil.TabKey);
  154. map.Add("Home", KeyInputUtil.VimKeyToKeyInput(VimKey.Home));
  155. map.Add("End", KeyInputUtil.VimKeyToKeyInput(VimKey.End));
  156. map.Add("F1", KeyInputUtil.VimKeyToKeyInput(VimKey.F1));
  157. map.Add("F2", KeyInputUtil.VimKeyToKeyInput(VimKey.F2));
  158. map.Add("F3", KeyInputUtil.VimKeyToKeyInput(VimKey.F3));
  159. map.Add("F4", KeyInputUtil.VimKeyToKeyInput(VimKey.F4));
  160. map.Add("F5", KeyInputUtil.VimKeyToKeyInput(VimKey.F5));
  161. map.Add("F6", KeyInputUtil.VimKeyToKeyInput(VimKey.F6));
  162. map.Add("F7", KeyInputUtil.VimKeyToKeyInput(VimKey.F7));
  163. map.Add("F8", KeyInputUtil.VimKeyToKeyInput(VimKey.F8));
  164. map.Add("F9", KeyInputUtil.VimKeyToKeyInput(VimKey.F9));
  165. map.Add("F10", KeyInputUtil.VimKeyToKeyInput(VimKey.F10));
  166. map.Add("F11", KeyInputUtil.VimKeyToKeyInput(VimKey.F11));
  167. map.Add("F12", KeyInputUtil.VimKeyToKeyInput(VimKey.F12));
  168. map.Add("Space", KeyInputUtil.CharToKeyInput(' '));
  169. _vsMap = map;
  170. }
  171. private static void EnsureVsMap()
  172. {
  173. if (null == _vsMap)
  174. {
  175. BuildVsMap();
  176. }
  177. }
  178. private static bool TryConvertToModifierKeys(string mod, out KeyModifiers modKeys)
  179. {
  180. var comp = StringComparer.OrdinalIgnoreCase;
  181. if (comp.Equals(mod, "shift"))
  182. {
  183. modKeys = KeyModifiers.Shift;
  184. }
  185. else if (comp.Equals(mod, "ctrl"))
  186. {
  187. modKeys = KeyModifiers.Control;
  188. }
  189. else if (comp.Equals(mod, "alt"))
  190. {
  191. modKeys = KeyModifiers.Alt;
  192. }
  193. else
  194. {
  195. modKeys = KeyModifiers.None;
  196. return false;
  197. }
  198. return true;
  199. }
  200. /// <summary>
  201. /// Convert the single character to a KeyInput. Visual Studio doesn't
  202. /// differentiate between upper and lower case alpha characters.
  203. /// Use all lower case for simplicity elsewhere
  204. /// </summary>
  205. private static KeyInput ConvertToKeyInput(char c)
  206. {
  207. c = Char.IsLetter(c) ? Char.ToLower(c) : c;
  208. return KeyInputUtil.CharToKeyInput(c);
  209. }
  210. private static KeyInput ConvertToKeyInput(string keystroke)
  211. {
  212. if (keystroke.Length == 1)
  213. {
  214. return ConvertToKeyInput(keystroke[0]);
  215. }
  216. KeyInput vs;
  217. if (TryConvertVsSpecificKey(keystroke, out vs))
  218. {
  219. return vs;
  220. }
  221. return null;
  222. }
  223. /// <summary>
  224. /// Try and convert the given string into a Visual Studio specific key stroke.
  225. /// </summary>
  226. private static bool TryConvertVsSpecificKey(string keystroke, out KeyInput keyInput)
  227. {
  228. EnsureVsMap();
  229. if (_vsMap.TryGetValue(keystroke, out keyInput))
  230. {
  231. return true;
  232. }
  233. if (keystroke.StartsWith("Num ", StringComparison.OrdinalIgnoreCase))
  234. {
  235. keyInput = null;
  236. switch (keystroke.ToLower())
  237. {
  238. case "num +":
  239. keyInput = KeyInputUtil.VimKeyToKeyInput(VimKey.KeypadPlus);
  240. break;
  241. case "num /":
  242. keyInput = KeyInputUtil.VimKeyToKeyInput(VimKey.KeypadDivide);
  243. break;
  244. case "num *":
  245. keyInput = KeyInputUtil.VimKeyToKeyInput(VimKey.KeypadMultiply);
  246. break;
  247. case "num -":
  248. keyInput = KeyInputUtil.VimKeyToKeyInput(VimKey.KeypadMinus);
  249. break;
  250. }
  251. return keyInput != null;
  252. }
  253. keyInput = null;
  254. return false;
  255. }
  256. private static KeyStroke ParseOne(string entry)
  257. {
  258. // If it's of length 1 it can only be a single keystroke entry
  259. if (entry.Length == 1)
  260. {
  261. return new KeyStroke(ConvertToKeyInput(entry), KeyModifiers.None);
  262. }
  263. // First get rid of the Modifiers
  264. var mod = KeyModifiers.None;
  265. while (ModifierPrefix.Any(x => entry.StartsWith(x, StringComparison.OrdinalIgnoreCase)))
  266. {
  267. var index = entry.IndexOf('+');
  268. if (index < 0)
  269. {
  270. return null;
  271. }
  272. var value = entry.Substring(0, index);
  273. var modKeys = KeyModifiers.None;
  274. if (!TryConvertToModifierKeys(value, out modKeys))
  275. {
  276. return null;
  277. }
  278. mod |= modKeys;
  279. entry = entry.Substring(index + 1).TrimStart();
  280. }
  281. var ki = ConvertToKeyInput(entry);
  282. if (ki == null)
  283. {
  284. return null;
  285. }
  286. return new KeyStroke(ki, mod);
  287. }
  288. /// <summary>
  289. /// Parse the key binding format as described by the Command.Bindings documentation
  290. ///
  291. /// http://msdn.microsoft.com/en-us/library/envdte.command.bindings.aspx
  292. /// </summary>
  293. public static KeyBinding Parse(string binding)
  294. {
  295. KeyBinding keyBinding;
  296. if (!TryParse(binding, out keyBinding))
  297. {
  298. throw new ArgumentException("Invalid key binding");
  299. }
  300. return keyBinding;
  301. }
  302. public static bool TryParse(string binding, out KeyBinding keyBinding)
  303. {
  304. keyBinding = default(KeyBinding);
  305. var scopeEnd = binding.IndexOf(':');
  306. if (scopeEnd < 0)
  307. {
  308. return false;
  309. }
  310. var scope = binding.Substring(0, scopeEnd);
  311. var rest = binding.Substring(scopeEnd + 2);
  312. var entries = rest
  313. .Split(new string[] { ", " }, StringSplitOptions.RemoveEmptyEntries)
  314. .Select(x => ParseOne(x));
  315. if (entries.Any(x => x == null))
  316. {
  317. return false;
  318. }
  319. keyBinding = new KeyBinding(scope, entries);
  320. return true;
  321. }
  322. #endregion
  323. }
  324. }