Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions docs/domain/file-io.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,13 @@
- If temp-file setup fails, saving falls back to writing the target directly. After a successful temp-file write, replacement tries `ReplaceFileW` for an existing target, then `MoveFileExW` with replace/write-through flags; a replacement failure makes the save fail.
- `Globals.dwLastError` holds the Win32 error. In `FileSave()`, check `ERROR_ACCESS_DENIED` and `ERROR_PATH_NOT_FOUND` before the generic fallback.

## Open-dialog initial directory

- `OpenFileDlg()` / `_ResolveOpenInitialDir()` in `src\Dialogs.c` prefer an explicit initial path, then the current file's directory, `Settings.DefaultDirectoryOverride`, and `Settings2.DefaultDirectory`.
- With none of those available and `Settings.SaveRecentFiles` enabled, Open uses the newest MRU entry's parent directory if it exists (the file itself need not exist). Otherwise it uses `Paths.WorkingDirectory`. It does not search older MRU entries.
- MRU paths must be sanitized, unquoted, and expanded with `Path_AbsoluteFromApp(..., true)` before taking the parent directory with the drive-root-safe `_GetParentDir()`. An unavailable parent clears the candidate so the shared resolver supplies the working directory. Save dialogs do not use history.
- An invalid final directory still falls back to Documents; Save also requires a writable directory. Focused coverage: `test\TestOpenDialogDirectory.ps1`.

## File watching

`InstallFileWatching()` uses `FindFirstChangeNotificationW` on the parent directory. Stop watching with `InstallFileWatching(false)` before saving, then reinstall it with `InstallFileWatching(true)` afterward.
Expand Down
4 changes: 4 additions & 0 deletions docs/domain/startup-and-window-lifecycle.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,7 @@ The runtime toggle is `IDM_EDIT_TOGGLE_PASTEBOARD`; `/B` enables Pasteboard Mode
- If there is no acknowledging target, the launched process adopts the AppID and opens a new window.
- The handoff to `DialogNewWindow` passes `-n` semantics, so a taskbar new-window request bypasses `ReuseWindow`.
- Every AppUserModel property set through the window property store must be cleared in `ClearWindowAppUserModelID()` during `WM_DESTROY`.

## Startup with a directory argument

- A directory passed as the startup path opens the file dialog rooted in that directory (`Notepad3.c` startup path handling). `OpenFileDlg()` may receive the same `HPATHL` as both output and initial-directory input; it must copy the initial directory before clearing the output path (`Dialogs.c`).
6 changes: 4 additions & 2 deletions readme/MenuEntriesAndCmds.md
Original file line number Diff line number Diff line change
Expand Up @@ -563,8 +563,10 @@ Editor behaviour, persistence, and platform integration. Most items are toggles

| Menu | Description |
|---|---|
| **Set to Current File's Directory** | Bind the *Open* / *Save As* default to the current file's directory. |
| **Reset to Default** | Restore the standard "Documents" default. |
| **Set to Current File's Directory** | Store the current file's directory as the *Open* / *Save As* default when no file is open. This is a fixed directory, not a setting that tracks later files. |
| **Reset to Default** | Clear the menu-set directory override and return to the configured `DefaultDirectory` or the normal fallback. |

An explicitly supplied directory (for example, a directory command-line argument) takes precedence over these defaults, as does the directory of an open file. With no current file or configured default, **Open** can use the most recent file's directory when **Remember Recent Files** is enabled. See [file-dialog starting directories](paths/FilePathHandling.md#file-dialog-starting-directory) for the full order and fallbacks.

### Remember

Expand Down
4 changes: 4 additions & 0 deletions readme/config/Configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,10 @@ This pattern is used by **Shift+F5** to update timestamps, e.g., `$Date: 2018/04

Default directory for open/save dialogs when no file is open. Paths can be relative to the Notepad3 program directory.

**Settings → Default Directory → Set to Current File's Directory** stores a separate override (`DefaultDirectoryOverride` under `[Settings]`); **Reset to Default** clears that override without changing this key. An explicit directory or the currently open file's directory takes precedence over both defaults.

If neither default is defined, **Open** tries the most recent file's existing directory when **Remember Recent Files** is enabled, then the working directory. This history fallback does not apply to **Save As**. See [file-dialog starting directories](../paths/FilePathHandling.md#file-dialog-starting-directory).

#### `DefaultExtension=txt`

Default extension for saved files (omit the leading dot).
Expand Down
2 changes: 2 additions & 0 deletions readme/faq/FAQ.md
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,8 @@ Yes. Notepad3 always looks beside the exe first — `%APPDATA%` is only consulte

`DefaultDirectory=` and `DefaultExtension=txt` under `[Settings2]`.

You can also choose **Settings → Default Directory → Set to Current File's Directory** to store a fixed default folder. With no current file and no default folder defined, **Open** tries the most recent file's directory if **Remember Recent Files** is enabled, then the working directory. See [file-dialog starting directories](../paths/FilePathHandling.md#file-dialog-starting-directory).

### How do I customize the date/time format for "Update Timestamps" (Shift+F5)?

You need to define **both** of the following in `[Settings2]`:
Expand Down
21 changes: 20 additions & 1 deletion readme/paths/FilePathHandling.md
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,25 @@ The Recent Files list in the File menu is persisted in `Notepad3.ini` under
`[Recent Files]`. Disable persistence with the `NoSaveRecent` setting.
Entries can be removed individually from the MRU dialog.

### File-dialog starting directory

The **Open** dialog chooses its starting directory in this order:

1. An explicitly supplied directory. Passing a directory rather than a file on the command line opens the dialog there.
2. The directory of the currently open file.
3. The fixed directory stored by **Settings → Default Directory → Set to Current File's Directory**.
4. `DefaultDirectory` under `[Settings2]`.
5. The most recent file's parent directory, if **Remember Recent Files** is enabled and that directory still exists.
6. The process working directory (set by the launcher, such as a shortcut's **Start in** folder).

To start Notepad3 with the Open dialog in the current directory, run `Notepad3.exe .`.

The recent file itself need not still exist. Only the newest history entry is considered; a missing directory falls back to the working directory rather than searching older entries. If the final directory is unavailable, the dialog falls back to **Documents**.

**Save As** uses the same defaults but does not use recent-file history, and falls back to **Documents** if the selected directory is not writable. **Reset to Default** clears only the menu-set override; it does not clear `DefaultDirectory` or force **Documents**.

Starting without a file or directory argument does not itself display the Open dialog. **Autoload Recent File** is a separate option that loads the newest history entry at startup; if loaded, that file's directory applies as the current-file directory above.

### Portable & redirected INI

By default `Notepad3.ini` lives next to `Notepad3.exe` (portable mode). The
Expand All @@ -304,7 +323,7 @@ descriptions.
|---|---|
| `AtomicFileSave` | Two-phase Save (temp file + ReplaceFileW). |
| `ResolveUNCPaths` | When opening a UNC path that's actually a mapped drive's share, prefer the drive-letter form for display. |
| `DefaultDirectory` | Initial directory of the **Open…** dialog. |
| `DefaultDirectory` | Default directory for **Open…** / **Save As** when no file is open; see [starting-directory precedence](#file-dialog-starting-directory). |
| `OpenWithDir` | Directory used by **Open with…**. |
| `FavoritesDir` | Where the Favorites list lives. |
| `GrepWinPath` | Override the grepWin executable lookup. |
Expand Down
24 changes: 22 additions & 2 deletions src/Dialogs.c
Original file line number Diff line number Diff line change
Expand Up @@ -7457,6 +7457,26 @@ static void _CanonicalizeInitialDir(HPATHL hpth_in_out, bool bMustBeWritable)
}
}

static void _ResolveOpenInitialDir(HPATHL hpth_in_out)
{
if (Path_IsEmpty(hpth_in_out) && Path_IsEmpty(Paths.CurrentFile) &&
Path_IsEmpty(Settings.DefaultDirectoryOverride) && Path_IsEmpty(Settings2.DefaultDirectory) &&
Settings.SaveRecentFiles && MRU_Count(Globals.pFileMRU) > 0) {
LPWSTR const fileBuf = Path_WriteAccessBuf(hpth_in_out, PATHLONG_MAX_CCH);
int const fileLength = MRU_Enum(Globals.pFileMRU, 0, fileBuf, (int)Path_GetBufCount(hpth_in_out));
Path_Sanitize(hpth_in_out);
if (fileLength > 0) {
Path_UnQuoteSpaces(hpth_in_out);
Path_AbsoluteFromApp(hpth_in_out, true);
_GetParentDir(hpth_in_out);
}
if (!Path_IsExistingDirectory(hpth_in_out)) {
Path_Empty(hpth_in_out, false);
}
}
_CanonicalizeInitialDir(hpth_in_out, false);
}


// ============================================================================
//
Expand Down Expand Up @@ -7691,15 +7711,15 @@ bool OpenFileDlg(HWND hwnd, HPATHL hfile_pth_io, const HPATHL hinidir_pth)
if (!hfile_pth_io) {
return false;
}
HPATHL hpth_dir = Path_Copy(hinidir_pth);
if (!Path_IsEmpty(hinidir_pth)) {
Path_Empty(hfile_pth_io, false);
}
WCHAR szDefExt[64] = { L'\0' };
WCHAR szFilter[EXTENSIONS_FILTER_BUFFER];
Style_GetFileFilterStr(szFilter, COUNTOF(szFilter), szDefExt, COUNTOF(szDefExt), false);

HPATHL hpth_dir = Path_Copy(hinidir_pth);
_CanonicalizeInitialDir(hpth_dir, false);
_ResolveOpenInitialDir(hpth_dir);

LPCWSTR defExt = StrIsNotEmpty(szDefExt) ? szDefExt : Settings2.DefaultExtension;
LPCWSTR initDir = Path_IsNotEmpty(hpth_dir) ? Path_Get(hpth_dir) : NULL;
Expand Down