mbeditor 0.9.0 → 0.10.1
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +95 -0
- data/README.md +4 -79
- data/app/assets/javascripts/mbeditor/application.js +2 -0
- data/app/assets/javascripts/mbeditor/components/EditorPanel.js +186 -16
- data/app/assets/javascripts/mbeditor/components/LogPanel.js +50 -1
- data/app/assets/javascripts/mbeditor/components/MbeditorApp.js +99 -20
- data/app/assets/javascripts/mbeditor/components/ProblemsPanel.js +217 -0
- data/app/assets/javascripts/mbeditor/components/QuickOpenDialog.js +34 -3
- data/app/assets/javascripts/mbeditor/editor_plugins.js +106 -52
- data/app/assets/javascripts/mbeditor/git_service.js +8 -0
- data/app/assets/javascripts/mbeditor/js_outline.js +110 -0
- data/app/assets/stylesheets/mbeditor/editor.css +176 -4
- data/app/assets/stylesheets/mbeditor/themes.css +35 -0
- data/app/controllers/mbeditor/editors_controller.rb +33 -4
- data/app/controllers/mbeditor/git_controller.rb +11 -0
- data/app/services/mbeditor/git_line_diff_service.rb +99 -0
- data/lib/mbeditor/route_map.rb +1 -0
- data/lib/mbeditor/version.rb +1 -1
- metadata +5 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 47a8199a082957d396dce3cfa9de010ece11a11b2b938cf0cf81adfa6dd171f1
|
|
4
|
+
data.tar.gz: 30dfee949bc6d06245c6bd1ba5b682431b36b064e0ad510b0dac44b320c5a287
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 81c342ce0dc3f80c44259ae3a130710013dcb81f45052d4cc4488d2a7dc112f7decc6b5d67f3f6aaa81cbcd7ef391d82136ee1adccdd369b369c7cfcd4d6afa7
|
|
7
|
+
data.tar.gz: b201dda9b9fe55e9f44981725788b1587bc052f940652e8eb7d86a8dcc3d17c45bb0984f71cd3bbbd9f3a001b3b0125ac09a9e53eedd3e580eab96a445be6a17
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,101 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.10.1] - 2026-07-27
|
|
9
|
+
|
|
10
|
+
### Removed
|
|
11
|
+
- **The `listen`-based file watcher, and with it `config.watch_files`.**
|
|
12
|
+
0.10.0 enabled a workspace watcher by default. On Linux each watched
|
|
13
|
+
directory costs an inotify watch from `fs.inotify.max_user_watches`, a
|
|
14
|
+
*per-user* budget shared with everything else watching files — including the
|
|
15
|
+
host app's own code reloader and any other gem using `listen`. Exhausting it
|
|
16
|
+
raises `iNotify max watches exceeded`, and because `listen` reports some of
|
|
17
|
+
those failures from its own background thread, mbeditor could not even
|
|
18
|
+
rescue them. Claiming a share of a scarce OS resource by default was the
|
|
19
|
+
wrong trade for a development tool, and raising the limit needs root, which
|
|
20
|
+
a developer may not have.
|
|
21
|
+
|
|
22
|
+
Nothing is lost: external changes are picked up by polling, which is how the
|
|
23
|
+
editor already tracked git state. If you set `config.watch_files`, remove it
|
|
24
|
+
— it is now ignored.
|
|
25
|
+
|
|
26
|
+
### Fixed
|
|
27
|
+
- **The file tree never refreshed for changes made outside the editor.** Its
|
|
28
|
+
10-second poll returned early whenever the Action Cable socket was connected,
|
|
29
|
+
on the reasoning that the push covered it — but the server only broadcasts
|
|
30
|
+
from mbeditor's own mutation endpoints. With a socket connected, which is the
|
|
31
|
+
normal case, an external `git checkout` or generator run was never picked up.
|
|
32
|
+
The poll now always runs; the push remains the instant path for our own
|
|
33
|
+
writes. This is the bug the 0.10.0 watcher was compensating for.
|
|
34
|
+
- **Git line-number tinting went stale for external changes**, for the same
|
|
35
|
+
reason, and its refresh timer was being cleared and recreated on every
|
|
36
|
+
re-render so it never survived long enough to fire. It now polls on its own
|
|
37
|
+
timer and on window focus, matching the file tree.
|
|
38
|
+
- **Watched paths were dropped when the workspace was reached through a
|
|
39
|
+
symlink** (macOS `/var` → `/private/var`, or a symlinked checkout), because
|
|
40
|
+
reported paths resolve to the real path and no longer matched the configured
|
|
41
|
+
root.
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## [0.10.0] - 2026-07-27
|
|
46
|
+
|
|
47
|
+
### Added
|
|
48
|
+
- **Git-status line numbers** — line numbers are tinted by what git thinks of
|
|
49
|
+
the line: green for added, orange for modified, red on the line a deleted
|
|
50
|
+
block sat after. Backed by a new `GET /mbeditor/git/line_diff` endpoint over
|
|
51
|
+
`git diff -U0`, and never a reason to fail a file load: a missing HEAD or a
|
|
52
|
+
non-repo simply leaves the numbers plain.
|
|
53
|
+
- **Problems panel** — error and warning counts in the status bar open a
|
|
54
|
+
drawer listing every diagnostic across the open files, grouped by file with
|
|
55
|
+
the offending source line and click-to-navigate. Counts cover the open tabs,
|
|
56
|
+
which is where Monaco's markers live; RuboCop, ruby-lsp and the TypeScript
|
|
57
|
+
worker all feed it through one subscription.
|
|
58
|
+
- **Outline for JS, JSX and TypeScript** — the Methods/Outline button now
|
|
59
|
+
works in those files, translating the TypeScript worker's own navigation
|
|
60
|
+
tree rather than adding a second lexer. Arrow functions and function
|
|
61
|
+
expressions assigned to variables are listed; data constants, object-literal
|
|
62
|
+
keys and anonymous callbacks are not.
|
|
63
|
+
- **Colour-coded Rails log** — the log drawer distinguishes request
|
|
64
|
+
boundaries, controller dispatch, SQL, renders, redirects and failures, with
|
|
65
|
+
`Completed` lines coloured by status code.
|
|
66
|
+
- **Optional workspace file watching** — with the host's
|
|
67
|
+
[`listen`](https://github.com/guard/listen) gem, changes made outside the
|
|
68
|
+
editor (a terminal `git checkout`, a generator, another editor) refresh the
|
|
69
|
+
file tree and git decorations instead of going stale. Absent the gem,
|
|
70
|
+
behaviour is unchanged. Configurable via `config.watch_files`.
|
|
71
|
+
|
|
72
|
+
### Fixed
|
|
73
|
+
- **ruby-lsp hover showed a dead link.** ruby-lsp renders its "Definitions"
|
|
74
|
+
line as VS Code `file://` links, which Monaco draws as links but nothing in
|
|
75
|
+
the browser can open, so clicking did nothing. In-workspace links are now
|
|
76
|
+
rewritten to a Monaco command that opens the file at the line; gem and
|
|
77
|
+
stdlib links, which the editor cannot open at all, degrade to plain code
|
|
78
|
+
spans. The `/module_members` breakdown is also restored beneath a constant
|
|
79
|
+
hover — ruby-lsp's constant hover never lists what the class or module
|
|
80
|
+
defines, so taking its output wholesale had dropped it.
|
|
81
|
+
- **False-positive type errors in plain JS/JSX.** The editor kept a denylist
|
|
82
|
+
of TypeScript diagnostic codes to suppress; it had grown to eight and still
|
|
83
|
+
leaked (TS2322 on a spread carrying an extra prop). Untyped JS gives
|
|
84
|
+
TypeScript nothing to check against, and which way it guesses is arbitrary —
|
|
85
|
+
state seeded with `useState({})` errors on every key while the same object
|
|
86
|
+
from `JSON.parse` stays silent. JS/JSX now keeps only the checks that are
|
|
87
|
+
sound without annotations: syntax errors, `Cannot find name`, and unused
|
|
88
|
+
locals. `.ts`/`.tsx` keeps full checking, where the types are hand-written.
|
|
89
|
+
|
|
90
|
+
### Changed
|
|
91
|
+
- The **Logs** button moved from the top toolbar to the status bar.
|
|
92
|
+
- **Quick-open ranks recently opened files** above the static file-type tier,
|
|
93
|
+
so a file you were just in outranks a never-opened controller. Match quality
|
|
94
|
+
still comes first, so a worse match cannot jump the queue.
|
|
95
|
+
- The title-bar search field now fills 75% of the space between the title and
|
|
96
|
+
the toolbar buttons instead of a fixed 340px.
|
|
97
|
+
- Test-suite compatibility fixes carried over from the unreleased 0.9.1/0.9.2
|
|
98
|
+
work: MiniRacer-dependent parser suites skip in minimal bundles, and the
|
|
99
|
+
Outline system tests exercise navigation through click semantics.
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
8
103
|
## [0.9.0] - 2026-07-23
|
|
9
104
|
|
|
10
105
|
### Added
|
data/README.md
CHANGED
|
@@ -9,6 +9,10 @@ Mbeditor (Mini Browser Editor) is a mountable Rails engine that adds a browser-b
|
|
|
9
9
|
- Two-pane tabbed editor with drag-to-move tabs
|
|
10
10
|
- File tree and project search
|
|
11
11
|
- Git panel with working tree changes, unpushed file changes, and branch commit titles
|
|
12
|
+
- Line numbers tinted by git status — green for added lines, orange for modified, red where lines were removed
|
|
13
|
+
- Problems panel with error/warning counts in the status bar, listing every diagnostic across the open files
|
|
14
|
+
- Outline dropdown for Ruby (methods, and test/spec structure in test files) and for JS/JSX/TS
|
|
15
|
+
- Colour-coded Rails log drawer
|
|
12
16
|
- Optional RuboCop lint and format endpoints (uses host app RuboCop)
|
|
13
17
|
- Optional Ruby language-server integration (definitions, hover, completion, diagnostics)
|
|
14
18
|
- Optional test runner with inline failure markers and a dedicated results panel (Minitest and RSpec)
|
|
@@ -202,85 +206,6 @@ The gem keeps host/tooling responsibilities in the host app:
|
|
|
202
206
|
|
|
203
207
|
All lint and test tools are auto-detected at runtime. The engine gracefully disables features if the tools are not available. Neither `rubocop`, `haml_lint`, nor any test framework are runtime dependencies of the gem itself — they are discovered from the host app's environment.
|
|
204
208
|
|
|
205
|
-
### Ruby language server (Optional)
|
|
206
|
-
|
|
207
|
-
Add [ruby-lsp](https://github.com/Shopify/ruby-lsp) to the host app's
|
|
208
|
-
development group and mbeditor uses it automatically for Ruby
|
|
209
|
-
go-to-definition, hover, completion, and diagnostics:
|
|
210
|
-
|
|
211
|
-
```ruby
|
|
212
|
-
gem "ruby-lsp", require: false, group: :development
|
|
213
|
-
gem "ruby-lsp-rails", require: false, group: :development # Rails-aware results
|
|
214
|
-
```
|
|
215
|
-
|
|
216
|
-
`ruby-lsp-rails` needs no mbeditor configuration — ruby-lsp loads it as an
|
|
217
|
-
addon, so associations, model attributes, and route helpers start resolving on
|
|
218
|
-
their own.
|
|
219
|
-
|
|
220
|
-
What changes when it's present:
|
|
221
|
-
|
|
222
|
-
- **Diagnostics.** Ruby files are checked by ruby-lsp instead of booting
|
|
223
|
-
RuboCop over HTTP on every debounce, so you also get Prism syntax errors and
|
|
224
|
-
warnings alongside RuboCop offenses. Quick-fix lightbulbs still work for
|
|
225
|
-
correctable cops. Very large files fall back to syntax-only diagnostics.
|
|
226
|
-
- **Definitions, hover, completion.** Answered from the language server's index
|
|
227
|
-
rather than a workspace grep, including your unsaved buffer contents.
|
|
228
|
-
|
|
229
|
-
Everything degrades on its own: if ruby-lsp is missing, times out (its first
|
|
230
|
-
index of a large app takes a while), or crashes, that request falls back to the
|
|
231
|
-
built-in grep/Ripper services. ERB templates always use the built-in services —
|
|
232
|
-
ruby-lsp cannot parse ERB.
|
|
233
|
-
|
|
234
|
-
### Realtime via Action Cable (Optional)
|
|
235
|
-
|
|
236
|
-
Mbeditor works without Action Cable. If Action Cable is unavailable, unreachable, or returns transient errors, the editor automatically falls back to polling.
|
|
237
|
-
|
|
238
|
-
To enable realtime features in a host app:
|
|
239
|
-
|
|
240
|
-
1. Ensure Action Cable is enabled in the host app (for apps that do not load it by default, add the framework/gem explicitly).
|
|
241
|
-
2. Mount cable in host routes:
|
|
242
|
-
|
|
243
|
-
```ruby
|
|
244
|
-
mount ActionCable.server => '/cable'
|
|
245
|
-
```
|
|
246
|
-
|
|
247
|
-
3. Make Action Cable JavaScript available to the page (for asset-pipeline apps, `actioncable.js` is typically sufficient).
|
|
248
|
-
|
|
249
|
-
If any of these are missing, mbeditor still runs in polling mode.
|
|
250
|
-
|
|
251
|
-
### Syntax Highlighting Support
|
|
252
|
-
Monaco runtime assets are served from the engine route namespace (`/mbeditor/monaco-editor/*` and `/mbeditor/monaco_worker.js`).
|
|
253
|
-
The gem includes syntax highlighting for common Rails and React development file types:
|
|
254
|
-
|
|
255
|
-
**Web & Template Languages:**
|
|
256
|
-
- **Ruby** (.rb, Gemfile, gemspec, Rakefile)
|
|
257
|
-
- **HTML**
|
|
258
|
-
- **ERB** (.html.erb, .erb) — dedicated ERB grammar, plus Ruby intellisense
|
|
259
|
-
inside `<% %>` tags: hover, completion, go-to-definition (Ctrl/Cmd+click or
|
|
260
|
-
F12) and auto-`end`, all inert in the surrounding HTML. ERB uses the built-in
|
|
261
|
-
workspace services rather than ruby-lsp, which cannot parse ERB.
|
|
262
|
-
- **HAML** (.haml) — plaintext syntax highlighting (no dedicated HAML grammar in Monaco; haml-lint provides inline error markers when available)
|
|
263
|
-
- **CSS** and **SCSS** stylesheets
|
|
264
|
-
|
|
265
|
-
**JavaScript & React:**
|
|
266
|
-
- **JavaScript / JSX** (.js, .jsx, .js.jsx) — Monaco's TypeScript worker runs in
|
|
267
|
-
checked-JS mode with JSX enabled. Built for the Sprockets world where every
|
|
268
|
-
top-level `var`/`function`/`class` (and `window.X =` assignment) is a global:
|
|
269
|
-
the editor scans the workspace once at boot (`GET /js_globals`) and declares
|
|
270
|
-
all of them as ambient globals, so cross-file component references need no
|
|
271
|
-
`import` and produce no "Cannot find name" diagnostics. The list refreshes
|
|
272
|
-
automatically when files change. Runtime-only globals the static scan can't
|
|
273
|
-
see (e.g. `Routes`, `I18n`) can be declared via
|
|
274
|
-
`config.js_global_identifiers = %w[Routes I18n]`.
|
|
275
|
-
Known limits: everything is typed `any` (no cross-file type inference), and
|
|
276
|
-
genuinely undefined names are shown as warnings, not errors.
|
|
277
|
-
- **TypeScript** (.ts, .tsx)
|
|
278
|
-
|
|
279
|
-
**Configuration & Documentation:**
|
|
280
|
-
- **YAML** (.yml, .yaml)
|
|
281
|
-
- **Markdown** (.md)
|
|
282
|
-
|
|
283
|
-
These language modules are packaged locally with the gem for true offline operation. No network fallback is needed—all highlighting works without internet connectivity.
|
|
284
209
|
|
|
285
210
|
## Asset Pipeline
|
|
286
211
|
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
//= require mbeditor/color_provider
|
|
13
13
|
//= require mbeditor/editor_plugins
|
|
14
14
|
//= require mbeditor/ruby_outline
|
|
15
|
+
//= require mbeditor/js_outline
|
|
15
16
|
//= require mbeditor/components/CollapsibleSection
|
|
16
17
|
//= require mbeditor/components/ShortcutHelp
|
|
17
18
|
//= require mbeditor/components/DiffViewer
|
|
@@ -21,6 +22,7 @@
|
|
|
21
22
|
//= require mbeditor/components/FileHistoryPanel
|
|
22
23
|
//= require mbeditor/components/TestResultsPanel
|
|
23
24
|
//= require mbeditor/components/LogPanel
|
|
25
|
+
//= require mbeditor/components/ProblemsPanel
|
|
24
26
|
//= require mbeditor/components/CodeReviewPanel
|
|
25
27
|
//= require mbeditor/components/EditorPanel
|
|
26
28
|
//= require mbeditor/components/FileTree
|
|
@@ -69,6 +69,10 @@ var EditorPanel = function EditorPanel(_ref) {
|
|
|
69
69
|
var setIsBlameLoading = _useState8[1];
|
|
70
70
|
|
|
71
71
|
var blameDecorationsRef = useRef([]);
|
|
72
|
+
var gitLineDecorationsRef = useRef([]);
|
|
73
|
+
// Latest git line-diff refresh, read by the poll effect so its interval does
|
|
74
|
+
// not have to be torn down whenever the active tab changes.
|
|
75
|
+
var gitLineRefreshRef = useRef(null);
|
|
72
76
|
var blameZoneIdsRef = useRef([]);
|
|
73
77
|
var testDecorationIdsRef = useRef([]);
|
|
74
78
|
var testZoneIdsRef = useRef([]);
|
|
@@ -105,6 +109,8 @@ var EditorPanel = function EditorPanel(_ref) {
|
|
|
105
109
|
|
|
106
110
|
var methodsBtnRef = useRef(null);
|
|
107
111
|
var methodsDropdownRef = useRef(null);
|
|
112
|
+
// Discards a JS outline that resolves after the dropdown was closed or reopened.
|
|
113
|
+
var methodsRequestRef = useRef(0);
|
|
108
114
|
|
|
109
115
|
// Local pagination state — initialized from tab props; updated on page navigation
|
|
110
116
|
var _useState21 = useState(tab.startLine || 0);
|
|
@@ -1281,6 +1287,115 @@ var EditorPanel = function EditorPanel(_ref) {
|
|
|
1281
1287
|
}
|
|
1282
1288
|
}, [tab.path]);
|
|
1283
1289
|
|
|
1290
|
+
// Tint line numbers by git status: green added, orange modified, red where
|
|
1291
|
+
// lines were removed.
|
|
1292
|
+
//
|
|
1293
|
+
// The ranges come from `git diff -U0` against HEAD, so they describe the file
|
|
1294
|
+
// as last written to disk. Monaco anchors decorations to the model and shifts
|
|
1295
|
+
// them as you type, which keeps them roughly right mid-edit; the authoritative
|
|
1296
|
+
// refresh happens on the signals wired up at the end of this effect.
|
|
1297
|
+
|
|
1298
|
+
// Poll cadence for the tint, matching the file tree's. Slower to react than a
|
|
1299
|
+
// filesystem watcher would be, at the cost of one `git diff -U0` on one file
|
|
1300
|
+
// per visible pane — against a resource nothing else is competing for, rather
|
|
1301
|
+
// than a share of the kernel's inotify budget.
|
|
1302
|
+
var GIT_LINE_POLL_MS = 10000;
|
|
1303
|
+
|
|
1304
|
+
useEffect(function () {
|
|
1305
|
+
if (!gitAvailable || !tab.path || tab.isDiff || tab.isCombinedDiff) return;
|
|
1306
|
+
|
|
1307
|
+
var cancelled = false;
|
|
1308
|
+
|
|
1309
|
+
function clear() {
|
|
1310
|
+
if (!monacoRef.current || !monacoRef.current.getModel()) return;
|
|
1311
|
+
gitLineDecorationsRef.current =
|
|
1312
|
+
monacoRef.current.deltaDecorations(gitLineDecorationsRef.current, []);
|
|
1313
|
+
}
|
|
1314
|
+
|
|
1315
|
+
function apply(data) {
|
|
1316
|
+
var editor = monacoRef.current;
|
|
1317
|
+
var model = editor && editor.getModel();
|
|
1318
|
+
if (!model || !window.monaco) return;
|
|
1319
|
+
|
|
1320
|
+
var lineCount = model.getLineCount();
|
|
1321
|
+
var decorations = [];
|
|
1322
|
+
|
|
1323
|
+
function push(ranges, className) {
|
|
1324
|
+
(ranges || []).forEach(function (range) {
|
|
1325
|
+
// A deletion above the first line is reported as line 0; mark line 1
|
|
1326
|
+
// so the file's opening line carries the flag.
|
|
1327
|
+
var start = Math.max(1, Math.min(range.start, lineCount));
|
|
1328
|
+
var end = Math.max(start, Math.min(range.end, lineCount));
|
|
1329
|
+
for (var line = start; line <= end; line++) {
|
|
1330
|
+
decorations.push({
|
|
1331
|
+
range: new window.monaco.Range(line, 1, line, 1),
|
|
1332
|
+
options: { isWholeLine: true, lineNumberClassName: className }
|
|
1333
|
+
});
|
|
1334
|
+
}
|
|
1335
|
+
});
|
|
1336
|
+
}
|
|
1337
|
+
|
|
1338
|
+
push(data.added, 'mbeditor-gitline-added');
|
|
1339
|
+
push(data.modified, 'mbeditor-gitline-modified');
|
|
1340
|
+
push(data.deleted, 'mbeditor-gitline-deleted');
|
|
1341
|
+
|
|
1342
|
+
gitLineDecorationsRef.current =
|
|
1343
|
+
editor.deltaDecorations(gitLineDecorationsRef.current, decorations);
|
|
1344
|
+
}
|
|
1345
|
+
|
|
1346
|
+
function refresh() {
|
|
1347
|
+
var path = tab.path;
|
|
1348
|
+
GitService.fetchLineDiff(path).then(function (data) {
|
|
1349
|
+
// The tab can be switched or closed while the request is in flight.
|
|
1350
|
+
if (cancelled || !data || tab.path !== path) return;
|
|
1351
|
+
apply(data);
|
|
1352
|
+
}).catch(function () {
|
|
1353
|
+
// Not a repo, file not readable, git missing — leave the numbers plain.
|
|
1354
|
+
if (!cancelled) clear();
|
|
1355
|
+
});
|
|
1356
|
+
}
|
|
1357
|
+
|
|
1358
|
+
refresh();
|
|
1359
|
+
|
|
1360
|
+
// Publish the current refresh for the poll below, which lives in its own
|
|
1361
|
+
// effect so a re-render of this one cannot restart its clock.
|
|
1362
|
+
gitLineRefreshRef.current = refresh;
|
|
1363
|
+
|
|
1364
|
+
// Immediate path for writes mbeditor made itself.
|
|
1365
|
+
var hasSocket = typeof WebSocketService !== 'undefined' && WebSocketService.onFilesChanged;
|
|
1366
|
+
var onChanged = hasSocket ? function () { refresh(); } : null;
|
|
1367
|
+
if (onChanged) WebSocketService.onFilesChanged(onChanged);
|
|
1368
|
+
|
|
1369
|
+
return function () {
|
|
1370
|
+
cancelled = true;
|
|
1371
|
+
gitLineRefreshRef.current = null;
|
|
1372
|
+
if (onChanged && WebSocketService.offFilesChanged) WebSocketService.offFilesChanged(onChanged);
|
|
1373
|
+
clear();
|
|
1374
|
+
};
|
|
1375
|
+
}, [tab.path, tab.externalContentVersion, tab.isDiff, tab.isCombinedDiff, gitAvailable]);
|
|
1376
|
+
|
|
1377
|
+
// The poll for changes made outside the editor — a terminal commit or branch
|
|
1378
|
+
// switch alters the diff without touching this buffer.
|
|
1379
|
+
//
|
|
1380
|
+
// Deliberately its own effect with no dependencies. Held inside the effect
|
|
1381
|
+
// above, the interval was cleared and recreated on every re-render of that
|
|
1382
|
+
// one and never survived long enough to fire, so the tint only ever updated
|
|
1383
|
+
// via the WebSocket — i.e. never for external changes, which is the whole
|
|
1384
|
+
// point of it. Reading the refresh through a ref keeps this clock running
|
|
1385
|
+
// across tab switches.
|
|
1386
|
+
useEffect(function () {
|
|
1387
|
+
var tick = function () {
|
|
1388
|
+
if (document.hidden) return;
|
|
1389
|
+
if (gitLineRefreshRef.current) gitLineRefreshRef.current();
|
|
1390
|
+
};
|
|
1391
|
+
var intervalId = setInterval(tick, GIT_LINE_POLL_MS);
|
|
1392
|
+
window.addEventListener('focus', tick);
|
|
1393
|
+
return function () {
|
|
1394
|
+
clearInterval(intervalId);
|
|
1395
|
+
window.removeEventListener('focus', tick);
|
|
1396
|
+
};
|
|
1397
|
+
}, []);
|
|
1398
|
+
|
|
1284
1399
|
// Handle Blame data fetching
|
|
1285
1400
|
useEffect(function () {
|
|
1286
1401
|
if (!isBlameVisible) {
|
|
@@ -1612,6 +1727,8 @@ var EditorPanel = function EditorPanel(_ref) {
|
|
|
1612
1727
|
var fileBaseName = (tab.path || '').split('/').pop().toLowerCase();
|
|
1613
1728
|
var isRubyFile = ext === 'rb' || ext === 'ruby' || ext === 'gemspec' || ext === 'rake' ||
|
|
1614
1729
|
fileBaseName === 'gemfile' || fileBaseName === 'gemfile.lock' || fileBaseName === 'rakefile';
|
|
1730
|
+
var isJsFile = ['js', 'jsx', 'mjs', 'cjs', 'ts', 'tsx'].indexOf(ext) !== -1;
|
|
1731
|
+
var hasOutline = isRubyFile || isJsFile;
|
|
1615
1732
|
var isTestOutline = false;
|
|
1616
1733
|
try {
|
|
1617
1734
|
isTestOutline = isRubyFile && window.RubyOutline &&
|
|
@@ -1695,6 +1812,33 @@ var EditorPanel = function EditorPanel(_ref) {
|
|
|
1695
1812
|
return window.RubyOutline.parse(lines, { path: path });
|
|
1696
1813
|
}
|
|
1697
1814
|
|
|
1815
|
+
// The JS/TS outline comes from the TypeScript worker rather than a lexer of
|
|
1816
|
+
// our own, which makes it async — the worker is where the parse already
|
|
1817
|
+
// lives, and it tracks the live buffer, so unsaved edits are included.
|
|
1818
|
+
function loadJsOutline(model) {
|
|
1819
|
+
var ts = window.monaco && window.monaco.languages && window.monaco.languages.typescript;
|
|
1820
|
+
if (!ts || !window.JsOutline) return Promise.reject(new Error('outline unavailable'));
|
|
1821
|
+
|
|
1822
|
+
var workerFor = model.getLanguageId() === 'typescript' ? ts.getTypeScriptWorker : ts.getJavaScriptWorker;
|
|
1823
|
+
return workerFor().then(function (getWorker) {
|
|
1824
|
+
return getWorker(model.uri);
|
|
1825
|
+
}).then(function (client) {
|
|
1826
|
+
return client.getNavigationTree(model.uri.toString());
|
|
1827
|
+
}).then(function (tree) {
|
|
1828
|
+
return window.JsOutline.fromNavigationTree(tree, {
|
|
1829
|
+
lineAt: function (offset) { return model.getPositionAt(offset).lineNumber; },
|
|
1830
|
+
textAt: function (offset, length) {
|
|
1831
|
+
var start = model.getPositionAt(offset);
|
|
1832
|
+
var end = model.getPositionAt(offset + length);
|
|
1833
|
+
return model.getValueInRange({
|
|
1834
|
+
startLineNumber: start.lineNumber, startColumn: start.column,
|
|
1835
|
+
endLineNumber: end.lineNumber, endColumn: end.column
|
|
1836
|
+
});
|
|
1837
|
+
}
|
|
1838
|
+
});
|
|
1839
|
+
});
|
|
1840
|
+
}
|
|
1841
|
+
|
|
1698
1842
|
if (tab.fileNotFound) {
|
|
1699
1843
|
return React.createElement(
|
|
1700
1844
|
'div',
|
|
@@ -1834,31 +1978,55 @@ var EditorPanel = function EditorPanel(_ref) {
|
|
|
1834
1978
|
React.createElement('i', { className: 'fas fa-history', style: { marginRight: editorPrefs.toolbarIconOnly ? 0 : '5px', flexShrink: 0 } }),
|
|
1835
1979
|
!editorPrefs.toolbarIconOnly && React.createElement('span', { className: 'ide-toolbar-label' }, 'History')
|
|
1836
1980
|
),
|
|
1837
|
-
|
|
1981
|
+
hasOutline && React.createElement(
|
|
1838
1982
|
'button',
|
|
1839
1983
|
{
|
|
1840
1984
|
ref: methodsBtnRef,
|
|
1841
1985
|
className: 'ide-icon-btn' + (methodsOpen ? ' active' : ''),
|
|
1842
1986
|
onClick: function() {
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1987
|
+
// Bump first: a click that closes the dropdown must also cancel an
|
|
1988
|
+
// outline request still in flight from the click that opened it.
|
|
1989
|
+
var requestId = ++methodsRequestRef.current;
|
|
1990
|
+
if (methodsOpen) {
|
|
1991
|
+
setMethodsOpen(false);
|
|
1992
|
+
return;
|
|
1993
|
+
}
|
|
1994
|
+
|
|
1995
|
+
var model = monacoRef.current && monacoRef.current.getModel();
|
|
1996
|
+
|
|
1997
|
+
function openWith(entries, truncated, unavailable) {
|
|
1998
|
+
setMethodsList(entries);
|
|
1999
|
+
setMethodsTruncated(truncated);
|
|
2000
|
+
setMethodsUnavailable(unavailable);
|
|
1856
2001
|
if (methodsBtnRef.current) {
|
|
1857
2002
|
var rect = methodsBtnRef.current.getBoundingClientRect();
|
|
1858
2003
|
setMethodsDropdownPos({ top: rect.bottom + 4, right: window.innerWidth - rect.right });
|
|
1859
2004
|
}
|
|
2005
|
+
setMethodsOpen(true);
|
|
2006
|
+
}
|
|
2007
|
+
|
|
2008
|
+
if (isJsFile) {
|
|
2009
|
+
// The worker has already parsed the open buffer for diagnostics,
|
|
2010
|
+
// so this resolves in a few ms — opening only once it answers
|
|
2011
|
+
// avoids flashing "No methods found" on the way.
|
|
2012
|
+
(model ? loadJsOutline(model) : Promise.resolve({ entries: [], truncated: false }))
|
|
2013
|
+
.then(function (result) {
|
|
2014
|
+
if (methodsRequestRef.current !== requestId) return;
|
|
2015
|
+
openWith(result.entries || [], !!result.truncated, false);
|
|
2016
|
+
})
|
|
2017
|
+
.catch(function () {
|
|
2018
|
+
if (methodsRequestRef.current !== requestId) return;
|
|
2019
|
+
openWith([], false, true);
|
|
2020
|
+
});
|
|
2021
|
+
return;
|
|
2022
|
+
}
|
|
2023
|
+
|
|
2024
|
+
try {
|
|
2025
|
+
var result = model ? parseRubyOutline(model, tab.path) : { entries: [], truncated: false };
|
|
2026
|
+
openWith(result.entries || [], !!result.truncated, false);
|
|
2027
|
+
} catch (err) {
|
|
2028
|
+
openWith([], false, true);
|
|
1860
2029
|
}
|
|
1861
|
-
setMethodsOpen(nextOpen);
|
|
1862
2030
|
},
|
|
1863
2031
|
title: isTestOutline ? 'Jump to Outline' : 'Jump to Method'
|
|
1864
2032
|
},
|
|
@@ -2099,7 +2267,9 @@ var EditorPanel = function EditorPanel(_ref) {
|
|
|
2099
2267
|
var row = React.createElement(
|
|
2100
2268
|
'button',
|
|
2101
2269
|
{
|
|
2102
|
-
|
|
2270
|
+
// Index-qualified: a JS class and its first member can share
|
|
2271
|
+
// a line, and kind+line alone would collide.
|
|
2272
|
+
key: entry.kind + '-' + entry.line + '-' + entryIndex,
|
|
2103
2273
|
type: 'button',
|
|
2104
2274
|
className: 'ide-methods-dropdown-item ide-outline-entry ide-outline-entry-' + entry.kind,
|
|
2105
2275
|
'data-outline-kind': entry.kind,
|
|
@@ -3,6 +3,49 @@
|
|
|
3
3
|
// LogPanel — bottom drawer that renders the live Rails log. Auto-scrolls to the
|
|
4
4
|
// tail, pauses auto-scroll when the user scrolls up, and supports a substring
|
|
5
5
|
// filter and clear. Driven entirely by LogService.
|
|
6
|
+
|
|
7
|
+
// Classify one Rails log line so CSS can colour it. First match wins, so the
|
|
8
|
+
// order matters: "Completed 500" has to be read as an error before the generic
|
|
9
|
+
// /error/ sweep at the bottom claims it, and a SQL line mentioning "error" in a
|
|
10
|
+
// string literal must not turn the whole row red.
|
|
11
|
+
//
|
|
12
|
+
// Rails writes these lines without ANSI codes when the log goes to a file, so
|
|
13
|
+
// there is nothing to parse — this is pattern matching on the text, and an
|
|
14
|
+
// unrecognised line simply renders in the default colour.
|
|
15
|
+
var LOG_LINE_RULES = [
|
|
16
|
+
// Request lifecycle. The status code decides the colour of a Completed line.
|
|
17
|
+
[/^Completed [45]\d\d\b/, 'error'],
|
|
18
|
+
[/^Completed 3\d\d\b/, 'muted'],
|
|
19
|
+
[/^Completed 2\d\d\b/, 'success'],
|
|
20
|
+
[/^Started [A-Z]+ /, 'request'],
|
|
21
|
+
[/^Processing by /, 'controller'],
|
|
22
|
+
[/^Redirected to /, 'muted'],
|
|
23
|
+
[/^\s*(Rendering|Rendered) /, 'render'],
|
|
24
|
+
// Queries: "User Load (0.3ms) SELECT ..." and its CACHE/TRANSACTION variants.
|
|
25
|
+
[/^\s*(CACHE\s+)?[\w:]+\s*(Load|Create|Update|Destroy|Exists\?|Count|Pluck|Sum|Delete)?\s*\(\d+(\.\d+)?ms\)\s+(SELECT|INSERT|UPDATE|DELETE|BEGIN|COMMIT|ROLLBACK|SAVEPOINT|RELEASE)\b/i, 'sql'],
|
|
26
|
+
[/^\s*(TRANSACTION|SQL)\s+\(/, 'sql'],
|
|
27
|
+
// Failures and noise.
|
|
28
|
+
[/^\s*(FATAL|ERROR)\b/, 'error'],
|
|
29
|
+
[/^\s*[\w/.]+:\d+:in [`']/, 'trace'],
|
|
30
|
+
[/DEPRECATION WARNING/, 'warn'],
|
|
31
|
+
[/^\s*(WARN|WARNING)\b/, 'warn'],
|
|
32
|
+
// Rails prints an unhandled exception as "Some::ConstantName (message):",
|
|
33
|
+
// which carries no Error/Exception in its name often enough to need its own
|
|
34
|
+
// rule (ActiveRecord::RecordNotFound, ActionController::RoutingError…).
|
|
35
|
+
[/^\s*[A-Z][A-Za-z0-9_]*(::[A-Z][A-Za-z0-9_]*)*\s+\(.*\):\s*$/, 'error'],
|
|
36
|
+
[/\b\w*(Error|Exception)\b\s*[:(]/, 'error'],
|
|
37
|
+
// mbeditor's own diagnostics, so they stand out from host app noise.
|
|
38
|
+
[/^\s*\[mbeditor\]/, 'mbeditor']
|
|
39
|
+
];
|
|
40
|
+
|
|
41
|
+
function classifyLogLine(line) {
|
|
42
|
+
var text = String(line == null ? '' : line);
|
|
43
|
+
for (var i = 0; i < LOG_LINE_RULES.length; i++) {
|
|
44
|
+
if (LOG_LINE_RULES[i][0].test(text)) return LOG_LINE_RULES[i][1];
|
|
45
|
+
}
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
|
|
6
49
|
var LogPanel = function LogPanel(_ref) {
|
|
7
50
|
var onClose = _ref.onClose;
|
|
8
51
|
|
|
@@ -118,10 +161,16 @@ var LogPanel = function LogPanel(_ref) {
|
|
|
118
161
|
'div',
|
|
119
162
|
{ className: 'ide-log-body', ref: bodyRef, onScroll: onScroll },
|
|
120
163
|
shown.map(function (line, i) {
|
|
121
|
-
|
|
164
|
+
var kind = classifyLogLine(line);
|
|
165
|
+
return React.createElement('div', {
|
|
166
|
+
className: 'ide-log-line' + (kind ? ' ide-log-line-' + kind : ''),
|
|
167
|
+
key: i
|
|
168
|
+
}, line);
|
|
122
169
|
})
|
|
123
170
|
)
|
|
124
171
|
);
|
|
125
172
|
};
|
|
126
173
|
|
|
174
|
+
LogPanel.classifyLine = classifyLogLine;
|
|
175
|
+
|
|
127
176
|
window.LogPanel = LogPanel;
|