match-highlighter.js 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100
  1. // Highlighting text that matches the selection
  2. //
  3. // Defines an option highlightSelectionMatches, which, when enabled,
  4. // will style strings that match the selection throughout the
  5. // document.
  6. //
  7. // The option can be set to true to simply enable it, or to a
  8. // {minChars, style, showToken} object to explicitly configure it.
  9. // minChars is the minimum amount of characters that should be
  10. // selected for the behavior to occur, and style is the token style to
  11. // apply to the matches. This will be prefixed by "cm-" to create an
  12. // actual CSS class name. showToken, when enabled, will cause the
  13. // current token to be highlighted when nothing is selected.
  14. (function(mod) {
  15. if (typeof exports == "object" && typeof module == "object") // CommonJS
  16. mod(require("../../lib/codemirror"));
  17. else if (typeof define == "function" && define.amd) // AMD
  18. define(["../../lib/codemirror"], mod);
  19. else // Plain browser env
  20. mod(CodeMirror);
  21. })(function(CodeMirror) {
  22. "use strict";
  23. var DEFAULT_MIN_CHARS = 2;
  24. var DEFAULT_TOKEN_STYLE = "matchhighlight";
  25. var DEFAULT_DELAY = 100;
  26. function State(options) {
  27. if (typeof options == "object") {
  28. this.minChars = options.minChars;
  29. this.style = options.style;
  30. this.showToken = options.showToken;
  31. this.delay = options.delay;
  32. }
  33. if (this.style == null) this.style = DEFAULT_TOKEN_STYLE;
  34. if (this.minChars == null) this.minChars = DEFAULT_MIN_CHARS;
  35. if (this.delay == null) this.delay = DEFAULT_DELAY;
  36. this.overlay = this.timeout = null;
  37. }
  38. CodeMirror.defineOption("highlightSelectionMatches", false, function(cm, val, old) {
  39. if (old && old != CodeMirror.Init) {
  40. var over = cm.state.matchHighlighter.overlay;
  41. if (over) cm.removeOverlay(over);
  42. clearTimeout(cm.state.matchHighlighter.timeout);
  43. cm.state.matchHighlighter = null;
  44. cm.off("cursorActivity", cursorActivity);
  45. }
  46. if (val) {
  47. cm.state.matchHighlighter = new State(val);
  48. highlightMatches(cm);
  49. cm.on("cursorActivity", cursorActivity);
  50. }
  51. });
  52. function cursorActivity(cm) {
  53. var state = cm.state.matchHighlighter;
  54. clearTimeout(state.timeout);
  55. state.timeout = setTimeout(function() {highlightMatches(cm);}, state.delay);
  56. }
  57. function highlightMatches(cm) {
  58. cm.operation(function() {
  59. var state = cm.state.matchHighlighter;
  60. if (state.overlay) {
  61. cm.removeOverlay(state.overlay);
  62. state.overlay = null;
  63. }
  64. if (!cm.somethingSelected() && state.showToken) {
  65. var re = state.showToken === true ? /[\w$]/ : state.showToken;
  66. var cur = cm.getCursor(), line = cm.getLine(cur.line), start = cur.ch, end = start;
  67. while (start && re.test(line.charAt(start - 1))) --start;
  68. while (end < line.length && re.test(line.charAt(end))) ++end;
  69. if (start < end)
  70. cm.addOverlay(state.overlay = makeOverlay(line.slice(start, end), re, state.style));
  71. return;
  72. }
  73. if (cm.getCursor("head").line != cm.getCursor("anchor").line) return;
  74. var selection = cm.getSelections()[0].replace(/^\s+|\s+$/g, "");
  75. if (selection.length >= state.minChars)
  76. cm.addOverlay(state.overlay = makeOverlay(selection, false, state.style));
  77. });
  78. }
  79. function boundariesAround(stream, re) {
  80. return (!stream.start || !re.test(stream.string.charAt(stream.start - 1))) &&
  81. (stream.pos == stream.string.length || !re.test(stream.string.charAt(stream.pos)));
  82. }
  83. function makeOverlay(query, hasBoundary, style) {
  84. return {token: function(stream) {
  85. if (stream.match(query) &&
  86. (!hasBoundary || boundariesAround(stream, hasBoundary)))
  87. return style;
  88. stream.next();
  89. stream.skipTo(query.charAt(0)) || stream.skipToEnd();
  90. }};
  91. }
  92. });