mbeditor 0.5.0 → 0.5.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 +6 -0
- data/app/assets/javascripts/mbeditor/components/EditorPanel.js +170 -2
- data/app/assets/javascripts/mbeditor/editor_plugins.js +53 -0
- data/app/assets/javascripts/mbeditor/file_service.js +9 -0
- data/app/assets/javascripts/mbeditor/tab_manager.js +23 -0
- data/app/controllers/mbeditor/editors_controller.rb +23 -0
- data/lib/mbeditor/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a6e34c84c4c7e3a0ef9d4382dd49c29ac3bd13fdc42f8b38d50666cdd30439b6
|
|
4
|
+
data.tar.gz: 07a9658a8d2faa837969d7c8b05556eaba59afeb48af63b9c8482d7693805b64
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4506a5f5c0e7d7c5b111f9e6cb665cf5d95b7a2e9be5c140948c49f3fbb3a7ee9c13e78d1924d891522131cb85d512242074202298172a10915ea57e6e48f71a
|
|
7
|
+
data.tar.gz: c7b06da62965639c18f0531a9459da9ddcfeb14ba29ecceadeb600aeec8eb28197657c3dcca4dec5311978fef1397dc610cc2d9f27ead2513ec2e768daceda62
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,12 @@ 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.5.1] - 2026-04-30
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- **Large file pagination** — files over 5 MB now open in read-only paginated mode (500 lines per page) instead of showing an error. A bar below the toolbar shows the current line range, total line count, and file size, with Prev/Next navigation. The backend streams only the requested line slice via `File.foreach` so arbitrarily large files never load fully into memory.
|
|
12
|
+
- **JSON auto pretty-print** — `.json` files are automatically formatted with 2-space indentation when opened. Invalid JSON falls back to raw display with Monaco's built-in error markers. The formatted content is set as the editor baseline so the file does not appear dirty after opening.
|
|
13
|
+
|
|
8
14
|
## [0.5.0] - 2026-04-30
|
|
9
15
|
|
|
10
16
|
### Added
|
|
@@ -86,6 +86,27 @@ var EditorPanel = function EditorPanel(_ref) {
|
|
|
86
86
|
|
|
87
87
|
var methodsBtnRef = useRef(null);
|
|
88
88
|
|
|
89
|
+
// Local pagination state — initialized from tab props; updated on page navigation
|
|
90
|
+
var _useState17 = useState(tab.startLine || 0);
|
|
91
|
+
var _useState18 = _slicedToArray(_useState17, 2);
|
|
92
|
+
var pageStartLine = _useState18[0];
|
|
93
|
+
var setPageStartLine = _useState18[1];
|
|
94
|
+
|
|
95
|
+
var _useState19 = useState(tab.lineCount || 0);
|
|
96
|
+
var _useState20 = _slicedToArray(_useState19, 2);
|
|
97
|
+
var pageLineCount = _useState20[0];
|
|
98
|
+
var setPageLineCount = _useState20[1];
|
|
99
|
+
|
|
100
|
+
var _useState21 = useState(tab.totalLines || 0);
|
|
101
|
+
var _useState22 = _slicedToArray(_useState21, 2);
|
|
102
|
+
var pageTotalLines = _useState22[0];
|
|
103
|
+
var setPageTotalLines = _useState22[1];
|
|
104
|
+
|
|
105
|
+
var _useState23 = useState(tab.totalBytes || 0);
|
|
106
|
+
var _useState24 = _slicedToArray(_useState23, 2);
|
|
107
|
+
var pageTotalBytes = _useState24[0];
|
|
108
|
+
var setPageTotalBytes = _useState24[1];
|
|
109
|
+
|
|
89
110
|
var onFormatRef = useRef(onFormat);
|
|
90
111
|
onFormatRef.current = onFormat;
|
|
91
112
|
|
|
@@ -95,6 +116,12 @@ var EditorPanel = function EditorPanel(_ref) {
|
|
|
95
116
|
var vimStatusRef = useRef(null);
|
|
96
117
|
var vimModeObjRef = useRef(null);
|
|
97
118
|
|
|
119
|
+
function humanSize(bytes) {
|
|
120
|
+
if (bytes < 1024) return bytes + ' B';
|
|
121
|
+
if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB';
|
|
122
|
+
return (bytes / (1024 * 1024)).toFixed(1) + ' MB';
|
|
123
|
+
}
|
|
124
|
+
|
|
98
125
|
var clearTestZones = function clearTestZones(editor) {
|
|
99
126
|
if (!editor) return;
|
|
100
127
|
if (testZoneIdsRef.current.length === 0) return;
|
|
@@ -488,7 +515,18 @@ var EditorPanel = function EditorPanel(_ref) {
|
|
|
488
515
|
} else {
|
|
489
516
|
// Evict the LRU model if the cache is at capacity before creating a new one.
|
|
490
517
|
TabManager.evictLruModel();
|
|
491
|
-
|
|
518
|
+
|
|
519
|
+
// Pretty-print JSON content before initial load
|
|
520
|
+
var contentForModel = tab.content;
|
|
521
|
+
if (language === 'json' && contentForModel) {
|
|
522
|
+
try {
|
|
523
|
+
contentForModel = JSON.stringify(JSON.parse(contentForModel), null, 2);
|
|
524
|
+
} catch (_) {
|
|
525
|
+
// invalid JSON — use raw content; Monaco will show error markers
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
modelObj = window.monaco.editor.createModel(contentForModel, language);
|
|
492
530
|
window.__mbeditorModels[tab.path] = { model: modelObj, aviBase: null, aviMax: null, lastAccessed: Date.now(), cleanVersionId: null };
|
|
493
531
|
_modelEntry = window.__mbeditorModels[tab.path];
|
|
494
532
|
}
|
|
@@ -578,6 +616,10 @@ var EditorPanel = function EditorPanel(_ref) {
|
|
|
578
616
|
|
|
579
617
|
monacoRef.current = editor;
|
|
580
618
|
window.__mbeditorActiveEditor = editor;
|
|
619
|
+
// Apply read-only for paginated (truncated) files
|
|
620
|
+
if (tab.truncated) {
|
|
621
|
+
editor.updateOptions({ readOnly: true });
|
|
622
|
+
}
|
|
581
623
|
setEditorReady(true);
|
|
582
624
|
|
|
583
625
|
// Stash the workspace-relative path on the model so code-action providers
|
|
@@ -768,7 +810,19 @@ var EditorPanel = function EditorPanel(_ref) {
|
|
|
768
810
|
// fires during setValue and skips the dirty check (cleanVersionId is null).
|
|
769
811
|
var _initEntry = window.__mbeditorModels && window.__mbeditorModels[tab.path];
|
|
770
812
|
if (_initEntry) _initEntry.cleanVersionId = null;
|
|
771
|
-
|
|
813
|
+
|
|
814
|
+
// Pretty-print JSON content before initial load
|
|
815
|
+
var contentToSet = tab.content;
|
|
816
|
+
var modelLang = model.getLanguageId();
|
|
817
|
+
if (modelLang === 'json' && contentToSet) {
|
|
818
|
+
try {
|
|
819
|
+
contentToSet = JSON.stringify(JSON.parse(contentToSet), null, 2);
|
|
820
|
+
} catch (_) {
|
|
821
|
+
// invalid JSON — use raw content; Monaco will show error markers
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
editor.setValue(contentToSet);
|
|
772
826
|
// Reset the AVI baseline: setValue clears the undo stack so anything before
|
|
773
827
|
// this point is no longer reachable. Also clear the canUndo/canRedo display.
|
|
774
828
|
var newBase = model.getAlternativeVersionId();
|
|
@@ -917,6 +971,21 @@ var EditorPanel = function EditorPanel(_ref) {
|
|
|
917
971
|
}
|
|
918
972
|
}, [markers, tab.id]);
|
|
919
973
|
|
|
974
|
+
// Sync pagination state when tab changes (different file or fresh load)
|
|
975
|
+
useEffect(function () {
|
|
976
|
+
setPageStartLine(tab.startLine || 0);
|
|
977
|
+
setPageLineCount(tab.lineCount || 0);
|
|
978
|
+
setPageTotalLines(tab.totalLines || 0);
|
|
979
|
+
setPageTotalBytes(tab.totalBytes || 0);
|
|
980
|
+
}, [tab.id]);
|
|
981
|
+
|
|
982
|
+
// Apply read-only mode based on tab.truncated whenever the editor or truncated flag changes
|
|
983
|
+
useEffect(function () {
|
|
984
|
+
if (monacoRef.current) {
|
|
985
|
+
monacoRef.current.updateOptions({ readOnly: !!tab.truncated });
|
|
986
|
+
}
|
|
987
|
+
}, [tab.truncated, editorReady]);
|
|
988
|
+
|
|
920
989
|
// Reset blame + test decorations when file path changes
|
|
921
990
|
useEffect(function () {
|
|
922
991
|
setBlameData(null);
|
|
@@ -1456,6 +1525,105 @@ var EditorPanel = function EditorPanel(_ref) {
|
|
|
1456
1525
|
!editorPrefs.toolbarIconOnly && !testLoading && React.createElement('span', { className: 'ide-toolbar-label' }, 'Test')
|
|
1457
1526
|
)
|
|
1458
1527
|
),
|
|
1528
|
+
tab.truncated && React.createElement(
|
|
1529
|
+
'div',
|
|
1530
|
+
{
|
|
1531
|
+
className: 'ide-pagination-bar',
|
|
1532
|
+
style: {
|
|
1533
|
+
display: 'flex',
|
|
1534
|
+
alignItems: 'center',
|
|
1535
|
+
gap: '8px',
|
|
1536
|
+
padding: '4px 10px',
|
|
1537
|
+
background: 'var(--ide-toolbar-bg, #252526)',
|
|
1538
|
+
borderBottom: '1px solid var(--ide-border, #3e3e3e)',
|
|
1539
|
+
fontSize: '12px',
|
|
1540
|
+
color: 'var(--ide-toolbar-fg, #ccc)',
|
|
1541
|
+
flexShrink: 0,
|
|
1542
|
+
userSelect: 'none'
|
|
1543
|
+
}
|
|
1544
|
+
},
|
|
1545
|
+
React.createElement(
|
|
1546
|
+
'button',
|
|
1547
|
+
{
|
|
1548
|
+
className: 'ide-icon-btn',
|
|
1549
|
+
style: { padding: '2px 8px', fontSize: '12px' },
|
|
1550
|
+
disabled: pageStartLine === 0,
|
|
1551
|
+
onClick: function() {
|
|
1552
|
+
var newStart = Math.max(0, pageStartLine - 500);
|
|
1553
|
+
FileService.getFileChunk(tab.path, newStart, 500).then(function(data) {
|
|
1554
|
+
var sl = data.start_line || 0;
|
|
1555
|
+
var lc = data.line_count || 0;
|
|
1556
|
+
var tl = data.total_lines || pageTotalLines;
|
|
1557
|
+
var tb = data.total_bytes || pageTotalBytes;
|
|
1558
|
+
setPageStartLine(sl);
|
|
1559
|
+
setPageLineCount(lc);
|
|
1560
|
+
setPageTotalLines(tl);
|
|
1561
|
+
setPageTotalBytes(tb);
|
|
1562
|
+
if (monacoRef.current) {
|
|
1563
|
+
monacoRef.current.setValue(data.content || '');
|
|
1564
|
+
var _paginatedEntry = window.__mbeditorModels && window.__mbeditorModels[tab.path];
|
|
1565
|
+
if (_paginatedEntry) {
|
|
1566
|
+
var _newBase = monacoRef.current.getModel().getAlternativeVersionId();
|
|
1567
|
+
aviBaseRef.current = _newBase;
|
|
1568
|
+
aviMaxRef.current = _newBase;
|
|
1569
|
+
_paginatedEntry.cleanVersionId = _newBase;
|
|
1570
|
+
_paginatedEntry.aviBase = _newBase;
|
|
1571
|
+
_paginatedEntry.aviMax = _newBase;
|
|
1572
|
+
}
|
|
1573
|
+
EditorStore.setState({ canUndo: false, canRedo: false });
|
|
1574
|
+
monacoRef.current.updateOptions({ readOnly: true });
|
|
1575
|
+
}
|
|
1576
|
+
}).catch(function(err) {
|
|
1577
|
+
EditorStore.setStatus('Failed to load page: ' + (err && err.message || 'Unknown error'), 'error');
|
|
1578
|
+
});
|
|
1579
|
+
}
|
|
1580
|
+
},
|
|
1581
|
+
'← Prev'
|
|
1582
|
+
),
|
|
1583
|
+
React.createElement(
|
|
1584
|
+
'span',
|
|
1585
|
+
{ style: { flex: 1, textAlign: 'center' } },
|
|
1586
|
+
'Lines ' + (pageStartLine + 1) + '–' + (pageStartLine + pageLineCount) + ' of ' + pageTotalLines + ' (' + humanSize(pageTotalBytes) + ')'
|
|
1587
|
+
),
|
|
1588
|
+
React.createElement(
|
|
1589
|
+
'button',
|
|
1590
|
+
{
|
|
1591
|
+
className: 'ide-icon-btn',
|
|
1592
|
+
style: { padding: '2px 8px', fontSize: '12px' },
|
|
1593
|
+
disabled: pageStartLine + pageLineCount >= pageTotalLines,
|
|
1594
|
+
onClick: function() {
|
|
1595
|
+
var newStart = pageStartLine + pageLineCount;
|
|
1596
|
+
FileService.getFileChunk(tab.path, newStart, 500).then(function(data) {
|
|
1597
|
+
var sl = data.start_line || 0;
|
|
1598
|
+
var lc = data.line_count || 0;
|
|
1599
|
+
var tl = data.total_lines || pageTotalLines;
|
|
1600
|
+
var tb = data.total_bytes || pageTotalBytes;
|
|
1601
|
+
setPageStartLine(sl);
|
|
1602
|
+
setPageLineCount(lc);
|
|
1603
|
+
setPageTotalLines(tl);
|
|
1604
|
+
setPageTotalBytes(tb);
|
|
1605
|
+
if (monacoRef.current) {
|
|
1606
|
+
monacoRef.current.setValue(data.content || '');
|
|
1607
|
+
var _paginatedEntry = window.__mbeditorModels && window.__mbeditorModels[tab.path];
|
|
1608
|
+
if (_paginatedEntry) {
|
|
1609
|
+
var _newBase = monacoRef.current.getModel().getAlternativeVersionId();
|
|
1610
|
+
aviBaseRef.current = _newBase;
|
|
1611
|
+
aviMaxRef.current = _newBase;
|
|
1612
|
+
_paginatedEntry.cleanVersionId = _newBase;
|
|
1613
|
+
_paginatedEntry.aviBase = _newBase;
|
|
1614
|
+
_paginatedEntry.aviMax = _newBase;
|
|
1615
|
+
}
|
|
1616
|
+
EditorStore.setState({ canUndo: false, canRedo: false });
|
|
1617
|
+
monacoRef.current.updateOptions({ readOnly: true });
|
|
1618
|
+
}
|
|
1619
|
+
}).catch(function(err) {
|
|
1620
|
+
EditorStore.setStatus('Failed to load page: ' + (err && err.message || 'Unknown error'), 'error');
|
|
1621
|
+
});
|
|
1622
|
+
}
|
|
1623
|
+
},
|
|
1624
|
+
'Next →'
|
|
1625
|
+
)
|
|
1626
|
+
),
|
|
1459
1627
|
React.createElement('div', { ref: editorRef, className: 'monaco-container', style: { flex: 1, minHeight: 0 } }),
|
|
1460
1628
|
methodsOpen && methodsDropdownPos && React.createElement(
|
|
1461
1629
|
'div',
|
|
@@ -51,6 +51,33 @@
|
|
|
51
51
|
|
|
52
52
|
var globalsRegistered = false;
|
|
53
53
|
|
|
54
|
+
// Enumerate window for user-defined (non-native) globals and return a TypeScript
|
|
55
|
+
// declaration string. Sprockets exposes every top-level var/function as a window
|
|
56
|
+
// property before Monaco initialises, so scanning window at registration time
|
|
57
|
+
// captures components, services, and helpers without any manual listing.
|
|
58
|
+
function buildWindowGlobalsShim() {
|
|
59
|
+
var alreadyDeclared = { React: 1, ReactDOM: 1, PropTypes: 1, MaterialUI: 1 };
|
|
60
|
+
var lines = [];
|
|
61
|
+
try {
|
|
62
|
+
var keys = Object.keys(window);
|
|
63
|
+
for (var i = 0; i < keys.length; i++) {
|
|
64
|
+
var key = keys[i];
|
|
65
|
+
if (alreadyDeclared[key]) continue;
|
|
66
|
+
if (!/^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(key)) continue;
|
|
67
|
+
var value;
|
|
68
|
+
try { value = window[key]; } catch (e) { continue; }
|
|
69
|
+
if (value === null || value === undefined) continue;
|
|
70
|
+
if (typeof value === 'function') {
|
|
71
|
+
try {
|
|
72
|
+
if (/\[native code\]/.test(Function.prototype.toString.call(value))) continue;
|
|
73
|
+
} catch (e) { continue; }
|
|
74
|
+
}
|
|
75
|
+
lines.push('declare var ' + key + ': any;');
|
|
76
|
+
}
|
|
77
|
+
} catch (e) {}
|
|
78
|
+
return lines.join('\n');
|
|
79
|
+
}
|
|
80
|
+
|
|
54
81
|
function leadingWhitespace(line) {
|
|
55
82
|
var match = line.match(/^\s*/);
|
|
56
83
|
return match ? match[0] : '';
|
|
@@ -455,6 +482,32 @@
|
|
|
455
482
|
});
|
|
456
483
|
}
|
|
457
484
|
|
|
485
|
+
// Declare globals that the sprockets asset pipeline injects at runtime so
|
|
486
|
+
// checkJs doesn't flag them as undefined. `interface Window` augmentation
|
|
487
|
+
// covers `window.myAppGlobal` access patterns. For app-specific component
|
|
488
|
+
// names not listed here, add `/* global MyComponent */` at the top of the
|
|
489
|
+
// file — TypeScript's checkJs mode respects that directive.
|
|
490
|
+
if (monaco.languages.typescript && monaco.languages.typescript.javascriptDefaults) {
|
|
491
|
+
monaco.languages.typescript.javascriptDefaults.addExtraLib(
|
|
492
|
+
[
|
|
493
|
+
'declare var React: any;',
|
|
494
|
+
'declare var ReactDOM: any;',
|
|
495
|
+
'declare var PropTypes: any;',
|
|
496
|
+
'declare var MaterialUI: any;',
|
|
497
|
+
'interface Window { [key: string]: any; }'
|
|
498
|
+
].join('\n'),
|
|
499
|
+
'inmemory://mbeditor/sprockets-globals.d.ts'
|
|
500
|
+
);
|
|
501
|
+
|
|
502
|
+
var dynamicShim = buildWindowGlobalsShim();
|
|
503
|
+
if (dynamicShim) {
|
|
504
|
+
monaco.languages.typescript.javascriptDefaults.addExtraLib(
|
|
505
|
+
dynamicShim,
|
|
506
|
+
'inmemory://mbeditor/window-globals.d.ts'
|
|
507
|
+
);
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
|
|
458
511
|
// TypeScript: enable JSX for .tsx files and catch unused locals.
|
|
459
512
|
if (monaco.languages.typescript && monaco.languages.typescript.typescriptDefaults) {
|
|
460
513
|
monaco.languages.typescript.typescriptDefaults.setCompilerOptions({
|
|
@@ -54,6 +54,14 @@ var FileService = (function () {
|
|
|
54
54
|
return axios.get(window.mbeditorBasePath() + '/file', { params: params }).then(function(res) { return res.data; });
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
+
function getFileChunk(path, startLine, lineCount) {
|
|
58
|
+
if (lineCount === undefined) {
|
|
59
|
+
lineCount = 500;
|
|
60
|
+
}
|
|
61
|
+
var params = { path: path, start_line: startLine, line_count: lineCount };
|
|
62
|
+
return axios.get(window.mbeditorBasePath() + '/file', { params: params }).then(function(res) { return res.data; });
|
|
63
|
+
}
|
|
64
|
+
|
|
57
65
|
function saveFile(path, code) {
|
|
58
66
|
return axios.post(window.mbeditorBasePath() + '/file', { path: path, code: code }).then(function(res) { return res.data; });
|
|
59
67
|
}
|
|
@@ -200,6 +208,7 @@ var FileService = (function () {
|
|
|
200
208
|
getWorkspace: getWorkspace,
|
|
201
209
|
getTree: getTree,
|
|
202
210
|
getFile: getFile,
|
|
211
|
+
getFileChunk: getFileChunk,
|
|
203
212
|
saveFile: saveFile,
|
|
204
213
|
createFile: createFile,
|
|
205
214
|
createDir: createDir,
|
|
@@ -207,6 +207,29 @@ var TabManager = (function () {
|
|
|
207
207
|
}
|
|
208
208
|
}).catch(function(err) {
|
|
209
209
|
if (path.startsWith('diff://')) return; // diff tabs handle their own loading
|
|
210
|
+
if (err.response && err.response.status === 413) {
|
|
211
|
+
FileService.getFileChunk(path, 0, 500).then(function(data) {
|
|
212
|
+
var loadedContent = typeof data.content === 'string' ? data.content : "";
|
|
213
|
+
_updateTab(paneId, path, {
|
|
214
|
+
content: loadedContent,
|
|
215
|
+
cleanContent: loadedContent,
|
|
216
|
+
externalContentVersion: 1,
|
|
217
|
+
isImage: false,
|
|
218
|
+
fileNotFound: false,
|
|
219
|
+
dirty: false,
|
|
220
|
+
loading: false,
|
|
221
|
+
truncated: true,
|
|
222
|
+
startLine: data.start_line || 0,
|
|
223
|
+
lineCount: data.line_count || 0,
|
|
224
|
+
totalLines: data.total_lines || 0,
|
|
225
|
+
totalBytes: data.total_bytes || 0
|
|
226
|
+
});
|
|
227
|
+
}).catch(function(chunkErr) {
|
|
228
|
+
EditorStore.setStatus("Failed to load large file: " + ((chunkErr.response && chunkErr.response.data && chunkErr.response.data.error) || chunkErr.message), "error");
|
|
229
|
+
closeTab(paneId, path);
|
|
230
|
+
});
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
210
233
|
EditorStore.setStatus("Failed to load file: " + ((err.response && err.response.data && err.response.data.error) || err.message), "error");
|
|
211
234
|
closeTab(paneId, path);
|
|
212
235
|
});
|
|
@@ -170,6 +170,29 @@ module Mbeditor
|
|
|
170
170
|
return render json: { error: "Not found" }, status: :not_found
|
|
171
171
|
end
|
|
172
172
|
|
|
173
|
+
start_line = params[:start_line] ? params[:start_line].to_i : nil
|
|
174
|
+
line_count = params.key?(:line_count) ? params[:line_count].to_i : 500
|
|
175
|
+
line_count = [line_count, 5000].min
|
|
176
|
+
|
|
177
|
+
if start_line
|
|
178
|
+
total_bytes = File.size(path)
|
|
179
|
+
chunk = []
|
|
180
|
+
total_lines = 0
|
|
181
|
+
File.foreach(path, encoding: "UTF-8", invalid: :replace, undef: :replace) do |line|
|
|
182
|
+
chunk << line if total_lines >= start_line && chunk.length < line_count
|
|
183
|
+
total_lines += 1
|
|
184
|
+
end
|
|
185
|
+
return render json: {
|
|
186
|
+
path: relative_path(path),
|
|
187
|
+
content: chunk.join,
|
|
188
|
+
truncated: total_lines > start_line + chunk.length,
|
|
189
|
+
start_line: start_line,
|
|
190
|
+
line_count: chunk.length,
|
|
191
|
+
total_lines: total_lines,
|
|
192
|
+
total_bytes: total_bytes
|
|
193
|
+
}
|
|
194
|
+
end
|
|
195
|
+
|
|
173
196
|
size = File.size(path)
|
|
174
197
|
return render_file_too_large(size) if size > MAX_OPEN_FILE_SIZE_BYTES
|
|
175
198
|
|
data/lib/mbeditor/version.rb
CHANGED