-
Notifications
You must be signed in to change notification settings - Fork 2.5k
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
[WIP] [editor] Renaming a file should rename a corresponding editor #582
Conversation
I tought you were trying to close and open just for experimentation. But if you ask for review, you should know this is not the proper way forward. |
We don't need anything in the widget-manager for closing. Just call |
The scope of this fix is when the rename is done within the IDE. |
let key = this.toKey({ factoryId, options }); | ||
let existingWidget = this.widgets.get(key); | ||
if (existingWidget) { | ||
existingWidget.title.label = newLabel; |
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.
Changing the label of a widget and changing it's key are 2 different things.
You should have a method to change the key here called updateWidgetKey for example.
And the title should be changed after filesysem.move is done and sucessful
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 should happen in the editor manager, nothing to do with the widget manager
dialog.open().then(name => { | ||
renameWidget(this.openerService, uri.parent.resolve(uri.path.base), uri.parent.resolve(name), name).then(() => { | ||
this.fileSystem.move(uri.toString(), uri.parent.resolve(name).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.
we should:
- move the file and make sure it worked
- update the widget key
- change the title
@@ -43,6 +43,8 @@ export interface OpenHandler { | |||
* Never reject if `canHandle` return a positive number; otherwise should reject. | |||
*/ | |||
open(uri: URI, options?: OpenerOptions): MaybePromise<object | undefined>; | |||
|
|||
renameWidget(uri: URI, newUri: URI, newLabel: string, options?: OpenerOptions): void; |
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.
You should not change OpenerService
, only EditorManager
should be changed.
To make it right, i think it shold be based on Resource. Editors are not working with files, but with resources, e.g. java resource to open source code, git resource to read history and so on. @svenefftinge What do you think about adding optional |
To implement it we would need also expose an underlying editor resource via So it would be here something like: if (textEditor.resource.onDidChangeUri) {
textEditor.resource.onDidChangeUri(newUri => {
newEditor.id = this.id + ":" + newUri.toString();
newEditor.title.label = newUri.path.base;
newEditor.title.iconClass = this.iconProvider.getFileIconForURI(textEditor.newUri);
});
} |
We also don't have an event so far to indicate a file rename, you should look in the chokidar watcher code in vscode to learn how to identify it. I saw something like that there. |
Nice thanks for the pointers @akosyakov! Note I think we could do this in 2 steps, first use the UI rename as a trigger to the onDidChangeUri and after use the FileWatcher. Also note that vscode doesn't rename if a file is moved from the filesystem, it marks the editor as delete from disk if you had unsaved changes or it closes the editor if you had no change. |
43d065c
to
8fa4026
Compare
Modifying a widget after creation will break the contract of the widget manager. After reload it will try to create a widget based on the old uri. |
Won't it flicker, lose an editor context (selection, viewport and so on) and break an existing layout if we just close one and open another? |
* check if a widget exists already and rename it | ||
*/ | ||
renameWidget(factoryId: string, options: any, newOptions: any, newLabel: any): void { | ||
let key = this.toKey({ factoryId, options }); |
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.
please check that tslint is enabled in VS code, you should have errors here
let key = this.toKey({ factoryId, options }); | ||
let existingWidget = this.widgets.get(key); | ||
if (existingWidget) { | ||
existingWidget.title.label = newLabel; |
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 should happen in the editor manager, nothing to do with the widget manager
@@ -61,7 +62,9 @@ export class WorkspaceCommandContribution implements CommandContribution { | |||
@inject(FileSystem) protected readonly fileSystem: FileSystem, | |||
@inject(WorkspaceServer) protected readonly workspaceServer: WorkspaceServer, | |||
@inject(SelectionService) protected readonly selectionService: SelectionService, | |||
@inject(OpenerService) protected readonly openerService: OpenerService | |||
@inject(OpenerService) protected readonly openerService: OpenerService, | |||
@inject(EditorManagerImpl) private editorService: EditorManagerImpl |
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.
program against the interface not implementation, i.e. EditorManager
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 moved the code over to the EditorManager, this introduced a dependency on the editor package. But now since the editor package also has a dependency on workspace, it won't compile, complaining that editor package doesn't exist (circular dependency). Any idea ?
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.
Could you try to remove a dependency from @theia/editor
to @theia/workspace
? The latter does not seem to be used in the former.
this.fileSystem.move(uri.toString(), uri.parent.resolve(name).toString()) | ||
); | ||
dialog.open().then(name => { | ||
this.fileSystem.move(uri.toString(), uri.parent.resolve(name).toString()).then(() => { |
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 @svenefftinge meant that you only should close an existing editor and open new:
const newUri = uri.parent.resolve(name);
this.fileSystem.move(uri.toString(), newUri.toString()).then(() => {
const widget = this.editorService.getEditor(uri);
if (editor) {
const selection = widget.editor.selection;
widget.close();
open(this.openerService, newUri, {selection});
}
});
Ideally, it should be done not here but on move/rename events from the filesystem in the editor extension. Making it here:
- breaks the unidirectional flow, only the filesystem should be the source of truth;
- covers only a specific case, e.g. renaming files in Finder won't make the same effect;
- makes the workspace extension depend on the editor extension.
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.
No, I did not mean that. A real rename seems better to me in general. But we have to take care of all the loose ends.
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.
Maybe we should have an generic key to reference the widget directly to avoid lose ends ?
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.
Could you elaborate on what you mean?
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 mean that if something has to keep a reference to an editor widget it should reference the widget object directly or we should provide a key that is not bound to any resource to refer to it. Like some abstract id.
So the URI would be used as key to open a new widget or find a widget for that URI but once you get a reference to it, it should be the id or the object directly, a bit like files names and files descriptors.. you open(filename) get a file descriptor so you could getWidgetForURI() get a widget for a URI but use getWidgetId() if you already had a widget and now know only it's id. I don't see where however that would be useful.. just keeping the object seems ok since serializing that doesn't make sense as the widget-manager is in the frontend.
Modifying a widget after creation will break the contract of the widget manager. After reload it will try to create a widget based on the old uri.
Why would it try to reload with an old uri ? if you updated it before you serialized the widget ?
Granted if it's changed between save and restore there's nothing we can do but that is true whatever we do...
Yes, that's probably not a good solution, but simply changing the widget state doesn't work either. |
hum.. with the workspace dependency removed from editor, now it makes another error pop up. In the editor package it complains that preferences is not there.
[compile] src/browser/editor-preferences.ts(16,8): error TS2307: Cannot find module '@theia/preferences/lib/common
Is there a way to specify the order of the compilation ?
________________________________
From: Anton Kosyakov <[email protected]>
Sent: October 2, 2017 10:32:04 AM
To: theia-ide/theia
Cc: Guy Perron; Author
Subject: Re: [theia-ide/theia] [WIP] [editor] Renaming a file should rename a corresponding editor (#582)
@akosyakov commented on this pull request.
________________________________
In packages/workspace/src/browser/workspace-commands.ts<#582 (comment)>:
@@ -61,7 +62,9 @@ export class WorkspaceCommandContribution implements CommandContribution {
@Inject(FileSystem) protected readonly fileSystem: FileSystem,
@Inject(WorkspaceServer) protected readonly workspaceServer: WorkspaceServer,
@Inject(SelectionService) protected readonly selectionService: SelectionService,
- @Inject(OpenerService) protected readonly openerService: OpenerService
+ @Inject(OpenerService) protected readonly openerService: OpenerService,
+ @Inject(EditorManagerImpl) private editorService: EditorManagerImpl
Could you try to remove a dependency from @theia/editor to @theia/workspace? The latter does not seem to be used in the former.
—
You are receiving this because you authored the thread.
Reply to this email directly, view it on GitHub<#582 (comment)>, or mute the thread<https://github.com/notifications/unsubscribe-auth/Aa23i_sYEAdOqprk6VeEt5qnn8nJ18ZFks5soPPkgaJpZM4PmiDZ>.
|
It actually fails now because preferences has a dependency on workspace. So we are back again with another circular dependency : workspace - editor - preferences.
That's since the introduction of the editor dependency in workspace.
You can fetch my patch if you want to try it out.
________________________________
From: Guy Perron
Sent: October 2, 2017 2:04:55 PM
To: theia-ide/theia
Subject: Re: [theia-ide/theia] [WIP] [editor] Renaming a file should rename a corresponding editor (#582)
hum.. with the workspace dependency removed from editor, now it makes another error pop up. In the editor package it complains that preferences is not there.
[compile] src/browser/editor-preferences.ts(16,8): error TS2307: Cannot find module '@theia/preferences/lib/common
Is there a way to specify the order of the compilation ?
________________________________
From: Anton Kosyakov <[email protected]>
Sent: October 2, 2017 10:32:04 AM
To: theia-ide/theia
Cc: Guy Perron; Author
Subject: Re: [theia-ide/theia] [WIP] [editor] Renaming a file should rename a corresponding editor (#582)
@akosyakov commented on this pull request.
________________________________
In packages/workspace/src/browser/workspace-commands.ts<#582 (comment)>:
@@ -61,7 +62,9 @@ export class WorkspaceCommandContribution implements CommandContribution {
@Inject(FileSystem) protected readonly fileSystem: FileSystem,
@Inject(WorkspaceServer) protected readonly workspaceServer: WorkspaceServer,
@Inject(SelectionService) protected readonly selectionService: SelectionService,
- @Inject(OpenerService) protected readonly openerService: OpenerService
+ @Inject(OpenerService) protected readonly openerService: OpenerService,
+ @Inject(EditorManagerImpl) private editorService: EditorManagerImpl
Could you try to remove a dependency from @theia/editor to @theia/workspace? The latter does not seem to be used in the former.
—
You are receiving this because you authored the thread.
Reply to this email directly, view it on GitHub<#582 (comment)>, or mute the thread<https://github.com/notifications/unsubscribe-auth/Aa23i_sYEAdOqprk6VeEt5qnn8nJ18ZFks5soPPkgaJpZM4PmiDZ>.
|
|
Changing to preferences-api got one step further. Now it fails to find |
got over it after adding filesystem dependency inside editor's package.json |
|
You should put a breakpoint here: https://github.com/theia-ide/theia/blob/31d18c38aa08e6b81d127fdc8de3a03d6796dbbf/packages/filesystem/src/common/file-resource.ts#L28
|
@akosyakov I didn't find anything for a file "rename" in chokidar i.e paulmillr/chokidar#303. I guess we would have to implement it chokidar-client side ourselves? |
I'm not sure about doing it in 2 steps though, how would the ui trigger the onDidChangeUri method of file-resource? Would we add a temporary method to that resource that can trigger the event from outside (instead of from the fs-watcher). Sorry maybe I misunderstood but I'm having an easier time understanding the event from the FS in the backend then UI -> Resource.OnDidChangeUri.fire() (usually the emitter is private right?). |
I meant to look into how vscode using chokidar. I've just looked myself, I was wrong: they are not able to detect rename as well and it only works if it triggered via UI. |
@epatpol even the fs-watcher would need such a method, it would fire it's own moved event I guess and all file resources that match would fire the onDidChangeURI event. We could do the same with the UI, a move would fire an event and the file resource would listen on that and fire its own event. If we were to change the UI to the fs-watcher we could just change how fires that first event. |
25cbc36
to
476d42c
Compare
this.fileSystem.move(uri.toString(), uri.parent.resolve(name).toString()).then(() => { | ||
this.editorService.renameWidget(uri.parent.resolve(uri.path.base), uri.parent.resolve(name), name); | ||
let event: IUriChangedEvent = { factoryId: uri.parent.resolve(uri.path.base), options: uri.parent.resolve(name), newOptions: name, newLabel: ' ' }; | ||
this.uiWatcher.onIUriChangedEmitter.fire(event); |
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.
couple of things
- you don't need a watcher
- nothing actually uses that event at the moment ?
- you still call editorservice rename-widget
It should be that workspace-commands fires the event
and editor service receive it at the very least
Might be other considerations too.. need to think about this more
4c16fa7
to
0280980
Compare
@@ -36,8 +36,11 @@ export default new ContainerModule((bind: interfaces.Bind, unbind: interfaces.Un | |||
(props: FileDialogProps) => | |||
createFileDialog(ctx.container, props) | |||
); | |||
bind(WorkspaceCommandContribution).toSelf().inSingletonScope(); |
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.
This should not be there
@@ -56,7 +56,8 @@ export class WidgetManager { | |||
|
|||
constructor( | |||
@inject(ContributionProvider) @named(WidgetFactory) protected readonly factoryProvider: ContributionProvider<WidgetFactory>, | |||
@inject(ILogger) protected logger: ILogger) { | |||
@inject(ILogger) protected logger: ILogger | |||
) { |
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.
no need to change this
getWidgetsMap(): Map<string, Widget> { | ||
return this.widgets; | ||
} | ||
|
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.
this is wrong you should not expose the internal map
@@ -105,7 +110,7 @@ export class WidgetManager { | |||
return undefined; | |||
} | |||
|
|||
protected toKey(options: WidgetConstructionOptions) { | |||
public toKey(options: WidgetConstructionOptions) { |
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.
You should not need this public either
@@ -16,6 +16,7 @@ import { EditorCommandHandlers } from "./editor-command"; | |||
import { EditorKeybindingContribution, EditorKeybindingContext } from "./editor-keybinding"; | |||
import { bindEditorPreferences } from './editor-preferences'; | |||
import { WidgetFactory } from '@theia/core/lib/browser/widget-manager'; | |||
// import { WorkspaceCommandContribution } from '@theia/workspace/lib/browser/workspace-commands'; |
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.
remove
options = newOptions; | ||
key = this.widgetManager.toKey({ factoryId, options }); | ||
this.widgetManager.getWidgetsMap().set(key, widget); | ||
} |
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.
Ditto... this one makes sense in the widget-manager
newOptions: uri.parent.resolve(name).toString(), | ||
newLabel: name | ||
}; | ||
this.onIUriChangedEmitter.fire(event); |
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 thinking more about this it would make more sense to have FileSytem.move() fire this event and have the event emitter there.
From the workspace command it looks quite odd..
bind(CommandContribution).to(WorkspaceCommandContribution).inSingletonScope(); | ||
bind(MenuContribution).to(FileMenuContribution).inSingletonScope(); | ||
|
||
|
||
|
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.
remove
|
||
protected readonly onIUriChangedEmitter = new Emitter<IUriChangedEvent>(); | ||
|
||
get onIUriChanged(): Event<IUriChangedEvent> { |
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.
We should move this to FileSytem and call it FileMovedEvent and it should be about what moved.
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.
Once the EditorManager receives it it can check if it has a editor for that URI and update it.
) { | ||
this.currentObserver = new EditorManagerImpl.Observer('current', app); | ||
this.activeObserver = new EditorManagerImpl.Observer('active', app); | ||
uiEvent.onIUriChanged(event => { | ||
this.renameWidget(event.factoryId.toString(), event.options, event.newOptions, event.newLabel); |
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.
We should get the FileMovedEvent from FS here.
And then figureout the proper way to rename based on the URI change. we have all the info needed.
Signed-off-by: Guy Perron <[email protected]>
This PR is not being actively worked-on - closing for now. |
Signed-off-by: Guy Perron [email protected]