@fresh-editor/fresh-editor 0.1.32 → 0.1.42

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,6 +1,61 @@
1
1
  # Release Notes
2
2
 
3
- ## 0.1.28 - Unreleased
3
+ ## 0.1.40
4
+
5
+ ### Features
6
+
7
+ * **Switch Project Command**: New "Switch Project" command (renamed from "Open Folder") to change project root with full context switch. Sessions are automatically saved and restored when switching projects, preserving open files, cursor positions, and split layouts.
8
+
9
+ * **Nested Submenu Support**: Menus now support nested submenus with proper arrow indicators and positioning.
10
+
11
+ * **Select Keybinding Map Command**: New popup selector to choose between different keybinding schemes.
12
+
13
+ * **Double-Click in File Dialog**: Can now double-click to open files in the file open dialog.
14
+
15
+ * **File Explorer UX Improvements**:
16
+ - Ctrl+E now focuses the file explorer instead of toggling it
17
+ - File explorer automatically focuses when closing the last tab
18
+ - Menu checkboxes properly sync with file explorer visibility state
19
+
20
+ * **Split Auto-Close**: Closing the last tab in a split now automatically closes the split.
21
+
22
+ ### Bug Fixes
23
+
24
+ * **Mouse Click Below Last Line**: Fixed mouse click below the last line incorrectly jumping to position 0,0.
25
+
26
+ * **Menu Checkbox Sync**: Fixed View menu checkboxes not syncing with file explorer visibility state.
27
+
28
+ * **Duplicate Buffer on Project Switch**: Fixed duplicate buffer creation when switching projects.
29
+
30
+ * **Wrong Upgrade Tip**: Fixed incorrect upgrade tip message (#293).
31
+
32
+ ### Infrastructure
33
+
34
+ * **Build System Overhaul**: Replaced cargo-dist with direct cargo builds and custom packaging for more control over the release process.
35
+
36
+ * **npm OIDC Publishing**: Improved npm publish workflow with OIDC trusted publishing and provenance attestations.
37
+
38
+ * **GitHub Actions Updates**: Bumped actions/checkout to v6, actions/upload-artifact to v5, actions/download-artifact to v6, and actions/setup-node to v6.
39
+
40
+ * **Test Improvements**: Many test reliability improvements including Windows compatibility fixes, flaky test fixes, and better test isolation for session persistence tests.
41
+
42
+ ---
43
+
44
+ ## 0.1.35
45
+
46
+ ### Features
47
+
48
+ * **XDG Config Paths**: Support standard XDG config paths for user configuration. On macOS, `~/.config/fresh/config.json` is now prioritized if it exists, in addition to the system default path. (@Yousa-Mirage)
49
+
50
+ ### Packaging
51
+
52
+ * **cargo-binstall**: Added cargo-binstall as an installation method in documentation. (@dvchd)
53
+
54
+ * **npm OIDC Publishing**: Switched npm publish to OIDC trusted publishing with provenance attestations.
55
+
56
+ ---
57
+
58
+ ## 0.1.28
4
59
 
5
60
  ### Features
6
61
 
package/README.md CHANGED
@@ -43,7 +43,9 @@ Fresh is engineered for speed. It delivers a low-latency experience, with text a
43
43
  | Fedora/RHEL | [.rpm](#fedorarhelopensuse-rpm) |
44
44
  | All platforms | [Pre-built binaries](#pre-built-binaries) |
45
45
  | npm | [npm / npx](#npm) |
46
+ | Rust users (Fast) | [cargo-binstall](#using-cargo-binstall) |
46
47
  | Rust users | [crates.io](#from-cratesio) |
48
+ | Nix | [Nix flakes](#nix-flakes) |
47
49
  | Developers | [From source](#from-source) |
48
50
 
49
51
  ### macOS (Homebrew)
@@ -95,6 +97,34 @@ Or try it without installing:
95
97
  npx @fresh-editor/fresh-editor
96
98
  ```
97
99
 
100
+ ### Using cargo-binstall
101
+
102
+ To install the binary directly without compiling (much faster than crates.io):
103
+
104
+ First, install cargo-binstall if you haven't already
105
+
106
+ ```bash
107
+ cargo install cargo-binstall
108
+ ```
109
+
110
+ Then install fresh
111
+
112
+ ```bash
113
+ cargo binstall fresh-editor
114
+ ```
115
+
116
+ ### Nix flakes
117
+
118
+ Run without installing:
119
+ ```bash
120
+ nix run github:sinelaw/fresh
121
+ ```
122
+
123
+ Or install to your profile:
124
+ ```bash
125
+ nix profile install github:sinelaw/fresh
126
+ ```
127
+
98
128
  ### From crates.io
99
129
 
100
130
  ```bash
package/binary-install.js CHANGED
@@ -1,212 +1,62 @@
1
- const { createWriteStream, existsSync, mkdirSync, mkdtemp } = require("fs");
2
- const { join, sep } = require("path");
3
- const { spawnSync } = require("child_process");
4
- const { tmpdir } = require("os");
5
-
6
- const axios = require("axios");
7
- const rimraf = require("rimraf");
8
- const tmpDir = tmpdir();
9
-
10
- const error = (msg) => {
11
- console.error(msg);
12
- process.exit(1);
13
- };
14
-
15
- class Package {
16
- constructor(platform, name, url, filename, zipExt, binaries) {
17
- let errors = [];
18
- if (typeof url !== "string") {
19
- errors.push("url must be a string");
20
- } else {
21
- try {
22
- new URL(url);
23
- } catch (e) {
24
- errors.push(e);
1
+ const https = require('https');
2
+ const fs = require('fs');
3
+ const path = require('path');
4
+ const { execSync } = require('child_process');
5
+ const { getBinaryInfo } = require('./binary');
6
+
7
+ const VERSION = require('./package.json').version;
8
+ const REPO = 'sinelaw/fresh';
9
+
10
+ function download(url, dest) {
11
+ return new Promise((resolve, reject) => {
12
+ const file = fs.createWriteStream(dest);
13
+ https.get(url, (response) => {
14
+ if (response.statusCode === 302 || response.statusCode === 301) {
15
+ download(response.headers.location, dest).then(resolve).catch(reject);
16
+ return;
25
17
  }
26
- }
27
- if (name && typeof name !== "string") {
28
- errors.push("package name must be a string");
29
- }
30
- if (!name) {
31
- errors.push("You must specify the name of your package");
32
- }
33
- if (binaries && typeof binaries !== "object") {
34
- errors.push("binaries must be a string => string map");
35
- }
36
- if (!binaries) {
37
- errors.push("You must specify the binaries in the package");
38
- }
39
-
40
- if (errors.length > 0) {
41
- let errorMsg =
42
- "One or more of the parameters you passed to the Binary constructor are invalid:\n";
43
- errors.forEach((error) => {
44
- errorMsg += error;
45
- });
46
- errorMsg +=
47
- '\n\nCorrect usage: new Package("my-binary", "https://example.com/binary/download.tar.gz", {"my-binary": "my-binary"})';
48
- error(errorMsg);
49
- }
50
-
51
- this.platform = platform;
52
- this.url = url;
53
- this.name = name;
54
- this.filename = filename;
55
- this.zipExt = zipExt;
56
- this.installDirectory = join(__dirname, "node_modules", ".bin_real");
57
- this.binaries = binaries;
58
-
59
- if (!existsSync(this.installDirectory)) {
60
- mkdirSync(this.installDirectory, { recursive: true });
61
- }
62
- }
63
-
64
- exists() {
65
- for (const binaryName in this.binaries) {
66
- const binRelPath = this.binaries[binaryName];
67
- const binPath = join(this.installDirectory, binRelPath);
68
- if (!existsSync(binPath)) {
69
- return false;
18
+ if (response.statusCode !== 200) {
19
+ reject(new Error(`Failed to download: ${response.statusCode}`));
20
+ return;
70
21
  }
71
- }
72
- return true;
73
- }
22
+ response.pipe(file);
23
+ file.on('finish', () => { file.close(); resolve(); });
24
+ }).on('error', reject);
25
+ });
26
+ }
74
27
 
75
- install(fetchOptions, suppressLogs = false) {
76
- if (this.exists()) {
77
- if (!suppressLogs) {
78
- console.error(
79
- `${this.name} is already installed, skipping installation.`,
80
- );
28
+ async function install() {
29
+ const info = getBinaryInfo();
30
+ const archiveName = `fresh-editor-${info.target}.${info.ext}`;
31
+ const url = `https://github.com/${REPO}/releases/download/v${VERSION}/${archiveName}`;
32
+ const archivePath = path.join(__dirname, archiveName);
33
+ const binDir = path.join(__dirname, 'bin');
34
+
35
+ console.log(`Downloading ${url}...`);
36
+ await download(url, archivePath);
37
+
38
+ fs.mkdirSync(binDir, { recursive: true });
39
+
40
+ if (info.ext === 'tar.xz') {
41
+ execSync(`tar -xJf "${archivePath}" -C "${binDir}" --strip-components=1`, { stdio: 'inherit' });
42
+ } else if (info.ext === 'zip') {
43
+ if (process.platform === 'win32') {
44
+ execSync(`powershell -Command "Expand-Archive -Path '${archivePath}' -DestinationPath '${binDir}' -Force"`, { stdio: 'inherit' });
45
+ // Move files from nested directory
46
+ const nested = path.join(binDir, `fresh-editor-${info.target}`);
47
+ if (fs.existsSync(nested)) {
48
+ fs.readdirSync(nested).forEach(f => {
49
+ fs.renameSync(path.join(nested, f), path.join(binDir, f));
50
+ });
51
+ fs.rmdirSync(nested);
81
52
  }
82
- return Promise.resolve();
83
- }
84
-
85
- if (existsSync(this.installDirectory)) {
86
- rimraf.sync(this.installDirectory);
87
- }
88
-
89
- mkdirSync(this.installDirectory, { recursive: true });
90
-
91
- if (!suppressLogs) {
92
- console.error(`Downloading release from ${this.url}`);
53
+ } else {
54
+ execSync(`unzip -o "${archivePath}" -d "${binDir}"`, { stdio: 'inherit' });
93
55
  }
94
-
95
- return axios({ ...fetchOptions, url: this.url, responseType: "stream" })
96
- .then((res) => {
97
- return new Promise((resolve, reject) => {
98
- mkdtemp(`${tmpDir}${sep}`, (err, directory) => {
99
- let tempFile = join(directory, this.filename);
100
- const sink = res.data.pipe(createWriteStream(tempFile));
101
- sink.on("error", (err) => reject(err));
102
- sink.on("close", () => {
103
- if (/\.tar\.*/.test(this.zipExt)) {
104
- const result = spawnSync("tar", [
105
- "xf",
106
- tempFile,
107
- // The tarballs are stored with a leading directory
108
- // component; we strip one component in the
109
- // shell installers too.
110
- "--strip-components",
111
- "1",
112
- "-C",
113
- this.installDirectory,
114
- ]);
115
- if (result.status == 0) {
116
- resolve();
117
- } else if (result.error) {
118
- reject(result.error);
119
- } else {
120
- reject(
121
- new Error(
122
- `An error occurred untarring the artifact: stdout: ${result.stdout}; stderr: ${result.stderr}`,
123
- ),
124
- );
125
- }
126
- } else if (this.zipExt == ".zip") {
127
- let result;
128
- if (this.platform.artifactName.includes("windows")) {
129
- // Windows does not have "unzip" by default on many installations, instead
130
- // we use Expand-Archive from powershell
131
- result = spawnSync("powershell.exe", [
132
- "-NoProfile",
133
- "-NonInteractive",
134
- "-Command",
135
- `& {
136
- param([string]$LiteralPath, [string]$DestinationPath)
137
- Expand-Archive -LiteralPath $LiteralPath -DestinationPath $DestinationPath -Force
138
- }`,
139
- tempFile,
140
- this.installDirectory,
141
- ]);
142
- } else {
143
- result = spawnSync("unzip", [
144
- "-q",
145
- tempFile,
146
- "-d",
147
- this.installDirectory,
148
- ]);
149
- }
150
-
151
- if (result.status == 0) {
152
- resolve();
153
- } else if (result.error) {
154
- reject(result.error);
155
- } else {
156
- reject(
157
- new Error(
158
- `An error occurred unzipping the artifact: stdout: ${result.stdout}; stderr: ${result.stderr}`,
159
- ),
160
- );
161
- }
162
- } else {
163
- reject(
164
- new Error(`Unrecognized file extension: ${this.zipExt}`),
165
- );
166
- }
167
- });
168
- });
169
- });
170
- })
171
- .then(() => {
172
- if (!suppressLogs) {
173
- console.error(`${this.name} has been installed!`);
174
- }
175
- })
176
- .catch((e) => {
177
- error(`Error fetching release: ${e.message}`);
178
- });
179
56
  }
180
57
 
181
- run(binaryName, fetchOptions) {
182
- const promise = !this.exists()
183
- ? this.install(fetchOptions, true)
184
- : Promise.resolve();
185
-
186
- promise
187
- .then(() => {
188
- const [, , ...args] = process.argv;
189
-
190
- const options = { cwd: process.cwd(), stdio: "inherit" };
191
-
192
- const binRelPath = this.binaries[binaryName];
193
- if (!binRelPath) {
194
- error(`${binaryName} is not a known binary in ${this.name}`);
195
- }
196
- const binPath = join(this.installDirectory, binRelPath);
197
- const result = spawnSync(binPath, args, options);
198
-
199
- if (result.error) {
200
- error(result.error);
201
- }
202
-
203
- process.exit(result.status);
204
- })
205
- .catch((e) => {
206
- error(e.message);
207
- process.exit(1);
208
- });
209
- }
58
+ fs.unlinkSync(archivePath);
59
+ console.log('fresh-editor installed successfully!');
210
60
  }
211
61
 
212
- module.exports.Package = Package;
62
+ module.exports = { install };
package/binary.js CHANGED
@@ -1,126 +1,29 @@
1
- const { Package } = require("./binary-install");
2
- const os = require("os");
3
- const cTable = require("console.table");
4
- const libc = require("detect-libc");
5
- const { configureProxy } = require("axios-proxy-builder");
6
-
7
- const error = (msg) => {
8
- console.error(msg);
9
- process.exit(1);
10
- };
11
-
12
- const {
13
- name,
14
- artifactDownloadUrl,
15
- supportedPlatforms,
16
- glibcMinimum,
17
- } = require("./package.json");
18
-
19
- const builderGlibcMajorVersion = glibcMinimum.major;
20
- const builderGlibcMInorVersion = glibcMinimum.series;
21
-
22
- const getPlatform = () => {
23
- const rawOsType = os.type();
24
- const rawArchitecture = os.arch();
25
-
26
- // We want to use rust-style target triples as the canonical key
27
- // for a platform, so translate the "os" library's concepts into rust ones
28
- let osType = "";
29
- switch (rawOsType) {
30
- case "Windows_NT":
31
- osType = "pc-windows-msvc";
32
- break;
33
- case "Darwin":
34
- osType = "apple-darwin";
35
- break;
36
- case "Linux":
37
- osType = "unknown-linux-gnu";
38
- break;
1
+ const os = require('os');
2
+ const path = require('path');
3
+
4
+ function getBinaryInfo() {
5
+ const platform = os.platform();
6
+ const arch = os.arch();
7
+
8
+ const targets = {
9
+ 'darwin-x64': { target: 'x86_64-apple-darwin', ext: 'tar.xz' },
10
+ 'darwin-arm64': { target: 'aarch64-apple-darwin', ext: 'tar.xz' },
11
+ 'linux-x64': { target: 'x86_64-unknown-linux-gnu', ext: 'tar.xz' },
12
+ 'linux-arm64': { target: 'aarch64-unknown-linux-gnu', ext: 'tar.xz' },
13
+ 'win32-x64': { target: 'x86_64-pc-windows-msvc', ext: 'zip' }
14
+ };
15
+
16
+ const key = `${platform}-${arch}`;
17
+ const info = targets[key];
18
+
19
+ if (!info) {
20
+ throw new Error(`Unsupported platform: ${platform}-${arch}`);
39
21
  }
40
22
 
41
- let arch = "";
42
- switch (rawArchitecture) {
43
- case "x64":
44
- arch = "x86_64";
45
- break;
46
- case "arm64":
47
- arch = "aarch64";
48
- break;
49
- }
50
-
51
- if (rawOsType === "Linux") {
52
- if (libc.familySync() == "musl") {
53
- osType = "unknown-linux-musl-dynamic";
54
- } else if (libc.isNonGlibcLinuxSync()) {
55
- console.warn(
56
- "Your libc is neither glibc nor musl; trying static musl binary instead",
57
- );
58
- osType = "unknown-linux-musl-static";
59
- } else {
60
- let libcVersion = libc.versionSync();
61
- let splitLibcVersion = libcVersion.split(".");
62
- let libcMajorVersion = splitLibcVersion[0];
63
- let libcMinorVersion = splitLibcVersion[1];
64
- if (
65
- libcMajorVersion != builderGlibcMajorVersion ||
66
- libcMinorVersion < builderGlibcMInorVersion
67
- ) {
68
- // We can't run the glibc binaries, but we can run the static musl ones
69
- // if they exist
70
- console.warn(
71
- "Your glibc isn't compatible; trying static musl binary instead",
72
- );
73
- osType = "unknown-linux-musl-static";
74
- }
75
- }
76
- }
77
-
78
- // Assume the above succeeded and build a target triple to look things up with.
79
- // If any of it failed, this lookup will fail and we'll handle it like normal.
80
- let targetTriple = `${arch}-${osType}`;
81
- let platform = supportedPlatforms[targetTriple];
82
-
83
- if (!platform) {
84
- error(
85
- `Platform with type "${rawOsType}" and architecture "${rawArchitecture}" is not supported by ${name}.\nYour system must be one of the following:\n\n${Object.keys(
86
- supportedPlatforms,
87
- ).join(",")}`,
88
- );
89
- }
90
-
91
- return platform;
92
- };
93
-
94
- const getPackage = () => {
95
- const platform = getPlatform();
96
- const url = `${artifactDownloadUrl}/${platform.artifactName}`;
97
- let filename = platform.artifactName;
98
- let ext = platform.zipExt;
99
- let binary = new Package(platform, name, url, filename, ext, platform.bins);
100
-
101
- return binary;
102
- };
103
-
104
- const install = (suppressLogs) => {
105
- if (!artifactDownloadUrl || artifactDownloadUrl.length === 0) {
106
- console.warn("in demo mode, not installing binaries");
107
- return;
108
- }
109
- const package = getPackage();
110
- const proxy = configureProxy(package.url);
111
-
112
- return package.install(proxy, suppressLogs);
113
- };
114
-
115
- const run = (binaryName) => {
116
- const package = getPackage();
117
- const proxy = configureProxy(package.url);
118
-
119
- package.run(binaryName, proxy);
120
- };
23
+ return {
24
+ ...info,
25
+ binaryName: platform === 'win32' ? 'fresh.exe' : 'fresh'
26
+ };
27
+ }
121
28
 
122
- module.exports = {
123
- install,
124
- run,
125
- getPackage,
126
- };
29
+ module.exports = { getBinaryInfo };
package/install.js CHANGED
@@ -1,4 +1,2 @@
1
- #!/usr/bin/env node
2
-
3
- const { install } = require("./binary");
4
- install(false);
1
+ const { install } = require('./binary-install');
2
+ install().catch(err => { console.error(err); process.exit(1); });
package/package.json CHANGED
@@ -1,101 +1,32 @@
1
1
  {
2
- "artifactDownloadUrl": "https://github.com/sinelaw/fresh/releases/download/v0.1.32",
3
- "author": "Noam Lewis",
2
+ "name": "@fresh-editor/fresh-editor",
3
+ "version": "0.1.42",
4
+ "description": "A modern terminal-based text editor with plugin support",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "https://github.com/sinelaw/fresh.git"
8
+ },
9
+ "license": "Apache-2.0",
4
10
  "bin": {
5
11
  "fresh": "run-fresh.js"
6
12
  },
7
- "dependencies": {
8
- "axios": "^1.12.2",
9
- "axios-proxy-builder": "^0.1.2",
10
- "console.table": "^0.10.0",
11
- "detect-libc": "^2.1.2",
12
- "rimraf": "^6.0.1"
13
- },
14
- "description": "A lightweight, fast terminal-based text editor with LSP support and TypeScript plugins",
15
- "devDependencies": {
16
- "prettier": "^3.6.2"
17
- },
18
- "engines": {
19
- "node": ">=14",
20
- "npm": ">=6"
21
- },
22
- "glibcMinimum": {
23
- "major": 2,
24
- "series": 35
25
- },
26
- "homepage": "https://sinelaw.github.io/fresh/",
27
- "keywords": [
28
- "command-line-utilities",
29
- "text-editors",
30
- "editor",
31
- "terminal",
32
- "tui",
33
- "text-editor",
34
- "lsp"
35
- ],
36
- "license": "GPL-2.0",
37
- "name": "@fresh-editor/fresh-editor",
38
- "preferUnplugged": true,
39
- "repository": "https://github.com/sinelaw/fresh",
40
13
  "scripts": {
41
- "fmt": "prettier --write **/*.js",
42
- "fmt:check": "prettier --check **/*.js",
43
- "postinstall": "node ./install.js"
14
+ "postinstall": "node install.js"
44
15
  },
45
- "supportedPlatforms": {
46
- "aarch64-apple-darwin": {
47
- "artifactName": "fresh-editor-aarch64-apple-darwin.tar.xz",
48
- "bins": {
49
- "fresh": "fresh"
50
- },
51
- "zipExt": ".tar.xz"
52
- },
53
- "aarch64-pc-windows-msvc": {
54
- "artifactName": "fresh-editor-x86_64-pc-windows-msvc.zip",
55
- "bins": {
56
- "fresh": "fresh.exe"
57
- },
58
- "zipExt": ".zip"
59
- },
60
- "aarch64-unknown-linux-gnu": {
61
- "artifactName": "fresh-editor-aarch64-unknown-linux-gnu.tar.xz",
62
- "bins": {
63
- "fresh": "fresh"
64
- },
65
- "zipExt": ".tar.xz"
66
- },
67
- "x86_64-apple-darwin": {
68
- "artifactName": "fresh-editor-x86_64-apple-darwin.tar.xz",
69
- "bins": {
70
- "fresh": "fresh"
71
- },
72
- "zipExt": ".tar.xz"
73
- },
74
- "x86_64-pc-windows-gnu": {
75
- "artifactName": "fresh-editor-x86_64-pc-windows-msvc.zip",
76
- "bins": {
77
- "fresh": "fresh.exe"
78
- },
79
- "zipExt": ".zip"
80
- },
81
- "x86_64-pc-windows-msvc": {
82
- "artifactName": "fresh-editor-x86_64-pc-windows-msvc.zip",
83
- "bins": {
84
- "fresh": "fresh.exe"
85
- },
86
- "zipExt": ".zip"
87
- },
88
- "x86_64-unknown-linux-gnu": {
89
- "artifactName": "fresh-editor-x86_64-unknown-linux-gnu.tar.xz",
90
- "bins": {
91
- "fresh": "fresh"
92
- },
93
- "zipExt": ".tar.xz"
94
- }
95
- },
96
- "version": "0.1.32",
97
- "volta": {
98
- "node": "18.14.1",
99
- "npm": "9.5.0"
16
+ "files": [
17
+ "binary.js",
18
+ "binary-install.js",
19
+ "install.js",
20
+ "run-fresh.js",
21
+ "plugins/**/*",
22
+ "README.md",
23
+ "LICENSE",
24
+ "CHANGELOG.md"
25
+ ],
26
+ "os": ["darwin", "linux", "win32"],
27
+ "cpu": ["x64", "arm64"],
28
+ "keywords": ["editor", "terminal", "cli", "text-editor", "vim"],
29
+ "engines": {
30
+ "node": ">=18"
100
31
  }
101
- }
32
+ }
package/run-fresh.js CHANGED
@@ -1,4 +1,10 @@
1
1
  #!/usr/bin/env node
2
+ const { spawn } = require('child_process');
3
+ const path = require('path');
4
+ const { getBinaryInfo } = require('./binary');
2
5
 
3
- const { run } = require("./binary");
4
- run("fresh");
6
+ const info = getBinaryInfo();
7
+ const binPath = path.join(__dirname, 'bin', info.binaryName);
8
+
9
+ const child = spawn(binPath, process.argv.slice(2), { stdio: 'inherit' });
10
+ child.on('exit', (code) => process.exit(code || 0));
package/.gitignore DELETED
@@ -1,2 +0,0 @@
1
- /node_modules
2
-