PageRenderTime 62ms CodeModel.GetById 15ms RepoModel.GetById 1ms app.codeStats 0ms

/SmarterSql/SmarterSql/Utils/HiddenRegions/HiddenRegion.cs

#
C# | 935 lines | 711 code | 101 blank | 123 comment | 178 complexity | 9bdb4c810de81f6294a857b850586c51 MD5 | raw file
  1. // ---------------------------------
  2. // SmarterSql (c) Johan Sassner 2008
  3. // ---------------------------------
  4. using System;
  5. using System.Collections.Generic;
  6. using System.Diagnostics;
  7. using System.Runtime.InteropServices;
  8. using System.Text;
  9. using EnvDTE;
  10. using EnvDTE80;
  11. using Microsoft.VisualStudio;
  12. using Microsoft.VisualStudio.Package;
  13. using Microsoft.VisualStudio.TextManager.Interop;
  14. using Sassner.SmarterSql.Parsing;
  15. using Sassner.SmarterSql.ParsingObjects;
  16. using TokenInfo=Sassner.SmarterSql.ParsingUtils.TokenInfo;
  17. namespace Sassner.SmarterSql.Utils.HiddenRegions {
  18. public class HiddenRegion : IVsHiddenTextClient, IDisposable {
  19. #region Member variables
  20. private const string ClassName = "HiddenRegion";
  21. internal static uint HiddenRegionCookie = 21983;
  22. private static bool isDisposed;
  23. private readonly uint docCookie;
  24. private readonly List<HiddenRegionData> lstHiddenRegions = new List<HiddenRegionData>();
  25. private readonly IVsTextLines ppBuffer;
  26. private bool doOutlining = true;
  27. private IVsHiddenTextSession hiddenTextSession;
  28. private bool runOnce;
  29. #endregion
  30. public HiddenRegion(uint docCookie, IVsTextLines ppBuffer) {
  31. this.docCookie = docCookie;
  32. this.ppBuffer = ppBuffer;
  33. GetHiddenTextSession();
  34. }
  35. #region Public properties
  36. public bool RunOnce {
  37. [DebuggerStepThrough]
  38. get { return runOnce; }
  39. }
  40. public uint DocCookie {
  41. [DebuggerStepThrough]
  42. get { return docCookie; }
  43. }
  44. public IVsTextLines activeView {
  45. [DebuggerStepThrough]
  46. get { return ppBuffer; }
  47. }
  48. public List<HiddenRegionData> HiddenRegions {
  49. [DebuggerStepThrough]
  50. get { return lstHiddenRegions; }
  51. }
  52. public bool DoOutlining {
  53. [DebuggerStepThrough]
  54. get { return doOutlining; }
  55. set { doOutlining = value; }
  56. }
  57. #endregion
  58. #region Region methods
  59. /// <summary>
  60. /// Construct hidden regions from two sources:
  61. /// * Parsed segments
  62. /// * Region between "--#region" and "--#endregion"
  63. /// * Multiline comments
  64. /// </summary>
  65. /// <param name="textEditor"></param>
  66. /// <param name="lstTokens"></param>
  67. /// <param name="ppBuffer"></param>
  68. public static void ConstructHiddenRegions(TextEditor textEditor, List<TokenInfo> lstTokens, IVsTextLines ppBuffer) {
  69. try {
  70. if (isDisposed || null == TextEditor.CurrentWindowData.HiddenRegion || !Instance.Settings.EnableOutlining) {
  71. return;
  72. }
  73. List<NewHiddenRegion> lstNewHiddenRegion = new List<NewHiddenRegion>();
  74. bool runOnce = TextEditor.CurrentWindowData.HiddenRegion.RunOnce;
  75. if (null != TextEditor.CurrentWindowData.Parser.SegmentUtils) {
  76. // Parsed segments
  77. foreach (StatementSpans statementSpans in TextEditor.CurrentWindowData.Parser.SegmentUtils.StartTokenIndexes) {
  78. TextSpan span = new TextSpan {
  79. iStartLine = statementSpans.Start.Span.iStartLine,
  80. iStartIndex = statementSpans.Start.Span.iStartIndex,
  81. iEndLine = statementSpans.End.Span.iEndLine,
  82. iEndIndex = statementSpans.End.Span.iEndIndex
  83. };
  84. if (span.iStartLine != span.iEndLine) {
  85. NewHiddenRegion newHiddenRegion;
  86. if (CreateRegion(span, true, false, runOnce, out newHiddenRegion)) {
  87. lstNewHiddenRegion.Add(newHiddenRegion);
  88. }
  89. }
  90. }
  91. // Segments that fit together, for example UNIONS
  92. for (int i = 0; i < TextEditor.CurrentWindowData.Parser.SegmentUtils.StartTokenIndexes.Count; i++) {
  93. StatementSpans statementSpans = TextEditor.CurrentWindowData.Parser.SegmentUtils.StartTokenIndexes[i];
  94. StatementSpans startJoinedSpans = null;
  95. StatementSpans endJoinedSpans;
  96. while (null != statementSpans.JoinedSpanNext) {
  97. if (null == startJoinedSpans) {
  98. startJoinedSpans = statementSpans;
  99. }
  100. statementSpans = statementSpans.JoinedSpanNext;
  101. i++;
  102. }
  103. if (null != startJoinedSpans) {
  104. endJoinedSpans = statementSpans;
  105. TextSpan span = new TextSpan {
  106. iStartLine = startJoinedSpans.Start.Span.iStartLine,
  107. iStartIndex = startJoinedSpans.Start.Span.iStartIndex,
  108. iEndLine = endJoinedSpans.End.Span.iEndLine,
  109. iEndIndex = endJoinedSpans.End.Span.iEndIndex
  110. };
  111. if (span.iStartLine != span.iEndLine) {
  112. NewHiddenRegion newHiddenRegion;
  113. if (CreateRegion(span, true, false, runOnce, out newHiddenRegion)) {
  114. lstNewHiddenRegion.Add(newHiddenRegion);
  115. }
  116. }
  117. }
  118. }
  119. }
  120. // Multiline comments
  121. int tokenCommentIndexStart = -1;
  122. for (int i = 0; i < lstTokens.Count; i++) {
  123. TokenInfo token = lstTokens[i];
  124. if (token.Kind == TokenKind.MultiLineComment) {
  125. if (-1 != tokenCommentIndexStart && ((CommentToken)token.Token).IsComplete) {
  126. TextSpan span = new TextSpan {
  127. iStartLine = lstTokens[tokenCommentIndexStart].Span.iStartLine,
  128. iStartIndex = lstTokens[tokenCommentIndexStart].Span.iStartIndex,
  129. iEndLine = lstTokens[i].Span.iEndLine,
  130. iEndIndex = lstTokens[i].Span.iEndIndex
  131. };
  132. if (span.iStartLine != span.iEndLine) {
  133. NewHiddenRegion newHiddenRegion;
  134. if (CreateRegion(span, true, false, runOnce, out newHiddenRegion)) {
  135. lstNewHiddenRegion.Add(newHiddenRegion);
  136. }
  137. }
  138. tokenCommentIndexStart = -1;
  139. } else if (-1 == tokenCommentIndexStart) {
  140. tokenCommentIndexStart = i;
  141. }
  142. }
  143. }
  144. // Region between "--#region" and "--#endregion"
  145. List<int> lstTokenRegionIndexes = new List<int>();
  146. List<int> lstMissmatchingRegions = new List<int>();
  147. for (int i = 0; i < lstTokens.Count; i++) {
  148. TokenInfo token = lstTokens[i];
  149. if (token.Kind == TokenKind.SingleLineComment) {
  150. if (token.Token.Image.Trim().StartsWith(Common.RegionStart.Substring(2), StringComparison.OrdinalIgnoreCase)) {
  151. lstTokenRegionIndexes.Add(i);
  152. } else if (token.Token.Image.Trim().StartsWith(Common.RegionEnd.Substring(2), StringComparison.OrdinalIgnoreCase)) {
  153. if (lstTokenRegionIndexes.Count > 0) {
  154. int tokenIndex1 = lstTokenRegionIndexes[lstTokenRegionIndexes.Count - 1];
  155. int tokenIndex2 = i;
  156. lstTokenRegionIndexes.RemoveAt(lstTokenRegionIndexes.Count - 1);
  157. TextSpan span = new TextSpan {
  158. iStartLine = lstTokens[tokenIndex1].Span.iStartLine,
  159. iStartIndex = lstTokens[tokenIndex1].Span.iStartIndex,
  160. iEndLine = lstTokens[tokenIndex2].Span.iEndLine,
  161. iEndIndex = lstTokens[tokenIndex2].Span.iEndIndex
  162. };
  163. NewHiddenRegion newHiddenRegion;
  164. if (CreateRegion(span, true, true, runOnce, out newHiddenRegion)) {
  165. lstNewHiddenRegion.Add(newHiddenRegion);
  166. }
  167. } else {
  168. lstMissmatchingRegions.Add(i);
  169. }
  170. }
  171. }
  172. }
  173. textEditor.MissmatchingRegionMarkers.RemoveAll();
  174. // Create missing starting region markers
  175. for (int i = 0; i < lstTokenRegionIndexes.Count; i++) {
  176. textEditor.MissmatchingRegionMarkers.CreateMarker(ppBuffer, "MissmatchingStartingRegions_" + i, textEditor.RawTokens, lstTokenRegionIndexes[i], lstTokenRegionIndexes[i], "Missmatching starting regions", null);
  177. }
  178. // Create missing ending region markers
  179. for (int i = 0; i < lstMissmatchingRegions.Count; i++) {
  180. textEditor.MissmatchingRegionMarkers.CreateMarker(ppBuffer, "MissmatchingEndingRegions_" + i, textEditor.RawTokens, lstMissmatchingRegions[i], lstMissmatchingRegions[i], "Missmatching ending regions", null);
  181. }
  182. // Sort the hidden regions
  183. lstNewHiddenRegion.Sort(HiddenRegionDataComparison);
  184. // Process regions
  185. TextEditor.CurrentWindowData.HiddenRegion.ProcessHiddenRegions(lstNewHiddenRegion);
  186. } catch (Exception e) {
  187. Common.LogEntry(ClassName, "ConstructHiddenRegions", e, Common.enErrorLvl.Error);
  188. }
  189. }
  190. private static int HiddenRegionDataComparison(NewHiddenRegion hiddenRegionData1, NewHiddenRegion hiddenRegionData2) {
  191. if (hiddenRegionData1.tsHiddenText.iStartLine == hiddenRegionData2.tsHiddenText.iStartLine) {
  192. return hiddenRegionData1.tsHiddenText.iStartIndex - hiddenRegionData2.tsHiddenText.iStartIndex;
  193. }
  194. return hiddenRegionData1.tsHiddenText.iStartLine - hiddenRegionData2.tsHiddenText.iStartLine;
  195. }
  196. public static void ToggleAllOutlining(TextEditor textEditor) {
  197. if (null != TextEditor.CurrentWindowData.HiddenRegion && Instance.Settings.EnableOutlining) {
  198. TextEditor.CurrentWindowData.HiddenRegion.ToggleRegions();
  199. }
  200. }
  201. public static void ToggleOutlining(TextEditor textEditor) {
  202. if (isDisposed || null == TextEditor.CurrentWindowData.HiddenRegion || !Instance.Settings.EnableOutlining) {
  203. return;
  204. }
  205. try {
  206. TextDocument document = Instance.ApplicationObject.ActiveWindow.Document.Object("TextDocument") as TextDocument;
  207. if (null != document) {
  208. TextSelection selection = document.Selection;
  209. EditPoint2 epStart = selection.TopPoint.CreateEditPoint() as EditPoint2;
  210. if (null != epStart) {
  211. TextSpan span = new TextSpan {
  212. iStartLine = (epStart.Line - 1),
  213. iStartIndex = 0,
  214. iEndIndex = epStart.LineLength,
  215. iEndLine = (epStart.Line - 1)
  216. };
  217. TextEditor.CurrentWindowData.HiddenRegion.ToggleRegion(span);
  218. }
  219. }
  220. } catch (Exception e) {
  221. Common.LogEntry(ClassName, "ToggleOutlining", e, Common.enErrorLvl.Error);
  222. }
  223. }
  224. public static bool CreateRegion(TextEditor textEditor) {
  225. if (isDisposed || null == TextEditor.CurrentWindowData.HiddenRegion || !Instance.Settings.EnableOutlining) {
  226. Common.LogEntry(ClassName, "CreateRegion", "Unable to create hidden region", Common.enErrorLvl.Error);
  227. return false;
  228. }
  229. try {
  230. TextDocument document = Instance.ApplicationObject.ActiveWindow.Document.Object("TextDocument") as TextDocument;
  231. if (null != document) {
  232. TextSelection selection = document.Selection;
  233. EditPoint2 epStart = selection.TopPoint.CreateEditPoint() as EditPoint2;
  234. EditPoint2 epEnd = selection.BottomPoint.CreateEditPoint() as EditPoint2;
  235. if (null != epStart && null != epEnd) {
  236. Instance.ApplicationObject.UndoContext.Open("CreateRegion.CreateRegion", true);
  237. try {
  238. string indentString = "";
  239. EditPoint2 epCurrentStart = epStart.CreateEditPoint() as EditPoint2;
  240. if (selection.Text.Length > 0) {
  241. // Get indent string
  242. EditPoint2 epCurrentEnd = epStart.CreateEditPoint() as EditPoint2;
  243. if (null != epCurrentStart && null != epCurrentEnd) {
  244. epCurrentStart.StartOfLine();
  245. epCurrentEnd.EndOfLine();
  246. string line = epCurrentStart.GetText(epCurrentEnd);
  247. if (line.Length > 0) {
  248. int pos = 0;
  249. while (pos <= line.Length) {
  250. if (!char.IsWhiteSpace(line[pos])) {
  251. break;
  252. }
  253. pos++;
  254. }
  255. if (pos >= 0 && pos <= line.Length) {
  256. indentString = line.Substring(0, pos);
  257. }
  258. }
  259. }
  260. } else {
  261. if (null != epCurrentStart) {
  262. indentString = SmartIndenter.FormatIndentString(epCurrentStart.DisplayColumn - 1);
  263. }
  264. }
  265. if (Instance.Settings.FormattingBlankLineAroundRegion && null != epCurrentStart) {
  266. epCurrentStart.Insert(Environment.NewLine);
  267. }
  268. epStart.Insert((selection.Text.Length > 0 ? indentString : "") + Common.RegionStart + " ");
  269. int endLine = epStart.Line;
  270. int endColumn = epStart.DisplayColumn;
  271. epStart.Insert(Environment.NewLine + (Instance.Settings.FormattingBlankLineInsideRegion ? Environment.NewLine : ""));
  272. // End
  273. if (selection.Text.Length > 0) {
  274. if (0 == epEnd.DisplayColumn - 1) {
  275. epEnd.LineUp(1);
  276. epEnd.EndOfLine();
  277. }
  278. } else {
  279. epEnd = epStart;
  280. }
  281. epEnd.Insert((Instance.Settings.FormattingBlankLineInsideRegion ? Environment.NewLine : "") + Environment.NewLine + indentString + Common.RegionEnd);
  282. if (Instance.Settings.FormattingBlankLineAroundRegion) {
  283. epEnd.Insert(Environment.NewLine);
  284. }
  285. selection.MoveToDisplayColumn(endLine, endColumn, false);
  286. // Parse tokens
  287. Instance.TextEditor.ScheduleFullReparse();
  288. return true;
  289. } finally {
  290. if (Instance.ApplicationObject.UndoContext.IsOpen) {
  291. Instance.ApplicationObject.UndoContext.Close();
  292. }
  293. }
  294. }
  295. }
  296. } catch (Exception e) {
  297. Common.LogEntry(ClassName, "CreateRegion", e, Common.enErrorLvl.Error);
  298. }
  299. return false;
  300. }
  301. #endregion
  302. #region IDisposable Members
  303. ///<summary>
  304. ///Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
  305. ///</summary>
  306. ///<filterpriority>2</filterpriority>
  307. public void Dispose() {
  308. isDisposed = true;
  309. try {
  310. foreach (HiddenRegionData region in lstHiddenRegions) {
  311. region.HiddenRegion.Invalidate((int)CHANGE_HIDDEN_REGION_FLAGS.chrDefault);
  312. }
  313. lstHiddenRegions.Clear();
  314. } catch (Exception e) {
  315. Common.LogEntry(ClassName, "Dispose", e, Common.enErrorLvl.Error);
  316. }
  317. }
  318. #endregion
  319. #region IVsHiddenTextClient Members
  320. void IVsHiddenTextClient.OnHiddenRegionChange(IVsHiddenRegion pHidReg, HIDDEN_REGION_EVENT EventCode, int fBufferModifiable) {
  321. if (isDisposed) {
  322. return;
  323. }
  324. try {
  325. HiddenRegionData hiddenRegionData = GetHiddenRegionData(pHidReg);
  326. switch (EventCode) {
  327. case HIDDEN_REGION_EVENT.hreAfterRegionCollapsed:
  328. string banner = "";
  329. try {
  330. if (null != hiddenRegionData) {
  331. TextSpan[] span = new TextSpan[1];
  332. pHidReg.GetSpan(span);
  333. // Store new values in the hidden region object
  334. hiddenRegionData.Expanded = false;
  335. hiddenRegionData.Span = span[0];
  336. // Get the first line of the text. Parse out the name of the region
  337. int intLineLength;
  338. ppBuffer.GetLengthOfLine(span[0].iStartLine, out intLineLength);
  339. string strBuffer;
  340. ppBuffer.GetLineText(span[0].iStartLine, 0, span[0].iStartLine, intLineLength, out strBuffer);
  341. banner = strBuffer.TrimStart().Replace(Common.RegionStart + " ", "").Trim();
  342. }
  343. } catch (Exception e) {
  344. Common.LogEntry(ClassName, "IVsHiddenTextClient.OnHiddenRegionChange", e, Common.enErrorLvl.Error);
  345. }
  346. // Set banner text
  347. if (0 == banner.Length) {
  348. banner = Common.RegionStart;
  349. }
  350. pHidReg.SetBanner(banner);
  351. break;
  352. case HIDDEN_REGION_EVENT.hreAfterRegionExpanded:
  353. try {
  354. if (null != hiddenRegionData) {
  355. TextSpan[] span = new TextSpan[1];
  356. pHidReg.GetSpan(span);
  357. // Store new values in the hidden region object
  358. hiddenRegionData.Expanded = true;
  359. hiddenRegionData.Span = span[0];
  360. int intLineLength;
  361. ppBuffer.GetLengthOfLine(span[0].iStartLine, out intLineLength);
  362. string strBuffer;
  363. ppBuffer.GetLineText(span[0].iStartLine, 0, span[0].iStartLine, intLineLength, out strBuffer);
  364. }
  365. } catch (Exception e) {
  366. Common.LogEntry(ClassName, "IVsHiddenTextClient.OnHiddenRegionChange", e, Common.enErrorLvl.Error);
  367. }
  368. break;
  369. case HIDDEN_REGION_EVENT.hreRegionDeleted:
  370. case HIDDEN_REGION_EVENT.hreRegionReloaded:
  371. case HIDDEN_REGION_EVENT.hreRegionResurrected:
  372. break;
  373. }
  374. } catch (Exception e) {
  375. Common.LogEntry(ClassName, "IVsHiddenTextClient.OnHiddenRegionChange", e, Common.enErrorLvl.Error);
  376. }
  377. }
  378. int IVsHiddenTextClient.GetTipText(IVsHiddenRegion pHidReg, string[] pbstrText) {
  379. if (isDisposed) {
  380. return NativeMethods.S_OK;
  381. }
  382. if (pbstrText != null && pbstrText.Length > 0) {
  383. string text = "";
  384. try {
  385. TextSpan[] aspan = new TextSpan[1];
  386. NativeMethods.ThrowOnFailure(pHidReg.GetSpan(aspan));
  387. text = GetText(aspan[0]);
  388. } catch (Exception) {
  389. // Do nothing
  390. }
  391. pbstrText[0] = text;
  392. }
  393. return NativeMethods.S_OK;
  394. }
  395. int IVsHiddenTextClient.GetMarkerCommandInfo(IVsHiddenRegion pHidReg, int iItem, string[] pbstrText, uint[] pcmdf) {
  396. if (pcmdf != null && pcmdf.Length > 0) {
  397. pcmdf[0] = 0;
  398. }
  399. if (pbstrText != null && pbstrText.Length > 0) {
  400. pbstrText[0] = null;
  401. }
  402. return NativeMethods.E_NOTIMPL;
  403. }
  404. int IVsHiddenTextClient.ExecMarkerCommand(IVsHiddenRegion pHidReg, int iItem) {
  405. return NativeMethods.E_NOTIMPL;
  406. }
  407. int IVsHiddenTextClient.MakeBaseSpanVisible(IVsHiddenRegion pHidReg, TextSpan[] pBaseSpan) {
  408. return NativeMethods.E_NOTIMPL;
  409. }
  410. void IVsHiddenTextClient.OnBeforeSessionEnd() {
  411. }
  412. #endregion
  413. /// <summary>
  414. /// Get the IVsHiddenTextSession object
  415. /// </summary>
  416. /// <returns></returns>
  417. private void GetHiddenTextSession() {
  418. if (isDisposed) {
  419. return;
  420. }
  421. try {
  422. IVsHiddenTextManager htextmgr = Instance.Sp.GetService(typeof (SVsTextManager)) as IVsHiddenTextManager;
  423. if (htextmgr != null) {
  424. int hr = htextmgr.GetHiddenTextSession(ppBuffer, out hiddenTextSession);
  425. // Remove the old session first
  426. if (hr != NativeMethods.E_FAIL) {
  427. hiddenTextSession.Terminate();
  428. }
  429. // Now create a new session, and listen for IVsHiddenTextClient events
  430. NativeMethods.ThrowOnFailure(htextmgr.CreateHiddenTextSession(0, ppBuffer, this, out hiddenTextSession));
  431. Marshal.ReleaseComObject(htextmgr);
  432. }
  433. } catch (Exception e) {
  434. Common.LogEntry(ClassName, "GetHiddenTextSession", e, Common.enErrorLvl.Error);
  435. }
  436. }
  437. /// <summary>
  438. /// Create a NewHiddenRegion object from a TextSpan object.
  439. /// </summary>
  440. /// <param name="span"></param>
  441. /// <param name="expanded"></param>
  442. /// <param name="isCodeRegion"></param>
  443. /// <param name="runOnce"></param>
  444. /// <param name="newHiddenRegion"></param>
  445. /// <returns></returns>
  446. public static bool CreateRegion(TextSpan span, bool expanded, bool isCodeRegion, bool runOnce, out NewHiddenRegion newHiddenRegion) {
  447. if (isDisposed) {
  448. newHiddenRegion = new NewHiddenRegion();
  449. return false;
  450. }
  451. try {
  452. if (isCodeRegion && !runOnce && Instance.Settings.AlwaysOutlineRegionsFirstTime) {
  453. expanded = false;
  454. }
  455. newHiddenRegion = new NewHiddenRegion {
  456. dwBehavior = ((uint)HIDDEN_REGION_BEHAVIOR.hrbClientControlled),
  457. dwClient = HiddenRegionCookie,
  458. dwState = (expanded ? (uint)HIDDEN_REGION_STATE.hrsExpanded : (uint)HIDDEN_REGION_STATE.hrsDefault),
  459. iType = ((int)HIDDEN_REGION_TYPE.hrtCollapsible)
  460. //dwState = (uint)HIDDEN_REGION_STATE.hrsExpanded;
  461. };
  462. IVsTextLines ppBuffer;
  463. TextEditor.CurrentWindowData.ActiveView.GetBuffer(out ppBuffer);
  464. int intLineLength;
  465. ppBuffer.GetLengthOfLine(span.iStartLine, out intLineLength);
  466. string strBuffer;
  467. ppBuffer.GetLineText(span.iStartLine, 0, span.iStartLine, intLineLength, out strBuffer);
  468. string banner = strBuffer.TrimStart().Replace(Common.RegionStart + " ", "").Trim();
  469. if (0 == banner.Length) {
  470. banner = Common.RegionStart;
  471. }
  472. newHiddenRegion.pszBanner = banner;
  473. newHiddenRegion.tsHiddenText = span;
  474. return true;
  475. } catch (Exception e) {
  476. Common.LogEntry(ClassName, "CreateRegion", e, Common.enErrorLvl.Error);
  477. newHiddenRegion = new NewHiddenRegion();
  478. return false;
  479. }
  480. }
  481. /// <summary>
  482. /// Add a hidden region
  483. /// </summary>
  484. /// <param name="span"></param>
  485. /// <param name="expanded"></param>
  486. /// <param name="isCodeRegion"></param>
  487. /// <returns></returns>
  488. public bool AddHiddenRegion(TextSpan span, bool expanded, bool isCodeRegion) {
  489. if (isDisposed) {
  490. return false;
  491. }
  492. try {
  493. NewHiddenRegion region;
  494. if (!CreateRegion(span, expanded, isCodeRegion, runOnce, out region)) {
  495. return false;
  496. }
  497. NewHiddenRegion[] chunk = new NewHiddenRegion[1];
  498. chunk[0] = region;
  499. IVsEnumHiddenRegions[] ppEnumHiddenRegions = new IVsEnumHiddenRegions[1];
  500. int hr = hiddenTextSession.AddHiddenRegions((int)CHANGE_HIDDEN_REGION_FLAGS.chrNonUndoable, 1, chunk, ppEnumHiddenRegions);
  501. if (NativeMethods.Succeeded(hr)) {
  502. EnumAndAddHiddenRegions(ppEnumHiddenRegions);
  503. return true;
  504. }
  505. } catch (Exception e) {
  506. Common.LogEntry(ClassName, "AddHiddenRegion", e, Common.enErrorLvl.Error);
  507. }
  508. return false;
  509. }
  510. /// <summary>
  511. /// Remove all hidden regions
  512. /// </summary>
  513. public virtual void RemoveHiddenRegions() {
  514. try {
  515. IVsEnumHiddenRegions ppenum;
  516. TextSpan[] aspan = new TextSpan[1];
  517. aspan[0] = GetDocumentSpan();
  518. NativeMethods.ThrowOnFailure(hiddenTextSession.EnumHiddenRegions((uint)FIND_HIDDEN_REGION_FLAGS.FHR_BY_CLIENT_DATA, HiddenRegionCookie, aspan, out ppenum));
  519. uint fetched;
  520. IVsHiddenRegion[] aregion = new IVsHiddenRegion[1];
  521. while (ppenum.Next(1, aregion, out fetched) == NativeMethods.S_OK && fetched == 1) {
  522. NativeMethods.ThrowOnFailure(aregion[0].Invalidate((int)CHANGE_HIDDEN_REGION_FLAGS.chrNonUndoable));
  523. }
  524. } catch (Exception e) {
  525. Common.LogEntry(ClassName, "RemoveHiddenRegions", e, Common.enErrorLvl.Error);
  526. }
  527. }
  528. /// <summary>
  529. /// Disable outlining
  530. /// </summary>
  531. public void DisableOutlining() {
  532. doOutlining = false;
  533. RemoveHiddenRegions();
  534. }
  535. /// <summary>
  536. /// Toggle a specific region for it's expanded/collapsed state
  537. /// </summary>
  538. /// <param name="span"></param>
  539. public void ToggleRegion(TextSpan span) {
  540. if (isDisposed) {
  541. return;
  542. }
  543. try {
  544. IVsEnumHiddenRegions ppenum;
  545. TextSpan[] aspan = new TextSpan[1];
  546. aspan[0] = span;
  547. NativeMethods.ThrowOnFailure(hiddenTextSession.EnumHiddenRegions((uint)FIND_HIDDEN_REGION_FLAGS.FHR_BY_CLIENT_DATA, HiddenRegionCookie, aspan, out ppenum));
  548. IVsHiddenRegion[] aregion = new IVsHiddenRegion[1];
  549. int minSpanDiff = int.MaxValue;
  550. IVsHiddenRegion minRegion = null;
  551. uint fetched;
  552. while (ppenum.Next(1, aregion, out fetched) == NativeMethods.S_OK && fetched == 1) {
  553. TextSpan[] textSpans = new TextSpan[1];
  554. NativeMethods.ThrowOnFailure(aregion[0].GetSpan(textSpans));
  555. if (textSpans[0].iStartLine <= span.iStartLine && textSpans[0].iEndLine >= span.iStartLine && textSpans[0].iEndLine - textSpans[0].iStartLine < minSpanDiff) {
  556. minSpanDiff = textSpans[0].iEndLine - textSpans[0].iStartLine;
  557. minRegion = aregion[0];
  558. }
  559. }
  560. if (null != minRegion) {
  561. uint dwState;
  562. minRegion.GetState(out dwState);
  563. dwState ^= (uint)HIDDEN_REGION_STATE.hrsExpanded;
  564. NativeMethods.ThrowOnFailure(minRegion.SetState(dwState, (uint)CHANGE_HIDDEN_REGION_FLAGS.chrDefault));
  565. }
  566. } catch (Exception e) {
  567. Common.LogEntry(ClassName, "ToggleRegion", e, Common.enErrorLvl.Error);
  568. }
  569. }
  570. /// <summary>
  571. /// Toggle outlining on all regions
  572. /// </summary>
  573. public void ToggleRegions() {
  574. if (isDisposed) {
  575. return;
  576. }
  577. try {
  578. IVsEnumHiddenRegions ppenum;
  579. TextSpan[] aspan = new TextSpan[1];
  580. aspan[0] = GetDocumentSpan();
  581. NativeMethods.ThrowOnFailure(hiddenTextSession.EnumHiddenRegions((uint)FIND_HIDDEN_REGION_FLAGS.FHR_BY_CLIENT_DATA, HiddenRegionCookie, aspan, out ppenum));
  582. using (new CompoundViewAction(TextEditor.CurrentWindowData.ActiveView, "ToggleAllRegions")) {
  583. IVsHiddenRegion[] aregion = new IVsHiddenRegion[1];
  584. uint fetched;
  585. while (ppenum.Next(1, aregion, out fetched) == NativeMethods.S_OK && fetched == 1) {
  586. uint dwState;
  587. aregion[0].GetState(out dwState);
  588. dwState ^= (uint)HIDDEN_REGION_STATE.hrsExpanded;
  589. NativeMethods.ThrowOnFailure(aregion[0].SetState(dwState, (uint)CHANGE_HIDDEN_REGION_FLAGS.chrDefault));
  590. }
  591. }
  592. } catch (Exception e) {
  593. Common.LogEntry(ClassName, "ToggleRegions", e, Common.enErrorLvl.Error);
  594. }
  595. }
  596. /// <summary>
  597. /// Comparison method sorting HiddenRegionData's
  598. /// </summary>
  599. /// <param name="hiddenRegionData1"></param>
  600. /// <param name="hiddenRegionData2"></param>
  601. /// <returns></returns>
  602. private static int HiddenRegionDataComparison(HiddenRegionData hiddenRegionData1, HiddenRegionData hiddenRegionData2) {
  603. if (hiddenRegionData1.Span.iStartLine == hiddenRegionData2.Span.iStartLine) {
  604. return hiddenRegionData1.Span.iStartIndex - hiddenRegionData2.Span.iStartIndex;
  605. }
  606. return hiddenRegionData1.Span.iStartLine - hiddenRegionData2.Span.iStartLine;
  607. }
  608. /// <summary>
  609. /// Enumerate hidden regions of a specific type
  610. /// </summary>
  611. /// <param name="regions"></param>
  612. /// <param name="spans"></param>
  613. /// <returns></returns>
  614. private bool EnumHiddenRegions(out List<IVsHiddenRegion> regions, out List<TextSpan> spans) {
  615. if (isDisposed) {
  616. regions = null;
  617. spans = null;
  618. return false;
  619. }
  620. try {
  621. regions = new List<IVsHiddenRegion>();
  622. spans = new List<TextSpan>();
  623. // Compare the existing regions with the new regions and remove any that do not match the new regions.
  624. IVsEnumHiddenRegions ppEnumHiddenRegions;
  625. TextSpan[] textSpans = new TextSpan[1];
  626. textSpans[0] = GetDocumentSpan();
  627. NativeMethods.ThrowOnFailure(hiddenTextSession.EnumHiddenRegions((uint)FIND_HIDDEN_REGION_FLAGS.FHR_BY_CLIENT_DATA, HiddenRegionCookie, textSpans, out ppEnumHiddenRegions));
  628. uint fetched;
  629. IVsHiddenRegion[] existingHiddenRegion = new IVsHiddenRegion[1];
  630. // Create a list of IVsHiddenRegion objects, sorted in the same order that the
  631. // authoring sink sorts. This is necessary because VS core editor does NOT return
  632. // the regions in the same order that we add them.
  633. while (ppEnumHiddenRegions.Next(1, existingHiddenRegion, out fetched) == NativeMethods.S_OK && fetched == 1) {
  634. NativeMethods.ThrowOnFailure(existingHiddenRegion[0].GetSpan(textSpans));
  635. TextSpan span = textSpans[0];
  636. int i = spans.Count - 1;
  637. while (i >= 0) {
  638. TextSpan span2 = spans[i];
  639. if (TextSpanHelper.StartsAfterStartOf(span, span2)) {
  640. break;
  641. }
  642. i--;
  643. }
  644. spans.Insert(i + 1, span);
  645. regions.Insert(i + 1, existingHiddenRegion[0]);
  646. //Common.LogEntry(ClassName, "EnumHiddenRegions", "Found " + GetHiddenRegionData(existingHiddenRegion[0]), Common.enErrorLvl.Information);
  647. }
  648. Marshal.ReleaseComObject(ppEnumHiddenRegions);
  649. return true;
  650. } catch (Exception e) {
  651. Common.LogEntry(ClassName, "EnumHiddenRegions", e, Common.enErrorLvl.Error);
  652. regions = null;
  653. spans = null;
  654. return false;
  655. }
  656. }
  657. /// <summary>
  658. /// Process hidden regions objects. Remove old regions not in use no more and add new ones.
  659. /// </summary>
  660. /// <param name="lstNewHiddenRegions"></param>
  661. public void ProcessHiddenRegions(List<NewHiddenRegion> lstNewHiddenRegions) {
  662. if (isDisposed || !doOutlining || null == hiddenTextSession) {
  663. return;
  664. }
  665. // int matched = 0;
  666. // int removed = 0;
  667. // int added = 0;
  668. int pos = 0;
  669. List<TextSpan> spans;
  670. List<IVsHiddenRegion> regions;
  671. if (!EnumHiddenRegions(out regions, out spans)) {
  672. return;
  673. }
  674. // Now merge the list found with the list in the AuthoringSink to figure out
  675. // what matches, what needs to be removed, and what needs to be inserted.
  676. try {
  677. NewHiddenRegion r = (pos < lstNewHiddenRegions.Count ? lstNewHiddenRegions[pos] : new NewHiddenRegion());
  678. for (int i = 0, n = regions.Count; i < n; i++) {
  679. IVsHiddenRegion region = regions[i];
  680. TextSpan span = spans[i];
  681. // In case we're inserting a new region, scan ahead to matching start line
  682. while (r.tsHiddenText.iStartLine < span.iStartLine) {
  683. pos++;
  684. if (pos >= lstNewHiddenRegions.Count) {
  685. r = new NewHiddenRegion();
  686. break;
  687. }
  688. r = lstNewHiddenRegions[pos];
  689. }
  690. if (TextSpanHelper.IsSameSpan(r.tsHiddenText, span)) {
  691. // This region is already there.
  692. // matched++;
  693. lstNewHiddenRegions.RemoveAt(pos);
  694. r = (pos < lstNewHiddenRegions.Count) ? lstNewHiddenRegions[pos] : new NewHiddenRegion();
  695. } else {
  696. HiddenRegionData hiddenRegionData = GetHiddenRegionData(region);
  697. lstHiddenRegions.Remove(hiddenRegionData);
  698. // Common.LogEntry(ClassName, "ProcessHiddenRegions", "Remove " + hiddenRegionData, Common.enErrorLvl.Information);
  699. // removed++;
  700. NativeMethods.ThrowOnFailure(region.Invalidate((int)CHANGE_HIDDEN_REGION_FLAGS.chrDefault));
  701. }
  702. }
  703. int start = Environment.TickCount;
  704. if (lstNewHiddenRegions.Count > 0) {
  705. int count = lstNewHiddenRegions.Count;
  706. // For very large documents this can take a while, so add them in chunks of 1000 and stop after 5 seconds.
  707. int maxTime = Environment.TickCount + 5000;
  708. const int chunkSize = 1000;
  709. NewHiddenRegion[] chunk = new NewHiddenRegion[chunkSize];
  710. int now = Environment.TickCount;
  711. int i = 0;
  712. while (i < count && start > now - maxTime) {
  713. int j = 0;
  714. while (i < count && j < chunkSize) {
  715. chunk[j] = lstNewHiddenRegions[i];
  716. // added++;
  717. i++;
  718. j++;
  719. }
  720. IVsEnumHiddenRegions[] ppEnumHiddenRegions = new IVsEnumHiddenRegions[1];
  721. int hr = hiddenTextSession.AddHiddenRegions((int)CHANGE_HIDDEN_REGION_FLAGS.chrNonUndoable, j, chunk, ppEnumHiddenRegions);
  722. if (NativeMethods.Failed(hr)) {
  723. break; // stop adding if we start getting errors.
  724. }
  725. runOnce = true;
  726. EnumAndAddHiddenRegions(ppEnumHiddenRegions);
  727. now = Environment.TickCount;
  728. }
  729. }
  730. // string debug = String.Format("Hidden Regions: Matched={0}, Removed={1}, Added={2}/{3}", matched, removed, added, lstNewHiddenRegions.Count);
  731. // Common.LogEntry(ClassName, "ProcessHiddenRegions", debug, Common.enErrorLvl.Information);
  732. } catch (Exception e) {
  733. Common.LogEntry(ClassName, "ProcessHiddenRegions", e, Common.enErrorLvl.Error);
  734. }
  735. }
  736. /// <summary>
  737. /// Enumerate newly added HiddenRegions, and create a HiddenRegionData object for each
  738. /// </summary>
  739. /// <param name="ppEnumHiddenRegions"></param>
  740. private void EnumAndAddHiddenRegions(IVsEnumHiddenRegions[] ppEnumHiddenRegions) {
  741. if (isDisposed) {
  742. return;
  743. }
  744. try {
  745. IVsHiddenRegion[] existingHiddenRegion = new IVsHiddenRegion[1];
  746. uint fetched;
  747. while (ppEnumHiddenRegions[0].Next(1, existingHiddenRegion, out fetched) == NativeMethods.S_OK && fetched == 1) {
  748. TextSpan[] textSpans = new TextSpan[1];
  749. NativeMethods.ThrowOnFailure(existingHiddenRegion[0].GetSpan(textSpans));
  750. HiddenRegionData hiddenRegionData = new HiddenRegionData(existingHiddenRegion[0], textSpans[0]);
  751. lstHiddenRegions.Add(hiddenRegionData);
  752. // Common.LogEntry(ClassName, "EnumHiddenRegions", "Adding " + hiddenRegionData, Common.enErrorLvl.Information);
  753. }
  754. lstHiddenRegions.Sort(HiddenRegionDataComparison);
  755. } catch (Exception e) {
  756. Common.LogEntry(ClassName, "EnumAndAddHiddenRegions", e, Common.enErrorLvl.Error);
  757. }
  758. }
  759. /// <summary>
  760. /// Get the whole document TextSpan object
  761. /// </summary>
  762. /// <returns></returns>
  763. public TextSpan GetDocumentSpan() {
  764. TextSpan span = new TextSpan {
  765. iStartIndex = 0,
  766. iStartLine = 0
  767. };
  768. try {
  769. NativeMethods.ThrowOnFailure(ppBuffer.GetLastLineIndex(out span.iEndLine, out span.iEndIndex));
  770. } catch (Exception e) {
  771. Common.LogEntry(ClassName, "GetDocumentSpan", e, Common.enErrorLvl.Error);
  772. span = new TextSpan();
  773. }
  774. return span;
  775. }
  776. /// <summary>
  777. /// Get the text contained in the supplied TextSpan
  778. /// </summary>
  779. /// <param name="span"></param>
  780. /// <returns></returns>
  781. private string GetText(TextSpan span) {
  782. try {
  783. StringBuilder sbText = new StringBuilder();
  784. string indentString = "";
  785. for (int i = span.iStartLine; i <= span.iEndLine; i++) {
  786. // Only allow 25 lines
  787. if (i - span.iStartLine > 25) {
  788. sbText.AppendLine("...");
  789. break;
  790. }
  791. int intLineLength;
  792. ppBuffer.GetLengthOfLine(i, out intLineLength);
  793. string strBuffer;
  794. ppBuffer.GetLineText(i, 0, i, intLineLength, out strBuffer);
  795. if (i == span.iStartLine) {
  796. int pos = 0;
  797. while (pos <= strBuffer.Length) {
  798. if (!char.IsWhiteSpace(strBuffer[pos])) {
  799. break;
  800. }
  801. pos++;
  802. }
  803. if (pos >= 0 && pos <= strBuffer.Length) {
  804. indentString = SmartIndenter.TabsToSpaces(strBuffer.Substring(0, pos));
  805. }
  806. }
  807. strBuffer = SmartIndenter.TabsToSpaces(strBuffer).Substring(indentString.Length);
  808. sbText.AppendLine(strBuffer);
  809. }
  810. return sbText.ToString();
  811. } catch (Exception e) {
  812. Common.LogEntry(ClassName, "GetText", e, Common.enErrorLvl.Error);
  813. return string.Empty;
  814. }
  815. }
  816. /// <summary>
  817. /// Retrieve a HiddenRegionData object from an IVsHiddenRegion object
  818. /// </summary>
  819. /// <param name="hiddenRegion"></param>
  820. /// <returns></returns>
  821. public HiddenRegionData GetHiddenRegionData(IVsHiddenRegion hiddenRegion) {
  822. try {
  823. foreach (HiddenRegionData region in lstHiddenRegions) {
  824. if (region.HiddenRegion == hiddenRegion) {
  825. return region;
  826. }
  827. }
  828. } catch (Exception e) {
  829. Common.LogEntry(ClassName, "GetHiddenRegionData", e, Common.enErrorLvl.Error);
  830. }
  831. return null;
  832. }
  833. }
  834. }