Skip to content

Add spreadsheet and markdown editing modes#643

Merged
TheJoeFin merged 11 commits intodevfrom
format-modes
Apr 25, 2026
Merged

Add spreadsheet and markdown editing modes#643
TheJoeFin merged 11 commits intodevfrom
format-modes

Conversation

@TheJoeFin
Copy link
Copy Markdown
Owner

Summary

  • add spreadsheet and markdown editing modes to the Edit Text window, including table document and undo/redo support
  • add markdown, file IO, and history updates needed for the new editing flows
  • update tests, dependencies, and package file associations for the new document formats

Introduce EditTextTableDocument for structured text editing, with parsing, serialization, and manipulation of tabular data (plain, CSV, TSV, XML). Add EtwEditorMode and EditTextTableDocumentJson to HistoryInfo for editor state tracking. Implement SpreadsheetUndoHistory for undo/redo of spreadsheet/table edits.
- Add IoUtilities methods for markdown/spreadsheet detection and editor mode selection
- Use ReadToEndAsync in FileUtilities for async file reading
- Add MarkdownDocumentUtilities for Markdown↔FlowDocument conversion, theming, and detection using Markdig
Switched from FileUtilities.SaveTextFile to SaveHistoryTextFileBlocking for saving history JSON files, delegating file writing to the new method.
Introduce new test classes for EditTextTableDocument, EditTextWindow, MarkdownDocumentUtilities, SpreadsheetUndoHistory, and file I/O/history services. Tests cover table parsing/serialization, row/column/view metric operations, markdown round-tripping, undo/redo logic, file extension mode detection, and edit window state handling. Includes WPF and theory tests for markdown and history features. Minor import changes to support new tests.
Added "Bash(pdm *)" and "Bash(pdm api *)" to the allow list in settings.local.json, broadening permitted Bash command patterns for the pdm CLI alongside the existing "Bash(bin/pdm *)". No permissions were removed.
Major refactor to support Raw Text, Spreadsheet, and Markdown modes in EditTextWindow. Added DataGrid-based spreadsheet editor with context menus, undo/redo, and row/column operations. Introduced Markdown editor with live preview and theming. Updated UI for mode switching, file open/save for new formats, and improved styles. Enhanced bottom bar, selection UI, and window lifecycle handling for all modes.
- Upgrade NuGet packages in Tests.csproj and Text-Grab.csproj, including Magick.NET, WindowsAppSDK, WPF-UI, and coverlet.collector
- Add Markdig for Markdown support
- Enhance app manifest to associate .csv, .tsv, .tab, .md, .markdown, and .txt files with "Open with Text Grab" verb
- No functional code changes; updates focus on dependencies and file type integration
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds new document-editing capabilities to the Edit Text window by introducing dedicated Spreadsheet and Markdown modes, including persisted history state, new IO behaviors, and supporting UI/styles/tests.

Changes:

  • Introduce Edit Text editor modes (Text/Markdown/Spreadsheet) with spreadsheet table document model + undo/redo.
  • Add markdown rendering/editing utilities (FlowDocument ↔ markdown) and update Edit Text UI to host RichTextBox/DataGrid.
  • Expand file IO + packaging associations to support .csv/.tsv/.tab/.md/.markdown, and add tests/dependency updates.

Reviewed changes

Copilot reviewed 22 out of 22 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
Text-Grab/Views/EditTextWindow.xaml.cs Implements editor mode switching, spreadsheet editing/undo, markdown editor syncing, and pending-file-close prompting.
Text-Grab/Views/EditTextWindow.xaml Adds RichTextBox/DataGrid editors, mode menu items, spreadsheet context menus, and a dialog host for close prompts.
Text-Grab/Utilities/MarkdownDocumentUtilities.cs New markdown parsing/serialization + theming helpers for the markdown editor.
Text-Grab/Utilities/IoUtilities.cs Adds markdown/spreadsheet extension handling and mode inference from file extension.
Text-Grab/Utilities/FileUtilities.cs Switches to async text read for packaged storage.
Text-Grab/Text-Grab.csproj Adds Markdig dependency and bumps several package versions.
Text-Grab/Styles/TextBoxStyles.xaml Enables template-bound scrollbars (instead of forcing Hidden).
Text-Grab/Styles/DataGridStyles.xaml Adjusts grid border styling and adds a row-header style.
Text-Grab/Services/HistoryService.cs Routes history JSON writes through a new blocking save helper.
Text-Grab/Models/SpreadsheetUndoHistory.cs Adds undo/redo stacks for spreadsheet document snapshots.
Text-Grab/Models/HistoryInfo.cs Persists editor mode + serialized spreadsheet document JSON in history items.
Text-Grab/Models/EditTextTableDocument.cs New structured table document model with parsing/serialization and operations (insert/move/delete/transpose).
Text-Grab-Package/Package.appxmanifest Adds file type associations for spreadsheet/markdown/text documents.
Tests/Tests.csproj Updates test dependencies (coverlet collector, VS diagnosers).
Tests/SpreadsheetUndoHistoryTests.cs Adds unit tests for spreadsheet undo/redo behavior.
Tests/MarkdownDocumentUtilitiesTests.cs Adds WPF tests for markdown parsing/serialization and detection helpers.
Tests/HistoryServiceTests.cs Adds coverage for preserving markdown editor mode in history and write-back behavior.
Tests/FilesIoTests.cs Adds tests for editor mode inference by file extension.
Tests/EditTextWindowSpreadsheetTests.cs Adds test for spreadsheet selection-clearing helper.
Tests/EditTextWindowFileStateTests.cs Adds tests for window title + pending-edit detection + save filter logic.
Tests/EditTextTableDocumentTests.cs Adds tests for table document parsing/serialization and operations.
.claude/settings.local.json Expands local tool permissions (includes a user-specific path).

