route editor config through a source-agnostic EditorSettings model (T-29)
Introduce EditorSettings as the effective, source-agnostic editor configuration the editor and save path obey. .editorconfig is demoted to one *source* (editorconfig.dart now returns EditorSettings); editor_settings_resolver.dart is the single composition seam where future sources — a settings panel, a clide settings file — layer in via merge() without the editor changing. The registry resolves settings on buffer load and, when a .editorconfig is saved in-app, re-resolves every open buffer and emits editor.settings-changed (a hook in save, not a filesystem watcher — the realistic case, cheaply). Buffer JSON carries editorSettings. 100% line coverage on the new model + resolver. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,23 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:clide/src/editor/editor_settings_resolver.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
void main() {
|
||||
group('resolveEditorSettings', () {
|
||||
late Directory root;
|
||||
setUp(() async => root = await Directory.systemTemp.createTemp('clide-ec-resolve-'));
|
||||
tearDown(() => root.deleteSync(recursive: true));
|
||||
|
||||
test('composes from the .editorconfig source', () async {
|
||||
await File('${root.path}/.editorconfig').writeAsString('root = true\n[*]\nindent_style = space\nindent_size = 2\n');
|
||||
final s = resolveEditorSettings(root, 'lib/a.dart');
|
||||
expect(s.indentStyle, 'space');
|
||||
expect(s.indentSize, 2);
|
||||
});
|
||||
|
||||
test('no source resolves to empty settings', () async {
|
||||
expect(resolveEditorSettings(root, 'a.txt').isEmpty, isTrue);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
import 'package:clide/src/editor/editor_settings.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
void main() {
|
||||
group('EditorSettings model', () {
|
||||
test('toJson omits unset keys; fromJson round-trips', () {
|
||||
expect(const EditorSettings().toJson(), isEmpty);
|
||||
const s = EditorSettings(indentStyle: 'space', indentSize: 2, maxLineLength: 80, insertFinalNewline: true);
|
||||
expect(EditorSettings.fromJson(s.toJson()).toJson(), s.toJson());
|
||||
expect(EditorSettings.fromJson('not a map'), EditorSettings.empty);
|
||||
});
|
||||
|
||||
test('isEmpty + eolString', () {
|
||||
expect(EditorSettings.empty.isEmpty, isTrue);
|
||||
expect(const EditorSettings(indentSize: 2).isEmpty, isFalse);
|
||||
expect(const EditorSettings(endOfLine: 'crlf').eolString, '\r\n');
|
||||
expect(const EditorSettings(endOfLine: 'lf').eolString, '\n');
|
||||
expect(const EditorSettings(endOfLine: 'cr').eolString, '\r');
|
||||
expect(EditorSettings.empty.eolString, isNull);
|
||||
});
|
||||
|
||||
test('indentUnit reflects style + size', () {
|
||||
expect(const EditorSettings(indentStyle: 'tab').indentUnit, '\t');
|
||||
expect(const EditorSettings(indentStyle: 'space', indentSize: 3).indentUnit, ' ');
|
||||
expect(const EditorSettings(indentStyle: 'space').indentUnit, ' '); // defaults to 4
|
||||
expect(const EditorSettings(indentSize: 2).indentUnit, ' '); // size with no style → spaces
|
||||
expect(EditorSettings.empty.indentUnit, isNull); // no opinion → keep default
|
||||
});
|
||||
|
||||
test('merge — later source wins per set field, others fall through', () {
|
||||
const base = EditorSettings(indentStyle: 'space', indentSize: 2, maxLineLength: 80);
|
||||
const over = EditorSettings(indentSize: 4, insertFinalNewline: true);
|
||||
final m = base.merge(over);
|
||||
expect(m.indentSize, 4); // overridden
|
||||
expect(m.indentStyle, 'space'); // inherited
|
||||
expect(m.maxLineLength, 80); // inherited
|
||||
expect(m.insertFinalNewline, isTrue); // added
|
||||
// Merging empty over a value is a no-op; value over empty adopts it.
|
||||
expect(base.merge(EditorSettings.empty).toJson(), base.toJson());
|
||||
expect(EditorSettings.empty.merge(base).toJson(), base.toJson());
|
||||
});
|
||||
});
|
||||
|
||||
group('EditorSettings.applyOnSave', () {
|
||||
test('trims trailing whitespace on every line, any EOL', () {
|
||||
const cfg = EditorSettings(trimTrailingWhitespace: true);
|
||||
expect(cfg.applyOnSave('a \nb\t\nc'), 'a\nb\nc');
|
||||
expect(cfg.applyOnSave('a \r\nb \r\n'), 'a\r\nb\r\n');
|
||||
});
|
||||
|
||||
test('inserts a final newline when missing', () {
|
||||
const cfg = EditorSettings(insertFinalNewline: true);
|
||||
expect(cfg.applyOnSave('abc'), 'abc\n');
|
||||
expect(cfg.applyOnSave('abc\n'), 'abc\n');
|
||||
});
|
||||
|
||||
test('insert_final_newline=false strips trailing newlines', () {
|
||||
expect(const EditorSettings(insertFinalNewline: false).applyOnSave('abc\n\n'), 'abc');
|
||||
});
|
||||
|
||||
test('final newline uses the configured EOL, else the detected one', () {
|
||||
expect(const EditorSettings(endOfLine: 'crlf', insertFinalNewline: true).applyOnSave('abc'), 'abc\r\n');
|
||||
expect(const EditorSettings(insertFinalNewline: true).applyOnSave('a\r\nb'), 'a\r\nb\r\n');
|
||||
});
|
||||
|
||||
test('normalizes EOL across the whole file', () {
|
||||
expect(const EditorSettings(endOfLine: 'crlf').applyOnSave('a\nb\nc'), 'a\r\nb\r\nc');
|
||||
expect(const EditorSettings(endOfLine: 'lf').applyOnSave('a\r\nb\r\n'), 'a\nb\n');
|
||||
});
|
||||
|
||||
test('no opinion (and empty content) leaves the text untouched', () {
|
||||
expect(EditorSettings.empty.applyOnSave('a \r\nb'), 'a \r\nb');
|
||||
expect(const EditorSettings(insertFinalNewline: true).applyOnSave(''), '');
|
||||
});
|
||||
|
||||
test('composes trim + EOL + final newline in order', () {
|
||||
const cfg = EditorSettings(trimTrailingWhitespace: true, endOfLine: 'lf', insertFinalNewline: true);
|
||||
expect(cfg.applyOnSave('a \r\nb\t'), 'a\nb\n');
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -4,9 +4,9 @@ import 'package:clide/src/editor/editorconfig.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
void main() {
|
||||
group('EditorConfig.fromProps', () {
|
||||
test('parses the common indent + newline keys', () {
|
||||
final c = EditorConfig.fromProps({
|
||||
group('editorSettingsFromProps', () {
|
||||
test('maps the common indent + newline keys', () {
|
||||
final c = editorSettingsFromProps({
|
||||
'indent_style': 'space',
|
||||
'indent_size': '2',
|
||||
'end_of_line': 'lf',
|
||||
@@ -24,83 +24,31 @@ void main() {
|
||||
});
|
||||
|
||||
test('indent_size = tab follows tab_width', () {
|
||||
final c = EditorConfig.fromProps({'indent_style': 'tab', 'indent_size': 'tab', 'tab_width': '4'});
|
||||
final c = editorSettingsFromProps({'indent_style': 'tab', 'indent_size': 'tab', 'tab_width': '4'});
|
||||
expect(c.indentSize, 4);
|
||||
expect(c.tabWidth, 4);
|
||||
});
|
||||
|
||||
test('tab indent with no indent_size defaults to tab_width', () {
|
||||
final c = EditorConfig.fromProps({'indent_style': 'tab', 'tab_width': '8'});
|
||||
expect(c.indentSize, 8);
|
||||
expect(editorSettingsFromProps({'indent_style': 'tab', 'tab_width': '8'}).indentSize, 8);
|
||||
});
|
||||
|
||||
test('max_line_length = off and unset values resolve to null', () {
|
||||
final c = EditorConfig.fromProps({'max_line_length': 'off', 'indent_style': 'unset', 'insert_final_newline': 'unset'});
|
||||
final c = editorSettingsFromProps({'max_line_length': 'off', 'indent_style': 'unset', 'insert_final_newline': 'unset'});
|
||||
expect(c.maxLineLength, isNull);
|
||||
expect(c.indentStyle, isNull);
|
||||
expect(c.insertFinalNewline, isNull);
|
||||
});
|
||||
|
||||
test('rejects junk values rather than guessing', () {
|
||||
final c = EditorConfig.fromProps({'indent_style': 'tabs', 'indent_size': '-3', 'end_of_line': 'mac'});
|
||||
final c = editorSettingsFromProps({'indent_style': 'tabs', 'indent_size': '-3', 'end_of_line': 'mac'});
|
||||
expect(c.indentStyle, isNull);
|
||||
expect(c.indentSize, isNull);
|
||||
expect(c.endOfLine, isNull);
|
||||
});
|
||||
|
||||
test('toJson omits unset keys', () {
|
||||
expect(const EditorConfig().toJson(), isEmpty);
|
||||
expect(const EditorConfig(indentStyle: 'space', indentSize: 2).toJson(), {'indent_style': 'space', 'indent_size': 2});
|
||||
});
|
||||
});
|
||||
|
||||
group('applyEditorConfigOnSave', () {
|
||||
test('trims trailing whitespace on every line, any EOL', () {
|
||||
const cfg = EditorConfig(trimTrailingWhitespace: true);
|
||||
expect(applyEditorConfigOnSave('a \nb\t\nc', cfg), 'a\nb\nc');
|
||||
expect(applyEditorConfigOnSave('a \r\nb \r\n', cfg), 'a\r\nb\r\n');
|
||||
});
|
||||
|
||||
test('inserts a final newline when missing', () {
|
||||
const cfg = EditorConfig(insertFinalNewline: true);
|
||||
expect(applyEditorConfigOnSave('abc', cfg), 'abc\n');
|
||||
expect(applyEditorConfigOnSave('abc\n', cfg), 'abc\n'); // already present
|
||||
});
|
||||
|
||||
test('insert_final_newline=false strips trailing newlines', () {
|
||||
const cfg = EditorConfig(insertFinalNewline: false);
|
||||
expect(applyEditorConfigOnSave('abc\n\n', cfg), 'abc');
|
||||
});
|
||||
|
||||
test('final newline uses the configured EOL', () {
|
||||
const cfg = EditorConfig(endOfLine: 'crlf', insertFinalNewline: true);
|
||||
expect(applyEditorConfigOnSave('abc', cfg), 'abc\r\n');
|
||||
});
|
||||
|
||||
test('normalizes EOL across the whole file', () {
|
||||
const cfg = EditorConfig(endOfLine: 'crlf');
|
||||
expect(applyEditorConfigOnSave('a\nb\nc', cfg), 'a\r\nb\r\nc');
|
||||
const lf = EditorConfig(endOfLine: 'lf');
|
||||
expect(applyEditorConfigOnSave('a\r\nb\r\n', lf), 'a\nb\n');
|
||||
});
|
||||
|
||||
test('no opinion leaves the content untouched', () {
|
||||
expect(applyEditorConfigOnSave('a \r\nb', EditorConfig.empty), 'a \r\nb');
|
||||
expect(applyEditorConfigOnSave('', const EditorConfig(insertFinalNewline: true)), '');
|
||||
});
|
||||
|
||||
test('with no EOL set, the inserted final newline matches the file style', () {
|
||||
const cfg = EditorConfig(insertFinalNewline: true);
|
||||
expect(applyEditorConfigOnSave('a\r\nb', cfg), 'a\r\nb\r\n'); // detected crlf
|
||||
});
|
||||
|
||||
test('composes trim + EOL + final newline in order', () {
|
||||
const cfg = EditorConfig(trimTrailingWhitespace: true, endOfLine: 'lf', insertFinalNewline: true);
|
||||
expect(applyEditorConfigOnSave('a \r\nb\t', cfg), 'a\nb\n');
|
||||
});
|
||||
});
|
||||
|
||||
group('resolveEditorConfig — glob matching', () {
|
||||
group('readEditorConfig — glob matching', () {
|
||||
late Directory root;
|
||||
setUp(() async => root = await Directory.systemTemp.createTemp('clide-ec-glob-'));
|
||||
tearDown(() => root.deleteSync(recursive: true));
|
||||
@@ -113,88 +61,80 @@ void main() {
|
||||
|
||||
test('[*] applies to every file', () async {
|
||||
await write('.editorconfig', 'root = true\n[*]\nindent_size = 2\n');
|
||||
expect(resolveEditorConfig(root, 'a.txt').indentSize, 2);
|
||||
expect(resolveEditorConfig(root, 'sub/b.dart').indentSize, 2);
|
||||
expect(readEditorConfig(root, 'a.txt').indentSize, 2);
|
||||
expect(readEditorConfig(root, 'sub/b.dart').indentSize, 2);
|
||||
});
|
||||
|
||||
test('extension globs and brace alternation', () async {
|
||||
await write('.editorconfig', 'root = true\n[*.dart]\nindent_size = 2\n[*.{js,ts}]\nindent_size = 4\n');
|
||||
expect(resolveEditorConfig(root, 'main.dart').indentSize, 2);
|
||||
expect(resolveEditorConfig(root, 'app.js').indentSize, 4);
|
||||
expect(resolveEditorConfig(root, 'app.ts').indentSize, 4);
|
||||
expect(resolveEditorConfig(root, 'readme.md').indentSize, isNull);
|
||||
expect(readEditorConfig(root, 'main.dart').indentSize, 2);
|
||||
expect(readEditorConfig(root, 'app.js').indentSize, 4);
|
||||
expect(readEditorConfig(root, 'app.ts').indentSize, 4);
|
||||
expect(readEditorConfig(root, 'readme.md').indentSize, isNull);
|
||||
});
|
||||
|
||||
test('a slash anchors the glob to the config dir; ** spans dirs', () async {
|
||||
await write('.editorconfig', 'root = true\n[lib/**.dart]\nindent_size = 3\n');
|
||||
expect(resolveEditorConfig(root, 'lib/a.dart').indentSize, 3);
|
||||
expect(resolveEditorConfig(root, 'lib/deep/b.dart').indentSize, 3);
|
||||
expect(resolveEditorConfig(root, 'test/c.dart').indentSize, isNull);
|
||||
});
|
||||
|
||||
test('character class and negation', () async {
|
||||
await write('.editorconfig', 'root = true\n[[a-c].txt]\nindent_size = 5\n[!x].md]\nindent_size = 6\n');
|
||||
expect(resolveEditorConfig(root, 'b.txt').indentSize, 5);
|
||||
expect(resolveEditorConfig(root, 'z.txt').indentSize, isNull);
|
||||
});
|
||||
|
||||
test('numeric range', () async {
|
||||
await write('.editorconfig', 'root = true\n[file{1..3}.txt]\nindent_size = 7\n');
|
||||
expect(resolveEditorConfig(root, 'file2.txt').indentSize, 7);
|
||||
expect(resolveEditorConfig(root, 'file9.txt').indentSize, isNull);
|
||||
expect(readEditorConfig(root, 'lib/a.dart').indentSize, 3);
|
||||
expect(readEditorConfig(root, 'lib/deep/b.dart').indentSize, 3);
|
||||
expect(readEditorConfig(root, 'test/c.dart').indentSize, isNull);
|
||||
});
|
||||
|
||||
test('? matches exactly one non-separator char', () async {
|
||||
await write('.editorconfig', 'root = true\n[?.txt]\nindent_size = 9\n');
|
||||
expect(resolveEditorConfig(root, 'a.txt').indentSize, 9);
|
||||
expect(resolveEditorConfig(root, 'ab.txt').indentSize, isNull);
|
||||
expect(readEditorConfig(root, 'a.txt').indentSize, 9);
|
||||
expect(readEditorConfig(root, 'ab.txt').indentSize, isNull);
|
||||
});
|
||||
|
||||
test('leading **/ matches in the config dir and below', () async {
|
||||
await write('.editorconfig', 'root = true\n[**/foo.txt]\nindent_size = 3\n');
|
||||
expect(resolveEditorConfig(root, 'foo.txt').indentSize, 3);
|
||||
expect(resolveEditorConfig(root, 'a/b/foo.txt').indentSize, 3);
|
||||
expect(readEditorConfig(root, 'foo.txt').indentSize, 3);
|
||||
expect(readEditorConfig(root, 'a/b/foo.txt').indentSize, 3);
|
||||
});
|
||||
|
||||
test('negated character class', () async {
|
||||
await write('.editorconfig', 'root = true\n[[!a-c].txt]\nindent_size = 4\n');
|
||||
expect(resolveEditorConfig(root, 'z.txt').indentSize, 4);
|
||||
expect(resolveEditorConfig(root, 'b.txt').indentSize, isNull);
|
||||
expect(readEditorConfig(root, 'z.txt').indentSize, 4);
|
||||
expect(readEditorConfig(root, 'b.txt').indentSize, isNull);
|
||||
});
|
||||
|
||||
test('numeric range', () async {
|
||||
await write('.editorconfig', 'root = true\n[file{1..3}.txt]\nindent_size = 7\n');
|
||||
expect(readEditorConfig(root, 'file2.txt').indentSize, 7);
|
||||
expect(readEditorConfig(root, 'file9.txt').indentSize, isNull);
|
||||
});
|
||||
|
||||
test('an oversized numeric range falls back to a generic integer match', () async {
|
||||
await write('.editorconfig', 'root = true\n[v{1..5000}]\nindent_size = 6\n');
|
||||
expect(resolveEditorConfig(root, 'v123').indentSize, 6);
|
||||
expect(resolveEditorConfig(root, 'vx').indentSize, isNull);
|
||||
expect(readEditorConfig(root, 'v123').indentSize, 6);
|
||||
expect(readEditorConfig(root, 'vx').indentSize, isNull);
|
||||
});
|
||||
|
||||
test('a brace with no top-level comma and an unterminated class are literal', () async {
|
||||
await write('.editorconfig', 'root = true\n[a{b}.txt]\nindent_size = 2\n[lit[.md]\nindent_size = 8\n');
|
||||
// {b} is a literal brace group → matches the literal text "a{b}.txt".
|
||||
expect(resolveEditorConfig(root, 'a{b}.txt').indentSize, 2);
|
||||
// "[lit[.md" has an unterminated class → the leading [ is literal.
|
||||
expect(resolveEditorConfig(root, 'lit[.md').indentSize, 8);
|
||||
expect(readEditorConfig(root, 'a{b}.txt').indentSize, 2);
|
||||
expect(readEditorConfig(root, 'lit[.md').indentSize, 8);
|
||||
});
|
||||
|
||||
test('backslash escapes a glob metacharacter to a literal', () async {
|
||||
await write('.editorconfig', 'root = true\n[a\\{b.txt]\nindent_size = 5\n');
|
||||
expect(resolveEditorConfig(root, 'a{b.txt').indentSize, 5);
|
||||
expect(resolveEditorConfig(root, 'aXb.txt').indentSize, isNull);
|
||||
expect(readEditorConfig(root, 'a{b.txt').indentSize, 5);
|
||||
expect(readEditorConfig(root, 'aXb.txt').indentSize, isNull);
|
||||
});
|
||||
|
||||
test('an unterminated brace (even with an escaped }) is literal', () async {
|
||||
await write('.editorconfig', 'root = true\n[x{a\\}b.txt]\nindent_size = 6\n');
|
||||
expect(resolveEditorConfig(root, 'x{a}b.txt').indentSize, 6);
|
||||
expect(readEditorConfig(root, 'x{a}b.txt').indentSize, 6);
|
||||
});
|
||||
|
||||
test('escaped chars inside a class and inside an alternation', () async {
|
||||
await write('.editorconfig', 'root = true\n[[a\\-c].txt]\nindent_size = 3\n[{a\\,b,c}.md]\nindent_size = 4\n');
|
||||
expect(resolveEditorConfig(root, 'c.txt').indentSize, 3); // c is in the class
|
||||
expect(resolveEditorConfig(root, 'c.md').indentSize, 4); // 'c' alternative
|
||||
expect(readEditorConfig(root, 'c.txt').indentSize, 3);
|
||||
expect(readEditorConfig(root, 'c.md').indentSize, 4);
|
||||
});
|
||||
});
|
||||
|
||||
group('resolveEditorConfig — precedence', () {
|
||||
group('readEditorConfig — precedence', () {
|
||||
late Directory root;
|
||||
setUp(() async => root = await Directory.systemTemp.createTemp('clide-ec-prec-'));
|
||||
tearDown(() => root.deleteSync(recursive: true));
|
||||
@@ -208,43 +148,42 @@ void main() {
|
||||
test('a nearer config overrides a farther one', () async {
|
||||
await write('.editorconfig', 'root = true\n[*]\nindent_size = 2\nindent_style = space\n');
|
||||
await write('sub/.editorconfig', '[*]\nindent_size = 4\n');
|
||||
final c = resolveEditorConfig(root, 'sub/x.dart');
|
||||
expect(c.indentSize, 4); // overridden by nearer
|
||||
expect(c.indentStyle, 'space'); // inherited from farther
|
||||
final c = readEditorConfig(root, 'sub/x.dart');
|
||||
expect(c.indentSize, 4);
|
||||
expect(c.indentStyle, 'space');
|
||||
});
|
||||
|
||||
test('root = true halts the ascent', () async {
|
||||
await write('.editorconfig', '[*]\nindent_style = tab\n'); // would apply if reached
|
||||
await write('.editorconfig', '[*]\nindent_style = tab\n');
|
||||
await write('sub/.editorconfig', 'root = true\n[*]\nindent_size = 4\n');
|
||||
final c = resolveEditorConfig(root, 'sub/x.dart');
|
||||
final c = readEditorConfig(root, 'sub/x.dart');
|
||||
expect(c.indentSize, 4);
|
||||
expect(c.indentStyle, isNull); // top-level file never consulted
|
||||
expect(c.indentStyle, isNull);
|
||||
});
|
||||
|
||||
test('later sections in one file win', () async {
|
||||
await write('.editorconfig', 'root = true\n[*]\nindent_size = 2\n[*.dart]\nindent_size = 4\n');
|
||||
expect(resolveEditorConfig(root, 'a.dart').indentSize, 4);
|
||||
expect(readEditorConfig(root, 'a.dart').indentSize, 4);
|
||||
});
|
||||
|
||||
test('a nearer "unset" clears an inherited property', () async {
|
||||
await write('.editorconfig', 'root = true\n[*]\nindent_style = space\n');
|
||||
await write('sub/.editorconfig', '[*]\nindent_style = unset\n');
|
||||
expect(resolveEditorConfig(root, 'sub/x.txt').indentStyle, isNull);
|
||||
expect(readEditorConfig(root, 'sub/x.txt').indentStyle, isNull);
|
||||
});
|
||||
|
||||
test('no .editorconfig anywhere resolves to empty', () async {
|
||||
expect(resolveEditorConfig(root, 'a.txt').isEmpty, isTrue);
|
||||
expect(readEditorConfig(root, 'a.txt').isEmpty, isTrue);
|
||||
});
|
||||
|
||||
test('a malformed file is skipped, not fatal', () async {
|
||||
await write('.editorconfig', 'root = true\n[*\nindent_size = 2\nnonsense line\n[*]\nindent_size = 8\n');
|
||||
// The broken section header is ignored; the valid [*] still applies.
|
||||
expect(resolveEditorConfig(root, 'a.txt').indentSize, 8);
|
||||
expect(readEditorConfig(root, 'a.txt').indentSize, 8);
|
||||
});
|
||||
|
||||
test('comments (# and ;) are ignored', () async {
|
||||
await write('.editorconfig', '# top comment\nroot = true\n[*]\n; inline note\nindent_size = 2\n');
|
||||
expect(resolveEditorConfig(root, 'a.txt').indentSize, 2);
|
||||
expect(readEditorConfig(root, 'a.txt').indentSize, 2);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -153,13 +153,13 @@ void main() {
|
||||
});
|
||||
|
||||
group('.editorconfig (T-29)', () {
|
||||
test('open resolves the editorconfig for the file', () async {
|
||||
test('open resolves the settings for the file', () async {
|
||||
await File('${sandbox.path}/.editorconfig').writeAsString('root = true\n[*]\nindent_style = space\nindent_size = 2\n');
|
||||
final buf = await reg.open('README.md');
|
||||
expect(buf.editorConfig.indentStyle, 'space');
|
||||
expect(buf.editorConfig.indentSize, 2);
|
||||
expect(buf.settings.indentStyle, 'space');
|
||||
expect(buf.settings.indentSize, 2);
|
||||
// Exposed over IPC for the UI.
|
||||
expect(buf.toJson()['editorConfig'], {'indent_style': 'space', 'indent_size': 2, 'tab_width': 2});
|
||||
expect(buf.toJson()['editorSettings'], {'indent_style': 'space', 'indent_size': 2, 'tab_width': 2});
|
||||
});
|
||||
|
||||
test('save trims trailing whitespace + adds a final newline on disk', () async {
|
||||
@@ -197,5 +197,22 @@ void main() {
|
||||
expect(sink.ofKind('editor.edited'), isEmpty); // nothing to reconcile
|
||||
expect(sink.ofKind('editor.saved'), hasLength(1));
|
||||
});
|
||||
|
||||
test('saving a .editorconfig re-resolves open buffers and notifies', () async {
|
||||
// README opens with no rules in effect.
|
||||
final readme = await reg.open('README.md');
|
||||
expect(readme.settings.indentSize, isNull);
|
||||
|
||||
// Author a .editorconfig in the editor and save it.
|
||||
final cfg = await reg.open('.editorconfig');
|
||||
reg.setContent(cfg.id, 'root = true\n[*]\nindent_size = 4\n');
|
||||
sink.events.clear();
|
||||
await reg.save(cfg.id);
|
||||
|
||||
// The open README picks up the new rules without reopening.
|
||||
expect(readme.settings.indentSize, 4);
|
||||
final changed = sink.ofKind('editor.settings-changed');
|
||||
expect(changed.map((e) => e.data['id']), contains(readme.id));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user