Commit 674702e0 authored by Tim Zallmann's avatar Tim Zallmann

Merge branch 'ide-diff-marker-cache-fix' into 'master'

Fixed IDE diff markers being cached too long

See merge request gitlab-org/gitlab-ce!18311
parents 54e26296 f2bf23fe
...@@ -32,7 +32,7 @@ export default class Model { ...@@ -32,7 +32,7 @@ export default class Model {
); );
} }
this.events = new Map(); this.events = new Set();
this.updateContent = this.updateContent.bind(this); this.updateContent = this.updateContent.bind(this);
this.updateNewContent = this.updateNewContent.bind(this); this.updateNewContent = this.updateNewContent.bind(this);
...@@ -76,10 +76,11 @@ export default class Model { ...@@ -76,10 +76,11 @@ export default class Model {
} }
onChange(cb) { onChange(cb) {
this.events.set( this.events.add(this.disposable.add(this.model.onDidChangeContent(e => cb(this, e))));
this.path, }
this.disposable.add(this.model.onDidChangeContent(e => cb(this, e))),
); onDispose(cb) {
this.events.add(cb);
} }
updateContent({ content, changed }) { updateContent({ content, changed }) {
...@@ -96,6 +97,11 @@ export default class Model { ...@@ -96,6 +97,11 @@ export default class Model {
dispose() { dispose() {
this.disposable.dispose(); this.disposable.dispose();
this.events.forEach(cb => {
if (typeof cb === 'function') cb();
});
this.events.clear(); this.events.clear();
eventHub.$off(`editor.update.model.dispose.${this.file.key}`, this.dispose); eventHub.$off(`editor.update.model.dispose.${this.file.key}`, this.dispose);
......
...@@ -38,6 +38,15 @@ export default class DecorationsController { ...@@ -38,6 +38,15 @@ export default class DecorationsController {
); );
} }
hasDecorations(model) {
return this.decorations.has(model.url);
}
removeDecorations(model) {
this.decorations.delete(model.url);
this.editorDecorations.delete(model.url);
}
dispose() { dispose() {
this.decorations.clear(); this.decorations.clear();
this.editorDecorations.clear(); this.editorDecorations.clear();
......
...@@ -3,7 +3,7 @@ import { throttle } from 'underscore'; ...@@ -3,7 +3,7 @@ import { throttle } from 'underscore';
import DirtyDiffWorker from './diff_worker'; import DirtyDiffWorker from './diff_worker';
import Disposable from '../common/disposable'; import Disposable from '../common/disposable';
export const getDiffChangeType = (change) => { export const getDiffChangeType = change => {
if (change.modified) { if (change.modified) {
return 'modified'; return 'modified';
} else if (change.added) { } else if (change.added) {
...@@ -16,12 +16,7 @@ export const getDiffChangeType = (change) => { ...@@ -16,12 +16,7 @@ export const getDiffChangeType = (change) => {
}; };
export const getDecorator = change => ({ export const getDecorator = change => ({
range: new monaco.Range( range: new monaco.Range(change.lineNumber, 1, change.endLineNumber, 1),
change.lineNumber,
1,
change.endLineNumber,
1,
),
options: { options: {
isWholeLine: true, isWholeLine: true,
linesDecorationsClassName: `dirty-diff dirty-diff-${getDiffChangeType(change)}`, linesDecorationsClassName: `dirty-diff dirty-diff-${getDiffChangeType(change)}`,
...@@ -31,6 +26,7 @@ export const getDecorator = change => ({ ...@@ -31,6 +26,7 @@ export const getDecorator = change => ({
export default class DirtyDiffController { export default class DirtyDiffController {
constructor(modelManager, decorationsController) { constructor(modelManager, decorationsController) {
this.disposable = new Disposable(); this.disposable = new Disposable();
this.models = new Map();
this.editorSimpleWorker = null; this.editorSimpleWorker = null;
this.modelManager = modelManager; this.modelManager = modelManager;
this.decorationsController = decorationsController; this.decorationsController = decorationsController;
...@@ -42,7 +38,15 @@ export default class DirtyDiffController { ...@@ -42,7 +38,15 @@ export default class DirtyDiffController {
} }
attachModel(model) { attachModel(model) {
if (this.models.has(model.url)) return;
model.onChange(() => this.throttledComputeDiff(model)); model.onChange(() => this.throttledComputeDiff(model));
model.onDispose(() => {
this.decorationsController.removeDecorations(model);
this.models.delete(model.url);
});
this.models.set(model.url, model);
} }
computeDiff(model) { computeDiff(model) {
...@@ -54,7 +58,11 @@ export default class DirtyDiffController { ...@@ -54,7 +58,11 @@ export default class DirtyDiffController {
} }
reDecorate(model) { reDecorate(model) {
if (this.decorationsController.hasDecorations(model)) {
this.decorationsController.decorate(model); this.decorationsController.decorate(model);
} else {
this.computeDiff(model);
}
} }
decorate({ data }) { decorate({ data }) {
...@@ -65,6 +73,7 @@ export default class DirtyDiffController { ...@@ -65,6 +73,7 @@ export default class DirtyDiffController {
dispose() { dispose() {
this.disposable.dispose(); this.disposable.dispose();
this.models.clear();
this.dirtyDiffWorker.removeEventListener('message', this.decorate); this.dirtyDiffWorker.removeEventListener('message', this.decorate);
this.dirtyDiffWorker.terminate(); this.dirtyDiffWorker.terminate();
......
...@@ -222,7 +222,7 @@ describe('RepoEditor', () => { ...@@ -222,7 +222,7 @@ describe('RepoEditor', () => {
vm.setupEditor(); vm.setupEditor();
expect(vm.editor.onPositionChange).toHaveBeenCalled(); expect(vm.editor.onPositionChange).toHaveBeenCalled();
expect(vm.model.events.size).toBe(1); expect(vm.model.events.size).toBe(2);
}); });
it('updates state when model content changed', done => { it('updates state when model content changed', done => {
......
...@@ -83,13 +83,6 @@ describe('Multi-file editor library model', () => { ...@@ -83,13 +83,6 @@ describe('Multi-file editor library model', () => {
}); });
describe('onChange', () => { describe('onChange', () => {
it('caches event by path', () => {
model.onChange(() => {});
expect(model.events.size).toBe(1);
expect(model.events.keys().next().value).toBe(model.file.key);
});
it('calls callback on change', done => { it('calls callback on change', done => {
const spy = jasmine.createSpy(); const spy = jasmine.createSpy();
model.onChange(spy); model.onChange(spy);
...@@ -132,5 +125,15 @@ describe('Multi-file editor library model', () => { ...@@ -132,5 +125,15 @@ describe('Multi-file editor library model', () => {
jasmine.anything(), jasmine.anything(),
); );
}); });
it('calls onDispose callback', () => {
const disposeSpy = jasmine.createSpy();
model.onDispose(disposeSpy);
model.dispose();
expect(disposeSpy).toHaveBeenCalled();
});
}); });
}); });
...@@ -117,4 +117,33 @@ describe('Multi-file editor library decorations controller', () => { ...@@ -117,4 +117,33 @@ describe('Multi-file editor library decorations controller', () => {
expect(controller.editorDecorations.size).toBe(0); expect(controller.editorDecorations.size).toBe(0);
}); });
}); });
describe('hasDecorations', () => {
it('returns true when decorations are cached', () => {
controller.addDecorations(model, 'key', [{ decoration: 'decorationValue' }]);
expect(controller.hasDecorations(model)).toBe(true);
});
it('returns false when no model decorations exist', () => {
expect(controller.hasDecorations(model)).toBe(false);
});
});
describe('removeDecorations', () => {
beforeEach(() => {
controller.addDecorations(model, 'key', [{ decoration: 'decorationValue' }]);
controller.decorate(model);
});
it('removes cached decorations', () => {
expect(controller.decorations.size).not.toBe(0);
expect(controller.editorDecorations.size).not.toBe(0);
controller.removeDecorations(model);
expect(controller.decorations.size).toBe(0);
expect(controller.editorDecorations.size).toBe(0);
});
});
}); });
...@@ -3,10 +3,7 @@ import monacoLoader from '~/ide/monaco_loader'; ...@@ -3,10 +3,7 @@ import monacoLoader from '~/ide/monaco_loader';
import editor from '~/ide/lib/editor'; import editor from '~/ide/lib/editor';
import ModelManager from '~/ide/lib/common/model_manager'; import ModelManager from '~/ide/lib/common/model_manager';
import DecorationsController from '~/ide/lib/decorations/controller'; import DecorationsController from '~/ide/lib/decorations/controller';
import DirtyDiffController, { import DirtyDiffController, { getDiffChangeType, getDecorator } from '~/ide/lib/diff/controller';
getDiffChangeType,
getDecorator,
} from '~/ide/lib/diff/controller';
import { computeDiff } from '~/ide/lib/diff/diff'; import { computeDiff } from '~/ide/lib/diff/diff';
import { file } from '../../helpers'; import { file } from '../../helpers';
...@@ -90,6 +87,14 @@ describe('Multi-file editor library dirty diff controller', () => { ...@@ -90,6 +87,14 @@ describe('Multi-file editor library dirty diff controller', () => {
expect(model.onChange).toHaveBeenCalled(); expect(model.onChange).toHaveBeenCalled();
}); });
it('adds dispose event callback', () => {
spyOn(model, 'onDispose');
controller.attachModel(model);
expect(model.onDispose).toHaveBeenCalled();
});
it('calls throttledComputeDiff on change', () => { it('calls throttledComputeDiff on change', () => {
spyOn(controller, 'throttledComputeDiff'); spyOn(controller, 'throttledComputeDiff');
...@@ -99,6 +104,12 @@ describe('Multi-file editor library dirty diff controller', () => { ...@@ -99,6 +104,12 @@ describe('Multi-file editor library dirty diff controller', () => {
expect(controller.throttledComputeDiff).toHaveBeenCalled(); expect(controller.throttledComputeDiff).toHaveBeenCalled();
}); });
it('caches model', () => {
controller.attachModel(model);
expect(controller.models.has(model.url)).toBe(true);
});
}); });
describe('computeDiff', () => { describe('computeDiff', () => {
...@@ -116,14 +127,22 @@ describe('Multi-file editor library dirty diff controller', () => { ...@@ -116,14 +127,22 @@ describe('Multi-file editor library dirty diff controller', () => {
}); });
describe('reDecorate', () => { describe('reDecorate', () => {
it('calls decorations controller decorate', () => { it('calls computeDiff when no decorations are cached', () => {
spyOn(controller, 'computeDiff');
controller.reDecorate(model);
expect(controller.computeDiff).toHaveBeenCalledWith(model);
});
it('calls decorate when decorations are cached', () => {
spyOn(controller.decorationsController, 'decorate'); spyOn(controller.decorationsController, 'decorate');
controller.decorationsController.decorations.set(model.url, 'test');
controller.reDecorate(model); controller.reDecorate(model);
expect(controller.decorationsController.decorate).toHaveBeenCalledWith( expect(controller.decorationsController.decorate).toHaveBeenCalledWith(model);
model,
);
}); });
}); });
...@@ -133,16 +152,15 @@ describe('Multi-file editor library dirty diff controller', () => { ...@@ -133,16 +152,15 @@ describe('Multi-file editor library dirty diff controller', () => {
controller.decorate({ data: { changes: [], path: model.path } }); controller.decorate({ data: { changes: [], path: model.path } });
expect( expect(controller.decorationsController.addDecorations).toHaveBeenCalledWith(
controller.decorationsController.addDecorations, model,
).toHaveBeenCalledWith(model, 'dirtyDiff', jasmine.anything()); 'dirtyDiff',
jasmine.anything(),
);
}); });
it('adds decorations into editor', () => { it('adds decorations into editor', () => {
const spy = spyOn( const spy = spyOn(controller.decorationsController.editor.instance, 'deltaDecorations');
controller.decorationsController.editor.instance,
'deltaDecorations',
);
controller.decorate({ controller.decorate({
data: { changes: computeDiff('123', '1234'), path: model.path }, data: { changes: computeDiff('123', '1234'), path: model.path },
...@@ -181,16 +199,22 @@ describe('Multi-file editor library dirty diff controller', () => { ...@@ -181,16 +199,22 @@ describe('Multi-file editor library dirty diff controller', () => {
}); });
it('removes worker event listener', () => { it('removes worker event listener', () => {
spyOn( spyOn(controller.dirtyDiffWorker, 'removeEventListener').and.callThrough();
controller.dirtyDiffWorker,
'removeEventListener',
).and.callThrough();
controller.dispose(); controller.dispose();
expect( expect(controller.dirtyDiffWorker.removeEventListener).toHaveBeenCalledWith(
controller.dirtyDiffWorker.removeEventListener, 'message',
).toHaveBeenCalledWith('message', jasmine.anything()); jasmine.anything(),
);
});
it('clears cached models', () => {
controller.attachModel(model);
model.dispose();
expect(controller.models.size).toBe(0);
}); });
}); });
}); });
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment