vscode 无法保存标题为空的笔记本文件,当notebook.formatOnSave.enabled设置为true时,

jexiocij  于 5个月前  发布在  Vscode
关注(0)|答案(8)|浏览(57)
  1. 设置 notebook.formatOnSave.enabled = true
  2. 创建一个新的笔记本,向单元格中添加一些文本
  3. 保存文件 saved.ipynb
    🐛 一个空白笔记本文件 saved.ipynb 被创建,未命名的笔记本仍然处于打开状态
    预期:保存的笔记本应该以正确的内容打开。
2ul0zpep

2ul0zpep1#

在深入研究代码后,我发现了类似的问题 #138850 ,它通过检查存储的工作副本是否由正确的所有者创建来修复,以避免意外被处理掉。

这里也是以类似的方式发生的:

  • 保存时启用笔记本格式
  • 创建一个未命名的编辑器( untitled 1 ),在 Jupyter Notebook 中重新打开
  • 输入并使其处于脏状态
  • 保存,因为它是未命名的,所以会经过 Save As 工作流程
  • 用户提供新的文件名/路径( test.ipynb )
  • 由 vscode/src/vs/workbench/services/workingCopy/common/fileWorkingCopyManager.ts 创建的新的工作副本(我们称之为 workingCopy1 )

vscode/src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts 中的第67行
| | this._workingCopy?.dispose(); |

  • Save As 继续运行,但然后它发现新的工作副本 workingCopy1 已经被处理掉了,于是提前退出

vscode/src/vs/workbench/services/workingCopy/common/storedFileWorkingCopy.ts 中的第934行到第942行
| | // 我们必须防止在这个点被处理掉。可能是触发了 save() 操作,然后立即跟着 dispose() 操作,没有等待。通常情况下,如果我们处于脏状态,我们是不能被处理掉的,但是如果我们不脏,save() 和 dispose() 可以在同一时间触发,而不需要等待 save() 完成。如果我们被处理掉了(isDisposed()),我们就有风险将过期的内容保存到磁盘上(参见 https://github.com/microsoft/vscode/issues/50942 )。为了解决这个问题,当我们被处理掉时,我们不会将内容存储到磁盘上。 |
| | if(this.isDisposed()){ |
| | return; |
| | } |
由于我们没有对工作副本对象进行引用计数,并且对于资源,我们共享相同的对象,因此它可能会被错误的调用者处理掉(如 #138850 )。不过我不确定这种情况的简单修复方法是什么,因为我们肯定希望从编辑器模型中处理掉工作副本(实际上这是最常见的情况)。@bpasero 这里有什么建议吗?

yks3o0rb

yks3o0rb2#

请注意,没有任何数据丢失。新文件被正确创建并保存到磁盘,只是由于提前返回,它没有用新资源替换脏的未命名编辑器。

7lrncoxx

7lrncoxx3#

@rebornix 这是笔记本模型集合与文本模型集合表现略有不同所导致的后果。
文本模型集合有一个额外的安全防护措施,永远不允许销毁一个脏的工作副本:
vscode/src/vs/workbench/services/textmodelResolver/common/textModelResolverService.ts
第124行到第128行
| | if(modelinstanceofTextFileEditorModel){ |
| | // 文本文件模型具有防止它们被销毁的条件,所以我们必须等待直到我们可以销毁 |
| | awaitthis.textFileService.files.canDispose(model); |
| | } |
这是由这里的一个 canDispose 方法提供的:
vscode/src/vs/workbench/services/textfile/common/textFileEditorModelManager.ts
第545行到第546行
| | canDispose(model: TextFileEditorModel): true|Promise{ |
我们在这里为存储的文件工作副本提供了相同的方法:
vscode/src/vs/workbench/services/workingCopy/common/storedFileWorkingCopyManager.ts
第647行到第648行
| | canDispose(workingCopy: IStoredFileWorkingCopy): true|Promise{ |
但它需要以类似的方式从笔记本模型集合中调用:
vscode/src/vs/workbench/contrib/notebook/common/notebookEditorModelResolverServiceImpl.ts
第104行到第105行
| | protecteddestroyReferencedObject(_key: string,object: Promise): void{ |
我能想到的唯一替代方案是将以某种方式将引用集合传递给文件工作副本管理器,以便使引用计数正确,但我实际上建议在这里的行为保持一致,并像文本文件一样执行相同操作。

kjthegm6

kjthegm64#

哦,我忘记了这段代码:
vscode/src/vs/workbench/contrib/notebook/common/notebookEditorModelResolverServiceImpl.ts
第77行到第80行
| | // 每当笔记本模型变得脏时,我们会自动引用它,以确保至少存在一个引用。这保证了具有未保存更改的模型永远不会被销毁。 |
| | letonDirtyAutoReference: IReference|undefined; |
所以我们确实有一个机制,在笔记本变得脏时自动引用,以确保我们永远不会销毁一个脏的工作副本。我认为在这种情况下,笔记本不是脏的,因此可以被妥善处理。
真正的问题是,一个笔记本工作副本在没有被引用计数的情况下就存在:
vscode/src/vs/workbench/services/workingCopy/common/fileWorkingCopyManager.ts
第467行到第471行
| | if(this.uriIdentityService.extUri.isEqual(source,target)&&this.get(source)){ |
| | targetStoredFileWorkingCopy=awaitthis.stored.resolve(source); |
| | }else{ |
| | targetStoredFileWorkingCopy=awaitthis.stored.resolve(target); |
| | } |
这只会在"另存为"调用或保存未命名的笔记本时发生。那时我已经不喜欢这样做了,因为这意味着工作副本的生命周期与外部世界的引用计数无关。
我不确定如何实际解决这个问题...

qvk1mo1f

qvk1mo1f5#

我认为这归结为在文件工作副本管理器内向笔记本收集教a+1关于参考。

cyvaqqii

cyvaqqii6#

感谢bpasero的解释,我尝试利用"canDispose"并发现它效果很好。我的理解是,在保存和格式化过程中,笔记本模型仍然是脏的,因此无法处理,但只有在StoredFileWorkingCopy完成保存(保存参与者+fs保存)后才会处理。然而,我不确定如何处理未命名的问题:https://github.com/microsoft/vscode/pull/180560/files#diff-9a4a06f8a593491389b6ab180d612e734522b33432a84c30d58485e09dd864f3R87 ,因为canDispose在IStoredFileWorkingCopyManager中实现,但IUntitledFileWorkingCopyManager没有实现。

muk1a3rh

muk1a3rh7#

是的,这段代码的主要目的是延长处理时间,直到笔记本变脏。但是我想知道这个bug在以下情况下是否还能重现:

  • 有一个非脏的笔记本
  • 只是保存以触发不使笔记本变脏的保存参与者

这很可能也会对文件产生影响,但我想到目前为止,我们从未将模型作为保存参与者的一部分再次解决。相反,我们正在传递模型:
vscode/src/vs/workbench/services/textfile/common/textfiles.ts
第322行至第327行
| | participate( |
| | model: ITextFileEditorModel, |
| | context: {reason: SaveReason}, |
| | progress: IProgress, |
| | token: CancellationToken |
| | ): Promise; |
我想知道我们是否可以为笔记本做同样的处理作为缓解措施?最终,工作副本也传递工作副本,所以不需要再次解析它:
vscode/src/vs/workbench/services/workingCopy/common/workingCopyFileService.ts
第93行至第98行
| | participate( |
| | workingCopy: IStoredFileWorkingCopy, |
| | context: {reason: SaveReason}, |
| | progress: IProgress, |
| | token: CancellationToken |
| | ): Promise; |
你所做的更改将在笔记本变脏时起作用,我认为你已经弄清楚了在等待 canDispose 时跟踪引用创建所需的微妙之处,特别是 modelsToDispose Set:
vscode/src/vs/workbench/services/textmodelResolver/common/textModelResolverService.ts
第25行
| | privatereadonlymodelsToDispose=newSet(); |
如果我们走这条路,我想知道是否应该有一个我们可以重用于文件和笔记本的通用基类,这样我们就不会重复这种逻辑。
我仍然认为将引用计数集合传递给管理器会更干净。至于未命名的工作副本/文件:我认为我从未需要实现 canDispose ,因为未命名副本的管理方式没有问题。再说一次,我对 canDispose 不太满意,如果可以避免的话,我不想把它推广得更远。澄清一下,今天在笔记本解析器中存在的来自脏工作副本的自动引用是为了避免这种技巧而明确做的(我认为Jo做了)。

f0brbegy

f0brbegy8#

我查看了你的更改,它已经✅ ,这意味着现在它与文本文件相同。也许我们暂时保持这样...
我查看了我们在管理器中解析工作副本的位置,有两个位置:

另存为(此问题):

vscode/src/vs/workbench/services/workingCopy/common/fileWorkingCopyManager.ts
第467行到第471行 in 3a69e15
| if(this.uriIdentityService.extUri.isEqual(source,target)&&this.get(source)){ |
| targetStoredFileWorkingCopy=awaitthis.stored.resolve(source); |
| }else{ |
| targetStoredFileWorkingCopy=awaitthis.stored.resolve(target); |
| } |

从移动/复制操作恢复

vscode/src/vs/workbench/services/workingCopy/common/storedFileWorkingCopyManager.ts
第417行到第420行 in 3a69e15
| awaitthis.resolve(workingCopyToRestore.target,{ |
| reload: {async: false},//强制重新加载 |
| contents: workingCopyToRestore.snapshot |
| }); |
即使我们有将引用传递到底层的引用集合,我甚至不知道在这两个地方何时释放引用:-/。在这里,我必须假设创建工作副本管理器的组件拥有所有工作副本的生命周期。

相关问题