Adds a better diff design and removes the select and copy feature
This commit is contained in:
+2
-2
@@ -27,7 +27,7 @@ export interface TerminalTheme {
|
||||
|
||||
export interface HelderConfig {
|
||||
ai: { command: string; autoLaunch: boolean }
|
||||
editor: { autoSave: boolean; tabSize: number; wordWrap: WordWrap; copyOnSelect: boolean }
|
||||
editor: { autoSave: boolean; tabSize: number; wordWrap: WordWrap }
|
||||
git: { confirmDiscard: boolean; confirmStage: boolean; confirmUnstage: boolean; defaultDiffMode: DiffMode; refreshInterval: number }
|
||||
files: { exclude: string[]; followGitignore: boolean }
|
||||
terminal: {
|
||||
@@ -54,7 +54,7 @@ export interface HelderConfig {
|
||||
|
||||
export const DEFAULTS: HelderConfig = {
|
||||
ai: { command: 'claude', autoLaunch: true },
|
||||
editor: { autoSave: false, tabSize: 4, wordWrap: 'markdown', copyOnSelect: true },
|
||||
editor: { autoSave: false, tabSize: 4, wordWrap: 'markdown' },
|
||||
git: { confirmDiscard: true, confirmStage: false, confirmUnstage: false, defaultDiffMode: 'diff', refreshInterval: 10000 },
|
||||
files: { exclude: [], followGitignore: false },
|
||||
terminal: {
|
||||
|
||||
+14
-1
@@ -1,3 +1,4 @@
|
||||
import { mkdirSync } from 'node:fs'
|
||||
import { app, crashReporter, dialog, shell, BrowserWindow, type WebContents } from 'electron'
|
||||
import { formatErr, getLogDir, getLogPath, initLogger, log, logger } from './logger'
|
||||
|
||||
@@ -26,10 +27,22 @@ let fatalDialogOpen = false
|
||||
* native crashes, and the log file should exist before anything can fail. */
|
||||
export function initDiagnostics(isDev: boolean): void {
|
||||
// app.getPath('logs') is ~/Library/Logs/<name> on macOS, so the name must be
|
||||
// set before we ask for the path or the folder is called "Electron".
|
||||
// set first or the folder is called "Electron". The name later becomes the
|
||||
// project's own, hence the paths are read here and pinned to "Helder".
|
||||
app.setName('Helder')
|
||||
let pinErr: unknown = null
|
||||
try {
|
||||
for (const name of ['userData', 'logs'] as const) {
|
||||
const dir = app.getPath(name)
|
||||
mkdirSync(dir, { recursive: true }) // setPath throws on a directory that does not exist yet
|
||||
app.setPath(name, dir)
|
||||
}
|
||||
} catch (e) {
|
||||
pinErr = e // startup must survive this; the logger does not exist yet
|
||||
}
|
||||
|
||||
initLogger({ dir: app.getPath('logs'), mirror: isDev })
|
||||
if (pinErr) logger.warn('session', 'could not pin the app paths — a rename may move them', { err: formatErr(pinErr) })
|
||||
|
||||
// Native minidumps for crashes no JS handler can see. Local only — nothing is
|
||||
// uploaded anywhere (there is no server, and this is a personal tool).
|
||||
|
||||
+11
-8
@@ -3,7 +3,7 @@ import { readFileSync, statSync } from 'node:fs'
|
||||
import { spawn } from 'node:child_process'
|
||||
import { app, dialog, shell, BrowserWindow, ipcMain, Menu } from 'electron'
|
||||
import { watch, type FSWatcher } from 'chokidar'
|
||||
import { addRecentProject, getName, getRecentProjects, getRoot, openDialog, setRoot } from './project'
|
||||
import { addRecentProject, getName, getProjectTitle, getRecentProjects, getRoot, openDialog, setRoot } from './project'
|
||||
import { createProjectDir, createProjectFile, deleteProjectFile, readAll, readDirChildren, readImageDataUrl, readProjectFile, readTree, renameProjectEntry, writeProjectFile } from './fs-service'
|
||||
import { commit, discard, load, push, stage, unstage } from './git-service'
|
||||
import { createPty, killAllPtys, killPty, ptyAvailable, resizePty, writePty } from './pty-service'
|
||||
@@ -200,12 +200,12 @@ function buildAppMenu(): Menu {
|
||||
/**
|
||||
* macOS reads the Dock tile's name from the bundle's CFBundleName at launch, so
|
||||
* every Helder process shows the same "Helder" tooltip — `app.setName()` moves
|
||||
* the menu-bar name and the paths, but never the Dock label. The open project is
|
||||
* the menu-bar name, but never the Dock label. The open project is
|
||||
* therefore named in the Dock *menu* instead: a disabled first item, so a
|
||||
* right-click tells the two tiles apart.
|
||||
*/
|
||||
function buildDockMenu(): Menu {
|
||||
const name = getName()
|
||||
const name = getRoot() ? getProjectTitle() : ''
|
||||
return Menu.buildFromTemplate([
|
||||
...(name ? [{ label: name, enabled: false }, { type: 'separator' as const }] : []),
|
||||
{ label: 'New Window', click: () => spawnInstance() },
|
||||
@@ -357,11 +357,13 @@ function registerIpc(): void {
|
||||
})
|
||||
}
|
||||
|
||||
/** The window title and the Dock menu both carry the open project's folder name
|
||||
* (the title falls back to the app name when nothing is open). Push both after a
|
||||
* project change. */
|
||||
/** Window title, app name and Dock menu all carry the open project's name. The
|
||||
* menu is rebuilt because the appMenu role items (About, Hide, Quit) read
|
||||
* `app.name` once, at build time. */
|
||||
function syncProjectChrome(): void {
|
||||
const title = getName() || 'Helder'
|
||||
const title = getProjectTitle()
|
||||
app.setName(title)
|
||||
Menu.setApplicationMenu(buildAppMenu())
|
||||
for (const w of BrowserWindow.getAllWindows()) w.setTitle(title)
|
||||
app.dock?.setMenu(buildDockMenu())
|
||||
}
|
||||
@@ -374,7 +376,7 @@ function createWindow(): void {
|
||||
minHeight: 680,
|
||||
show: false,
|
||||
backgroundColor: '#16171a',
|
||||
title: getName() || 'Helder',
|
||||
title: getProjectTitle(),
|
||||
titleBarStyle: isMac ? 'hiddenInset' : 'default',
|
||||
trafficLightPosition: isMac ? { x: 14, y: 12 } : undefined,
|
||||
webPreferences: {
|
||||
@@ -424,6 +426,7 @@ app.whenReady().then(async () => {
|
||||
logger.info('session', 'ready', { root: initialRoot, logPath: getLogPath() })
|
||||
if (initialRoot) {
|
||||
await resolveConfig(initialRoot)
|
||||
syncProjectChrome()
|
||||
await addRecentProject(initialRoot)
|
||||
startWatcher()
|
||||
startConfigWatcher()
|
||||
|
||||
+27
-1
@@ -1,7 +1,8 @@
|
||||
import { basename, join, resolve } from 'node:path'
|
||||
import { existsSync, statSync } from 'node:fs'
|
||||
import { existsSync, readFileSync, statSync } from 'node:fs'
|
||||
import { readFile, writeFile } from 'node:fs/promises'
|
||||
import { app, dialog, BrowserWindow } from 'electron'
|
||||
import { formatErr, logger } from './logger'
|
||||
|
||||
/**
|
||||
* One project per window. The root is resolved (in order) from $HELDER_PROJECT or
|
||||
@@ -34,6 +35,31 @@ export function getName(): string {
|
||||
return root ? basename(root) || root : ''
|
||||
}
|
||||
|
||||
/**
|
||||
* The name this process shows in the macOS menu bar: `APP_NAME` from the
|
||||
* project's .env, else the folder name. Kept apart from `getName()`, which stays
|
||||
* the folder name for recents and the renderer.
|
||||
*/
|
||||
export function getProjectTitle(): string {
|
||||
if (!root) return 'Helder'
|
||||
try {
|
||||
let raw: string | null = null
|
||||
for (const line of readFileSync(join(root, '.env'), 'utf8').split(/\r?\n/)) {
|
||||
if (line.trimStart().startsWith('#')) continue
|
||||
const m = /^\s*APP_NAME\s*=(.*)$/.exec(line)
|
||||
if (m) raw = m[1]
|
||||
}
|
||||
const value = (raw ?? '').trim().replace(/^(['"])([\s\S]*)\1$/, '$2').trim()
|
||||
if (value) return value
|
||||
} catch (e) {
|
||||
// A project without a .env is the normal case, not a fault.
|
||||
if ((e as NodeJS.ErrnoException)?.code !== 'ENOENT') {
|
||||
logger.warn('project', '.env unreadable — using the folder name', { err: formatErr(e) })
|
||||
}
|
||||
}
|
||||
return getName()
|
||||
}
|
||||
|
||||
/** Point the window at a project root (absolute) and remember it in recents. */
|
||||
export function setRoot(next: string): void {
|
||||
root = resolve(next)
|
||||
|
||||
Reference in New Issue
Block a user