Skip to content

Commit 264ebb3

Browse files
committed
Reload active editor after sidebar refresh
Move was_clean to code_mirror instance so the state survives editor detach. Only reload the active buffer when it has no unsaved edits, preventing user work from being clobbered after a GitHub pull.
1 parent e2c25d6 commit 264ebb3

3 files changed

Lines changed: 85 additions & 8 deletions

File tree

cloudpebble/ide/static/ide/js/__tests__/sidebar-refresh.test.js

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,8 @@ function makeCloudPebble() {
7272
return {
7373
Editor: {
7474
GetUnsavedFiles: vi.fn(() => 3),
75-
Add: vi.fn()
75+
Add: vi.fn(),
76+
ReloadActive: vi.fn(() => Promise.resolve())
7677
},
7778
Resources: {
7879
Add: vi.fn(),
@@ -185,4 +186,32 @@ describe('Sidebar.Refresh', () => {
185186
});
186187
expect(CloudPebble.Resources.Add).toHaveBeenCalledWith({ id: 1, file_name: 'icon.png' });
187188
});
189+
190+
it('calls Editor.ReloadActive after a successful refresh', () => {
191+
Sidebar.Refresh();
192+
deferred.resolve({
193+
type: 'native',
194+
source_files: [],
195+
resources: []
196+
});
197+
expect(CloudPebble.Editor.ReloadActive).toHaveBeenCalled();
198+
});
199+
200+
it('does not call Editor.ReloadActive when the ajax request fails', () => {
201+
Sidebar.Refresh();
202+
deferred.reject();
203+
expect(CloudPebble.Editor.ReloadActive).not.toHaveBeenCalled();
204+
});
205+
206+
it('does not throw when Editor.ReloadActive is not provided', () => {
207+
delete CloudPebble.Editor.ReloadActive;
208+
Sidebar.Refresh();
209+
expect(() => {
210+
deferred.resolve({
211+
type: 'native',
212+
source_files: [],
213+
resources: []
214+
});
215+
}).not.toThrow();
216+
});
188217
});

cloudpebble/ide/static/ide/js/editor.js

Lines changed: 50 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -519,12 +519,12 @@ CloudPebble.Editor = (function() {
519519
var check_safe = function() {
520520
return Ajax.Get('/ide/project/' + PROJECT_ID + '/source/' + file.id + '/is_safe?modified=' + file.lastModified).then(function(data) {
521521
if(!data.safe) {
522-
if(was_clean) {
522+
if(code_mirror.was_clean) {
523523
code_mirror.setOption('readOnly', true);
524524
return CloudPebble.Get('/ide/project/' + PROJECT_ID + '/source/' + file.id + '/load', function(data) {
525525
code_mirror.setValue(data.source);
526526
file.lastModified = data.modified;
527-
was_clean = true; // this will get reset to false by setValue.
527+
code_mirror.was_clean = true; // this will get reset to false by setValue.
528528
}).finally(function() {
529529
code_mirror.setOption('readOnly', false);
530530
});
@@ -552,24 +552,24 @@ CloudPebble.Editor = (function() {
552552
}
553553
},
554554
onDestroy: function() {
555-
if(!was_clean) {
555+
if(!code_mirror.was_clean) {
556556
--unsaved_files;
557557
}
558558
delete open_codemirrors[file.id];
559559
}
560560
});
561561

562-
var was_clean = true;
562+
code_mirror.was_clean = true;
563563
code_mirror.on('change', function() {
564-
if(was_clean) {
564+
if(code_mirror.was_clean) {
565565
CloudPebble.Sidebar.SetIcon('source-' + file.id, 'edit');
566-
was_clean = false;
566+
code_mirror.was_clean = false;
567567
++unsaved_files;
568568
}
569569
});
570570

571571
var mark_clean = function() {
572-
was_clean = true;
572+
code_mirror.was_clean = true;
573573
--unsaved_files;
574574
CloudPebble.Sidebar.ClearIcon('source-' + file.id);
575575
};
@@ -1284,6 +1284,18 @@ CloudPebble.Editor = (function() {
12841284
}
12851285
}
12861286

1287+
var get_active_source_file_id = function() {
1288+
var pane_id = $('#main-pane').data('pane-id');
1289+
if (!pane_id || pane_id.indexOf('source-') !== 0) {
1290+
return null;
1291+
}
1292+
var file_id = parseInt(pane_id.substring('source-'.length), 10);
1293+
if (isNaN(file_id)) {
1294+
return null;
1295+
}
1296+
return file_id;
1297+
};
1298+
12871299
return {
12881300
Create: function() {
12891301
create_source_file();
@@ -1311,6 +1323,37 @@ CloudPebble.Editor = (function() {
13111323
},
13121324
RenameFile: function(file, new_name) {
13131325
return rename_file(file, new_name)
1326+
},
1327+
/**
1328+
* Re-fetch the source of the currently-active editor from the server and
1329+
* replace its buffer. No-op if the active pane isn't a source file, if
1330+
* the editor is currently detached, or if the buffer has unsaved local
1331+
* edits (we never clobber user work).
1332+
*
1333+
* Called from Sidebar.Refresh after a GitHub pull, so the user doesn't
1334+
* end up staring at a stale buffer for a file that just changed on
1335+
* the server.
1336+
*/
1337+
ReloadActive: function() {
1338+
var file_id = get_active_source_file_id();
1339+
if (file_id === null) {
1340+
return Promise.resolve();
1341+
}
1342+
var code_mirror = open_codemirrors[file_id];
1343+
if (!code_mirror) {
1344+
return Promise.resolve();
1345+
}
1346+
if (!code_mirror.was_clean) {
1347+
return Promise.resolve();
1348+
}
1349+
return Ajax.Get('/ide/project/' + PROJECT_ID + '/source/' + file_id + '/load').then(function(data) {
1350+
code_mirror.setValue(data.source);
1351+
code_mirror.was_clean = true;
1352+
var file = project_source_files[code_mirror.file_path];
1353+
if (file) {
1354+
file.lastModified = data.modified;
1355+
}
1356+
});
13141357
}
13151358
};
13161359
})();

cloudpebble/ide/static/ide/js/sidebar.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -341,6 +341,11 @@ CloudPebble.Sidebar = (function() {
341341
} finally {
342342
CloudPebble.Editor.GetUnsavedFiles = saved_GetUnsavedFiles;
343343
}
344+
// Re-load the buffer for the currently-open file, if any, so
345+
// the user isn't staring at stale content after a pull.
346+
if (typeof CloudPebble.Editor.ReloadActive === 'function') {
347+
CloudPebble.Editor.ReloadActive();
348+
}
344349
}).fail(function() {
345350
CloudPebble.Editor.GetUnsavedFiles = saved_GetUnsavedFiles;
346351
});

0 commit comments

Comments
 (0)