PageRenderTime 59ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 1ms

/ScintillaNET/Annotation.cs

https://bitbucket.org/nekokun/nekokun
C# | 442 lines | 215 code | 81 blank | 146 comment | 39 complexity | 9134453deca4101a1223b374ea43a6fc MD5 | raw file
Possible License(s): MIT, CC-BY-SA-3.0
  1. #region Using Directives
  2. using System;
  3. using System.Collections.Generic;
  4. using System.Text;
  5. using ScintillaNET.Properties;
  6. #endregion Using Directives
  7. namespace ScintillaNET
  8. {
  9. /// <summary>
  10. /// Represents a customizable read-only block of text which can be displayed below
  11. /// each line in a <see cref="Scintilla" /> control.
  12. /// </summary>
  13. public class Annotation
  14. {
  15. #region Constants
  16. private const int INDIVIDUAL_STYLES = 0x100; // From the Scintilla source code: "implies array of styles"
  17. #endregion Constants
  18. #region Fields
  19. private Scintilla _scintilla;
  20. private int _lineIndex;
  21. #endregion Fields
  22. #region Methods
  23. private void CheckInvalid()
  24. {
  25. // Are we in a state where we can no longer accurately
  26. // represent the annotation we were originally created for?
  27. if (_lineIndex == -1)
  28. throw new InvalidOperationException(Resources.Exception_InvalidAnnotation);
  29. }
  30. /// <summary>
  31. /// Removes all text and styles associated with the annotation.
  32. /// </summary>
  33. public virtual void Clear()
  34. {
  35. CheckInvalid();
  36. // Remove the annotation
  37. _scintilla.DirectMessage(NativeMethods.SCI_ANNOTATIONSETTEXT, new IntPtr(_lineIndex), IntPtr.Zero);
  38. }
  39. /// <summary>
  40. /// Overridden. Determines whether the specified <see cref="Object" /> is equal to the current <see cref="Object" />.
  41. /// </summary>
  42. /// <param name="obj">The object to compare with the current object.</param>
  43. /// <returns>
  44. /// true if the specified <see cref="Object" /> is equal to the
  45. /// current <see cref="Object" />; otherwise, false.
  46. /// </returns>
  47. public override bool Equals(object obj)
  48. {
  49. if (obj is Annotation)
  50. {
  51. // If another annotation has the same Scintilla
  52. // control and line index--it is the same.
  53. Annotation a = (Annotation)obj;
  54. if (a._scintilla == _scintilla && a._lineIndex == _lineIndex)
  55. return true;
  56. }
  57. return false;
  58. }
  59. /// <summary>
  60. /// Determines whether the specified <see cref="Annotation" /> is equal to the current <see cref="Annotation" />.
  61. /// </summary>
  62. /// <param name="a">The annotation to compare with the current annotation.</param>
  63. /// <returns>
  64. /// true if the specified <see cref="Annotation" /> is equal to the
  65. /// current <see cref="Annotation" />; otherwise, false.
  66. /// </returns>
  67. public virtual bool Equals(Annotation a)
  68. {
  69. // Per Microsoft's recommendations we have an Equals for
  70. // our specific type "to enhance performance".
  71. if (a != null)
  72. {
  73. // Just remember to keep in sync with standard Equals above
  74. if (a._scintilla == _scintilla && a._lineIndex == _lineIndex)
  75. return true;
  76. }
  77. return false;
  78. }
  79. /// <summary>
  80. /// Overridden. Serves as a hash function for a particular type.
  81. /// </summary>
  82. /// <returns>A hash code for the current <see cref="Object" />.</returns>
  83. public override int GetHashCode()
  84. {
  85. return _scintilla.GetHashCode() ^ _lineIndex;
  86. }
  87. /// <summary>
  88. /// Returns a <see cref="StyleRun" /> enumerable representing the individual character styling of the annotation text.
  89. /// </summary>
  90. /// <returns>
  91. /// A <see cref="StyleRun" /> enumerable representing the individual character styling,
  92. /// where the <see cref="StyleRun.Length" /> property of each run represents the number
  93. /// of characters the run spans.
  94. /// </returns>
  95. public virtual IEnumerable<StyleRun> GetStyles()
  96. {
  97. CheckInvalid();
  98. // We need to translate the array Scintilla gives us representing the style of each text
  99. // byte into a list of style runs. Our run lengths, however, are measured in characters,
  100. // not bytes, so we need to also read the annotation text and adjust as necessary when we find
  101. // characters that span more than one byte.
  102. int length = _scintilla.DirectMessage(NativeMethods.SCI_ANNOTATIONGETTEXT, new IntPtr(_lineIndex), IntPtr.Zero).ToInt32();
  103. byte[] textBuffer = new byte[length];
  104. byte[] stylesBuffer = new byte[length];
  105. unsafe
  106. {
  107. fixed (byte* bp = textBuffer)
  108. _scintilla.DirectMessage(NativeMethods.SCI_ANNOTATIONGETTEXT, new IntPtr(_lineIndex), new IntPtr(bp)).ToInt32();
  109. fixed (byte* bp = stylesBuffer)
  110. _scintilla.DirectMessage(NativeMethods.SCI_ANNOTATIONGETSTYLES, new IntPtr(_lineIndex), new IntPtr(bp)).ToInt32();
  111. }
  112. List<StyleRun> styles = new List<StyleRun>();
  113. Decoder decoder = _scintilla.Encoding.GetDecoder();
  114. StyleRun sr = new StyleRun() { Style = -1 };
  115. int index = 0;
  116. int count = 1;
  117. while (index < stylesBuffer.Length)
  118. {
  119. if (sr.Style != stylesBuffer[index])
  120. {
  121. // A new style has been encountered. Save the last one
  122. // to the list we're building and start tracking a new one
  123. if (sr.Length > 0)
  124. styles.Add(sr);
  125. sr = new StyleRun();
  126. sr.Style = stylesBuffer[index];
  127. }
  128. // At the end of this loop, the 'count' variable will tell us
  129. // how many bytes there are for one character.
  130. while (decoder.GetCharCount(textBuffer, index, count) != 1)
  131. count++;
  132. sr.Length++;
  133. index += count;
  134. count = 1;
  135. }
  136. // Add the last style run
  137. styles.Add(sr);
  138. return styles.ToArray();
  139. }
  140. /*
  141. private void ScintillaTextChangedHandler(object sender, EventArgs e)
  142. {
  143. // Why listen for text change events? We're not meant to be a long living
  144. // object. Should any user forget that and assume we're still valid after
  145. // the text has changed we'll remind them. In the future we could optimze
  146. // this by only invalidating if our line has been changed (deleted).
  147. _scintilla.TextChanged -= new EventHandler(ScintillaTextChangedHandler);
  148. _lineIndex = -1;
  149. }
  150. */
  151. /// <summary>
  152. /// Uses the enumerable <see cref="StyleRun" /> specified to individually style characters in the annotation text.
  153. /// </summary>
  154. /// <param name="styles">
  155. /// The enumerable <see cref="StyleRun" /> indicating how to style the annotation text,
  156. /// where the <see cref="StyleRun.Length" /> property of each run represents the number
  157. /// of characters the run spans.
  158. /// </param>
  159. /// <exception cref="ArgumentNullException"><paramref name="styles" /> is null.</exception>
  160. /// <remarks>
  161. /// The <see cref="Text" /> property must be set prior to styling and the sum length of
  162. /// all runs should match the text length.
  163. /// </remarks>
  164. public virtual void SetStyles(IEnumerable<StyleRun> styles)
  165. {
  166. CheckInvalid();
  167. if (styles == null)
  168. throw new ArgumentNullException("styles");
  169. // We need to build a byte array with a style byte for each text byte.
  170. // Our style runs are in character lengths (good for our user) but we need to
  171. // convert them to byte lengths (bad for us). To do that we need to analyze the
  172. // annotation text and determine if any of our character lengths span more than
  173. // one byte and fill the array accordingly.
  174. int length = _scintilla.DirectMessage(NativeMethods.SCI_ANNOTATIONGETTEXT, new IntPtr(_lineIndex), IntPtr.Zero).ToInt32();
  175. byte[] textBuffer = new byte[length];
  176. byte[] stylesBuffer = new byte[length];
  177. unsafe
  178. {
  179. fixed (byte* bp = textBuffer)
  180. _scintilla.DirectMessage(NativeMethods.SCI_ANNOTATIONGETTEXT, new IntPtr(_lineIndex), new IntPtr(bp)).ToInt32();
  181. }
  182. Decoder decoder = _scintilla.Encoding.GetDecoder();
  183. StyleRun sr;
  184. int index = 0;
  185. int count = 1;
  186. using (IEnumerator<StyleRun> enumerator = styles.GetEnumerator())
  187. {
  188. while (enumerator.MoveNext())
  189. {
  190. sr = enumerator.Current;
  191. while (sr.Length > 0 && index < stylesBuffer.Length)
  192. {
  193. // At the end of this loop, the 'count' variable will tell us
  194. // how many bytes there are for one character.
  195. while (decoder.GetCharCount(textBuffer, index, count) != 1)
  196. count++;
  197. // For each character of text (the unit of our style runs)
  198. // add the appropriate number of style bytes.
  199. for (int i = 0; i < count; i++)
  200. stylesBuffer[index + i] = (byte)sr.Style;
  201. index += count;
  202. count = 1;
  203. sr.Length--;
  204. };
  205. }
  206. }
  207. // Our processing above is designed to stop if we are given more style run data
  208. // than we have bytes to fill. If we are given less style run data than we have
  209. // bytes to fill, the remaining bytes just get their default value of 0.
  210. unsafe
  211. {
  212. fixed (byte* bp = stylesBuffer)
  213. _scintilla.DirectMessage(NativeMethods.SCI_ANNOTATIONSETSTYLES, new IntPtr(_lineIndex), new IntPtr(bp));
  214. }
  215. }
  216. #endregion Methods
  217. #region Properties
  218. /// <summary>
  219. /// Gets the total number of text lines in the annotation.
  220. /// </summary>
  221. /// <remarks>An <see cref="Int32" /> representing the total number of text lines in the annotation.</remarks>
  222. public virtual int LineCount
  223. {
  224. get
  225. {
  226. CheckInvalid();
  227. return _scintilla.DirectMessage(NativeMethods.SCI_ANNOTATIONGETLINES, new IntPtr(_lineIndex), IntPtr.Zero).ToInt32();
  228. }
  229. }
  230. /// <summary>
  231. /// Gets the index of the document line containing the annotation.
  232. /// </summary>
  233. /// <returns>
  234. /// An <see cref="Int32" /> representing the zero-based index of the document line
  235. /// containing the annotation, or -1 if the annotation has been rendered invalid
  236. /// from a change in the <see cref="Scintilla" /> control that created it.
  237. /// </returns>
  238. public int LineIndex
  239. {
  240. get
  241. {
  242. return _lineIndex;
  243. }
  244. }
  245. /// <summary>
  246. /// Gets or sets the index of the style used to style the annotation text.
  247. /// </summary>
  248. /// <returns>
  249. /// An <see cref="Int32" /> representing the zero-based index of the style used to style the annotation text,
  250. /// or -1 if the annotation has individually style characters.
  251. /// </returns>
  252. public virtual int Style
  253. {
  254. get
  255. {
  256. CheckInvalid();
  257. // By default Scintilla will return a value of 256 when there are individual styles. In the .NET
  258. // world that would be a little weird for our users. A more common pattern would be to return -1.
  259. int style = _scintilla.DirectMessage(NativeMethods.SCI_ANNOTATIONGETSTYLE, new IntPtr(_lineIndex), IntPtr.Zero).ToInt32();
  260. if (style == INDIVIDUAL_STYLES)
  261. return -1;
  262. return style;
  263. }
  264. set
  265. {
  266. CheckInvalid();
  267. _scintilla.DirectMessage(NativeMethods.SCI_ANNOTATIONSETSTYLE, new IntPtr(_lineIndex), new IntPtr(value)).ToInt32();
  268. }
  269. }
  270. /// <summary>
  271. /// Gets or sets the text of the annotation.
  272. /// </summary>
  273. /// <returns>A <see cref="String" /> representing the annotation text, or null if there is no annotation.</returns>
  274. /// <remarks>
  275. /// Only line feed characters ('\n') are recognized as line breaks.
  276. /// All other control characters are not rendered.
  277. /// </remarks>
  278. public virtual string Text
  279. {
  280. get
  281. {
  282. CheckInvalid();
  283. // Normally you wouldn't want to return null from a text property because
  284. // an empty string usually means the same thing and avoids null reference
  285. // exceptions. However, in Scintilla a null annotation is very different
  286. // from an empty string in the way they are rendered. For that reason we have
  287. // to support returning null from this property and this is the only reliable
  288. // way that I've found to do it.
  289. if (LineCount == 0)
  290. return null;
  291. // Determine the buffer size, fill it, and convert it to a string
  292. int length = _scintilla.DirectMessage(NativeMethods.SCI_ANNOTATIONGETTEXT, new IntPtr(_lineIndex), IntPtr.Zero).ToInt32();
  293. byte[] buffer = new byte[length];
  294. unsafe
  295. {
  296. fixed (byte* bp = buffer)
  297. length = (int)_scintilla.DirectMessage(NativeMethods.SCI_ANNOTATIONGETTEXT, new IntPtr(_lineIndex), new IntPtr(bp)).ToInt32();
  298. }
  299. return _scintilla.Encoding.GetString(buffer, 0, length);
  300. }
  301. set
  302. {
  303. CheckInvalid();
  304. if (value == null)
  305. {
  306. // Same thing...
  307. Clear();
  308. return;
  309. }
  310. unsafe
  311. {
  312. // Set the annotation text
  313. fixed (byte* bp = Utilities.GetZeroTerminatedBytes(value, _scintilla.Encoding))
  314. _scintilla.DirectMessage(NativeMethods.SCI_ANNOTATIONSETTEXT, new IntPtr(_lineIndex), new IntPtr(bp));
  315. }
  316. }
  317. }
  318. #endregion Properties
  319. #region Operators
  320. /// <summary>
  321. /// Tests whether two <see cref="Annotation" /> object differ in location or content.
  322. /// </summary>
  323. /// <param name="left">The <see cref="Annotation" /> object that is to the left of the inequality operator.</param>
  324. /// <param name="right">The <see cref="Annotation" /> object that is to the right of the inequality operator.</param>
  325. /// <returns>true if the objects are considered unequal; otherwise, false.</returns>
  326. public static bool operator !=(Annotation left, Annotation right)
  327. {
  328. return !(left == right);
  329. }
  330. /// <summary>
  331. /// Tests whether two <see cref="Annotation" /> objects have equal location and content.
  332. /// </summary>
  333. /// <param name="left">The <see cref="Annotation" /> object that is to the left of the equality operator.</param>
  334. /// <param name="right">The <see cref="Annotation" /> object that is to the right of the equality operator.</param>
  335. /// <returns>true if the objects are considered equal; otherwise, false.</returns>
  336. public static bool operator ==(Annotation left, Annotation right)
  337. {
  338. // If both are null, or both are same instance
  339. if (Object.ReferenceEquals(left, right))
  340. return true;
  341. // If one is null, but not both
  342. if (((object)left == null) || ((object)right == null))
  343. return false;
  344. return left.Equals((Annotation)right);
  345. }
  346. #endregion Operators
  347. #region Constructors
  348. /// <summary>
  349. /// Initializes a new instance of the <see cref="Annotation" /> class.
  350. /// </summary>
  351. /// <param name="scintilla">The <see cref="Scintilla" /> control that created this object.</param>
  352. /// <param name="lineIndex">The zero-based index of the document line containing the annotation.</param>
  353. protected internal Annotation(Scintilla scintilla, int lineIndex)
  354. {
  355. _lineIndex = lineIndex;
  356. _scintilla = scintilla;
  357. /*_scintilla.TextChanged += new EventHandler(ScintillaTextChangedHandler);*/
  358. }
  359. #endregion Constructors
  360. }
  361. }