Skip to content

add copy as text, export, and keybinds#17717

Open
Roger-Gu wants to merge 1 commit into
eclipse-theia:masterfrom
Roger-Gu:problem-view-enhancements
Open

add copy as text, export, and keybinds#17717
Roger-Gu wants to merge 1 commit into
eclipse-theia:masterfrom
Roger-Gu:problem-view-enhancements

Conversation

@Roger-Gu

@Roger-Gu Roger-Gu commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

What it does

Implements #17706.

  • Add an "export" menu, which will open up a file dialog and write the json object to a user-specified file. Note: Unlike "copy", when only a header is selected, we export all of its children. Also, unlike "copy", the exported json is always a list, even when there is only one problem selected. This would be easier for automated process with the exported file.
  • Add a "copy as text" menu. It is to copy message, but will also output the file names, severity level, and other info that are visible and selected. Unlike "copy", this menu works for headers as well (it will just copy the file name as displayed in the selected item).
  • Wire up Ctrl+C and Ctrl+A key bindings for copy and select all, respectively.

How to test

  1. Open the Problems view.
  2. Create at least two problems.
  3. Press Ctrl+a.
  4. Observe all nodes are now selected.
  5. Press Ctrl+c.
  6. Paste the content somewhere, confirm it is the correct list of json object.
  7. Select a problem.
  8. Right click on any node, see there is a "export" in the context menu.
  9. Click "export".
  10. Observe a file dialog pops up.
  11. Input a file destination, and click OK.
  12. Observe the correct list of json objects is exported.
  13. Repeat 9~13 on multiple problems.
  14. Select a problem header.
  15. Repeat 9~12.
  16. Observe all of the children of the header are exported. Note that if both header and the problem itself are selected, there should still be only one copy of the corresponding object in the list.
  17. Select any number of nodes.
  18. . Right click on any node, see there is a "copy as text" in the context menu.
  19. Click "copy as text".
  20. Paste the content somewhere, confirm it is the correct text as displayed.

Follow-ups

  • Now that "copy as text" and "export" works for headers, I'm not sure if it would be better if we make "copy" to work on headesr as well. However, I personally find it does not make too much sense if "copy message" copies all the children for a selected header.
  • If we don't want to make "copy" to work on headers, I think Ctrl+c is better wire up with "copy as text" instead, and rename "copy as text" to "copy", while the existing "copy" to "copy as json" or "copy details", since it would be weird if "copy" and "Ctrl+c" is not the more general version.
  • Behavior of "export" is not the same with "copy", since I think it would make more sense this way. I feel that a user should be able to export all of its children problem if they click on a header. We may certainly change this to match "copy" exactly if that is better.
  • Also, currently the copy and copy message returns the result in whatever order the nodes are selected. Maybe we want to do the same as what copy as text is doing now, and return in the order displayed in the view?
  • In future, it might be good to refactor "copy as text" to allow the users to decide the format themselves, as in VScode: Problems view select/copy all. microsoft/vscode#133408 (comment). I don't think it's a priority now, though.

Breaking changes

  • This PR introduces breaking changes and requires careful review. If yes, the breaking changes section in the changelog has been updated.

Attribution

Review checklist

Reminder for reviewers

Signed-off-by: Roger Gu <r-gu@ti.com>

@ndoschek ndoschek left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks a lot for the PR and for tackling this follow up, @Roger-Gu!
The added features work great, I just left a few comments inline, could you take a look at those? TIA!

id: 'problems.export',
category: 'Problems',
label: 'Export',
iconClass: codicon('arrow-circle-up', true)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
iconClass: codicon('arrow-circle-up', true)
iconClass: codicon('arrow-circle-up')

Does not need to be declared as action item, since i breaks if it s rendered in a (context) menu, e.g.
Image

Comment on lines +435 to +437
title: 'Export Problems',
filters: { 'JSON Files': ['json'] },
saveLabel: 'Export'

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

These string are user-facing but not localized. Please make sure to use nls.localizeByDefault/nls.localize for those.

Comment on lines +340 to +341
const output = selectionsArray.length === 1
? JSON.stringify(serializedProblems[0], undefined, '\t')

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This changes the existing Copy output: a single selection now serializes a bare object instead of a one-element array. I think we changed that previously to array right? So I'd be in favour of keeping it this way now.

});
commands.registerCommand(ProblemsCommands.COPY_AS_TEXT, {
isEnabled: () => this.withWidget(undefined, () => true),
isVisible: () => this.withWidget(undefined, () => true),

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

isVisible is always true here (and for EXPORT at line 219), so both entries always show in the context menu even with an empty selection, where execute then no-ops. COPY/COPY_MESSAGE instead hide when nothing is selected (getSelectedMarkerNodes(widget).length > 0). Please align for consistency.

return description ? `${name} ${description}` : name;
}

protected formatMarkerNode(node: MarkerNode): string {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This re-implements the message/source/code/location formatting already done in ProblemWidget.decorateMarkerNode, and the two have already drifted (severity info here vs information there). Consider extracting a shared helper so the copied text and the rendered row can't diverge.

case 1: return 'error';
case 2: return 'warning';
case 3: return 'info';
default: return 'hint';

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

These severity labels are hardcoded numbers plus untranslated strings. marker.data.severity is a DiagnosticSeverity (already imported in problem-widget.tsx), so please switch on DiagnosticSeverity.Error/.Warning/.Information/.Hint instead of the magic 1/2/3, and localize the returned labels (e.g. nls.localizeByDefault('Error')).

this.addToClipboard(messages.join('\n'));
}

protected serializeMarker(marker: ProblemMarker): object {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

serializeMarker returns object, which discards all type information for callers (copy, exportProblems). Please define a proper interface for the serialized shape and return that instead — it documents the export/copy format and, combined with the severity fix above, keeps severity typed as DiagnosticSeverity end-to-end:
Something like:

export interface SerializedProblemMarker {
   resource: string;
   ...
}

...

protected serializeMarker(marker: ProblemMarker): SerializedProblemMarker

Comment on lines +178 to +184
override toNodeName(node: TreeNode): string {
return super.toNodeName(node);
}

override toNodeDescription(node: TreeNode): string {
return super.toNodeDescription(node);
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

These overrides only call super to make toNodeName/toNodeDescription public so the contribution can reach them. Widening a widget's protected methods just to call them from outside is not good practice.

You can avoid this though. Both base methods just delegate to the LabelProvider (getName / getLongName), which ProblemContribution already injects. So compute the text directly in the contribution and drop both overrides:

protected formatMarkerInfoNode(node: MarkerInfoNode): string {
   const name = this.labelProvider.getName(node);
   const description = this.labelProvider.getLongName(node);
   return description ? `${name} ${description}` : name;
}

}
}

protected collectMarkerNodes(nodes: readonly TreeNode[]): MarkerNode[] {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

collectMarkerNodes and collectMarkerNodesRecursive are split into two methods for what is really one recursive walk. You can fold them into a single recursive function (seed the Set and recurse in one place), since a MarkerNode never has MarkerNode children anyway.

More generally, this PR now has three separate walks over the same tree: getSelectedMarkerNodes (flat), collectSelectedLinesInOrder, and this pair. It would be worth consolidating the "collect selected marker nodes" logic into one helper that the copy, copy-as-text, and export paths all reuse.

@github-project-automation github-project-automation Bot moved this from Waiting on reviewers to Waiting on author in PR Backlog Jul 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Waiting on author

Development

Successfully merging this pull request may close these issues.

2 participants