public void InsertCharacters(int index, string chars, TerminalFont font) { lock (this) { if (chars.Length == 0) { return; } if (length < index) { extend(index); } int runIndex = 0; for (int i = 0; i < runs.Count; ++i) { var run = runs[i]; if (runIndex + run.Text.Length >= index) { if (run.Font == font) { run.Text = run.Text.Insert(index - runIndex, chars); } else { var newRun = new TerminalRun() { Text = chars, Font = font }; var splitRun = new TerminalRun() { Text = run.Text.Substring(index - runIndex), Font = run.Font }; run.Text = run.Text.Substring(0, index - runIndex); if (run.Text.Length == 0) { run.Text = newRun.Text; run.Font = newRun.Font; if (splitRun.Text.Length > 0) { runs.Insert(i + 1, splitRun); } } else if (splitRun.Text.Length > 0) { runs.InsertRange(i + 1, new[] { newRun, splitRun }); } else { runs.Insert(i + 1, newRun); } } break; } runIndex += run.Text.Length; } savedRuns = null; } if (RunsChanged != null) { RunsChanged(this, EventArgs.Empty); } }
/// <summary> /// Replaces the characters on this line in the range [<paramref name="index"/>, /// <paramref name="index"/> + <paramref name="chars"/>.Length) with those in /// <paramref name="chars"/>. /// /// If the run at (<paramref name="index"/> - 1) or (<paramref name="index"/> + /// <paramref name="chars"/>.Length) has the same font as <paramref name="font"/>, /// it will be extended to include the new characters. Otherwise, a new run will /// be created. /// </summary> /// <param name="index"></param> /// <param name="chars"></param> /// <param name="font"></param> public void SetCharacters(int index, string chars, TerminalFont font) { lock (this) { if (chars.Length == 0) { return; } if (length < index + chars.Length) { extend(index + chars.Length); } int totalIndex = 0; for (int i = 0; i < runs.Count; ++i) { var run = runs[i]; // Completely replacing an existing run if (index == totalIndex && chars.Length == run.Text.Length) { run.Text = chars; run.Font = font; break; } // Inside an existing run else if (index >= totalIndex && index < totalIndex + run.Text.Length) { int newLength = Math.Min(chars.Length, totalIndex + run.Text.Length - index); var newRun = new TerminalRun() { Text = chars.Substring(0, newLength), Font = font }; var splitRun = new TerminalRun() { Text = run.Text.Substring(index - totalIndex + newLength), Font = run.Font }; run.Text = run.Text.Substring(0, index - totalIndex); if (run.Text.Length == 0) { run.Text = newRun.Text; run.Font = newRun.Font; if (splitRun.Text.Length > 0) { runs.Insert(i + 1, splitRun); } } else if (splitRun.Text.Length > 0) { runs.InsertRange(i + 1, new[] { newRun, splitRun }); } else { runs.Insert(i + 1, newRun); } if (newLength != chars.Length) { SetCharacters(index + newLength, chars.Substring(newLength), font); } break; } totalIndex += run.Text.Length; } Color black = Color.FromRgb(0, 0, 0); for (int i = 0; i < runs.Count - 1; ++i) { var run1 = runs[i]; var run2 = runs[i + 1]; bool specialMerge = run1.Font.Background == run2.Font.Background && run1.Font.Inverse == run2.Font.Inverse && (run2.Font.Hidden || run2.Text == " "); if (run1.Font == run2.Font || specialMerge) { if (specialMerge) { run1.Text += new string(' ', run2.Text.Length); } else { run1.Text += run2.Text; } runs.RemoveAt(i + 1); i--; } } savedRuns = null; } if (RunsChanged != null) { RunsChanged(this, EventArgs.Empty); } }
private void redraw(TerminalRun[] runs) { Dispatcher.VerifyAccess(); //drawnRuns = new List<VisualRun>(runs.Length); //for (int i = 0; i < runs.Length; ++i) //{ // drawnRuns.Add(new VisualRun() { Run = runs[i], Text = null }); //} bool changed = false; for (int i = 0; i < Math.Min(drawnRuns.Count, runs.Length); ++i) { if (drawnRuns[i].Run.Text != runs[i].Text || drawnRuns[i].Run.Font != runs[i].Font) { drawnRuns[i] = new VisualRun() { Run = runs[i], Text = null }; changed = true; } } if (drawnRuns.Count > runs.Length) drawnRuns.RemoveRange(runs.Length, drawnRuns.Count - runs.Length); else if (drawnRuns.Count < runs.Length) drawnRuns.AddRange(runs.Skip(drawnRuns.Count).Select(x => new VisualRun() { Run = x })); else if (!changed && !selectionChanged) return; selectionChanged = false; for (int i = 0; i < drawnRuns.Count; ++i) { var run = drawnRuns[i].Run; if (drawnRuns[i].Text != null) continue; SolidColorBrush foreground = Terminal.GetFontForegroundBrush(run.Font); // Format the text for this run. However, this is drawn NEXT run. The // background is drawn one run ahead of the text so the text doesn't get // clipped. var ft = new FormattedText( run.Text, System.Globalization.CultureInfo.CurrentUICulture, Terminal.FlowDirection, Terminal.GetFontTypeface(run.Font), Terminal.FontSize, foreground, new NumberSubstitution(), TextFormattingMode.Ideal ); if (run.Font.Underline || run.Font.Strike) { var textDecorations = new TextDecorationCollection(2); if (run.Font.Underline) textDecorations.Add(TextDecorations.Underline); if (run.Font.Strike) textDecorations.Add(TextDecorations.Strikethrough); ft.SetTextDecorations(textDecorations); } drawnRuns[i] = new VisualRun() { Run = drawnRuns[i].Run, Text = ft }; } var context = RenderOpen(); try { var drawPoint = new System.Windows.Point(0, 0); int index = 0; foreach (var run in drawnRuns) { if (run.Run.Font.Hidden && index == drawnRuns.Count - 1) break; SolidColorBrush background = Terminal.GetFontBackgroundBrush(run.Run.Font); // Draw the background and border for the current run Pen border = null; if (Terminal.DrawRunBoxes) border = new Pen(DebugColors.GetBrush(index), 1); var backgroundTopLeft = new System.Windows.Point(Math.Floor(drawPoint.X), Math.Floor(drawPoint.Y)); var backgroundSize = new Vector(Math.Floor(run.Text.WidthIncludingTrailingWhitespace), Math.Ceiling(run.Text.Height)); context.DrawRectangle(background, border, new Rect(backgroundTopLeft, backgroundSize)); drawPoint.X += run.Text.WidthIncludingTrailingWhitespace; index++; } drawPoint = new System.Windows.Point(0, 0); index = 0; foreach (var run in drawnRuns) { if (run.Run.Font.Hidden && index == drawnRuns.Count - 1) break; context.DrawText(run.Text, drawPoint); drawPoint.X += run.Text.WidthIncludingTrailingWhitespace; index++; } // TODO: This selection drawing logic doesn't account for multi-width characters. if (SelectionStart != SelectionEnd) { var selectRect = new Rect( new System.Windows.Point(Math.Floor(Math.Min(drawPoint.X, Terminal.CharWidth * SelectionStart)), 0.0), new System.Windows.Point(Math.Ceiling(Math.Min(drawPoint.X, Terminal.CharWidth * SelectionEnd)), Math.Ceiling(Terminal.CharHeight))); var brush = new SolidColorBrush(System.Windows.Media.Color.FromArgb(128, 90, 180, 230)); context.DrawRectangle(brush, null, selectRect); } } finally { context.Close(); } }
TerminalRun[] CopyLine(TerminalLine line) { var runs = line.Runs; var result = new TerminalRun[runs.Count]; for (int i = 0; i < runs.Count; ++i) { var run = runs[i]; result[i] = new TerminalRun(run.Text, run.Font); } return result; }
void AssertAreEqual(TerminalRun run1, TerminalRun run2) { Assert.AreEqual(run1.Text, run2.Text); Assert.AreEqual(run1.Font, run2.Font); }
/// <summary> /// Replaces the characters on this line in the range [<paramref name="index"/>, /// <paramref name="index"/> + <paramref name="chars"/>.Length) with those in /// <paramref name="chars"/>. /// /// If the run at (<paramref name="index"/> - 1) or (<paramref name="index"/> + /// <paramref name="chars"/>.Length) has the same font as <paramref name="font"/>, /// it will be extended to include the new characters. Otherwise, a new run will /// be created. /// </summary> /// <param name="index"></param> /// <param name="chars"></param> /// <param name="font"></param> public void SetCharacters(int index, string chars, TerminalFont font) { lock (this) { if (chars.Length == 0) return; if (length < index + chars.Length) extend(index + chars.Length); int totalIndex = 0; for (int i = 0; i < runs.Count; ++i) { var run = runs[i]; // Completely replacing an existing run if (index == totalIndex && chars.Length == run.Text.Length) { run.Text = chars; run.Font = font; break; } // Inside an existing run else if (index >= totalIndex && index < totalIndex + run.Text.Length) { int newLength = Math.Min(chars.Length, totalIndex + run.Text.Length - index); var newRun = new TerminalRun() { Text = chars.Substring(0, newLength), Font = font }; var splitRun = new TerminalRun() { Text = run.Text.Substring(index - totalIndex + newLength), Font = run.Font }; run.Text = run.Text.Substring(0, index - totalIndex); if (run.Text.Length == 0) { run.Text = newRun.Text; run.Font = newRun.Font; if (splitRun.Text.Length > 0) runs.Insert(i + 1, splitRun); } else if (splitRun.Text.Length > 0) runs.InsertRange(i + 1, new[] { newRun, splitRun }); else runs.Insert(i + 1, newRun); if (newLength != chars.Length) { SetCharacters(index + newLength, chars.Substring(newLength), font); } break; } totalIndex += run.Text.Length; } Color black = Color.FromRgb(0, 0, 0); for (int i = 0; i < runs.Count - 1; ++i) { var run1 = runs[i]; var run2 = runs[i + 1]; bool specialMerge = run1.Font.Background == run2.Font.Background && run1.Font.Inverse == run2.Font.Inverse && (run2.Font.Hidden || run2.Text == " "); if (run1.Font == run2.Font || specialMerge) { if (specialMerge) run1.Text += new string(' ', run2.Text.Length); else run1.Text += run2.Text; runs.RemoveAt(i + 1); i--; } } savedRuns = null; } if (RunsChanged != null) RunsChanged(this, EventArgs.Empty); }
public void InsertCharacters(int index, string chars, TerminalFont font) { lock (this) { if (chars.Length == 0) return; if (length < index) extend(index); int runIndex = 0; for (int i = 0; i < runs.Count; ++i) { var run = runs[i]; if (runIndex + run.Text.Length >= index) { if (run.Font == font) { run.Text = run.Text.Insert(index - runIndex, chars); } else { var newRun = new TerminalRun() { Text = chars, Font = font }; var splitRun = new TerminalRun() { Text = run.Text.Substring(index - runIndex), Font = run.Font }; run.Text = run.Text.Substring(0, index - runIndex); if (run.Text.Length == 0) { run.Text = newRun.Text; run.Font = newRun.Font; if (splitRun.Text.Length > 0) runs.Insert(i + 1, splitRun); } else if (splitRun.Text.Length > 0) runs.InsertRange(i + 1, new[] { newRun, splitRun }); else runs.Insert(i + 1, newRun); } break; } runIndex += run.Text.Length; } savedRuns = null; } if (RunsChanged != null) RunsChanged(this, EventArgs.Empty); }