Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7850481565 | ||
|
|
b536291989 | ||
|
|
f8737557d3 | ||
|
|
d597b4087d | ||
|
|
41aa025d3d | ||
|
|
de956b7bab | ||
|
|
1d3d413b93 | ||
|
|
50ad1e06f9 |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -5,4 +5,6 @@
|
|||||||
/.mypy_cache
|
/.mypy_cache
|
||||||
/.pytest_cache
|
/.pytest_cache
|
||||||
/.tox
|
/.tox
|
||||||
|
/build
|
||||||
|
/dist
|
||||||
/venv*
|
/venv*
|
||||||
|
|||||||
@@ -67,6 +67,12 @@ the syntax highlighting setup is a bit manual right now
|
|||||||
json) and put it at `~/.config/babi/theme.json`. a helper script is
|
json) and put it at `~/.config/babi/theme.json`. a helper script is
|
||||||
provided to make this easier: `./bin/download-theme NAME URL`
|
provided to make this easier: `./bin/download-theme NAME URL`
|
||||||
|
|
||||||
|
here's a modified vs dark plus theme that works:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./bin/download-theme vs-dark-asottile https://gist.github.com/asottile/b465856c82b1aaa4ba8c7c6314a72e13/raw/22d602fb355fb12b04f176a733941ba5713bc36c/vs_dark_asottile.json
|
||||||
|
```
|
||||||
|
|
||||||
## demos
|
## demos
|
||||||
|
|
||||||
most things work! here's a few screenshots
|
most things work! here's a few screenshots
|
||||||
|
|||||||
27
babi/file.py
27
babi/file.py
@@ -464,9 +464,28 @@ class File:
|
|||||||
with self.edit_action_context('replace', final=True):
|
with self.edit_action_context('replace', final=True):
|
||||||
replaced = match.expand(replace)
|
replaced = match.expand(replace)
|
||||||
line = screen.file.lines[line_y]
|
line = screen.file.lines[line_y]
|
||||||
line = line[:match.start()] + replaced + line[match.end():]
|
if '\n' in replaced:
|
||||||
screen.file.lines[line_y] = line
|
replaced_lines = replaced.split('\n')
|
||||||
search.offset = len(replaced)
|
self.lines[line_y] = (
|
||||||
|
f'{line[:match.start()]}{replaced_lines[0]}'
|
||||||
|
)
|
||||||
|
for i, ins_line in enumerate(replaced_lines[1:-1], 1):
|
||||||
|
self.lines.insert(line_y + i, ins_line)
|
||||||
|
last_insert = line_y + len(replaced_lines) - 1
|
||||||
|
self.lines.insert(
|
||||||
|
last_insert,
|
||||||
|
f'{replaced_lines[-1]}{line[match.end():]}',
|
||||||
|
)
|
||||||
|
self.y = last_insert
|
||||||
|
self.x = self.x_hint = 0
|
||||||
|
search.offset = len(replaced_lines[-1])
|
||||||
|
else:
|
||||||
|
self.lines[line_y] = (
|
||||||
|
f'{line[:match.start()]}'
|
||||||
|
f'{replaced}'
|
||||||
|
f'{line[match.end():]}'
|
||||||
|
)
|
||||||
|
search.offset = len(replaced)
|
||||||
elif res == 'n':
|
elif res == 'n':
|
||||||
search.offset = 1
|
search.offset = 1
|
||||||
else:
|
else:
|
||||||
@@ -844,7 +863,7 @@ class File:
|
|||||||
for region in file_hl.regions[l_y]:
|
for region in file_hl.regions[l_y]:
|
||||||
if region.x >= l_x_max:
|
if region.x >= l_x_max:
|
||||||
break
|
break
|
||||||
elif region.end < l_x:
|
elif region.end <= l_x:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if l_x and region.x <= l_x:
|
if l_x and region.x <= l_x:
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ def _replace_esc(s: str, chars: str) -> str:
|
|||||||
|
|
||||||
class _Reg:
|
class _Reg:
|
||||||
def __init__(self, s: str) -> None:
|
def __init__(self, s: str) -> None:
|
||||||
self._pattern = s
|
self._pattern = _replace_esc(s, 'z')
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return f'{type(self).__name__}({self._pattern!r})'
|
return f'{type(self).__name__}({self._pattern!r})'
|
||||||
|
|||||||
@@ -416,7 +416,7 @@ class Screen:
|
|||||||
self.file.filename = filename
|
self.file.filename = filename
|
||||||
|
|
||||||
if os.path.isfile(self.file.filename):
|
if os.path.isfile(self.file.filename):
|
||||||
with open(self.file.filename) as f:
|
with open(self.file.filename, newline='') as f:
|
||||||
*_, sha256 = get_lines(f)
|
*_, sha256 = get_lines(f)
|
||||||
else:
|
else:
|
||||||
sha256 = hashlib.sha256(b'').hexdigest()
|
sha256 = hashlib.sha256(b'').hexdigest()
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[metadata]
|
[metadata]
|
||||||
name = babi
|
name = babi
|
||||||
version = 0.0.3
|
version = 0.0.4
|
||||||
description = a text editor
|
description = a text editor
|
||||||
long_description = file: README.md
|
long_description = file: README.md
|
||||||
long_description_content_type = text/markdown
|
long_description_content_type = text/markdown
|
||||||
|
|||||||
2
testing/vsc_test/.gitignore
vendored
Normal file
2
testing/vsc_test/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
/node_modules
|
||||||
|
/package-lock.json
|
||||||
5
testing/vsc_test/package.json
Normal file
5
testing/vsc_test/package.json
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"dependencies": [
|
||||||
|
"vscode-textmate"
|
||||||
|
]
|
||||||
|
}
|
||||||
51
testing/vsc_test/vsc.js
Normal file
51
testing/vsc_test/vsc.js
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
const fs = require('fs');
|
||||||
|
const vsctm = require('vscode-textmate');
|
||||||
|
|
||||||
|
if (process.argv.length < 4) {
|
||||||
|
console.log('usage: t.js GRAMMAR FILE');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const grammar = process.argv[2];
|
||||||
|
const file = process.argv[3];
|
||||||
|
|
||||||
|
const scope = JSON.parse(fs.readFileSync(grammar, {encoding: 'UTF-8'})).scopeName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility to read a file as a promise
|
||||||
|
*/
|
||||||
|
function readFile(path) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
fs.readFile(path, (error, data) => error ? reject(error) : resolve(data));
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a registry that can create a grammar from a scope name.
|
||||||
|
const registry = new vsctm.Registry({
|
||||||
|
loadGrammar: (scopeName) => {
|
||||||
|
if (scopeName === scope) {
|
||||||
|
return readFile(grammar).then(data => vsctm.parseRawGrammar(data.toString(), grammar))
|
||||||
|
}
|
||||||
|
console.log(`Unknown scope name: ${scopeName}`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Load the JavaScript grammar and any other grammars included by it async.
|
||||||
|
registry.loadGrammar(scope).then(grammar => {
|
||||||
|
const text = fs.readFileSync(file, {encoding: 'UTF-8'}).trimEnd('\n').split(/\n/);
|
||||||
|
let ruleStack = vsctm.INITIAL;
|
||||||
|
for (let i = 0; i < text.length; i++) {
|
||||||
|
const line = text[i];
|
||||||
|
const lineTokens = grammar.tokenizeLine(line, ruleStack);
|
||||||
|
console.log(`\nTokenizing line: ${line}`);
|
||||||
|
for (let j = 0; j < lineTokens.tokens.length; j++) {
|
||||||
|
const token = lineTokens.tokens[j];
|
||||||
|
console.log(` - token from ${token.startIndex} to ${token.endIndex} ` +
|
||||||
|
`(${line.substring(token.startIndex, token.endIndex)}) ` +
|
||||||
|
`with scopes ${token.scopes.join(', ')}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
ruleStack = lineTokens.ruleStack;
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -73,6 +73,7 @@ class Screen:
|
|||||||
self.attrs[y] = (line_attr[:x] + new + line_attr[x:])[:self.width]
|
self.attrs[y] = (line_attr[:x] + new + line_attr[x:])[:self.width]
|
||||||
|
|
||||||
def chgat(self, y, x, n, attr):
|
def chgat(self, y, x, n, attr):
|
||||||
|
assert n >= 0 # TODO: switch to > 0, we should never do 0-length
|
||||||
self.attrs[y][x:x + n] = [attr] * n
|
self.attrs[y][x:x + n] = [attr] * n
|
||||||
|
|
||||||
def move(self, y, x):
|
def move(self, y, x):
|
||||||
|
|||||||
@@ -272,3 +272,31 @@ def test_replace_separate_line_after_wrapping(run, ten_lines):
|
|||||||
h.await_text_missing('line_0')
|
h.await_text_missing('line_0')
|
||||||
h.press('y')
|
h.press('y')
|
||||||
h.await_text_missing('line_1')
|
h.await_text_missing('line_1')
|
||||||
|
|
||||||
|
|
||||||
|
def test_replace_with_newline_characters(run, ten_lines):
|
||||||
|
with run(str(ten_lines)) as h, and_exit(h):
|
||||||
|
h.press('^\\')
|
||||||
|
h.await_text('search (to replace):')
|
||||||
|
h.press_and_enter('(line)_([01])')
|
||||||
|
h.await_text('replace with:')
|
||||||
|
h.press_and_enter(r'\1\n\2')
|
||||||
|
h.await_text('replace [yes, no, all]?')
|
||||||
|
h.press('a')
|
||||||
|
h.await_text_missing('line_0')
|
||||||
|
h.await_text_missing('line_1')
|
||||||
|
h.await_text('line\n0\nline\n1\n')
|
||||||
|
|
||||||
|
|
||||||
|
def test_replace_with_multiple_newline_characters(run, ten_lines):
|
||||||
|
with run(str(ten_lines)) as h, and_exit(h):
|
||||||
|
h.press('^\\')
|
||||||
|
h.await_text('search (to replace):')
|
||||||
|
h.press_and_enter('(li)(ne)_(1)')
|
||||||
|
h.await_text('replace with:')
|
||||||
|
h.press_and_enter(r'\1\n\2\n\3\n')
|
||||||
|
h.await_text('replace [yes, no, all]?')
|
||||||
|
h.press('a')
|
||||||
|
|
||||||
|
h.await_text_missing('line_1')
|
||||||
|
h.await_text('li\nne\n1\n\nline_2')
|
||||||
|
|||||||
@@ -12,6 +12,19 @@ def test_mixed_newlines(run, tmpdir):
|
|||||||
h.await_text(r"mixed newlines will be converted to '\n'")
|
h.await_text(r"mixed newlines will be converted to '\n'")
|
||||||
|
|
||||||
|
|
||||||
|
def test_modify_file_with_windows_newlines(run, tmpdir):
|
||||||
|
f = tmpdir.join('f')
|
||||||
|
f.write_binary(b'foo\r\nbar\r\n')
|
||||||
|
with run(str(f)) as h, and_exit(h):
|
||||||
|
# should not start modified
|
||||||
|
h.await_text_missing('*')
|
||||||
|
h.press('Enter')
|
||||||
|
h.await_text('*')
|
||||||
|
h.press('^S')
|
||||||
|
h.await_text('saved!')
|
||||||
|
assert f.read_binary() == b'\r\nfoo\r\nbar\r\n'
|
||||||
|
|
||||||
|
|
||||||
def test_new_file(run):
|
def test_new_file(run):
|
||||||
with run('this_is_a_new_file') as h, and_exit(h):
|
with run('this_is_a_new_file') as h, and_exit(h):
|
||||||
h.await_text('this_is_a_new_file')
|
h.await_text('this_is_a_new_file')
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ THEME = json.dumps({
|
|||||||
'settings': {'foreground': '#5f0000', 'background': '#ff5f5f'},
|
'settings': {'foreground': '#5f0000', 'background': '#ff5f5f'},
|
||||||
},
|
},
|
||||||
{'scope': 'tqs', 'settings': {'foreground': '#00005f'}},
|
{'scope': 'tqs', 'settings': {'foreground': '#00005f'}},
|
||||||
|
{'scope': 'qmark', 'settings': {'foreground': '#5f0000'}},
|
||||||
{'scope': 'b', 'settings': {'fontStyle': 'bold'}},
|
{'scope': 'b', 'settings': {'fontStyle': 'bold'}},
|
||||||
{'scope': 'i', 'settings': {'fontStyle': 'italic'}},
|
{'scope': 'i', 'settings': {'fontStyle': 'italic'}},
|
||||||
{'scope': 'u', 'settings': {'fontStyle': 'underline'}},
|
{'scope': 'u', 'settings': {'fontStyle': 'underline'}},
|
||||||
@@ -28,6 +29,7 @@ SYNTAX = json.dumps({
|
|||||||
{'match': r'#.*$\n?', 'name': 'comment'},
|
{'match': r'#.*$\n?', 'name': 'comment'},
|
||||||
{'match': r'^-.*$\n?', 'name': 'diffremove'},
|
{'match': r'^-.*$\n?', 'name': 'diffremove'},
|
||||||
{'begin': '"""', 'end': '"""', 'name': 'tqs'},
|
{'begin': '"""', 'end': '"""', 'name': 'tqs'},
|
||||||
|
{'match': r'\?', 'name': 'qmark'},
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
DEMO_S = '''\
|
DEMO_S = '''\
|
||||||
@@ -97,3 +99,17 @@ def test_syntax_highlighting_off_screen_does_not_crash(run, tmpdir):
|
|||||||
h.await_text('"""b"""')
|
h.await_text('"""b"""')
|
||||||
expected = [(236, 40, 0)] * 11 + [(17, 40, 0)] * 7 + [(236, 40, 0)] * 2
|
expected = [(236, 40, 0)] * 11 + [(17, 40, 0)] * 7 + [(236, 40, 0)] * 2
|
||||||
h.assert_screen_attr_equals(1, expected)
|
h.assert_screen_attr_equals(1, expected)
|
||||||
|
|
||||||
|
|
||||||
|
def test_syntax_highlighting_one_off_left_of_screen(run, tmpdir):
|
||||||
|
f = tmpdir.join('f.demo')
|
||||||
|
f.write(f'{"x" * 11}?123456789')
|
||||||
|
|
||||||
|
with run(str(f), term='screen-256color', width=20) as h, and_exit(h):
|
||||||
|
h.await_text('xxx?123')
|
||||||
|
expected = [(236, 40, 0)] * 11 + [(52, 40, 0)] + [(236, 40, 0)] * 8
|
||||||
|
h.assert_screen_attr_equals(1, expected)
|
||||||
|
|
||||||
|
h.press('End')
|
||||||
|
h.await_text_missing('?')
|
||||||
|
h.assert_screen_attr_equals(1, [(236, 40, 0)] * 20)
|
||||||
|
|||||||
@@ -582,3 +582,26 @@ def test_begin_end_substitute_special_chars(compiler_state):
|
|||||||
Region(1, 7, ('test', 'italic')),
|
Region(1, 7, ('test', 'italic')),
|
||||||
Region(7, 8, ('test', 'italic')),
|
Region(7, 8, ('test', 'italic')),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_backslash_z(compiler_state):
|
||||||
|
# similar to text.git-commit grammar, \z matches nothing!
|
||||||
|
compiler, state = compiler_state({
|
||||||
|
'scopeName': 'test',
|
||||||
|
'patterns': [
|
||||||
|
{'begin': '#', 'end': r'\z', 'name': 'comment'},
|
||||||
|
{'name': 'other', 'match': '.'},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
|
||||||
|
state, regions1 = highlight_line(compiler, state, '# comment', True)
|
||||||
|
state, regions2 = highlight_line(compiler, state, 'other?', False)
|
||||||
|
|
||||||
|
assert regions1 == (
|
||||||
|
Region(0, 1, ('test', 'comment')),
|
||||||
|
Region(1, 9, ('test', 'comment')),
|
||||||
|
)
|
||||||
|
|
||||||
|
assert regions2 == (
|
||||||
|
Region(0, 6, ('test', 'comment')),
|
||||||
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user