Skip to content

feat(inspector): split and merge columns in the CSV editor#1941

Merged
datlechin merged 3 commits into
mainfrom
feat/1469-csv-split-merge
Jul 22, 2026
Merged

feat(inspector): split and merge columns in the CSV editor#1941
datlechin merged 3 commits into
mainfrom
feat/1469-csv-split-merge

Conversation

@datlechin

Copy link
Copy Markdown
Member

Part 3 of 4 toward #1469 (CSV structural editing). Focused on Split / Merge. Stacked on #1940 (base branch feat/1469-csv-column-editing).

What

  • Split Column… splits each value into new columns at every match of a delimiter or a regular expression. The clicked column is replaced by the pieces; short rows get empty cells. New columns are named <column> 1, <column> 2, ...
  • Merge Columns… joins the clicked column with the one to its right into a single column, placing a separator you choose between the two values.
  • Both appear in the column-header menu and the Edit menu, and each is a single named undo step.

Why

#1469 asks for split/merge as first-class column verbs, matching Tablecruncher (its csvmenu.cpp Split/Merge dialogs), Modern CSV, and the Text-to-Columns / Join conventions from Excel and Sheets.

How (native / HIG)

  • New CSVRowStore.splitColumn(at:spec:) / mergeColumns(at:separator:) primitives. Because a split changes the column count per row, these materialize the affected rows (the lazy byte-range transform log can't express a split), then resync the header row and clear the transform log. This is edit-mode only; large read-only files are a separate concern (feat(csv): read-only mode for files past a size threshold (memory-mapped / DuckDB-backed) #1472).
  • Split accepts a delimiter (components(separatedBy:)) or a compiled NSRegularExpression, chosen in the dialog via an NSSegmentedControl. An invalid regex shows an error and does nothing.
  • Undo/redo use a full store-state snapshot captured before and after the change (cheap: Swift arrays are copy-on-write), since split/merge have no simple element-wise inverse. Inferred column types are re-derived and captured in the same snapshot.
  • The dialogs are NSAlert sheets with an accessory view, matching the existing rename/insert-column prompt. Column layout stays correct because the grid already tolerates a stale columnOrder (unknown names skipped, new columns appended).
  • splitColumn/mergeColumns are added to the InspectorDocument protocol with default no-op implementations. This is an additive PluginKit change (Library Evolution fills the default for existing conformers), so no currentPluginKitVersion bump.

Tests

  • CSVRowStore.split(_:spec:) for literal and regex separators; splitColumn padding; mergeColumns joining; captureState/restore round-trip.
  • InspectorColumnMenuBuilder: Split/Merge wiring, and Merge omitted for the last column.

Docs / changelog

  • docs/features/csv-inspector.mdx column-operations section updated.
  • CHANGELOG entry under [Unreleased].

Needs a local Xcode build to confirm compilation. PluginKit change is additive (new protocol requirements with defaults); no version bump.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9b42ef2038

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

presentInvalidPattern()
return
}
inspectorDocument?.splitColumn(at: column, separator: separator, isRegex: isRegex)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Rebase active filters and sorts after splitting

When a filter or sort currently targets a column to the right of the split, its stored integer index is not shifted after this call. recomputeDisplay() subsequently builds predicates from those stale indices, so the active filter/sort operates on a newly created or preceding column rather than the original data; Merge has the analogous left-shift problem. Rebase or clear state.filters and state.sortState whenever these schema changes occur.

Useful? React with 👍 / 👎.

private func recomputeInferredTypes() {
let sample = store.pageRows(offset: 0, limit: Self.typeInferenceSampleSize)
inferredTypes = CSVTypeInferrer.inferColumns(rows: sample, columnCount: store.columnCount)
typeOverrides = [:]

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve type overrides on untouched columns

A split or merge invokes this method and clears every manual type override, including overrides on columns unrelated to the operation. For example, splitting column A removes a user-selected type for column C and can change its subsequent sort behavior; retain unaffected overrides and shift their indices while replacing only the structurally affected ones.

Useful? React with 👍 / 👎.

@datlechin

Copy link
Copy Markdown
Member Author

Addressed a code review. Findings and fixes:

  • Double parse: splitColumn fetched each row's cells twice (the second pass re-parsed from bytes past the 4000-entry cache). It now parses each row once and reuses the cells.
  • Column layout sync: split/merge skipped the columnLayout maintenance every other structural handler does. Split now replaces the old column's layout key with the new column names (preserving position); merge drops the merged-away column's key.

@datlechin
datlechin force-pushed the feat/1469-csv-column-editing branch from 06c60c1 to 88c3e26 Compare July 22, 2026 03:52
@datlechin
datlechin force-pushed the feat/1469-csv-split-merge branch from 8faf1f6 to 9dd6e93 Compare July 22, 2026 03:53
Base automatically changed from feat/1469-csv-column-editing to main July 22, 2026 03:54

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9dd6e936d8

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +355 to +356
regex.enumerateMatches(in: value, range: NSRange(location: 0, length: text.length)) { match, _, _ in
guard let match, match.range.length > 0 else { return }

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Handle zero-width regex separators

When users choose Regex mode with a valid zero-width separator such as (?=,), ^, or a lookahead/lookbehind, this guard skips every match and returns the original value as one piece. The UI promises to split at every regex match, so these commonly useful boundary patterns silently produce no split; process zero-length matches while advancing safely instead of discarding them.

Useful? React with 👍 / 👎.

@mintlify

mintlify Bot commented Jul 22, 2026

Copy link
Copy Markdown

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated (UTC)
TablePro 🟢 Ready View Preview Jul 22, 2026, 3:56 AM

💡 Tip: Enable Workflows to automatically generate PRs for you.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d22b41d309

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

let pieceCount = inspector.columnNames.count - oldCount + 1
let upper = min(column + max(pieceCount, 0), inspector.columnNames.count)
let newNames = column < upper ? Array(inspector.columnNames[column..<upper]) : []
replaceLayoutKey(oldName, with: newNames)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Restore column layout when undoing structural edits

When the user has a custom column order, widths, or hidden columns, this mutates state.columnLayout outside the document undo operation. Cmd-Z restores only the CSV snapshot, so after undoing a split the layout still contains the generated piece names and the restored original column is appended with default visibility/width; merge similarly drops the restored right-hand column's layout state. Register an inverse layout update with the same undo step (or restore a layout snapshot) so the advertised single undo fully restores the editor state.

Useful? React with 👍 / 👎.

@mintlify

mintlify Bot commented Jul 22, 2026

Copy link
Copy Markdown

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated (UTC)
TablePro 🟡 Building Jul 22, 2026, 3:56 AM

💡 Tip: Enable Workflows to automatically generate PRs for you.

@datlechin
datlechin merged commit 927fd49 into main Jul 22, 2026
2 checks passed
@datlechin
datlechin deleted the feat/1469-csv-split-merge branch July 22, 2026 06:59
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.

1 participant