internal void InsertSnippet(Snippet snip, int startPos)
{
NativeScintilla.BeginUndoAction();
this.IsActive = false;
string snippet = snip.RealCode;
// First properly indent the template. We do this by
// getting the indent string of the current line and
// adding it to all newlines
int indentPoint = 0;
string line = Scintilla.Lines.Current.Text;
if (line != string.Empty)
{
while (indentPoint < line.Length)
{
char c = line[indentPoint];
if (c != ' ' && c != '\t')
break;
indentPoint++;
}
}
// Grab the current selected text in case we have a surrounds with scenario.
string selText = Scintilla.Selection.Text;
// Now we clear the selection
if (selText != string.Empty)
Scintilla.Selection.Clear();
if (indentPoint > 0)
{
string indent = line.Substring(0, indentPoint);
// This is a bit of a tough decision, but I think the best way to handle it
// is to assume that the Snippet's Eol Marker matches the Platform DOCUMENT_DEFAULT
// but the target Eol Marker should match the Document's.
snippet = snippet.Replace(Environment.NewLine, Scintilla.EndOfLine.EolString + indent);
// Same deal with the selected text if any
selText = selText.Replace(Environment.NewLine, Scintilla.EndOfLine.EolString + indent);
}
int anchorPos = -1;
int caretPos = -1;
int endPos = -1;
int selPos = -1;
var dropMarkers = new SortedList<int, int>();
var indexedRangesToActivate = new SortedList<int, SnippetLinkRange>();
var unindexedRangesToActivate = new List<SnippetLinkRange>();
Match m = this.snippetRegex1.Match(snippet);
while (m.Success)
{
// Did it match a $DropMarker$ token?
if (m.Groups["dm"].Success)
{
// Yep, was it an indexed or unindexed DropMarker
if (m.Groups["dmi"].Success)
{
// Indexed, set the indexed drop marker's character offset
// if it is specified more than once the last one wins.
dropMarkers[int.Parse(m.Groups["dmi"].Value)] = m.Groups["dm"].Index;
}
else
{
// Unindexed, just tack it on at the _end
dropMarkers[dropMarkers.Count] = m.Groups["dm"].Index;
}
// Take the token out of the string
snippet = snippet.Remove(m.Groups["dm"].Index, m.Groups["dm"].Length);
}
else if (m.Groups["c"].Success)
{
// We matched the $Caret$ Token. Since there can be
// only 1 we set the caretPos. If this is specified
// more than once the last one wins
caretPos = m.Groups["c"].Index;
// Take the token out of the string
snippet = snippet.Remove(m.Groups["c"].Index, m.Groups["c"].Length);
}
else if (m.Groups["a"].Success)
{
// We matched the $Anchor$ Token. Since there can be
// only 1 we set the anchorPos. If this is specified
// more than once the last one wins
anchorPos = m.Groups["a"].Index;
// Take the token out of the string
snippet = snippet.Remove(m.Groups["a"].Index, m.Groups["a"].Length);
}
else if (m.Groups["e"].Success)
{
// We matched the $End$ Token. Since there can be
// only 1 we set the endPos. If this is specified
// more than once the last one wins
endPos = m.Groups["e"].Index;
// Take the token out of the string
snippet = snippet.Remove(m.Groups["e"].Index, m.Groups["e"].Length);
}
else if (m.Groups["s"].Success)
{
// We matched the $Selection$ Token. Simply insert the
// selected text at this position
selPos = m.Groups["s"].Index;
// Take the token out of the string
snippet = snippet.Remove(m.Groups["s"].Index, m.Groups["s"].Length);
snippet = snippet.Insert(m.Groups["s"].Index, selText);
}
else if (m.Groups["l"].Success)
{
// Finally match for Snippet Link Ranges. This is at the bottom of the if/else
// because we want the more specific findRegex groups to match first so that this
// generic expression group doesn't create a SnippetLinkRange for say the
// $Caret$ Token.
Group g = m.Groups["l"];
int rangeIndex;
string groupKey;
if (m.Groups["li"].Success)
{
// We have a subindexed SnippetLinkRange along the lines of $sometoken[1]$
Group sg = m.Groups["li"];
// At this point g.Value = $sometoken[1]$
// and sg.Value = [1].
// We want the range's Key, which would be sometoken
groupKey = g.Value.Substring(1, g.Value.Length - sg.Length - 2);
// Now we need the range's Index which would be 1 in our fictitional case
rangeIndex = int.Parse(sg.Value.Substring(1, sg.Value.Length - 2));
// Now we need to determine the actual _start and _end positions of the range.
// Keep in mind we'll be stripping out the 2 $ and the subindex string (eg [1])
int start = startPos + g.Index;
int end = start + g.Length - sg.Length - 2;
// And now we add (or replace) the snippet link range at the index
// keep in mind duplicates will stomp all over each other, the last
// one wins. Replaced tokens won't get a range
indexedRangesToActivate[rangeIndex] = new SnippetLinkRange(start, end, Scintilla, groupKey);
// And remove all the token info including the subindex from the snippet text
// leaving only the key
snippet = snippet.Remove(g.Index, 1).Remove(g.Index - 2 + g.Length - sg.Length, sg.Length + 1);
}
else
{
// We have a regular old SnippetLinkRange along the lines of $sometoken$
// We want the range's Key, which would be sometoken
groupKey = g.Value.Substring(1, g.Value.Length - 2);
// Now we need to determine the actual _start and _end positions of the range.
// Keep in mind we'll be stripping out the 2 $
int start = startPos + g.Index;
int end = start + g.Length - 2;
// Now create the range object
unindexedRangesToActivate.Add(new SnippetLinkRange(start, end, Scintilla, groupKey));
// And remove all the token info from the snippet text
// leaving only the key
snippet = snippet.Remove(g.Index, 1).Remove(g.Index + g.Length - 2, 1);
}
}
// Any more matches? Note that I'm rerunning the regexp query
// on the snippet string becuase it's contents have been modified
// and we need to get the updated index values.
m = this.snippetRegex1.Match(snippet);
}
// Replace the snippet Keyword with the snippet text. Or if this
// isn't triggered by a shortcut, it will insert at the current
// Caret Position
Scintilla.GetRange(startPos, NativeScintilla.GetCurrentPos()).Text = snippet;
// Now that we have the text set we can activate our link ranges
// we couldn't do it before becuase they were managed ranges and
// would get offset by the text change
// Since we are done adding new SnippetLinkRanges we can tack
// on the unindexed ranges to the _end of the indexed ranges
var allLinks = new SnippetLinkRange[indexedRangesToActivate.Count + unindexedRangesToActivate.Count];
for (int i = 0; i < indexedRangesToActivate.Values.Count; i++)
allLinks[i] = indexedRangesToActivate[i];
for (int i = 0; i < unindexedRangesToActivate.Count; i++)
allLinks[i + indexedRangesToActivate.Count] = unindexedRangesToActivate[i];
foreach (SnippetLinkRange slr in allLinks)
this.addSnippetLink(slr);
foreach (SnippetLinkRange slr in allLinks)
slr.Init();
// Now we need to activate the Snippet links. However we have a bit
// of a styling confilct. If we set the indicator styles before the
// SQL Lexer styles the newly added text it won't get styled. So to
// make sure we set the Indicator Styles after we put the call on
// a timer.
if (this._snippetLinks.Count > 0)
{
var t = new Timer
{
Interval = 10
};
// Oh how I love anonymous delegates, this is starting to remind
// me of JavaScript and SetTimeout...
t.Tick += delegate
{
t.Dispose();
this.IsActive = true;
};
t.Start();
}
// Add all the Drop markers in the indexed order. The
// order is reversed of course because drop markers work
// in a FILO manner
for (int i = dropMarkers.Count - 1; i >= 0; i--)
Scintilla.DropMarkers.Drop(startPos + dropMarkers.Values[i]);
// Place the caret at either the position of the token or
// at the _end of the snippet text.
if (caretPos >= 0)
Scintilla.Caret.Goto(startPos + caretPos);
else
Scintilla.Caret.Goto(startPos + snippet.Length);
// Ahoy, way anchor!
if (anchorPos >= 0)
Scintilla.Caret.Anchor = startPos + anchorPos;
// Do we have an _end cursor?
if (endPos >= 0)
{
// If they have snippet link ranges activated in this snippet
// go ahead and set up an EndPoint marker
if (allLinks.Length > 0)
{
var eci = new SnippetLinkEnd(endPos + startPos, Scintilla);
Scintilla.ManagedRanges.Add(eci);
this._snippetLinks.EndPoint = eci;
}
else
{
// Otherwise we treat it like an Anchor command because
// the SnippetLink mode isn't activated
Scintilla.Caret.Goto(endPos + startPos);
}
}
NativeScintilla.EndUndoAction();
}