Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion EditorContext/LanguageEnum.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ public enum LanguageEnum
Powershell,
JSON,
YAML,
XML
XML,
Markdown
}
}
277 changes: 277 additions & 0 deletions EditorContext/MarkdownEditorContext.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,277 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using Terminal.Gui;

namespace psedit
{
public class MarkdownEditorContext : EditorContext
{
private List<ParseResult> parsedTokens;

public MarkdownEditorContext(int TabWidth)
{
_tabWidth = TabWidth;
CanSyntaxHighlight = true;
}
public List<ParseResult> ParseMarkdownToken(string text, List<List<Rune>> Runes)
{
List<ParseResult> returnValue = new List<ParseResult>();
var theme = ThemeService.Instance;

try
{
var lines = text.Split('\n');
bool inCodeBlock = false;

for (int lineIndex = 0; lineIndex < lines.Length; lineIndex++)
{
var line = lines[lineIndex];
int lineNumber = lineIndex + 1;

// Check for code block delimiters (```)
if (Regex.IsMatch(line, @"^\s*```"))
{
inCodeBlock = !inCodeBlock;
returnValue.Add(new ParseResult
{
LineNumber = lineNumber,
StartIndex = 1,
EndIndex = line.Length + 1,
Color = theme.GetColor("Accent")
});
continue;
}

// If we're inside a code block, highlight the entire line
if (inCodeBlock)
{
returnValue.Add(new ParseResult
{
LineNumber = lineNumber,
StartIndex = 1,
EndIndex = line.Length + 1,
Color = theme.GetColor("Accent")
});
continue;
}

// Headers (# ## ### #### ##### ######)
var headerMatch = Regex.Match(line, @"^(#{1,6})\s+(.*)$");
if (headerMatch.Success)
{
returnValue.Add(new ParseResult
{
LineNumber = lineNumber,
StartIndex = 1,
EndIndex = line.Length + 1,
Color = theme.GetColor("Accent")
});
continue;
}

// Blockquotes (> at start) - color only the marker, then continue parsing
var blockquoteMatch = Regex.Match(line, @"^(>\s*)");
if (blockquoteMatch.Success)
{
returnValue.Add(new ParseResult
{
LineNumber = lineNumber,
StartIndex = 1,
EndIndex = blockquoteMatch.Length + 1,
Color = theme.GetColor("Warning")
});
// Don't continue - let other patterns color the rest of the line
}

// Horizontal rules (--- or *** or ___)
if (Regex.IsMatch(line.Trim(), @"^(\*{3,}|-{3,}|_{3,})$"))
{
returnValue.Add(new ParseResult
{
LineNumber = lineNumber,
StartIndex = 1,
EndIndex = line.Length + 1,
Color = theme.GetColor("Accent")
});
continue;
}

// Lists (- or * or + at start, or numbered lists)
var listMatch = Regex.Match(line, @"^\s*([*\-+]|\d+\.)\s+");
if (listMatch.Success)
{
returnValue.Add(new ParseResult
{
LineNumber = lineNumber,
StartIndex = listMatch.Index + 1,
EndIndex = listMatch.Index + listMatch.Length + 1,
Color = theme.GetColor("Warning")
});
}

// Bold (**text** or __text**)
foreach (Match match in Regex.Matches(line, @"(\*\*|__)(.+?)\1"))
{
returnValue.Add(new ParseResult
{
LineNumber = lineNumber,
StartIndex = match.Index + 1,
EndIndex = match.Index + match.Length + 1,
Color = theme.GetColor("Info")
});
}

// Italic (*text* or _text_)
foreach (Match match in Regex.Matches(line, @"(?<!\*)\*(?!\*)([^*]+)\*(?!\*)|(?<!_)_(?!_)([^_]+)_(?!_)"))
{
returnValue.Add(new ParseResult
{
LineNumber = lineNumber,
StartIndex = match.Index + 1,
EndIndex = match.Index + match.Length + 1,
Color = theme.GetColor("Info")
});
}

// Inline code (`text`)
foreach (Match match in Regex.Matches(line, @"`([^`]+)`"))
{
returnValue.Add(new ParseResult
{
LineNumber = lineNumber,
StartIndex = match.Index + 1,
EndIndex = match.Index + match.Length + 1,
Color = theme.GetColor("Accent")
});
}

// Links ([text](url))
foreach (Match match in Regex.Matches(line, @"\[([^\]]+)\]\(([^\)]+)\)"))
{
// Color the link text [text]
var textGroup = match.Groups[1];
returnValue.Add(new ParseResult
{
LineNumber = lineNumber,
StartIndex = match.Index + 1, // Start of [
EndIndex = textGroup.Index + textGroup.Length + 1 + 1, // End after ]
Color = theme.GetColor("Info")
});

// Color the URL (url)
var urlGroup = match.Groups[2];
returnValue.Add(new ParseResult
{
LineNumber = lineNumber,
StartIndex = urlGroup.Index - 1 + 1, // Start of (
EndIndex = match.Index + match.Length + 1, // End after )
Color = theme.GetColor("Accent")
});
}
}
}
catch (Exception)
{
// If parsing fails, return empty list (no syntax highlighting)
return new List<ParseResult>();
}

return returnValue;
}

public override void ParseText(int height, int topRow, int left, int right, string text, List<List<Rune>> Runes)
{
// Quick exit when text is the same and the top row / right col has not changed
if (_originalText == text && topRow == _lastParseTopRow && right == _lastParseRightColumn)
{
return;
}

if (_originalText != text)
{
// Clear errors before parsing new ones
Errors.Clear();
ColumnErrors.Clear();

parsedTokens = ParseMarkdownToken(text, Runes);
}

Dictionary<Point, Terminal.Gui.Color> returnDict = new Dictionary<Point, Terminal.Gui.Color>();
int bottom = topRow + height;
_originalText = text;
_lastParseTopRow = topRow;
_lastParseRightColumn = right;
var row = 0;

for (int idxRow = topRow; idxRow < Runes.Count; idxRow++)
{
if (row > bottom)
{
break;
}

var line = EditorExtensions.GetLine(Runes, idxRow);
int lineRuneCount = line.Count;
var col = 0;
var tokenCol = 1 + left;
var rowTokens = parsedTokens.Where(m => m.LineNumber == idxRow + 1);

for (int idxCol = left; idxCol < lineRuneCount; idxCol++)
{
var rune = idxCol >= lineRuneCount ? ' ' : line[idxCol];
var cols = Rune.ColumnWidth(rune);

// Get token, note that we must provide +1 for the end column, as Start will be 1 and End will be 2 for the example: A
var colToken = rowTokens.FirstOrDefault(e =>
(e.StartIndex == null && e.EndIndex == null) ||
(e.StartIndex == null && tokenCol + 1 <= e.EndIndex) ||
(tokenCol >= e.StartIndex && e.EndIndex == null) ||
(tokenCol >= e.StartIndex && tokenCol + 1 <= e.EndIndex));

if (rune == '\t')
{
cols += _tabWidth + 1;
if (col + cols > right)
{
cols = right - col;
}
for (int i = 1; i < cols; i++)
{
if (col + i < right)
{
// Handle tab spacing
}
}
tokenCol++;
}
else
{
var color = Color.White;
if (colToken != null)
{
color = colToken.Color;
}
var point = new Point(idxCol, row);
returnDict.Add(point, color);
tokenCol++;
}

if (!EditorExtensions.SetCol(ref col, right, cols))
{
break;
}

if (idxCol + 1 < lineRuneCount && col + Rune.ColumnWidth(line[idxCol + 1]) > right)
{
break;
}
}
row++;
}

pointColorDict = returnDict;
}
}
}
4 changes: 4 additions & 0 deletions EditorTextView.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@ public void SetLanguage(LanguageEnum language)
{
editorContext = new XmlEditorContext(TabWidth);
}
else if (language == LanguageEnum.Markdown)
{
editorContext = new MarkdownEditorContext(TabWidth);
}
else
{
editorContext = null;
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ psedit
| JSON | ✖️ | ✔ | ✔ | |
| YAML | ✖️ | ✔ | ✔ | |
| XML | ✖️ | ✔ | ✔ | |
| Markdown | | ✔ | | |

All other text based files are supported, and will be treated as plain text files.

Expand Down
4 changes: 4 additions & 0 deletions ShowEditorCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ protected override void BeginProcessing()
_allowedFileTypes.Add(".yaml");
_allowedFileTypes.Add(".config");
_allowedFileTypes.Add(".csproj");
_allowedFileTypes.Add(".md");
// ...existing code...
}

Expand Down Expand Up @@ -300,6 +301,9 @@ private void SetLanguage(string path)
case ".xml": case ".config": case ".csproj":
textEditor.SetLanguage(LanguageEnum.XML);
break;
case ".md":
textEditor.SetLanguage(LanguageEnum.Markdown);
break;
default:
textEditor.SetLanguage(LanguageEnum.Text);
break;
Expand Down
5 changes: 4 additions & 1 deletion ThemeService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,10 @@ public class Theme
{ "Accent", Color.Cyan },
{ "Error", Color.Red },
{ "Warning", Color.BrightYellow },
{ "Info", Color.BrightBlue }
{ "Info", Color.BrightBlue },
{ "String", Color.White },
{ "Comment", Color.Green },
{ "Secondary", Color.Gray }
}
};
}