Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Automatically open QMD files in visual/source editor if specified #577

Merged
merged 18 commits into from
Oct 30, 2024

Conversation

isabelizimm
Copy link
Collaborator

@isabelizimm isabelizimm commented Oct 18, 2024

as part of posit-dev/positron#2933

Should have the following behavior:

  • if editor: XXXX is specified in yaml at top of file, use that
  • if no editor in file yaml, but editor: XXXX is specified in _quarto.yml/_quarto.yaml, use that
  • otherwise, use configurable default editor

Copy link
Collaborator

@DavisVaughan DavisVaughan left a comment

Choose a reason for hiding this comment

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

Some drive by thoughts

apps/vscode/src/providers/editor/toggle.ts Outdated Show resolved Hide resolved
apps/vscode/src/providers/editor/toggle.ts Outdated Show resolved Hide resolved
apps/vscode/src/providers/editor/toggle.ts Outdated Show resolved Hide resolved
apps/vscode/src/providers/editor/toggle.ts Show resolved Hide resolved
apps/vscode/src/main.ts Outdated Show resolved Hide resolved
const viewType = config === 'visual' ? VisualEditorProvider.viewType : 'textEditor';
vscode.workspace.getConfiguration('workbench').update('editor.defaultView', viewType, true);
await vscode.commands.executeCommand("workbench.action.setDefaultEditor",
vscode.Uri.file('filename.qmd'),
Copy link
Collaborator

Choose a reason for hiding this comment

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

i think this could be replaced as "*.qmd" if we instead expose updateUserAssociations from the Positron side

Copy link
Collaborator

Choose a reason for hiding this comment

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

Actually I wonder if we could expose updateUserAssociations() with the slightly better name of setEditorAssociations(), but then expose it from positron.d.ts rather than as a command (either in the top level 'positron' namespace, or under a new 'workbench' sub namespace, mirroring what VS Code has. That would work like our existing 'language' and 'runtime' sub namespaces).

That would probably be nice because it would force you to look at Quarto's hasHooks() and hooksApi() helpers to determine whether or not you are in positron (we use that in a few other places too)

(FWIW saveAllTitled had to be a command to be able to call it from R, but this feels more like an Extension API feature that can be a "real" function exported from positron.d.ts)

@isabelizimm isabelizimm marked this pull request as ready for review October 22, 2024 18:53
apps/vscode/src/main.ts Outdated Show resolved Hide resolved
apps/vscode/src/main.ts Outdated Show resolved Hide resolved
apps/vscode/package.json Outdated Show resolved Hide resolved
apps/vscode/src/providers/editor/toggle.ts Outdated Show resolved Hide resolved
apps/vscode/src/providers/editor/toggle.ts Outdated Show resolved Hide resolved
const viewType = config === 'visual' ? VisualEditorProvider.viewType : 'textEditor';
vscode.workspace.getConfiguration('workbench').update('editor.defaultView', viewType, true);
await vscode.commands.executeCommand("workbench.action.setDefaultEditor",
vscode.Uri.file('filename.qmd'),
Copy link
Collaborator

Choose a reason for hiding this comment

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

Actually I wonder if we could expose updateUserAssociations() with the slightly better name of setEditorAssociations(), but then expose it from positron.d.ts rather than as a command (either in the top level 'positron' namespace, or under a new 'workbench' sub namespace, mirroring what VS Code has. That would work like our existing 'language' and 'runtime' sub namespaces).

That would probably be nice because it would force you to look at Quarto's hasHooks() and hooksApi() helpers to determine whether or not you are in positron (we use that in a few other places too)

(FWIW saveAllTitled had to be a command to be able to call it from R, but this feels more like an Extension API feature that can be a "real" function exported from positron.d.ts)


const documentOpenHandler = vscode.window.onDidChangeActiveTextEditor(async () => {
// Check if the document language is "quarto"

Copy link
Collaborator

Choose a reason for hiding this comment

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

It seems like we don't have things quite right in this changing active text editor listener

  1. A diff editor view of a quarto document immediately closes
Screen.Recording.2024-10-22.at.4.37.39.PM.mov
  1. I can't seem to go from Visual Mode to Source Mode explicitly
Screen.Recording.2024-10-22.at.4.37.57.PM.mov

Comment on lines 1039 to 1048
"quarto.defaultEditor": {
"order": 11,
"type": "string",
"markdownDescription": "Default editor. Only applicable in Positron.",
"enum": [
"source",
"visual"
],
"default": "source"
},
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can remove now?

apps/vscode/package.json Outdated Show resolved Hide resolved
apps/vscode/src/providers/editor/configurations.ts Outdated Show resolved Hide resolved
apps/vscode/src/main.ts Outdated Show resolved Hide resolved
apps/vscode/src/providers/editor/editor.ts Outdated Show resolved Hide resolved
apps/vscode/src/providers/editor/editor.ts Outdated Show resolved Hide resolved
apps/vscode/src/providers/editor/editor.ts Outdated Show resolved Hide resolved
apps/vscode/src/providers/editor/editor.ts Outdated Show resolved Hide resolved
@juliasilge
Copy link
Collaborator

juliasilge commented Oct 29, 2024

I got the .vsix from here to test out the behavior and unfortunately 😬 I see:

command 'quarto.editInVisualMode' not found

Might something have gone wrong with how the command is registered?

Never mind! Needed to restart the extension host.

Comment on lines 147 to 148
if (t.closed.length > 0) { return; }
const tabs = t.changed.length > 0 ? t.changed : t.opened;
Copy link
Collaborator

Choose a reason for hiding this comment

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

I'm not sure this is quite right, because I don't think changed, closed, and opened are mutually exclusive.

i.e. i think we can get a tab change event with both newly opened and newly changed tabs in it

So maybe these two lines really need to just be const tabs = [t.changed, t.opened] (or whatever the syntax is to combine two arrays of Tabs together. We'd just ignore closed tabs i guess.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Hmmm... I think we can just target opened tabs, and ignore tab changes. We will presume that the editor should stay the same as the user leaves it, eg, if you open something in source mode, go to another tab, update a _quarto.yml to have a default as visual, then going back to the original file should still be source (unless they close and reopen it).

opened tabs are never considered active, but I think if we're only looking at newly opened tabs, we can always treat them as if they are. That should eliminate the switching error you're seeing as well.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I played around with this for awhile and i think the problem is going to be that at open time the tabs aren't in the window.tabGroup array yet. You have to wait for them to become isActive first, which would come in a change event.

apps/vscode/src/providers/editor/editor.ts Show resolved Hide resolved
apps/vscode/src/providers/editor/editor.ts Outdated Show resolved Hide resolved
const fileContent = Buffer.from(fileData).toString('utf8');
const editorMode = determineMode(fileContent, uri);
let isSwitch = this.visualEditorPendingSwitchToSource.has(uri.toString()) || this.editorPendingSwitchToVisual.has(uri.toString());
this.editorPendingSwitchToVisual.delete(uri.toString());
Copy link
Collaborator

Choose a reason for hiding this comment

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

Gosh, it would be super nice if we also managed this.visualEditorLastSourcePos.delete(uri); here. It's a bit strange that we only .delete one of the two here.

It is currently managed by onDidChangeActiveTextEditor() down below, but I'm wondering if we just didn't know about the Tabs API at the time that hook was written.

I think it would be pretty straightforward to remove the onDidChangeActiveTextEditor() hook and put its code here, we are already doing a lot of the same stuff!

We know:

  • That we are on the active document
  • That we are in a quarto document
  • Whether or not we are switching or not

So really after this branch:

if (editorMode && editorMode != viewType && !isSwitch) {

it looks like we could just add another branch of

if (isSwitchToSource) { // you'd need to split out isSwitch into isSwitchToSource and isSwitchToVisual

and that would handle all the pos related code you see below about putting the cursor in the right place when we switch into source mode from visual mode (where we were tracking the editor position for this exact purpose).

And then you get to remove onDidChangeActiveTextEditor entirely

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I'm not sure if we would be able to move everything super cleanly; Tabs don't have access to a TextEditor AFAICT, which is how the user is navigated to the location

Copy link
Collaborator

Choose a reason for hiding this comment

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

ah good point

apps/vscode/src/providers/editor/editor.ts Outdated Show resolved Hide resolved
apps/vscode/src/providers/editor/editor.ts Outdated Show resolved Hide resolved
apps/vscode/src/main.ts Show resolved Hide resolved
apps/vscode/src/providers/editor/editor.ts Show resolved Hide resolved
Copy link
Collaborator

@DavisVaughan DavisVaughan left a comment

Choose a reason for hiding this comment

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

A few more small bugs but looks good enough to approve now - the timing of all this stuff is so confusing!

Don't forget a CHANGELOG bullet!

apps/vscode/src/providers/editor/editor.ts Outdated Show resolved Hide resolved
apps/vscode/src/providers/editor/editor.ts Outdated Show resolved Hide resolved
}

if (editorMode && editorMode != viewType && !isSwitch) {
const allTabs = window.tabGroups.all?.[0]?.tabs;
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think we need a big meaningful comment here, because this is (at first glance) really weird that we have to do this.

In my experience playing around with this if you set await window.tabGroups.close(tab, true); using the tab you get from the change event, it won't close and you get a "tab does not exist" error in the debug console.

From what I can tell, the tab we get in the change event is not PRECISELY equal to the tabs that exist in window.tabGroups at this point in time. It's possible that during this opened event they are a little out of sync. The tab we care about closing does exist in there, we just have to find it by matching by URI - something we know is stable across change events.

So I think it looks like this:

// Unfortunately we cannot just `await window.tabGroups.close(tab, true);`.
// The `tab` we get from the change event seems to not be precisely the same
// as the tab in `window.tabGroups`, so if we try and close `tab` we get a "tab
// not found" error. The one we care about does exist in `window.tabGroups`,
// we just have to manually find it using the URI as a stable field to match on.

// find tab to close if swapping editor type. we don't want to close an active
// tab since a tab we are opening has not been set as active yet. we also don't
// want to close preview tabs since they will automatically be overriden
const tabsToClose = allTabs.filter(tab =>
Copy link
Collaborator

Choose a reason for hiding this comment

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

I believe we are expecting exactly 1 tab to match here, so you could use find() instead of filter() and only do the close/open dance when we find() a tab, like

const tabToClose = allTabs.find(stuffhere);

if (tabToClose) {
  await window.tabGroups.close(tabToClose, true);
  await commands.executeCommand("vscode.openWith", uri, editorMode);
}

Copy link
Collaborator

Choose a reason for hiding this comment

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

That might also be slightly safer in some weird edge cases. Like with the "double open" bugs I've reported in this review, we would not have actually opened the visual editor if we programmed it this way, because tabToClose would have been undefined and the entire branch would not have run (it still would have been a bug on our part, just a slightly less bad one).

context.subscriptions.push(window.tabGroups.onDidChangeTabs(async (t) => {
const tabs = t.opened;

if (tabs.length > 0) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

can still flatten this if you want

isabelizimm and others added 2 commits October 30, 2024 12:42
Co-authored-by: Davis Vaughan <davis@posit.co>
@isabelizimm isabelizimm merged commit 227af78 into main Oct 30, 2024
1 check passed
@isabelizimm isabelizimm deleted the save-editor-type branch October 30, 2024 17:48
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.

4 participants