case Run run:
builder.Append(GetInlineRole(run) switch
{
MarkdownInlineRole.TaskListMarker => GetTaskListMarkerChecked(run) ? "[x]" : "[ ]",
Copy link

Copilot AI Apr 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inline code spans are marked with MarkdownInlineRole.CodeSpan on a Run (see parsing), but WriteInline doesn’t special-case that role for Run. As a result, inline code like `code` will serialize as escaped plain text instead of being wrapped in backticks. Handle MarkdownInlineRole.CodeSpan in the Run branch (or represent code spans with a dedicated Span) so round-tripping preserves inline code formatting.

Suggested change
MarkdownInlineRole.TaskListMarker => GetTaskListMarkerChecked(run) ? "[x]" : "[ ]",
MarkdownInlineRole.TaskListMarker => GetTaskListMarkerChecked(run) ? "[x]" : "[ ]",
MarkdownInlineRole.CodeSpan => $"`{NormalizeDocumentText(run.Text)}`",

Copilot uses AI. Check for mistakes.
string rawText = NormalizeDocumentText(new TextRange(cell.ContentStart, cell.ContentEnd).Text);
return rawText
.Replace("|", "\\|", StringComparison.Ordinal)
.Replace(Environment.NewLine, "<br />", StringComparison.Ordinal);
Copy link

Copilot AI Apr 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SerializeTableCell normalizes text newlines to \n (via NormalizeDocumentText), but then replaces Environment.NewLine, which typically won’t match. This means multi-line table cell content won’t be converted to <br /> and can break pipe-table serialization. Replace/normalize against \n (or call the existing newline normalizer here) before emitting markdown.

Suggested change
.Replace(Environment.NewLine, "<br />", StringComparison.Ordinal);
.Replace("\n", "<br />", StringComparison.Ordinal);

Copilot uses AI. Check for mistakes.
Comment thread Text-Grab/Models/HistoryInfo.cs Outdated
Comment on lines +47 to +49
public bool HasCalcPaneOpen { get; set; } = false;

public int CalcPaneWidth { get; set; } = 0;
public int CalcPaneWidth { get; set; } = 0;
Copy link

Copilot AI Apr 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These properties have inconsistent indentation (one extra leading space) compared to the rest of the file, which will show up in diffs/formatting checks. Please align them with the surrounding property declarations.

Copilot uses AI. Check for mistakes.
Comment thread .claude/settings.local.json Outdated
Comment on lines +21 to +24
"Bash(bin/pdm *)",
"Bash(pdm *)",
"Bash(pdm api *)",
"Read(//c/Users/josep/.claude/skills/pdm/**)"
Copy link

Copilot AI Apr 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This adds a user-specific absolute path permission (Read(//c/Users/josep/...)) and broadens allowed commands (pdm *). Committing local-machine paths and expanded tool permissions to the repo can break other dev environments and unintentionally weaken security. Consider removing these entries or moving them to a non-committed local config.

Suggested change
"Bash(bin/pdm *)",
"Bash(pdm *)",
"Bash(pdm api *)",
"Read(//c/Users/josep/.claude/skills/pdm/**)"
"Bash(pdm api *)"

Copilot uses AI. Check for mistakes.
Comment thread Text-Grab/Views/EditTextWindow.xaml.cs Outdated
isLoadingOpenedFile = true;
try
{
PassedTextControl.AppendText(TextContent);
Copy link

Copilot AI Apr 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OpenPath appends the opened file contents to the existing editor text (AppendText). This makes opening a file produce a combined document (previous content + file content), and subsequent saves will overwrite the file with the appended text. Replace the editor contents instead (e.g., assign Text / clear before append), and ensure markdown/spreadsheet refresh uses the replaced text.

Suggested change
PassedTextControl.AppendText(TextContent);
PassedTextControl.Text = TextContent;

Copilot uses AI. Check for mistakes.
Comment thread Text-Grab/Views/EditTextWindow.xaml.cs Outdated
};
private void SaveBTN_Click(object sender, RoutedEventArgs e)
{
SyncTextFromActiveEditor();
Copy link

Copilot AI Apr 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SaveBTN_Click calls SyncTextFromActiveEditor() and then SaveCurrentDocument(), but SaveCurrentDocument() already calls SyncTextFromActiveEditor() internally. This double-sync is redundant and can be removed (keep the sync in one place) to avoid duplicate work and reduce chances of re-entrancy side effects.

Suggested change
SyncTextFromActiveEditor();

Copilot uses AI. Check for mistakes.
Comment on lines +359 to +360
if (!string.IsNullOrWhiteSpace(linkInline.GetDynamicUrl != null ? linkInline.GetDynamicUrl() : linkInline.Url))
hyperlink.NavigateUri = new Uri(linkInline.GetDynamicUrl != null ? linkInline.GetDynamicUrl()! : linkInline.Url!, UriKind.RelativeOrAbsolute);
Copy link

Copilot AI Apr 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Creating new Uri(...) directly from linkInline.Url / GetDynamicUrl() can throw UriFormatException for user-typed/invalid markdown links (spaces, malformed schemes, etc.), which would crash markdown rendering. Consider wrapping URI creation in a try/catch or using Uri.TryCreate and falling back to a non-navigable hyperlink (or literal markdown) when parsing fails.

Suggested change
if (!string.IsNullOrWhiteSpace(linkInline.GetDynamicUrl != null ? linkInline.GetDynamicUrl() : linkInline.Url))
hyperlink.NavigateUri = new Uri(linkInline.GetDynamicUrl != null ? linkInline.GetDynamicUrl()! : linkInline.Url!, UriKind.RelativeOrAbsolute);
string? linkUrl = linkInline.GetDynamicUrl != null ? linkInline.GetDynamicUrl() : linkInline.Url;
if (!string.IsNullOrWhiteSpace(linkUrl) &&
Uri.TryCreate(linkUrl, UriKind.RelativeOrAbsolute, out Uri? navigateUri))
{
hyperlink.NavigateUri = navigateUri;
}

Copilot uses AI. Check for mistakes.
@TheJoeFin
Copy link
Copy Markdown
Owner Author

@copilot apply changes based on the comments in this thread

…fety, indentation, file open, save sync

Agent-Logs-Url: https://github.com/TheJoeFin/Text-Grab/sessions/63f3f784-4485-4547-a38c-4d37de922366

Co-authored-by: TheJoeFin <7809853+TheJoeFin@users.noreply.github.com>
Copy link
Copy Markdown
Contributor

Copilot AI commented Apr 25, 2026

@copilot apply changes based on the comments in this thread

Applied all 7 review changes in commit 46ab38f:

  1. SerializeTableCell – replaced Environment.NewLine with "\n" so multi-line table cell content correctly becomes <br /> after NormalizeDocumentText normalization.
  2. WriteInline CodeSpan – added MarkdownInlineRole.CodeSpan => $"\{NormalizeDocumentText(run.Text)}`"` so inline code round-trips correctly.
  3. URI creation – replaced new Uri(...) with Uri.TryCreate to avoid UriFormatException for malformed/user-typed markdown links.
  4. HistoryInfo.cs indentation – fixed extra leading space on HasCalcPaneOpen and CalcPaneWidth properties.
  5. .claude/settings.local.json – removed Bash(pdm *) and Read(//c/Users/josep/...) entries, keeping only the scoped Bash(pdm api *).
  6. OpenPath – replaced AppendText(TextContent) with PassedTextControl.Text = TextContent so opening a file replaces the editor contents instead of accumulating.
  7. SaveBTN_Click – removed the redundant SyncTextFromActiveEditor() call since SaveCurrentDocument() already calls it internally.

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • az667904.vo.msecnd.net
    • Triggering command: /usr/bin/dotnet dotnet build Text-Grab/Text-Grab.csproj -p:EnableWindowsTargeting=true --no-restore (dns block)

If you need me to access, download, or install something from one of these locations, you can either:

- Add new spreadsheet, translation, AI, and utility buttons to ButtonInfo.cs with appropriate handlers and icons
- Update translation prompt to use local alphabet/characters
- Add event handlers for new actions and editor mode toggles in EditTextWindow.xaml.cs
- Refactor list initializations with C# collection expressions; add ToggleMenuItem helper
- Add EditTextWindowActionCatalogTests to verify all ButtonInfo actions are valid and expected
- Minor code cleanups and naming consistency improvements
Refactored EditTextWindow to unify text and spreadsheet cell transformation logic, enabling all text operations (replace, case toggle, trim, AI transforms, etc.) to work on selected spreadsheet cells. Added helper methods for cell selection and value updates. Improved word selection for spreadsheet cells. Updated command handlers and context menus for spreadsheet awareness. Enhanced CursorWordBoundaries for edge cases and added related unit tests. Minor UI tweaks to spreadsheet context menu.
@TheJoeFin TheJoeFin merged commit 4a619f8 into dev Apr 25, 2026
1 check passed
@TheJoeFin TheJoeFin deleted the format-modes branch April 25, 2026 21:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants