diff kitten: Fix exiting while highlighting is incomplete causes kitten to hang until highlighting is done

This commit is contained in:
Kovid Goyal 2022-01-18 13:43:39 +05:30
parent 4778e0eb23
commit 2c0ac895e6
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
2 changed files with 45 additions and 13 deletions

View File

@ -4,7 +4,10 @@
import concurrent import concurrent
import os import os
import re import re
from typing import IO, Dict, Iterable, List, Optional, Tuple, Union, cast from concurrent.futures import ProcessPoolExecutor
from typing import (
IO, Dict, Iterable, Iterator, List, Optional, Tuple, Union, cast
)
from pygments import highlight # type: ignore from pygments import highlight # type: ignore
from pygments.formatter import Formatter # type: ignore from pygments.formatter import Formatter # type: ignore
@ -136,10 +139,22 @@ def highlight_for_diff(path: str, aliases: Dict[str, str]) -> DiffHighlight:
return ans return ans
process_pool_executor: Optional[ProcessPoolExecutor] = None
def get_highlight_processes() -> Iterator[int]:
if process_pool_executor is None:
return
for pid in process_pool_executor._processes:
yield pid
def highlight_collection(collection: Collection, aliases: Optional[Dict[str, str]] = None) -> Union[str, Dict[str, DiffHighlight]]: def highlight_collection(collection: Collection, aliases: Optional[Dict[str, str]] = None) -> Union[str, Dict[str, DiffHighlight]]:
global process_pool_executor
jobs = {} jobs = {}
ans: Dict[str, DiffHighlight] = {} ans: Dict[str, DiffHighlight] = {}
with get_process_pool_executor(prefer_fork=True) as executor: with get_process_pool_executor(prefer_fork=True) as executor:
process_pool_executor = executor
for path, item_type, other_path in collection: for path, item_type, other_path in collection:
if item_type != 'rename': if item_type != 'rename':
for p in (path, other_path): for p in (path, other_path):
@ -159,8 +174,9 @@ def highlight_collection(collection: Collection, aliases: Optional[Dict[str, str
def main() -> None: def main() -> None:
# kitty +runpy "from kittens.diff.highlight import main; main()" file # kitty +runpy "from kittens.diff.highlight import main; main()" file
from .options.types import defaults
import sys import sys
from .options.types import defaults
initialize_highlighter() initialize_highlighter()
with open(sys.argv[-1]) as f: with open(sys.argv[-1]) as f:
highlighted = highlight_data(f.read(), f.name, defaults.syntax_aliases) highlighted = highlight_data(f.read(), f.name, defaults.syntax_aliases)

View File

@ -13,7 +13,7 @@ from contextlib import suppress
from functools import partial from functools import partial
from gettext import gettext as _ from gettext import gettext as _
from typing import ( from typing import (
Any, DefaultDict, Dict, Iterable, List, Optional, Tuple, Union Any, DefaultDict, Dict, Iterable, Iterator, List, Optional, Tuple, Union
) )
from kitty.cli import CONFIG_HELP, parse_args from kitty.cli import CONFIG_HELP, parse_args
@ -44,7 +44,8 @@ from .search import BadRegex, Search
try: try:
from .highlight import ( from .highlight import (
DiffHighlight, highlight_collection, initialize_highlighter DiffHighlight, get_highlight_processes, highlight_collection,
initialize_highlighter
) )
has_highlighter = True has_highlighter = True
DiffHighlight DiffHighlight
@ -54,6 +55,10 @@ except ImportError:
def highlight_collection(collection: 'Collection', aliases: Optional[Dict[str, str]] = None) -> Union[str, Dict[str, 'DiffHighlight']]: def highlight_collection(collection: 'Collection', aliases: Optional[Dict[str, str]] = None) -> Union[str, Dict[str, 'DiffHighlight']]:
return '' return ''
def get_highlight_processes() -> Iterator[int]:
if has_highlighter:
yield -1
INITIALIZING, COLLECTED, DIFFED, COMMAND, MESSAGE = range(5) INITIALIZING, COLLECTED, DIFFED, COMMAND, MESSAGE = range(5)
@ -90,14 +95,18 @@ class DiffHandler(Handler):
if self.current_context_count < 0: if self.current_context_count < 0:
self.current_context_count = self.original_context_count = self.opts.num_context_lines self.current_context_count = self.original_context_count = self.opts.num_context_lines
self.highlighting_done = False self.highlighting_done = False
self.doing_background_work = ''
self.restore_position: Optional[Reference] = None self.restore_position: Optional[Reference] = None
for key_def, action in self.opts.key_definitions.items(): for key_def, action in self.opts.key_definitions.items():
self.add_shortcut(action, key_def) self.add_shortcut(action, key_def)
def terminate(self, return_code: int = 0) -> None:
self.quit_loop(return_code)
def perform_action(self, action: KeyAction) -> None: def perform_action(self, action: KeyAction) -> None:
func, args = action func, args = action
if func == 'quit': if func == 'quit':
self.quit_loop(0) self.terminate()
return return
if self.state <= DIFFED: if self.state <= DIFFED:
if func == 'scroll_by': if func == 'scroll_by':
@ -130,6 +139,7 @@ class DiffHandler(Handler):
def create_collection(self) -> None: def create_collection(self) -> None:
def collect_done(collection: Collection) -> None: def collect_done(collection: Collection) -> None:
self.doing_background_work = ''
self.collection = collection self.collection = collection
self.state = COLLECTED self.state = COLLECTED
self.generate_diff() self.generate_diff()
@ -139,13 +149,15 @@ class DiffHandler(Handler):
self.asyncio_loop.call_soon_threadsafe(collect_done, collection) self.asyncio_loop.call_soon_threadsafe(collect_done, collection)
self.asyncio_loop.run_in_executor(None, collect, self.left, self.right) self.asyncio_loop.run_in_executor(None, collect, self.left, self.right)
self.doing_background_work = 'collecting'
def generate_diff(self) -> None: def generate_diff(self) -> None:
def diff_done(diff_map: Union[str, Dict[str, Patch]]) -> None: def diff_done(diff_map: Union[str, Dict[str, Patch]]) -> None:
self.doing_background_work = ''
if isinstance(diff_map, str): if isinstance(diff_map, str):
self.report_traceback_on_exit = diff_map self.report_traceback_on_exit = diff_map
self.quit_loop(1) self.terminate(1)
return return
self.state = DIFFED self.state = DIFFED
self.diff_map = diff_map self.diff_map = diff_map
@ -163,7 +175,7 @@ class DiffHandler(Handler):
initialize_highlighter(self.opts.pygments_style) initialize_highlighter(self.opts.pygments_style)
except StyleNotFound as e: except StyleNotFound as e:
self.report_traceback_on_exit = str(e) self.report_traceback_on_exit = str(e)
self.quit_loop(1) self.terminate(1)
return return
self.syntax_highlight() self.syntax_highlight()
@ -172,13 +184,15 @@ class DiffHandler(Handler):
self.asyncio_loop.call_soon_threadsafe(diff_done, diff_map) self.asyncio_loop.call_soon_threadsafe(diff_done, diff_map)
self.asyncio_loop.run_in_executor(None, diff, self.collection, self.current_context_count) self.asyncio_loop.run_in_executor(None, diff, self.collection, self.current_context_count)
self.doing_background_work = 'diffing'
def syntax_highlight(self) -> None: def syntax_highlight(self) -> None:
def highlighting_done(hdata: Union[str, Dict[str, 'DiffHighlight']]) -> None: def highlighting_done(hdata: Union[str, Dict[str, 'DiffHighlight']]) -> None:
self.doing_background_work = ''
if isinstance(hdata, str): if isinstance(hdata, str):
self.report_traceback_on_exit = hdata self.report_traceback_on_exit = hdata
self.quit_loop(1) self.terminate(1)
return return
set_highlight_data(hdata) set_highlight_data(hdata)
self.render_diff() self.render_diff()
@ -189,6 +203,7 @@ class DiffHandler(Handler):
self.asyncio_loop.call_soon_threadsafe(highlighting_done, result) self.asyncio_loop.call_soon_threadsafe(highlighting_done, result)
self.asyncio_loop.run_in_executor(None, highlight, self.collection, self.opts.syntax_aliases) self.asyncio_loop.run_in_executor(None, highlight, self.collection, self.opts.syntax_aliases)
self.doing_background_work = 'highlighting'
def calculate_statistics(self) -> None: def calculate_statistics(self) -> None:
self.added_count = self.collection.added_count self.added_count = self.collection.added_count
@ -532,10 +547,10 @@ class DiffHandler(Handler):
self.draw_screen() self.draw_screen()
def on_interrupt(self) -> None: def on_interrupt(self) -> None:
self.quit_loop(1) self.terminate(1)
def on_eot(self) -> None: def on_eot(self) -> None:
self.quit_loop(1) self.terminate(1)
OPTIONS = partial('''\ OPTIONS = partial('''\
@ -639,8 +654,9 @@ def main(args: List[str]) -> None:
for message in showwarning.warnings: for message in showwarning.warnings:
from kitty.utils import safe_print from kitty.utils import safe_print
safe_print(message, file=sys.stderr) safe_print(message, file=sys.stderr)
highlight_processes = getattr(highlight_collection, 'processes', ()) if handler.doing_background_work == 'highlighting':
terminate_processes(tuple(highlight_processes)) terminate_processes(tuple(get_highlight_processes()))
elif handler.doing_background_work == 'diffing':
terminate_processes(tuple(worker_processes)) terminate_processes(tuple(worker_processes))
if loop.return_code != 0: if loop.return_code != 0:
if handler.report_traceback_on_exit: if handler.report_traceback_on_exit: