import { NgZone } from '@angular/core'; import { SidebarPlusNoticesService } from './notices.service'; import { SFTPFile, SFTPPanelComponent } from 'tabby-ssh'; import { SidebarPlusEditorService } from './editorLauncher.service'; import { SidebarPlusTempFilesService } from './tempFiles.service'; import { SftpTransfers } from './transfers'; /** * How the local copy is handed over once downloaded. * * `editor` is the only thing a double-click can reach; `openWith` is the * explicit context-menu escape hatch (Windows' own "Open with..." dialog). */ export type OpenMode = 'editor' | 'openWith'; /** The live SFTP transport, borrowed from the one member that publicly exposes its type. */ type SftpSession = SFTPPanelComponent['sftp']; /** Asks the user a yes/no question. Supplied by the panel, which owns the HTML modal (piège #42). */ export type ConfirmFn = (message: string, confirmLabel: string) => Promise; /** * Opens a remote file in the configured editor, and sends it back on every * save. * * The file is copied to a private temp directory, handed to the editor, and * watched; each save re-uploads it. Nothing is ever silent — every upload * raises a notification, because this writes to a live server. * * Never `shell.openPath()`, which is what this used to do: opening by OS * association *runs* an executable, or a script whose extension is bound to an * interpreter, instead of editing it. The editor is spawned explicitly, and the * only route to the OS association is the caller passing `openWith` — the * context menu's deliberate escape hatch, unreachable by double-click. * * Changes made *on the server* while a file is open are detected, not ignored. * Two moments matter, and neither is decided without asking: * * - **Reopening** an already-open file used to hand back the local copy * unconditionally, so a file edited on the server came back as the old * version. It is now compared against the server first. * - **Saving** used to overwrite whatever had appeared in the meantime, with * no warning at all — the failure mode that actually loses someone's work. */ export declare class SftpRemoteEditor { private notifications; private editors; private transfers; private temp; private confirm; private zone; private sessions; /** Sends still in flight — see `track()`. */ private uploads; constructor(notifications: SidebarPlusNoticesService, editors: SidebarPlusEditorService, transfers: SftpTransfers, temp: SidebarPlusTempFilesService, confirm: ConfirmFn, zone: NgZone); /** * Runs something back inside Angular's zone. * * Everything this class does after an edit starts from an `fs.watch` * callback, and zone.js — the browser build, the only one Tabby loads — * does not patch Node's EventEmitter. So the conflict modal and every * notification raised from that path were being opened outside the zone: no * change detection followed, and they appeared only when something else * happened to trigger a cycle (piège #41). This is the same guard * `SftpDragOut` already applies to its own state. */ private inZone; /** Hands a local copy over. The opener is settled by `edit()` before anything is downloaded. */ private open; /** * Watching the *directory* rather than the file: many editors save by * writing a new file and renaming it over the old one, which severs a * watch held on the original inode and would make the second save onwards * go unnoticed. */ edit(sftp: SftpSession, item: SFTPFile, mode?: OpenMode): Promise; /** * Reopening a file already open here. * * The old behaviour — hand the local copy straight back — exists for a * reason: a second download would wipe unsaved work. It is kept, but only * once the server has been asked. When the remote file has moved on, an * untouched copy is refreshed silently, and a copy with unsaved changes * never is without a decision: no automatic answer can be right when both * sides changed. */ private reopen; /** The remote entry as the server reports it now; falls back to the listing's own view. */ private remoteStamp; private changed; /** Whether the editor wrote to the copy since the last transfer in either direction. */ private hasLocalEdits; /** * Editors emit several filesystem events per save (truncate, write, * rename, mtime touch), so the upload is deferred until they stop coming. */ private scheduleUpload; /** * Keeps a handle on a send in flight, so `dispose()` can wait for it. * * `session.uploading` already says one is running, but a flag cannot be * awaited, and the temp directory must not be deleted from under a file * still being read out of it. */ private track; private uploadIfChanged; /** * Called when the browser is torn down — stops watching and removes the * temp copies. * * Watching stops at once: a save made after the panel is gone has nowhere * to be sent. The copies, on the other hand, wait for any send still in * flight — deleting the directory a file is being read out of is how a * save silently fails to reach the server, and it would happen precisely * when a connection drop tears the panel down mid-save. */ dispose(): void; } export {};