-
Notifications
You must be signed in to change notification settings - Fork 29
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
Conversation
There was a problem hiding this 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
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'), |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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 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'), |
There was a problem hiding this comment.
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
)
apps/vscode/src/main.ts
Outdated
|
||
const documentOpenHandler = vscode.window.onDidChangeActiveTextEditor(async () => { | ||
// Check if the document language is "quarto" | ||
|
There was a problem hiding this comment.
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
- A diff editor view of a quarto document immediately closes
Screen.Recording.2024-10-22.at.4.37.39.PM.mov
- I can't seem to go from Visual Mode to Source Mode explicitly
Screen.Recording.2024-10-22.at.4.37.57.PM.mov
Co-authored-by: Davis Vaughan <davis@posit.co>
apps/vscode/package.json
Outdated
"quarto.defaultEditor": { | ||
"order": 11, | ||
"type": "string", | ||
"markdownDescription": "Default editor. Only applicable in Positron.", | ||
"enum": [ | ||
"source", | ||
"visual" | ||
], | ||
"default": "source" | ||
}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can remove now?
Never mind! Needed to restart the extension host. |
if (t.closed.length > 0) { return; } | ||
const tabs = t.changed.length > 0 ? t.changed : t.opened; |
There was a problem hiding this comment.
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 Tab
s together. We'd just ignore closed tabs i guess.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
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()); |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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; Tab
s don't have access to a TextEditor
AFAICT, which is how the user is navigated to the location
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ah good point
There was a problem hiding this 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!
} | ||
|
||
if (editorMode && editorMode != viewType && !isSwitch) { | ||
const allTabs = window.tabGroups.all?.[0]?.tabs; |
There was a problem hiding this comment.
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 => |
There was a problem hiding this comment.
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);
}
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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
Co-authored-by: Davis Vaughan <davis@posit.co>
as part of posit-dev/positron#2933
Should have the following behavior:
editor: XXXX
is specified in yaml at top of file, use thateditor: XXXX
is specified in_quarto.yml
/_quarto.yaml
, use that