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
|
||||
/.pytest_cache
|
||||
/.tox
|
||||
/build
|
||||
/dist
|
||||
/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
|
||||
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
|
||||
|
||||
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):
|
||||
replaced = match.expand(replace)
|
||||
line = screen.file.lines[line_y]
|
||||
line = line[:match.start()] + replaced + line[match.end():]
|
||||
screen.file.lines[line_y] = line
|
||||
search.offset = len(replaced)
|
||||
if '\n' in replaced:
|
||||
replaced_lines = replaced.split('\n')
|
||||
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':
|
||||
search.offset = 1
|
||||
else:
|
||||
@@ -844,7 +863,7 @@ class File:
|
||||
for region in file_hl.regions[l_y]:
|
||||
if region.x >= l_x_max:
|
||||
break
|
||||
elif region.end < l_x:
|
||||
elif region.end <= l_x:
|
||||
continue
|
||||
|
||||
if l_x and region.x <= l_x:
|
||||
|
||||
@@ -43,7 +43,7 @@ def _replace_esc(s: str, chars: str) -> str:
|
||||
|
||||
class _Reg:
|
||||
def __init__(self, s: str) -> None:
|
||||
self._pattern = s
|
||||
self._pattern = _replace_esc(s, 'z')
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f'{type(self).__name__}({self._pattern!r})'
|
||||
|
||||
@@ -416,7 +416,7 @@ class Screen:
|
||||
self.file.filename = 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)
|
||||
else:
|
||||
sha256 = hashlib.sha256(b'').hexdigest()
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[metadata]
|
||||
name = babi
|
||||
version = 0.0.3
|
||||
version = 0.0.4
|
||||
description = a text editor
|
||||
long_description = file: README.md
|
||||
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]
|
||||
|
||||
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
|
||||
|
||||
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.press('y')
|
||||
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'")
|
||||
|
||||
|
||||
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):
|
||||
with run('this_is_a_new_file') as h, and_exit(h):
|
||||
h.await_text('this_is_a_new_file')
|
||||
|
||||
@@ -15,6 +15,7 @@ THEME = json.dumps({
|
||||
'settings': {'foreground': '#5f0000', 'background': '#ff5f5f'},
|
||||
},
|
||||
{'scope': 'tqs', 'settings': {'foreground': '#00005f'}},
|
||||
{'scope': 'qmark', 'settings': {'foreground': '#5f0000'}},
|
||||
{'scope': 'b', 'settings': {'fontStyle': 'bold'}},
|
||||
{'scope': 'i', 'settings': {'fontStyle': 'italic'}},
|
||||
{'scope': 'u', 'settings': {'fontStyle': 'underline'}},
|
||||
@@ -28,6 +29,7 @@ SYNTAX = json.dumps({
|
||||
{'match': r'#.*$\n?', 'name': 'comment'},
|
||||
{'match': r'^-.*$\n?', 'name': 'diffremove'},
|
||||
{'begin': '"""', 'end': '"""', 'name': 'tqs'},
|
||||
{'match': r'\?', 'name': 'qmark'},
|
||||
],
|
||||
})
|
||||
DEMO_S = '''\
|
||||
@@ -97,3 +99,17 @@ def test_syntax_highlighting_off_screen_does_not_crash(run, tmpdir):
|
||||
h.await_text('"""b"""')
|
||||
expected = [(236, 40, 0)] * 11 + [(17, 40, 0)] * 7 + [(236, 40, 0)] * 2
|
||||
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(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