diff options
Diffstat (limited to 'main/src/addins/AspNet/Razor/RazorCSharpEditorExtension.cs')
-rw-r--r-- | main/src/addins/AspNet/Razor/RazorCSharpEditorExtension.cs | 1300 |
1 files changed, 659 insertions, 641 deletions
diff --git a/main/src/addins/AspNet/Razor/RazorCSharpEditorExtension.cs b/main/src/addins/AspNet/Razor/RazorCSharpEditorExtension.cs index 99bde2109f..e88d34589d 100644 --- a/main/src/addins/AspNet/Razor/RazorCSharpEditorExtension.cs +++ b/main/src/addins/AspNet/Razor/RazorCSharpEditorExtension.cs @@ -1,641 +1,659 @@ -//
-// RazorCSharpEditorExtension.cs
-//
-// Author:
-// Piotr Dowgiallo <sparekd@gmail.com>
-//
-// Copyright (c) 2012 Piotr Dowgiallo
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in
-// all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-// THE SOFTWARE.
-
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text.RegularExpressions;
-using System.Web.Razor.Generator;
-using System.Web.Razor.Parser.SyntaxTree;
-using ICSharpCode.NRefactory;
-using ICSharpCode.NRefactory.Completion;
-using ICSharpCode.NRefactory.TypeSystem;
-using Mono.TextEditor;
-using MonoDevelop.Core;
-using MonoDevelop.Ide.CodeCompletion;
-using MonoDevelop.Ide.Gui;
-using MonoDevelop.Ide.TypeSystem;
-using MonoDevelop.Xml.Dom;
-using MonoDevelop.Xml.Parser;
-using MonoDevelop.AspNet.Html;
-using MonoDevelop.AspNet.Razor.Dom;
-using MonoDevelop.AspNet.Razor.Parser;
-
-namespace MonoDevelop.AspNet.Razor
-{
- public class RazorCSharpEditorExtension : BaseHtmlEditorExtension
- {
- protected RazorCSharpParsedDocument razorDocument;
- protected UnderlyingDocumentInfo hiddenInfo;
- IRazorCompletionBuilder completionBuilder;
-
- bool isInCSharpContext;
- static readonly Regex DocTypeRegex = new Regex (@"(?:PUBLIC|public)\s+""(?<fpi>[^""]*)""\s+""(?<uri>[^""]*)""");
-
- ICompletionWidget defaultCompletionWidget;
- Document defaultDocument;
-
- RazorSyntaxMode syntaxMode;
-
- UnderlyingDocument HiddenDoc {
- get { return hiddenInfo.UnderlyingDocument; }
- }
-
- RazorPageInfo PageInfo {
- get { return razorDocument.PageInfo; }
- }
-
- protected override XmlRootState CreateRootState ()
- {
- return new RazorRootState ();
- }
-
- public override string CompletionLanguage {
- get {
- return "Razor";
- }
- }
- public override void Initialize ()
- {
- base.Initialize ();
-
- defaultCompletionWidget = CompletionWidget;
- defaultDocument = Document;
- completionBuilder = RazorCompletionBuilderService.GetBuilder ("C#");
-
- defaultDocument.Editor.Document.TextReplacing += UnderlyingDocument_TextReplacing;
- defaultDocument.Editor.Caret.PositionChanged += delegate
- {
- OnCompletionContextChanged (CompletionWidget, EventArgs.Empty);
- };
- syntaxMode = new RazorSyntaxMode (Document);
- defaultDocument.Editor.Document.SyntaxMode = syntaxMode;
-
- }
-
- public override void Dispose ()
- {
- if (syntaxMode != null) {
- defaultDocument.Editor.Document.SyntaxMode = null;
- syntaxMode.Dispose ();
- syntaxMode = null;
- }
- defaultDocument.Editor.Document.TextReplacing -= UnderlyingDocument_TextReplacing;
- base.Dispose ();
- }
-
- // Handles text modifications in hidden document
- void UnderlyingDocument_TextReplacing (object sender, DocumentChangeEventArgs e)
- {
- if (razorDocument == null)
- return;
-
- EnsureUnderlyingDocumentSet ();
- int off = CalculateCaretPosition (e.Offset);
-
- if (e.RemovalLength > 0) {
- int removalLength = e.RemovalLength;
- if (off + removalLength > HiddenDoc.Editor.Length)
- removalLength = HiddenDoc.Editor.Length - off;
- HiddenDoc.Editor.Remove (new TextSegment (off, removalLength));
- }
- if (e.InsertionLength > 0) {
- if (isInCSharpContext)
- HiddenDoc.Editor.Insert (off, e.InsertedText.Text);
- else // Insert spaces to correctly calculate offsets until next reparse
- HiddenDoc.Editor.Insert (off, new String (' ', e.InsertionLength));
- }
- if (codeFragment != null)
- codeFragment.EndOffset += (e.InsertionLength - e.RemovalLength);
- }
-
- protected override void OnParsedDocumentUpdated ()
- {
- base.OnParsedDocumentUpdated ();
- try {
- razorDocument = CU as RazorCSharpParsedDocument;
- if (razorDocument == null || razorDocument.PageInfo.CSharpParsedFile == null)
- return;
-
- CreateDocType ();
-
- // Don't update C# code in hiddenInfo when:
- // 1) We are in a RazorState, and the completion window is visible,
- // it'll freeze (or disappear if we call OnCompletionContextChanged).
- // 2) We're in the middle of writing a Razor expression - if we're in an incorrect state,
- // the generated code migh be behind what we've been already written.
-
- var state = Tracker.Engine.CurrentState;
- if (state is RazorState && CompletionWindowManager.IsVisible ||
- (!updateNeeded && (state is RazorSpeculativeState || state is RazorExpressionState)))
- UpdateHiddenDocument (false);
- else {
- UpdateHiddenDocument ();
- updateNeeded = false;
- }
- } catch (Exception e) {
- LoggingService.LogError ("Error while updating razor completion.", e);
- }
- }
-
- void CreateDocType ()
- {
- DocType = new XDocType (TextLocation.Empty);
- var matches = DocTypeRegex.Match (razorDocument.PageInfo.DocType);
- if (matches.Success) {
- DocType.PublicFpi = matches.Groups ["fpi"].Value;
- DocType.Uri = matches.Groups ["uri"].Value;
- }
- }
-
- void EnsureUnderlyingDocumentSet ()
- {
- if (hiddenInfo == null)
- UpdateHiddenDocument ();
- }
-
- void UpdateHiddenDocument (bool updateSourceCode = true)
- {
- if (!updateSourceCode && hiddenInfo != null) {
- hiddenInfo.UnderlyingDocument.HiddenParsedDocument = razorDocument.PageInfo.CSharpParsedFile;
- hiddenInfo.UnderlyingDocument.HiddenCompilation = razorDocument.PageInfo.Compilation;
- return;
- }
-
- hiddenInfo = new UnderlyingDocumentInfo ();
-
- var viewContent = new HiddenTextEditorViewContent ();
- viewContent.Project = Document.Project;
- viewContent.ContentName = "Generated.cs"; // Use a name with .cs extension to get csharp ambience
- viewContent.Text = razorDocument.PageInfo.CSharpCode;
-
- var workbenchWindow = new HiddenWorkbenchWindow ();
- workbenchWindow.ViewContent = viewContent;
- hiddenInfo.UnderlyingDocument = new UnderlyingDocument (workbenchWindow) {
- HiddenParsedDocument = razorDocument.PageInfo.CSharpParsedFile,
- HiddenCompilation = razorDocument.PageInfo.Compilation
- };
-
- // completion window needs this
- hiddenInfo.UnderlyingDocument.Editor.Parent = Editor.Parent;
-
- currentMappings = razorDocument.PageInfo.GeneratorResults.DesignTimeLineMappings;
- codeFragment = null;
- }
-
- #region Code completion
-
- XObject prevNode;
- bool updateNeeded;
-
- public override bool KeyPress (Gdk.Key key, char keyChar, Gdk.ModifierType modifier)
- {
- Tracker.UpdateEngine ();
- if (razorDocument == null)
- return NonCSharpCompletion (key, keyChar, modifier);
-
- var n = Tracker.Engine.Nodes.Peek ();
- if (prevNode is RazorExpression && !(n is RazorExpression))
- updateNeeded = true;
- prevNode = n;
-
- var state = Tracker.Engine.CurrentState;
- int off = document.Editor.Caret.Offset;
-
- char previousChar = off > 0 ? document.Editor.GetCharAt (off - 1) : ' ';
- char beforePrevious = off > 1 ? document.Editor.GetCharAt (off - 2) : ' ';
-
- // Determine completion context here, before calling base method to set the context correctly
-
- // Rule out Razor comments, html, transition sign (@) and e-mail addresses
- if (state is RazorCommentState || (previousChar != '@' && !(state is RazorState)) || keyChar == '@'
- || (previousChar == '@' && Char.IsLetterOrDigit (beforePrevious)))
- return NonCSharpCompletion (key, keyChar, modifier);
-
- // Determine if we are inside generics
- if (previousChar == '<') {
- var codeState = state as RazorCodeFragmentState;
- if (codeState == null || !codeState.IsInsideGenerics)
- return NonCSharpCompletion (key, keyChar, modifier);
- }
- // Determine whether we begin an html tag or generics
- else if (keyChar == '<' && (n is XElement || !Char.IsLetterOrDigit (previousChar)))
- return NonCSharpCompletion (key, keyChar, modifier);
- // Determine whether we are inside html text or in code
- else if (previousChar != '@' && n is XElement && !(state is RazorSpeculativeState) && !(state is RazorExpressionState))
- return NonCSharpCompletion (key, keyChar, modifier);
-
- // We're in C# context
- InitializeCodeCompletion ();
- SwitchToHidden ();
-
- bool result;
- try {
- result = base.KeyPress (key, keyChar, modifier);
- if (/*EnableParameterInsight &&*/ (keyChar == ',' || keyChar == ')') && CanRunParameterCompletionCommand ())
- base.RunParameterCompletionCommand ();
- } finally {
- SwitchToReal ();
- }
-
- return result;
- }
-
- protected void SwitchToHidden ()
- {
- isInCSharpContext = true;
- document = HiddenDoc;
- CompletionWidget = completionBuilder.CreateCompletionWidget (defaultDocument, hiddenInfo);
- }
-
- protected void SwitchToReal ()
- {
- isInCSharpContext = false;
- document = defaultDocument;
- CompletionWidget = defaultCompletionWidget;
- }
-
- bool NonCSharpCompletion (Gdk.Key key, char keyChar, Gdk.ModifierType modifier)
- {
- isInCSharpContext = false;
- return base.KeyPress (key, keyChar, modifier);
- }
-
- protected void InitializeCodeCompletion ()
- {
- EnsureUnderlyingDocumentSet ();
- hiddenInfo.OriginalCaretPosition = defaultDocument.Editor.Caret.Offset;
- hiddenInfo.CaretPosition = CalculateCaretPosition ();
- HiddenDoc.Editor.Caret.Offset = hiddenInfo.CaretPosition;
- }
-
- class CodeFragment
- {
- public int StartOffset { get; set; }
- public int StartRealOffset { get; set; }
- public int EndOffset { get; set; }
-
- public CodeFragment ()
- {}
-
- public CodeFragment (int startOff, int startRealOff, int endOffset)
- {
- StartOffset = startOff;
- StartRealOffset = startRealOff;
- EndOffset = endOffset;
- }
- }
-
- int GetDefaultPosition ()
- {
- var type = razorDocument.PageInfo.CSharpParsedFile.TopLevelTypeDefinitions.FirstOrDefault ();
- if (type == null) {
- return -1;
- }
- var method = type.Members.FirstOrDefault (m => m.Name == "Execute");
- if (method == null) {
- return -1;
- }
- return HiddenDoc.Editor.LocationToOffset (method.BodyRegion.Begin) + 1;
- }
-
- IDictionary<int, GeneratedCodeMapping> currentMappings;
- CodeFragment codeFragment;
-
- int CalculateCaretPosition ()
- {
- return CalculateCaretPosition (defaultDocument.Editor.Caret.Offset);
- }
-
- int CalculateCaretPosition (int currentOffset)
- {
- if (codeFragment != null) {
- int diff = currentOffset - codeFragment.StartRealOffset;
- int off = codeFragment.StartOffset + diff;
- if (diff >= 0 && off <= codeFragment.EndOffset)
- return off;
- }
-
- KeyValuePair<int, GeneratedCodeMapping> map;
-
- var defaultPosition = GetDefaultPosition ();
- if (defaultPosition < 0) {
- defaultPosition = 0;
- }
-
- // If it's first line of code, create a default temp mapping, and use it until next reparse
- if (currentMappings.Count == 0) {
- string newLine = "\r\n#line 0 \r\n ";
- HiddenDoc.Editor.Insert (defaultPosition, newLine);
- map = new KeyValuePair<int, GeneratedCodeMapping> (0, new GeneratedCodeMapping (currentOffset - 1, 0, 0, 0, 0));
- currentMappings.Add (map);
- } else {
- var result = currentMappings.Where (m => m.Value.StartOffset <= currentOffset);
- if (!result.Any ())
- return defaultPosition;
- map = result.Last ();
- }
-
- string pattern = "#line " + map.Key + " ";
- int pos = HiddenDoc.Editor.Document.IndexOf (pattern, 0, HiddenDoc.Editor.Document.TextLength, StringComparison.Ordinal);
- if (pos == -1 || !map.Value.StartOffset.HasValue)
- return defaultPosition;
-
- int startRealOff = map.Value.StartOffset.Value;
- int offDifference = currentOffset - (startRealOff + map.Value.CodeLength);
- var line = HiddenDoc.Editor.Document.GetLineByOffset (pos);
- int endHiddenOff = line.NextLine.Offset + map.Value.StartGeneratedColumn + map.Value.CodeLength;
-
- int hiddenOff;
-
- // If off is inside the map
- if (offDifference <= 0) {
- int delta = currentOffset - startRealOff;
- hiddenOff = line.NextLine.Offset + map.Value.StartGeneratedColumn + delta - 1;
- codeFragment = new CodeFragment (hiddenOff, currentOffset, endHiddenOff);
- } else {
- // It's a new code fragment - create a temp mapping, and use it until next reparse
- int key = currentMappings.Last ().Key + 1;
- string newLine = "\r\n#line " + key + " \r\n ";
- int newOff = endHiddenOff;
-
- if (HiddenDoc.Editor.GetCharAt (newOff) == '\n')
- newOff++;
-
- // We start a new mapping right after the preceding one, but need to include the difference
- // between mapping's start and the current offset
- HiddenDoc.Editor.Insert (newOff, newLine);
- HiddenDoc.Editor.Insert (newOff + newLine.Length, new String (' ', offDifference) + " \r\n");
-
- var newMap = new KeyValuePair<int, GeneratedCodeMapping> (key, new GeneratedCodeMapping (
- startRealOff + map.Value.CodeLength, 0, 0, 0, offDifference));
- currentMappings.Add (newMap);
- hiddenOff = newOff + newLine.Length + offDifference;
- codeFragment = new CodeFragment (newOff + newLine.Length, newMap.Value.StartOffset.Value,
- newOff + newLine.Length + offDifference);
- }
-
- return hiddenOff;
- }
-
- public override ICompletionDataList HandleCodeCompletion (CodeCompletionContext completionContext,
- char completionChar, ref int triggerWordLength)
- {
-// if (!EnableCodeCompletion)
-// return null;
-
- char previousChar = defaultDocument.Editor.Caret.Offset > 1 ? defaultDocument.Editor.GetCharAt (
- defaultDocument.Editor.Caret.Offset - 2) : ' ';
-
- // Don't show completion window when directive's name is being typed
- var directive = Tracker.Engine.Nodes.Peek () as RazorDirective;
- if (directive != null && !directive.FirstBracket.HasValue)
- return null;
-
- if (hiddenInfo != null && isInCSharpContext) {
- var list = (CompletionDataList) completionBuilder.HandleCompletion (defaultDocument, completionContext,
- hiddenInfo, completionChar, ref triggerWordLength);
-
- if (list != null) {
- //filter out the C# templates, many of them are not valid
- int oldCount = list.Count;
- list = FilterCSharpTemplates (list);
- int templates = list.Count - oldCount;
-
- if (previousChar == '@') {
- RazorCompletion.AddAllRazorSymbols (list, razorDocument.PageInfo.HostKind);
- }
- if (templates > 0) {
- AddFilteredRazorTemplates (list, previousChar == '@', true);
- }
- }
- return list;
- }
-
- return base.HandleCodeCompletion (completionContext, completionChar, ref triggerWordLength);
- }
-
- //recreating the list is over 2x as fast as using remove operations, saves typically 10ms
- static CompletionDataList FilterCSharpTemplates (CompletionDataList list)
- {
- var newList = new CompletionDataList () {
- AutoCompleteEmptyMatch = list.AutoCompleteEmptyMatch,
- AutoCompleteUniqueMatch = list.AutoCompleteUniqueMatch,
- AutoSelect = list.AutoSelect,
- CloseOnSquareBrackets = list.CloseOnSquareBrackets,
- CompletionSelectionMode = list.CompletionSelectionMode,
- DefaultCompletionString = list.DefaultCompletionString,
- IsSorted = list.IsSorted,
- };
- foreach (var l in list) {
- var c = l as CompletionData;
- if (c == null || (c.Icon.Name != "md-template" && c.Icon.Name != "md-template-surroundwith"))
- newList.Add (c);
- }
- return newList;
- }
-
- static void AddFilteredRazorTemplates (CompletionDataList list, bool atTemplates, bool stripLeadingAt)
- {
- //add the razor templates then filter them based on whether we follow an @ char, so we don't have
- //lots of duplicates
- int count = list.Count;
- MonoDevelop.Ide.CodeTemplates.CodeTemplateService.AddCompletionDataForMime ("text/x-cshtml", list);
- for (int i = count; i < list.Count; i++) {
- var d = (CompletionData) list[i];
- if (atTemplates) {
- if (d.CompletionText[0] != '@') {
- list.RemoveAt (i);
- } else if (stripLeadingAt) {
- //avoid inserting a double-@, which would not expand correctly
- d.CompletionText = d.CompletionText.Substring (1);
- }
- } else if (d.CompletionText[0] == '@') {
- list.RemoveAt (i);
- }
- }
- }
-
- protected override ICompletionDataList HandleCodeCompletion (CodeCompletionContext completionContext,
- bool forced, ref int triggerWordLength)
- {
-// if (!EnableCodeCompletion)
-// return null;
-
- var currentLocation = new TextLocation (completionContext.TriggerLine, completionContext.TriggerLineOffset);
- char currentChar = completionContext.TriggerOffset < 1 ? ' ' : Buffer.GetCharAt (completionContext.TriggerOffset - 1);
-
- var codeState = Tracker.Engine.CurrentState as RazorCodeFragmentState;
- if (currentChar == '<' && codeState != null) {
- if (!codeState.IsInsideParentheses && !codeState.IsInsideGenerics) {
- var list = new CompletionDataList ();
- GetElementCompletions (list);
- return list;
- }
- } else if (currentChar == '>' && Tracker.Engine.CurrentState is RazorCodeFragmentState)
- return ClosingTagCompletion (EditableBuffer, currentLocation);
-
- return base.HandleCodeCompletion (completionContext, forced, ref triggerWordLength);
- }
-
- //we override to ensure we get parent element name even if there's a razor node in between
- protected override void GetElementCompletions (CompletionDataList list)
- {
- var el = Tracker.Engine.Nodes.OfType<XElement> ().FirstOrDefault ();
- var parentName = el == null ? new XName () : el.Name;
-
- AddHtmlTagCompletionData (list, Schema, parentName);
- AddMiscBeginTags (list);
-
- //FIXME: don't show this after any elements
- if (DocType == null)
- list.Add ("!DOCTYPE", "md-literal", MonoDevelop.Core.GettextCatalog.GetString ("Document type"));
- }
-
- public override ICompletionDataList CodeCompletionCommand (CodeCompletionContext completionContext)
- {
- if (hiddenInfo != null && (isInCSharpContext || Tracker.Engine.CurrentState is RazorState)
- && !(Tracker.Engine.Nodes.Peek () is XElement)) {
- InitializeCodeCompletion ();
- return completionBuilder.HandlePopupCompletion (defaultDocument, hiddenInfo);
- }
-
- return base.CodeCompletionCommand (completionContext);
- }
-
- public override bool GetParameterCompletionCommandOffset (out int cpos)
- {
- if (hiddenInfo != null && isInCSharpContext)
- return completionBuilder.GetParameterCompletionCommandOffset (defaultDocument, hiddenInfo, out cpos);
-
- return base.GetParameterCompletionCommandOffset (out cpos);
- }
-
- public override int GetCurrentParameterIndex (int startOffset)
- {
- if (hiddenInfo != null && isInCSharpContext) {
- return completionBuilder.GetCurrentParameterIndex (defaultDocument, hiddenInfo, startOffset);
- }
-
- return base.GetCurrentParameterIndex (startOffset);
- }
-
- public override ParameterDataProvider HandleParameterCompletion (CodeCompletionContext completionContext,
- char completionChar)
- {
- if (hiddenInfo != null && isInCSharpContext) {
- return completionBuilder.HandleParameterCompletion (defaultDocument, completionContext,
- hiddenInfo, completionChar);
- }
-
- return base.HandleParameterCompletion (completionContext, completionChar);
- }
-
- #endregion
-
- #region Document outline
-
- protected override void RefillOutlineStore (ParsedDocument doc, Gtk.TreeStore store)
- {
- var htmlRoot = razorDocument.PageInfo.HtmlRoot;
- var razorRoot = razorDocument.PageInfo.RazorRoot;
- var blocks = new List<Block> ();
- GetBlocks (razorRoot, blocks);
- BuildTreeChildren (store, Gtk.TreeIter.Zero, htmlRoot, blocks);
- }
-
- void GetBlocks (Block root, IList<Block> blocks)
- {
- foreach (var block in root.Children.Where (n => n.IsBlock).Select (n => n as Block)) {
- if (block.Type != BlockType.Markup)
- blocks.Add (block);
- if (block.Type != BlockType.Helper)
- GetBlocks (block, blocks);
- }
- }
-
- protected override void InitializeOutlineColumns (MonoDevelop.Ide.Gui.Components.PadTreeView outlineTree)
- {
- outlineTree.TextRenderer.Xpad = 0;
- outlineTree.TextRenderer.Ypad = 0;
- outlineTree.AppendColumn ("OutlineNode", outlineTree.TextRenderer, new Gtk.TreeCellDataFunc (OutlineTreeDataFunc));
- }
-
- protected override void OutlineSelectionChanged (object selection)
- {
- SelectNode ((RazorOutlineNode)selection);
- }
-
- void BuildTreeChildren (Gtk.TreeStore store, Gtk.TreeIter parent, XContainer p, IList<Block> blocks)
- {
- foreach (XNode node in p.Nodes) {
- var el = node as XElement;
- if (el == null) {
- var startLoc = node.Region.Begin;
- var endLoc = node.Region.End;
- var doc = defaultDocument.Editor.Document;
-
- var blocksBetween = blocks.Where (n => n.Start.AbsoluteIndex >= doc.GetOffset (startLoc)
- && n.Start.AbsoluteIndex <= doc.GetOffset (endLoc));
-
- foreach (var block in blocksBetween) {
- var outlineNode = new RazorOutlineNode (block) {
- Location = new DomRegion (doc.OffsetToLocation (block.Start.AbsoluteIndex),
- doc.OffsetToLocation (block.Start.AbsoluteIndex + block.Length))
- };
- if (!parent.Equals (Gtk.TreeIter.Zero))
- store.AppendValues (parent, outlineNode);
- else
- store.AppendValues (outlineNode);
- }
- continue;
- }
-
- Gtk.TreeIter childIter;
- if (!parent.Equals (Gtk.TreeIter.Zero))
- childIter = store.AppendValues (parent, new RazorOutlineNode(el));
- else
- childIter = store.AppendValues (new RazorOutlineNode(el));
-
- BuildTreeChildren (store, childIter, el, blocks);
- }
- }
-
- void OutlineTreeDataFunc (Gtk.TreeViewColumn column, Gtk.CellRenderer cell, Gtk.TreeModel model, Gtk.TreeIter iter)
- {
- Gtk.CellRendererText txtRenderer = (Gtk.CellRendererText)cell;
- RazorOutlineNode n = (RazorOutlineNode)model.GetValue (iter, 0);
- txtRenderer.Text = n.Name;
- }
-
- void SelectNode (RazorOutlineNode n)
- {
- EditorSelect (n.Location);
- }
-
- #endregion
- }
-}
+// +// RazorCSharpEditorExtension.cs +// +// Author: +// Piotr Dowgiallo <sparekd@gmail.com> +// +// Copyright (c) 2012 Piotr Dowgiallo +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; +using System.Web.Razor.Generator; +using System.Web.Razor.Parser.SyntaxTree; +using Mono.TextEditor; +using MonoDevelop.Core; +using MonoDevelop.Core.Text; +using MonoDevelop.Ide.Editor; +using MonoDevelop.Ide.CodeCompletion; +using MonoDevelop.Ide.Gui; +using MonoDevelop.Ide.TypeSystem; +using MonoDevelop.Xml.Dom; +using MonoDevelop.Xml.Parser; +using MonoDevelop.AspNet.Html; +using MonoDevelop.AspNet.Razor.Dom; +using MonoDevelop.AspNet.Razor.Parser; +using ICSharpCode.NRefactory6.CSharp.Completion; +using MonoDevelop.Ide.Editor.Extension; + +namespace MonoDevelop.AspNet.Razor +{ + public class RazorCSharpEditorExtension : BaseHtmlEditorExtension + { + protected RazorCSharpParsedDocument razorDocument; + protected UnderlyingDocumentInfo hiddenInfo; + IRazorCompletionBuilder completionBuilder; + + bool isInCSharpContext; + static readonly Regex DocTypeRegex = new Regex (@"(?:PUBLIC|public)\s+""(?<fpi>[^""]*)""\s+""(?<uri>[^""]*)"""); + + ICompletionWidget defaultCompletionWidget; + MonoDevelop.Ide.Editor.TextEditor defaultEditor; + DocumentContext defaultDocumentContext; + + RazorSyntaxMode syntaxMode; + + UnderlyingDocument HiddenDoc { + get { return hiddenInfo.UnderlyingDocument; } + } + + RazorPageInfo PageInfo { + get { return razorDocument.PageInfo; } + } + + protected override XmlRootState CreateRootState () + { + return new RazorRootState (); + } + + public override string CompletionLanguage { + get { + return "Razor"; + } + } + + protected override void Initialize () + { + base.Initialize (); + + defaultCompletionWidget = CompletionWidget; + defaultDocumentContext = DocumentContext; + defaultEditor = Editor; + completionBuilder = RazorCompletionBuilderService.GetBuilder ("C#"); + + defaultEditor.TextChanging += UnderlyingDocument_TextReplacing; + syntaxMode = new RazorSyntaxMode (DocumentContext); + var textEditorData = DocumentContext.GetContent<TextEditorData> (); + if (textEditorData != null) + textEditorData.Document.SyntaxMode = syntaxMode; + } + + public override void Dispose () + { + if (syntaxMode != null) { + var textEditorData = DocumentContext.GetContent<TextEditorData> (); + if (textEditorData != null) + textEditorData.Document.SyntaxMode = null; + syntaxMode.Dispose (); + syntaxMode = null; + } + defaultEditor.TextChanging -= UnderlyingDocument_TextReplacing; + base.Dispose (); + } + + // Handles text modifications in hidden document + void UnderlyingDocument_TextReplacing (object sender, TextChangeEventArgs e) + { + if (razorDocument == null) + return; + + // TODO Roslyn port. + return; + + EnsureUnderlyingDocumentSet (); + int off = CalculateCaretPosition (e.Offset); + + if (e.RemovalLength > 0) { + int removalLength = e.RemovalLength; + if (off + removalLength > HiddenDoc.Editor.Length) + removalLength = HiddenDoc.Editor.Length - off; + HiddenDoc.Editor.RemoveText (off, removalLength); + } + if (e.InsertionLength > 0) { + if (isInCSharpContext) + HiddenDoc.Editor.InsertText (off, e.InsertedText.Text); + else // Insert spaces to correctly calculate offsets until next reparse + HiddenDoc.Editor.InsertText (off, new String (' ', e.InsertionLength)); + } + if (codeFragment != null) + codeFragment.EndOffset += (e.InsertionLength - e.RemovalLength); + } + + protected override void OnParsedDocumentUpdated () + { + base.OnParsedDocumentUpdated (); + try { + razorDocument = CU as RazorCSharpParsedDocument; + if (razorDocument == null || razorDocument.PageInfo.CSharpParsedFile == null) + return; + + CreateDocType (); + + // Don't update C# code in hiddenInfo when: + // 1) We are in a RazorState, and the completion window is visible, + // it'll freeze (or disappear if we call OnCompletionContextChanged). + // 2) We're in the middle of writing a Razor expression - if we're in an incorrect state, + // the generated code migh be behind what we've been already written. + + var state = Tracker.Engine.CurrentState; + if (state is RazorState && CompletionWindowManager.IsVisible || + (!updateNeeded && (state is RazorSpeculativeState || state is RazorExpressionState))) + UpdateHiddenDocument (false); + else { + UpdateHiddenDocument (); + updateNeeded = false; + } + } catch (Exception e) { + LoggingService.LogError ("Error while updating razor completion.", e); + } + } + + void CreateDocType () + { + DocType = new XDocType (MonoDevelop.Ide.Editor.DocumentLocation.Empty); + var matches = DocTypeRegex.Match (razorDocument.PageInfo.DocType); + if (matches.Success) { + DocType.PublicFpi = matches.Groups ["fpi"].Value; + DocType.Uri = matches.Groups ["uri"].Value; + } + } + + void EnsureUnderlyingDocumentSet () + { + if (hiddenInfo == null) + UpdateHiddenDocument (); + } + + void UpdateHiddenDocument (bool updateSourceCode = true) + { + if (!updateSourceCode && hiddenInfo != null) { + // TODO : Roslyn port + //hiddenInfo.UnderlyingDocument.HiddenParsedDocument = razorDocument.PageInfo.CSharpParsedFile; + //hiddenInfo.UnderlyingDocument.HiddenCompilation = razorDocument.PageInfo.Compilation; + return; + } + + hiddenInfo = new UnderlyingDocumentInfo (); + + var viewContent = new HiddenTextEditorViewContent (); + viewContent.Project = DocumentContext.Project; + viewContent.ContentName = "Generated.cs"; // Use a name with .cs extension to get csharp ambience + viewContent.Text = razorDocument.PageInfo.CSharpCode; + + var workbenchWindow = new HiddenWorkbenchWindow (); + workbenchWindow.ViewContent = viewContent; + hiddenInfo.UnderlyingDocument = new UnderlyingDocument (workbenchWindow) { + // TODO: Roslyn port + // HiddenParsedDocument = razorDocument.PageInfo.CSharpParsedFile + /*, + HiddenCompilation = razorDocument.PageInfo.Compilation*/ + }; + + // completion window needs this + Gtk.Widget editor = hiddenInfo.UnderlyingDocument.Editor; + editor.Parent = ((Gtk.Widget)Editor).Parent; + + currentMappings = razorDocument.PageInfo.GeneratorResults.DesignTimeLineMappings; + codeFragment = null; + } + + #region Code completion + + XObject prevNode; + bool updateNeeded; + + public override bool KeyPress (KeyDescriptor descriptor) + { + Tracker.UpdateEngine (); + if (razorDocument == null) + return NonCSharpCompletion (descriptor); + + var n = Tracker.Engine.Nodes.Peek (); + if (prevNode is RazorExpression && !(n is RazorExpression)) + updateNeeded = true; + prevNode = n; + var state = Tracker.Engine.CurrentState; + int off = Editor.CaretOffset; + + char previousChar = off > 0 ? Editor.GetCharAt (off - 1) : ' '; + char beforePrevious = off > 1 ? Editor.GetCharAt (off - 2) : ' '; + + // Determine completion context here, before calling base method to set the context correctly + + // Rule out Razor comments, html, transition sign (@) and e-mail addresses + if (state is RazorCommentState || (previousChar != '@' && !(state is RazorState)) || descriptor.KeyChar == '@' + || (previousChar == '@' && Char.IsLetterOrDigit (beforePrevious))) + return NonCSharpCompletion (descriptor); + + // Determine if we are inside generics + if (previousChar == '<') { + var codeState = state as RazorCodeFragmentState; + if (codeState == null || !codeState.IsInsideGenerics) + return NonCSharpCompletion (descriptor); + } + // Determine whether we begin an html tag or generics + else if (descriptor.KeyChar == '<' && (n is XElement || !Char.IsLetterOrDigit (previousChar))) + return NonCSharpCompletion (descriptor); + // Determine whether we are inside html text or in code + else if (previousChar != '@' && n is XElement && !(state is RazorSpeculativeState) && !(state is RazorExpressionState)) + return NonCSharpCompletion (descriptor); + + // We're in C# context + InitializeCodeCompletion (); + SwitchToHidden (); + + bool result; + try { + result = base.KeyPress (descriptor); + if (/*EnableParameterInsight &&*/ (descriptor.KeyChar == ',' || descriptor.KeyChar == ')') && CanRunParameterCompletionCommand ()) + base.RunParameterCompletionCommand (); + } finally { + SwitchToReal (); + } + + return result; + } + + protected void SwitchToHidden () + { + isInCSharpContext = true; +// TODO: Roslyn port. +// DocumentContext = HiddenDoc; +// Editor = HiddenDoc.Editor; +// CompletionWidget = completionBuilder.CreateCompletionWidget (defaultEditor, defaultDocumentContext, hiddenInfo); + } + + protected void SwitchToReal () + { + isInCSharpContext = false; +// TODO: Roslyn port. +// DocumentContext = defaultDocumentContext; +// Editor = defaultEditor; +// CompletionWidget = defaultCompletionWidget; + } + + bool NonCSharpCompletion (KeyDescriptor descriptor) + { + isInCSharpContext = false; + return base.KeyPress (descriptor); + } + + protected void InitializeCodeCompletion () + { + // TODO Roslyn port +// EnsureUnderlyingDocumentSet (); +// hiddenInfo.OriginalCaretPosition = defaultEditor.CaretOffset; +// hiddenInfo.CaretPosition = CalculateCaretPosition (); +// HiddenDoc.Editor.CaretOffset = hiddenInfo.CaretPosition; + } + + class CodeFragment + { + public int StartOffset { get; set; } + public int StartRealOffset { get; set; } + public int EndOffset { get; set; } + + public CodeFragment () + {} + + public CodeFragment (int startOff, int startRealOff, int endOffset) + { + StartOffset = startOff; + StartRealOffset = startRealOff; + EndOffset = endOffset; + } + } + + int GetDefaultPosition () + { + // TODO: Roslyn port + return -1; +// var type = razorDocument.PageInfo.CSharpParsedFile.TopLevelTypeDefinitions.FirstOrDefault (); +// if (type == null) { +// return -1; +// } +// var method = type.Members.FirstOrDefault (m => m.Name == "Execute"); +// if (method == null) { +// return -1; +// } +// return HiddenDoc.Editor.LocationToOffset (method.BodyRegion.Begin) + 1; + } + + IDictionary<int, GeneratedCodeMapping> currentMappings; + CodeFragment codeFragment; + + int CalculateCaretPosition () + { + return CalculateCaretPosition (defaultEditor.CaretOffset); + } + + int CalculateCaretPosition (int currentOffset) + { + if (codeFragment != null) { + int diff = currentOffset - codeFragment.StartRealOffset; + int off = codeFragment.StartOffset + diff; + if (diff >= 0 && off <= codeFragment.EndOffset) + return off; + } + + KeyValuePair<int, GeneratedCodeMapping> map; + + var defaultPosition = GetDefaultPosition (); + if (defaultPosition < 0) { + defaultPosition = 0; + } + + // If it's first line of code, create a default temp mapping, and use it until next reparse + if (currentMappings.Count == 0) { + string newLine = "\r\n#line 0 \r\n "; + HiddenDoc.Editor.InsertText (defaultPosition, newLine); + map = new KeyValuePair<int, GeneratedCodeMapping> (0, new GeneratedCodeMapping (currentOffset - 1, 0, 0, 0, 0)); + currentMappings.Add (map); + } else { + var result = currentMappings.Where (m => m.Value.StartOffset <= currentOffset); + if (!result.Any ()) + return defaultPosition; + map = result.Last (); + } + + string pattern = "#line " + map.Key + " "; + int pos = HiddenDoc.Editor.Text.IndexOf (pattern, 0, HiddenDoc.Editor.Length, StringComparison.Ordinal); + if (pos == -1 || !map.Value.StartOffset.HasValue) + return defaultPosition; + + int startRealOff = map.Value.StartOffset.Value; + int offDifference = currentOffset - (startRealOff + map.Value.CodeLength); + var line = HiddenDoc.Editor.GetLineByOffset (pos); + int endHiddenOff = line.NextLine.Offset + map.Value.StartGeneratedColumn + map.Value.CodeLength; + + int hiddenOff; + + // If off is inside the map + if (offDifference <= 0) { + int delta = currentOffset - startRealOff; + hiddenOff = line.NextLine.Offset + map.Value.StartGeneratedColumn + delta - 1; + codeFragment = new CodeFragment (hiddenOff, currentOffset, endHiddenOff); + } else { + // It's a new code fragment - create a temp mapping, and use it until next reparse + int key = currentMappings.Last ().Key + 1; + string newLine = "\r\n#line " + key + " \r\n "; + int newOff = endHiddenOff; + + if (HiddenDoc.Editor.GetCharAt (newOff) == '\n') + newOff++; + + // We start a new mapping right after the preceding one, but need to include the difference + // between mapping's start and the current offset + HiddenDoc.Editor.InsertText (newOff, newLine); + HiddenDoc.Editor.InsertText (newOff + newLine.Length, new String (' ', offDifference) + " \r\n"); + + var newMap = new KeyValuePair<int, GeneratedCodeMapping> (key, new GeneratedCodeMapping ( + startRealOff + map.Value.CodeLength, 0, 0, 0, offDifference)); + currentMappings.Add (newMap); + hiddenOff = newOff + newLine.Length + offDifference; + codeFragment = new CodeFragment (newOff + newLine.Length, newMap.Value.StartOffset.Value, + newOff + newLine.Length + offDifference); + } + + return hiddenOff; + } + +// TODO: Roslyn port +// public override async System.Threading.Tasks.Task<ICompletionDataList> HandleCodeCompletionAsync (CodeCompletionContext completionContext, char completionChar, System.Threading.CancellationToken token) +// { +//// if (!EnableCodeCompletion) +//// return null; +// +// char previousChar = defaultEditor.CaretOffset > 1 ? defaultEditor.GetCharAt ( +// defaultEditor.CaretOffset - 2) : ' '; +// +// // Don't show completion window when directive's name is being typed +// var directive = Tracker.Engine.Nodes.Peek () as RazorDirective; +// if (directive != null && !directive.FirstBracket.HasValue) +// return null; +// +// if (hiddenInfo != null && isInCSharpContext) { +// var list = (CompletionDataList) await completionBuilder.HandleCompletion (defaultEditor, defaultDocumentContext, completionContext, +// hiddenInfo, completionChar); +// +// if (list != null) { +// //filter out the C# templates, many of them are not valid +// int oldCount = list.Count; +// list = FilterCSharpTemplates (list); +// int templates = list.Count - oldCount; +// +// if (previousChar == '@') { +// RazorCompletion.AddAllRazorSymbols (list, razorDocument.PageInfo.HostKind); +// } +// if (templates > 0) { +// AddFilteredRazorTemplates (list, previousChar == '@', true); +// } +// } +// return list; +// } +// +// return base.HandleCodeCompletionAsync (completionContext, completionChar, token); +// } + + //recreating the list is over 2x as fast as using remove operations, saves typically 10ms + static CompletionDataList FilterCSharpTemplates (CompletionDataList list) + { + var newList = new CompletionDataList () { + AutoCompleteEmptyMatch = list.AutoCompleteEmptyMatch, + AutoCompleteUniqueMatch = list.AutoCompleteUniqueMatch, + AutoSelect = list.AutoSelect, + CloseOnSquareBrackets = list.CloseOnSquareBrackets, + CompletionSelectionMode = list.CompletionSelectionMode, + DefaultCompletionString = list.DefaultCompletionString, + IsSorted = list.IsSorted, + }; + foreach (var l in list) { + var c = l as CompletionData; + if (c == null || (c.Icon.Name != "md-template" && c.Icon.Name != "md-template-surroundwith")) + newList.Add (c); + } + return newList; + } + + static void AddFilteredRazorTemplates (CompletionDataList list, bool atTemplates, bool stripLeadingAt) + { + //add the razor templates then filter them based on whether we follow an @ char, so we don't have + //lots of duplicates + int count = list.Count; + MonoDevelop.Ide.CodeTemplates.CodeTemplateService.AddCompletionDataForMime ("text/x-cshtml", list); + for (int i = count; i < list.Count; i++) { + var d = (CompletionData) list[i]; + if (atTemplates) { + if (d.CompletionText[0] != '@') { + list.RemoveAt (i); + } else if (stripLeadingAt) { + //avoid inserting a double-@, which would not expand correctly + d.CompletionText = d.CompletionText.Substring (1); + } + } else if (d.CompletionText[0] == '@') { + list.RemoveAt (i); + } + } + } + + // TODO: Roslyn port +// protected override ICompletionDataList HandleCodeCompletion (CodeCompletionContext completionContext, +// bool forced, ref int triggerWordLength) +// { +//// if (!EnableCodeCompletion) +//// return null; +// +// var currentLocation = new ICSharpCode.NRefactory.TextLocation (completionContext.TriggerLine, completionContext.TriggerLineOffset); +// char currentChar = completionContext.TriggerOffset < 1 ? ' ' : Editor.GetCharAt (completionContext.TriggerOffset - 1); +// +// var codeState = Tracker.Engine.CurrentState as RazorCodeFragmentState; +// if (currentChar == '<' && codeState != null) { +// if (!codeState.IsInsideParentheses && !codeState.IsInsideGenerics) { +// var list = new CompletionDataList (); +// GetElementCompletions (list); +// return list; +// } +// } else if (currentChar == '>' && Tracker.Engine.CurrentState is RazorCodeFragmentState) +// return ClosingTagCompletion (Editor, currentLocation); +// +// return base.HandleCodeCompletion (completionContext, forced, ref triggerWordLength); +// } + + //we override to ensure we get parent element name even if there's a razor node in between + protected override void GetElementCompletions (CompletionDataList list) + { + var el = Tracker.Engine.Nodes.OfType<XElement> ().FirstOrDefault (); + var parentName = el == null ? new XName () : el.Name; + + AddHtmlTagCompletionData (list, Schema, parentName); + AddMiscBeginTags (list); + + //FIXME: don't show this after any elements + if (DocType == null) + list.Add ("!DOCTYPE", "md-literal", MonoDevelop.Core.GettextCatalog.GetString ("Document type")); + } + + public override ICompletionDataList CodeCompletionCommand (CodeCompletionContext completionContext) + { + if (hiddenInfo != null && (isInCSharpContext || Tracker.Engine.CurrentState is RazorState) + && !(Tracker.Engine.Nodes.Peek () is XElement)) { + InitializeCodeCompletion (); + return completionBuilder.HandlePopupCompletion (defaultEditor, defaultDocumentContext, hiddenInfo); + } + + return base.CodeCompletionCommand (completionContext); + } + /* + public override bool GetParameterCompletionCommandOffset (out int cpos) + { + if (hiddenInfo != null && isInCSharpContext) + return completionBuilder.GetParameterCompletionCommandOffset (defaultEditor, defaultDocumentContext, hiddenInfo, out cpos); + + return base.GetParameterCompletionCommandOffset (out cpos); + }*/ + + public override int GetCurrentParameterIndex (int startOffset) + { + if (hiddenInfo != null && isInCSharpContext) { + return completionBuilder.GetCurrentParameterIndex (defaultEditor, defaultDocumentContext, hiddenInfo, startOffset); + } + + return base.GetCurrentParameterIndex (startOffset); + } + +// TODO: Roslyn port +// public override ParameterHintingResult HandleParameterCompletionAsync (CodeCompletionContext completionContext, +// char completionChar) +// { +// if (hiddenInfo != null && isInCSharpContext) { +// return completionBuilder.HandleParameterCompletion (defaultEditor, defaultDocumentContext, completionContext, +// hiddenInfo, completionChar); +// } +// +// return base.HandleParameterCompletionAsync (completionContext, completionChar); +// } + + #endregion + + #region Document outline + + protected override void RefillOutlineStore (ParsedDocument doc, Gtk.TreeStore store) + { + var htmlRoot = razorDocument.PageInfo.HtmlRoot; + var razorRoot = razorDocument.PageInfo.RazorRoot; + var blocks = new List<Block> (); + GetBlocks (razorRoot, blocks); + BuildTreeChildren (store, Gtk.TreeIter.Zero, htmlRoot, blocks); + } + + void GetBlocks (Block root, IList<Block> blocks) + { + foreach (var block in root.Children.Where (n => n.IsBlock).Select (n => n as Block)) { + if (block.Type != BlockType.Markup) + blocks.Add (block); + if (block.Type != BlockType.Helper) + GetBlocks (block, blocks); + } + } + + protected override void InitializeOutlineColumns (MonoDevelop.Ide.Gui.Components.PadTreeView outlineTree) + { + outlineTree.TextRenderer.Xpad = 0; + outlineTree.TextRenderer.Ypad = 0; + outlineTree.AppendColumn ("OutlineNode", outlineTree.TextRenderer, new Gtk.TreeCellDataFunc (OutlineTreeDataFunc)); + } + + protected override void OutlineSelectionChanged (object selection) + { + SelectNode ((RazorOutlineNode)selection); + } + + void BuildTreeChildren (Gtk.TreeStore store, Gtk.TreeIter parent, XContainer p, IList<Block> blocks) + { + foreach (XNode node in p.Nodes) { + var el = node as XElement; + if (el == null) { + var startLoc = node.Region.Begin; + var endLoc = node.Region.End; + var doc = defaultEditor; + + var blocksBetween = blocks.Where (n => n.Start.AbsoluteIndex >= doc.LocationToOffset (startLoc.Line, startLoc.Column) + && n.Start.AbsoluteIndex <= doc.LocationToOffset (endLoc.Line, endLoc.Column)); + + foreach (var block in blocksBetween) { + var outlineNode = new RazorOutlineNode (block) { + Location = new MonoDevelop.Ide.Editor.DocumentRegion (doc.OffsetToLocation (block.Start.AbsoluteIndex), + doc.OffsetToLocation (block.Start.AbsoluteIndex + block.Length)) + }; + if (!parent.Equals (Gtk.TreeIter.Zero)) + store.AppendValues (parent, outlineNode); + else + store.AppendValues (outlineNode); + } + continue; + } + + Gtk.TreeIter childIter; + if (!parent.Equals (Gtk.TreeIter.Zero)) + childIter = store.AppendValues (parent, new RazorOutlineNode(el)); + else + childIter = store.AppendValues (new RazorOutlineNode(el)); + + BuildTreeChildren (store, childIter, el, blocks); + } + } + + void OutlineTreeDataFunc (Gtk.TreeViewColumn column, Gtk.CellRenderer cell, Gtk.TreeModel model, Gtk.TreeIter iter) + { + Gtk.CellRendererText txtRenderer = (Gtk.CellRendererText)cell; + RazorOutlineNode n = (RazorOutlineNode)model.GetValue (iter, 0); + txtRenderer.Text = n.Name; + } + + void SelectNode (RazorOutlineNode n) + { + EditorSelect (n.Location); + } + + #endregion + } +} |