I have a richTextBox and a Regex with some words. Once, I find all the words I want to change their color to blue. I can use SelectionColor = Blue, but when it comes to coloring thousands of words it becomes quite slow.
After some search, I read that changing the RTF of the richTextBox is a faster way to change the text (e.g. it's size and/or color).
Here is my unfinished code:
MatchCollection matches = myRegex.Matches(richTextBox.text);
foreach (Match match in matches)
{
richTextBox.Select(match.Index, match.Length);
string addColor = #"{\colortbl ;\red0\green0\blue255;}" + Environment.NewLine;
richTextBox.SelectionColor = Color.Blue; //Must be replaced
}
I also found out that in every case (in my case, the entire text uses the same font and has the same size, only the color of some words changes) the SelectedRtf is:
{\rtf1\ansi\deff0{\fonttbl{\f0\fnil\fcharset0 Consolas;}}
\uc1\pard\lang1033\f0\fs18 word} // richTextBox.SelectedRtf
Moreover, using the Selection.Color = Blue changes the SelectedRtf to:
{\rtf1\ansi\deff0{\fonttbl{\f0\fnil\fcharset0 Consolas;}}
{\colortbl ;\red0\green0\blue255;} // The addColor string!
\uc1\pard\lang1033\f0\fs18 word}
To get the above string, I use this: richTextBox.SelectedRtf.Insert(59, addColor), so what I need to do is to replace SelectedRtf with that. However, after some attempts, nothing seems to happen. The color of the words remains the same. Any ideas?
Yes, it is possible and about twice as fast than the 'regular' way..:
Changing 30k words in a 3M text takes 28 seconds over 60 seconds before..
Here are the steps I would recommend, assuming your words are identifyable in the richTextBox.Rtf (*):
You could create your own color table, but it seems safer to let the system do it for you: I cheat by coloring the 1st letter before and resetting it after coloring the matches..
I pre- and postfix the search word by the rtf code for a foreground color index into the table. My code assumes that there is only one extra color in addition to the default one.
If you have more you should keep track and/ or analyze the colortable..
Here is a RTF reference, btw..
I do the replacement with the RegEx in my RichTextBox RTB like this:
string search "find me!";
RTB.SelectionStart = 0;
RTB.SelectionLength = 1;
RTB.SelectionColor = Color.HotPink;
Regex RX = new Regex(search);
MatchCollection matches = RX.Matches(RTB.Rtf);
RTB.Rtf = RX.Replace(RTB.Rtf, "\\cf1 " + search + "\\cf0 ");
RTB.SelectionStart = 0;
RTB.SelectionLength = 1;
RTB.SelectionColor = RTB.ForeColor;
(*) Note that modifying the Rtf property like this assumes that your search texts are identifiable in the Rtf. You can and should check this by comparing the matches count when searching the Rtf and the Text ! when they don't agree you probably need to use the 'regular' way..
Note that this only deals with Colors. For Font sizes etc you will have to add \fn (which index into the stylesheet) commands in a similar way..
Update: I have wrapped the code above in an expanded function, also taking care of more colors, word boundaries and some checks..:
int colorWords(RichTextBox RTB, String searchWord, Color color)
{
string wordChar = #"\w*"; // or #"\b*" for stricter search
Regex RX = new Regex(wordChar + searchWord + wordChar);
RTB.SelectionStart = 0;
RTB.SelectionLength = 0;
RTB.SelectedText = "~"; // insert a dummy character
RTB.SelectionStart = 0;
RTB.SelectionLength = 1;
RTB.SelectionColor = color; // and color it
MatchCollection matches = null;
matches = RX.Matches(RTB.Text);
int textCount = matches.Count;
matches = RX.Matches(RTB.Rtf);
// we should not find more in the rtf code, less is ok
if (textCount < matches.Count) return -1;
if (matches.Count <= 0) return 0;
List<Color> colors = getRtfColorTable(RTB);
int cIndex = 1;
Color cRGB = Color.FromArgb(255, color);
if (colors.Contains(cRGB) )
cIndex = colors.FindIndex(x => x == cRGB) + 1;
RTB.Rtf = RX.Replace(RTB.Rtf, "\\cf" + cIndex + " " + searchWord + "\\cf0 ");
RTB.SelectionStart = 0;
RTB.SelectionLength = 1;
RTB.Cut(); // remove the dummy
return matches.Count;
}
Here is a function that pulls out the current colors from the Rtf color table. (Hopefully, the full spec is not exactly very small and tackling it with two simple IndexOf is a little optimistic.. ;-)
List<Color> getRtfColorTable(RichTextBox RTB)
{ // \red255\green0\blue0;
List<Color> colors = new List<Color>();
string tabString = #"\colortbl ;";
int ct0 = RTB.Rtf.IndexOf(tabString);
if (ct0 >= 0)
{
ct0 += tabString.Length;
int ct1 = RTB.Rtf.IndexOf(#"}", ct0);
var table = RTB.Rtf.Substring(ct0, ct1 - ct0).Split(';');
foreach(string t in table)
{
var ch = t.Split('\\');
if (ch.Length == 4)
{
int r = Convert.ToInt16(ch[1].Replace("red", ""));
int g = Convert.ToInt16(ch[2].Replace("green", ""));
int b = Convert.ToInt16(ch[3].Replace("blue", ""));
colors.Add(Color.FromArgb(255, r, g, b));
}
}
}
return colors;
}
The example was called like this:
colorWords(RTB, "<DIR>", Color.SaddleBrown);
colorWords(RTB, "Verzeichnis", Color.BlueViolet);
colorWords(RTB, "2012", Color.OrangeRed);
Related
I'm trying to create a colorized RichTextBox based on a pattern.
The text is:
Hey, This{Red} IS A {Cyan}sample. The {Green}color is green color
Each { } contains a color which is a style for next words:
Hey, This IS Asample. The color is green color
Hey, This default color.
IS A should be Red color.
sample. The should be Cyan color.
color is green color should be green.
Here this is my code:
// Hey, This{Red} IS A {Cyan}sample. The {Green}color is green color
// shown text should be:
// Hey, This IS A sample. The color is green
const string OriginalText = "Hey, This{Red} IS A {Cyan}sample. The {Green}color is green color";
const string ShownText = "Hey, This IS A sample. The color is green color";
const string Pattern = "(?<=\\{)(.*?)(?=\\})";
rtbMain.Text = ShownText;
rtbMain.SelectAll();
rtbMain.SelectionColor = Color.Black;
rtbMain.SelectionBackColor = Color.White;
Regex regex = new(Pattern, RegexOptions.IgnoreCase);
MatchCollection matches = regex.Matches(OriginalText);
if (matches.Count > 0)
{
var rtbText = rtbMain.Text;
var length = ShownText.Length;
var allMatches = new List<Match>();
for (int i = 0; i < matches.Count; i++)
{
var m = matches[i];
allMatches.Add(m);
Match nextMatch = null;
if (matches.Count > i + 1)
{
nextMatch = matches[i + 1];
}
var sum = GetSum();
var start = m.Index;
var currentLength = m.Length;
if (nextMatch != null)
{
var end = nextMatch.Index - start- sum;
rtbMain.Select(start- 1, end);
}
else
{
var currentIndex = OriginalText.IndexOf(m.Value);
rtbMain.Select(length - currentIndex, (length - currentIndex) - sum);
}
rtbMain.SelectionColor = GetColor(m.Value);
}
int GetSum()
{
return allMatches!.Select(m => m.Value.Length - 1).Sum();
}
Color GetColor(string color)
{
return Color.FromName(color);
}
}
else
{
Debug.WriteLine("No matches found");
}
Since RichTextBox doesn't have the color tags, I don't know how to calculate the correct position of index/length.
Screen shot:
You could also match the end position of the string you're parsing,
then, when looping the Matches collection, you just need to calculate the current position inside the string, considering the length of each match.
With a slightly modified regex, the Index and Length of each Match refer to a matched tag (e.g., {green}), and each value in Group 1 is the name of a Color.
Something like this:
(note that just SelectionColor is used here, since I'm appending a new string to the Control on each iteration. The new string added is actually already a Selection, so there's no need to set the selection's length explicitly)
string originalText =
"Hey, This{Red} IS A {Cyan}sample. The {Green}color is green color\n" +
"plus other text {blue} and some more {orange}colors";
string pattern = #"\{(.*?)\}|$";
var matches = Regex.Matches(originalText, pattern, RegexOptions.IgnoreCase);
int currentPos = 0;
foreach (Match m in matches) {
someRichTextBox.AppendText(originalText.Substring(currentPos, m.Index - currentPos));
currentPos = m.Index + m.Length;
someRichTextBox.SelectionColor = Color.FromName(m.Groups[1].Value);
};
someRichTextBox.SelectionColor = someRichTextBox.ForeColor;
Resulting in:
If the searched word is at the beginning or end of the line of, I want to color it. If it's in the middle of the line not colorful. I tried many thing but it's not working right.
Seems like beginning of the line is working. But end of the first line only can color. I want to color for all lines end. I think i need index of each line's beggining and loop but i can't do it.
How can i fix it?
private void button1_Click(object sender, EventArgs e)
{
int wordLength = textBox1.Text.Length;
string word = textBox1.Text;
for (int i = 0; i < richTextBox1.Lines.Count(); i++)
{
int startIndex = richTextBox1.GetFirstCharIndexFromLine(i);
richTextBox1.Find(word, startIndex, startIndex+wordLength, RichTextBoxFinds.None);
richTextBox1.SelectionColor = Color.Red;
richTextBox1.SelectionBackColor = Color.Yellow;
int newLineIndex = richTextBox1.Lines[i].Length;
richTextBox1.Find(textBox1.Text, (newLineIndex - wordLength), newLineIndex, RichTextBoxFinds.None);
richTextBox1.SelectionColor = Color.Red;
richTextBox1.SelectionBackColor = Color.Yellow;
}
Try:
int newLineIndex = i + 1 < richTextBox1.Lines.Length ? richTextBox1.GetFirstCharIndexFromLine(i + 1) - 1 : richTextBox1.TextLength;
I suggest to change your code a little bit. You'll notice why when the RichTextBox text length grows.
Asking for the Lines[] content is not exactly a good thing, much worse in a loop, when you access this Property probably many of times.
You can see in the .Net Source Code what happens (each time - the Lines Property values are not cached and cannot be).
GetLineFromCharIndex() and GetFirstCharIndexFromLine() use instead SendMessage to send the EM_LINEFROMCHAR and EM_LINEINDEX messages to the Edit control - which uses cached values - and are pretty fast.
Use Regex.Matches() to collect the indexes of the matched word(s) (you can use more than one word, separated by a pipe: "|", but here we handle just one word. When matching more than one word, use a List<Match> and the Match.Length instead of searchWord.Length) and extract just the Index position of each match.
Then, loop the indexes and check whether the current index position meets the criteria.
The current line end is found with IndexOf("\n", [StartPosition]), using the first line index (which is also used for the selection) as the starting position.
The RichTextBox Control uses only \n as line separator, so we don't need to worry about \r.
string searchWord = "John";
var txt = richTextBox1.Text;
int textLenght = txt.Length;
// the indexes list can be created with the alternative method (using IndexOf() in a loop)
var indexes = Regex.Matches(txt, searchWord, RegexOptions.Multiline)
.OfType<Match>()
.Select(m => m.Index).ToList();
foreach (int index in indexes) {
int currentLine = richTextBox1.GetLineFromCharIndex(index);
int lineFirstIndex = richTextBox1.GetFirstCharIndexFromLine(currentLine);
int lineLastIndex = txt.IndexOf("\n", lineFirstIndex);
if (index == lineFirstIndex ||
index == lineLastIndex - searchWord.Length ||
index == textLenght - searchWord.Length) {
richTextBox1.Select(index, searchWord.Length);
richTextBox1.SelectionColor = Color.Red;
}
}
Edit: Since Regex.Matches is not allowed, you can use IndexOf() in a loop:
var indexes = new List<int>();
int wordPosition = -1;
do {
if ((wordPosition = txt.IndexOf(searchWord, wordPosition + 1)) >= 0) {
indexes.Add(wordPosition);
}
} while (wordPosition >= 0);
I have a RichTextBox for a simple chat where I add lines programmatically.
I make the usernames bold and the messages in regular style.
After some lines I want to delete the first lines to keep the chat in a acceptably length. But when I do so I lose the text format and everything appears bold. What am I doing wrong and how can I fix this?
EDIT
I could solve the problem where I wasn't able to delete the first line.
I had to set the the ReadOnly property to false. Even though I was able to add new lines it prevented deleting lines. So the following code works to delete lines. Thanks to #TaW!
if (ChatText.Lines.Length >= 10)
{
int p = 0; int count = 0;
do
{
p = ChatText.Text.IndexOf("\n\r");
if (p >= 0)
{
ChatText.SelectionStart = p;
ChatText.SelectionLength = 2; // length of "\n\r"
ChatText.SelectedText = "\n";
count++;
}
}
while(p >= 0);
int nll = 1; // <<=== pick the length of your new line character(s)!!
int pS = ChatText.Lines.Take(0).Select(x => x.Length + nll).Sum() - nll;
int pL = ChatText.Lines.Take(1).Select(x => x.Length + nll).Sum() - nll;
if (pS < 0) { pS = 0; pL++; }
ChatText.SelectionStart = pS;
ChatText.SelectionLength = pL - pS;
ChatText.Cut();
}
//////////////////////////////////
// now add new lines
//////////////////////////////////
string[] chatstr;
// string text is given as method parameter
chatstr = text.Split(new string[] { ": " }, 2, StringSplitOptions.None);
// go to the end of the text
ChatText.SelectionStart = ChatText.Text.Length;
ChatText.SelectionLength = 0;
// make text bold
ChatText.SelectionFont = new Font(ChatText.Font, FontStyle.Bold);
// add username (chatstr[0]) and colon
ChatText.AppendText(chatstr[0] + ": ");
// make text regular
ChatText.SelectionFont = new Font(ChatText.Font, FontStyle.Regular);
// add message (chatstr[1])
ChatText.AppendText(chatstr[1] + "\n");
// and finaly scroll down
ChatText.ScrollToCaret();
So deleting lines works and new lines are added as intended. Finaly!
solved :)
Never change the Text of a RichtTextBox if it contains any formatting.
Changing the Lines property (by Skip) is just another way to change the Text.
Instead only use the functions the RTB provides: Always start by selecting the portion you want to format, then apply one or more of the functions and/or set one or more of the properties..:
To delete portions use Cut.
Here is a function that will delete a number of entire lines:
void DeleteLines(RichTextBox rtb, int fromLine, int count)
{
int p1 = rtb.GetFirstCharIndexFromLine(fromLine);
int p2 = rtb.GetFirstCharIndexFromLine(fromLine + count);
rtb.SelectionStart = p1;
rtb.SelectionLength = p2 - p1;
bool readOnly = rtb.ReadOnly; // allow change even when the RTB is readonly
rtb.ReadOnly = false; ;
rtb.Cut();
rtb.ReadOnly = readOnly;
}
Trying to keept the formatting alive yourself is a tedious and error-prone waste of your time.
In addition to font properties you would also have to resore all other things you can set with the SelectedXXX properties, like colors, alignment, spacing etc etc..
To delete the first 3 lines use:
DeleteLines(yourRTB, 0, 3);
To restrict the text to 10 lines use:
DeleteLines(yourRTB, 0, yourRTB.Lines.Length - 10);
Note that the function above should have a few checks for valid input; I left them out as the checks somehow need a decision what to do, if count or fromLine if greater than Lines.Length or if fromLine is negative..
While we are at it, here is how to append a bold line:
yourRTB.SelectionStart = yourRTB.Text.Length;
yourRTB.SelectionLength = 0;
using (Font font = new Font(yourRTB.SelectionFont, FontStyle.Bold))
yourRTB.SelectionFont = font;
yourRTB.AppendText(yourNewLine + textOfNewLine);
Of course it really shold go into a reuseable function that the the bolding as a parameter..
Update:
since you are using WordWrap you may prefer this function. It deletes the actual lines, not the visible ones:
void DeleteLinesWW(RichTextBox rtb, int fromLine, int count)
{
int nll = 1; // <<=== pick the length of your new line character(s)!!
int pS = rtb.Lines.Take(fromLine).Select(x => x.Length + nll).Sum() - nll;
int pL = rtb.Lines.Take(fromLine + count).Select(x => x.Length + nll).Sum() - nll;
if (pS < 0) { pS = 0; pL++; }
rtb.SelectionStart = pS;
rtb.SelectionLength = pL - pS ;
bool readOnly = rtb.ReadOnly;
rtb.ReadOnly = false; // allow change even when the RTB is readonly
rtb.Cut();
rtb.ReadOnly = readOnly;
}
A word on NewLine: Do note that I have not used the Environment.NewLine constant as it not really a good idea. If you add multiline text to the RichTextBox in the designer and then look at it you will see that it uses simple '\n' new lines, no returns, no NL-CR, just '\n'. So this seems to be the generic way in a winforms RTB and I recommend using it..
The new function relies on all lines having a newline of the same length!
To make sure you can use this replacement function:
int RTBReplace(RichTextBox rtb, string oldText, string newText)
{
int p = 0; int count = 0;
do
{
p = richTextBox1.Text.IndexOf(oldText);
if (p >= 0)
{
richTextBox1.SelectionStart = p;
richTextBox1.SelectionLength = oldText.Length;
richTextBox1.SelectedText = newText;
count ++;
}
}
while (p >= 0);
return count;
}
Calling it like this:
RTBReplace(yourrichTextBox, "\r\n", "\n");
Update 2:
Here is an example how to add your chat lines:
private void button1_Click(object sender, EventArgs e)
{
string cLine = "Taw: Hello World"; // use your own lines!
var chatstr = cLine.Split(new string[] { ": " }, 2, StringSplitOptions.None);
AppendLineBold(yourrichTextBox, "\n" + chatstr[0], true);
AppendLineBold(yourrichTextBox, chatstr[1], false);
yourrichTextBox.ScrollToCaret();
}
void AppendLineBold(RichTextBox rtb, string text, bool bold)
{
rtb.SelectionStart = richTextBox1.Text.Length;
rtb.SelectionLength = 0;
using (Font font = new Font(rtb.SelectionFont,
bold ? FontStyle.Bold : FontStyle.Regular))
rtb.SelectionFont = font;
rtb.AppendText(text);
}
Update 3:
Looks like the ReadOnly property disallows the use of Cut. So we need to temporatily allow changes.
Funny: SelectedText can't be set either, but AppendText works fine..
To keep text formatting, you can also try the following (it's a little shorter and should also do the trick)
string text = "Username: hello this is a chat message";
// delete the first line when after 10 lines
if (ChatText.Lines.Length >= 10)
{
ChatText.SelectionStart = 0; // set SelectionStart to the beginning of chat text (RichTextBox)
ChatText.SelectionLength = ChatText.Text.IndexOf("\n", 0) + 1; // select the first line
ChatText.SelectedText = ""; // replace by an empty string
ChatText.SelectionStart = ChatText.Text.Length; // set SelectionStart to text end to make SelectionFont work for appended text
}
// split the string in chatstr[0] = username, chatstr[1] = message
string[] chatstr = text.Split(new string[] { ": " }, 2, StringSplitOptions.None);
// make the username bold
ChatText.SelectionFont = new Font(ChatText.Font, FontStyle.Bold);
ChatText.AppendText(chatstr[0] + ": ");
// make the message regular
ChatText.SelectionFont = new Font(ChatText.Font, FontStyle.Regular);
ChatText.AppendText(chatstr[1] + Environment.NewLine);
ChatText.ScrollToCaret();
Iam working on a project in Unity3d, using C# scripting, with strings that are written in Hebrew and English.
The problem with Unity3d is that RTL languages (like Hebrew) are not supported when writing UI texts.
The problem looks like this:
original text:
מערכת העטלף הינה מערכת, אשר מתבססת על טכנולוגית ה- GPR
(Ground Penetrating Radar) - ראדאר חודר קרקע.
in unity:
GPR -ה תיגולונכט לע תססבתמ רשא ,תכרעמ הניה ףלטעה תכרעמ
.עקרק רדוח ראדאר - (Ground Penetrating Radar)
Iam trying to hack the order that the text is printed to the text boxes for a month now and I cant get it just right.
I made up the following function:
public string CorrectText (string text)
{
string result = "";
string temp = "";
string charListString = "";
List<Char> charList = new List<char> ();
char[] charArray = text.ToCharArray ();
Array.Reverse (charArray);
//go through each char in charArray
for (int x = 0; x <= charArray.Length -1; x++) {
//if the current char we're examing isn't a Space char -
if (!char.IsWhiteSpace (charArray [x])) {
//add it to charList
charList.Add (charArray [x]);
} else { //if the current char we're examing is a Space char -
charListString = new string (charList.ToArray ());
//if charListString doesn't contains English or Numeric chars -
if (!Regex.IsMatch (charListString, "^[0-9a-zA-Z.,()-]*$")) {
//go through each char in charList
for (int y = 0; y <= charList.Count - 1; y++) {
//add the current char to temp as is (not flipped)
temp += charList [y];
}
//add temp to result
if (x < charArray.Length - 1)
result += temp + " ";
if (x == charArray.Length - 1)
result += temp;
//clear charList and temp
charList.Clear ();
temp = "";
} else { //if temp contains English or Numeric chars -
//go through each char in charList - This flipps the order of letters
for (int y = charList.Count - 1; y >= 0; y--) {
//add the current char to temp
temp += charList [y];
}
//add temp to result
if (x < charArray.Length - 1)
result += temp + " ";
if (x == charArray.Length - 1)
result += temp;
//clear charList and temp
charList.Clear ();
temp = "";
}
}
}
return result;
}
this almost fixed the problem, but the text is written from bottom up for example:
input:
מערכת העטלף הינה מערכת, אשר מתבססת על טכנולוגית ה- GPR
(Ground Penetrating Radar) - ראדאר חודר קרקע.
output:
(Ground Penetrating Radar) - ראדאר חודר קרקע.
מערכת העטלף הינה מערכת, אשר מתבססת על טכנולוגית ה- GPR
and sometimes the parenthesis are getting mixed up in the sentence, showing like this:
)Ground Penetrating( Radar - ראדאר חודר קרקע.
I've found a tool online that turns Visual Hebrew to Logic Hebrew, and when I copied the flipped text to unity it worked like magic!
But I cant find any useful information online on how to make my own script with C# that does the same thing.
I managed to overcome this... sort of:
Assign the string into the text element.
Force the canvas to update using Canvas.ForceUpdateCanvases(), or another means.
Get the cached text generator (NOT for layout) of the text component. It will lie to you if you didn't force an update or yielded and waited for the next frame; so don't skip step 2.
Use getLines of the cached text generator to get the information of the text wrapping; it holds the index where every line starts.
Take the maximum difference in the values of those and you have the likely amount of characters per row before it wraps.
Insert '\n' (new line) into the original string manually using the row length you found in the previous step before flipping the characters!
Do the reversal process on the wrapped string. If you do these things in a different order you will end up with the first line being the shortest, instead of the last. So wrap first, reverse second.
Set the string to the text component and it should work. Usually.
I have a TextBox txtEditor. I want to find the nearest line breaks and select it
Example 1: With "no selection"
suppose the selection/cursor is *
this is the 1st line of text
this is *the 2nd line of text
this is the 3rd line of text
I want to extend the selection such that the selection is now
this is the 1st line of text
*this is the 2nd line of text*
this is the 3rd line of text
Example 2: With selection
this is the 1st line of text
this is *the 2nd line* of text
this is the 3rd line of text
I want to extend the selection such that the selection is now
this is the 1st line of text
*this is the 2nd line of text*
this is the 3rd line of text
Update: Possible solution
I found a possible solution, wonder if anyone got a better solution?
string tmp = txtEditor.Text;
int selStart = txtEditor.SelectionStart;
int selLength = txtEditor.SelectionLength;
int newSelStart;
int newSelLength;
string tmp1 = tmp.Substring(0, selStart);
if (tmp1.LastIndexOf(Environment.NewLine) < 0)
{
newSelStart = 0;
}
else
{
newSelStart = tmp1.LastIndexOf(Environment.NewLine) + Environment.NewLine.Length;
}
tmp1 = tmp.Substring(selStart);
if (tmp1.IndexOf(Environment.NewLine) < 0)
{
newSelLength = tmp.Length;
}
else
{
newSelLength = tmp1.IndexOf(Environment.NewLine) + selStart - newSelStart;
}
txtEditor.SelectionStart = newSelStart;
txtEditor.SelectionLength = newSelLength;
Well, mostly the problem is that your code is somwhat bloated (and thus harder to read) and a lot less efficient than it needs to be. Performance probably doesn't really matter in your case (those duplicate calls to IndexOf and LastIndexOf rub me the wrong way), but personally I'd rewrite your code like this:
string tmp = txtEditor.Text;
int selStart = txtEditor.SelectionStart;
int selLength = txtEditor.SelectionLength;
int newSelStart = tmp.LastIndexOf(Environment.NewLine, selStart);
if (newSelStart == -1)
newSelStart = 0;
int newSelEnd = tmp.IndexOf(Environment.NewLine, selStart);
if (newSelEnd == -1)
newSelEnd = tmp.Length;
txtEditor.SelectionStart = newSelStart;
txtEditor.SelectionLength = newSelEnd - newSelStart;