def test_pathcompleter_completes_directories_with_only_directories(): # setup: create a test dir with 10 files test_dir = tempfile.mkdtemp() write_test_files(test_dir) # create a sub directory there os.mkdir(os.path.join(test_dir, 'subdir')) if not test_dir.endswith(os.path.sep): test_dir += os.path.sep with chdir(test_dir): completer = PathCompleter(only_directories=True) doc_text = '' doc = Document(doc_text, len(doc_text)) event = CompleteEvent() completions = list(completer.get_completions(doc, event)) result = [c.text for c in completions] assert ['subdir'] == result # check that there is no completion when passing a file with chdir(test_dir): completer = PathCompleter(only_directories=True) doc_text = '1' doc = Document(doc_text, len(doc_text)) event = CompleteEvent() completions = list(completer.get_completions(doc, event)) assert [] == completions # cleanup shutil.rmtree(test_dir)
def test_word_completer_ignore_case(): completer = WordCompleter(['abc', 'def', 'aaa'], ignore_case=True) completions = completer.get_completions(Document('a'), CompleteEvent()) assert [c.text for c in completions] == ['abc', 'aaa'] completions = completer.get_completions(Document('A'), CompleteEvent()) assert [c.text for c in completions] == ['abc', 'aaa']
def _get_positions_to_highlight( self, document: Document) -> List[Tuple[int, int]]: """ Return a list of (row, col) tuples that need to be highlighted. """ pos: Optional[int] # Try for the character under the cursor. if document.current_char and document.current_char in self.chars: pos = document.find_matching_bracket_position( start_pos=document.cursor_position - self.max_cursor_distance, end_pos=document.cursor_position + self.max_cursor_distance) # Try for the character before the cursor. elif (document.char_before_cursor and document.char_before_cursor in self._closing_braces and document.char_before_cursor in self.chars): document = Document(document.text, document.cursor_position - 1) pos = document.find_matching_bracket_position( start_pos=document.cursor_position - self.max_cursor_distance, end_pos=document.cursor_position + self.max_cursor_distance) else: pos = None # Return a list of (row, col) tuples that need to be highlighted. if pos: pos += document.cursor_position # pos is relative. row, col = document.translate_index_to_position(pos) return [(row, col), (document.cursor_position_row, document.cursor_position_col)] else: return []
def test_pathcompleter_respects_completions_under_min_input_len(): # setup: create a test dir with 10 files test_dir = tempfile.mkdtemp() write_test_files(test_dir) # min len:1 and no text with chdir(test_dir): completer = PathCompleter(min_input_len=1) doc_text = '' doc = Document(doc_text, len(doc_text)) event = CompleteEvent() completions = list(completer.get_completions(doc, event)) assert [] == completions # min len:1 and text of len 1 with chdir(test_dir): completer = PathCompleter(min_input_len=1) doc_text = '1' doc = Document(doc_text, len(doc_text)) event = CompleteEvent() completions = list(completer.get_completions(doc, event)) result = [c.text for c in completions] assert [''] == result # min len:0 and text of len 2 with chdir(test_dir): completer = PathCompleter(min_input_len=0) doc_text = '1' doc = Document(doc_text, len(doc_text)) event = CompleteEvent() completions = list(completer.get_completions(doc, event)) result = [c.text for c in completions] assert [''] == result # create 10 files with a 2 char long name for i in range(10): with open(os.path.join(test_dir, str(i) * 2), 'wb') as out: out.write(b'') # min len:1 and text of len 1 with chdir(test_dir): completer = PathCompleter(min_input_len=1) doc_text = '2' doc = Document(doc_text, len(doc_text)) event = CompleteEvent() completions = list(completer.get_completions(doc, event)) result = sorted(c.text for c in completions) assert ['', '2'] == result # min len:2 and text of len 1 with chdir(test_dir): completer = PathCompleter(min_input_len=2) doc_text = '2' doc = Document(doc_text, len(doc_text)) event = CompleteEvent() completions = list(completer.get_completions(doc, event)) assert [] == completions # cleanup shutil.rmtree(test_dir)
def test_word_completer_pattern(): # With a pattern which support '.' completer = WordCompleter( ['abc', 'a.b.c', 'a.b', 'xyz'], pattern=re.compile(r'^([a-zA-Z0-9_.]+|[^a-zA-Z0-9_.\s]+)')) completions = completer.get_completions(Document('a.'), CompleteEvent()) assert [c.text for c in completions] == ['a.b.c', 'a.b'] # Without pattern completer = WordCompleter(['abc', 'a.b.c', 'a.b', 'xyz']) completions = completer.get_completions(Document('a.'), CompleteEvent()) assert [c.text for c in completions] == []
def test_word_completer_sentence(): # With sentence=True completer = WordCompleter( ['hello world', 'www', 'hello www', 'hello there'], sentence=True) completions = completer.get_completions(Document('hello w'), CompleteEvent()) assert [c.text for c in completions] == ['hello world', 'hello www'] # With sentence=False completer = WordCompleter( ['hello world', 'www', 'hello www', 'hello there'], sentence=False) completions = completer.get_completions(Document('hello w'), CompleteEvent()) assert [c.text for c in completions] == ['www']
def test_cursor_position(document): assert document.cursor_position_row == 1 assert document.cursor_position_col == 3 d = Document('', 0) assert d.cursor_position_row == 0 assert d.cursor_position_col == 0
def validate(self, document: Document) -> None: # Parse input document. # We use `match`, not `match_prefix`, because for validation, we want # the actual, unambiguous interpretation of the input. m = self.compiled_grammar.match(document.text) if m: for v in m.variables(): validator = self.validators.get(v.varname) if validator: # Unescape text. unwrapped_text = self.compiled_grammar.unescape( v.varname, v.value) # Create a document, for the completions API (text/cursor_position) inner_document = Document(unwrapped_text, len(unwrapped_text)) try: validator.validate(inner_document) except ValidationError as e: raise ValidationError(cursor_position=v.start + e.cursor_position, message=e.message) else: raise ValidationError(cursor_position=len(document.text), message='Invalid command')
def _get_completions_for_match( self, match: Match, complete_event: CompleteEvent) -> Iterable[Completion]: """ Yield all the possible completions for this input string. (The completer assumes that the cursor position was at the end of the input string.) """ for match_variable in match.end_nodes(): varname = match_variable.varname start = match_variable.start completer = self.completers.get(varname) if completer: text = match_variable.value # Unwrap text. unwrapped_text = self.compiled_grammar.unescape(varname, text) # Create a document, for the completions API (text/cursor_position) document = Document(unwrapped_text, len(unwrapped_text)) # Call completer for completion in completer.get_completions(document, complete_event): new_text = unwrapped_text[:len(text) + completion.start_position] + completion.text # Wrap again. yield Completion( text=self.compiled_grammar.escape(varname, new_text), start_position=start - len(match.string), display=completion.display, display_meta=completion.display_meta)
def get_completions(self, document: Document, complete_event: CompleteEvent) -> Iterable[Completion]: # Get list of words. words = self.words if callable(words): words = words() # Get word/text before cursor. if self.sentence: word_before_cursor = document.text_before_cursor else: word_before_cursor = document.get_word_before_cursor( WORD=self.WORD, pattern=self.pattern) if self.ignore_case: word_before_cursor = word_before_cursor.lower() def word_matches(word: str) -> bool: """ True when the word before the cursor matches. """ if self.ignore_case: word = word.lower() if self.match_middle: return word_before_cursor in word else: return word.startswith(word_before_cursor) for a in words: if word_matches(a): display_meta = self.meta_dict.get(a, '') yield Completion(a, -len(word_before_cursor), display_meta=display_meta)
def test_completer(): class completer1(Completer): def get_completions(self, document, complete_event): yield Completion('before-%s-after' % document.text, -len(document.text)) yield Completion('before-%s-after-B' % document.text, -len(document.text)) class completer2(Completer): def get_completions(self, document, complete_event): yield Completion('before2-%s-after2' % document.text, -len(document.text)) yield Completion('before2-%s-after2-B' % document.text, -len(document.text)) # Create grammar. "var1" + "whitespace" + "var2" g = compile(r'(?P<var1>[a-z]*) \s+ (?P<var2>[a-z]*)') # Test 'get_completions()' completer = GrammarCompleter(g, { 'var1': completer1(), 'var2': completer2() }) completions = list( completer.get_completions(Document('abc def', len('abc def')), CompleteEvent())) assert len(completions) == 2 assert completions[0].text == 'before2-def-after2' assert completions[0].start_position == -3 assert completions[1].text == 'before2-def-after2-B' assert completions[1].start_position == -3
def test_pathcompleter_can_expanduser(): completer = PathCompleter(expanduser=True) doc_text = '~' doc = Document(doc_text, len(doc_text)) event = CompleteEvent() completions = list(completer.get_completions(doc, event)) assert len(completions) > 0
def test_pathcompleter_does_not_expanduser_by_default(): completer = PathCompleter() doc_text = '~' doc = Document(doc_text, len(doc_text)) event = CompleteEvent() completions = list(completer.get_completions(doc, event)) assert [] == completions
def test_pathcompleter_completes_in_current_directory(): completer = PathCompleter() doc_text = '' doc = Document(doc_text, len(doc_text)) event = CompleteEvent() completions = list(completer.get_completions(doc, event)) assert len(completions) > 0
def _get_fuzzy_completions( self, document: Document, complete_event: CompleteEvent) -> Iterable[Completion]: word_before_cursor = document.get_word_before_cursor( pattern=re.compile(self._get_pattern())) # Get completions document2 = Document(text=document.text[:document.cursor_position - len(word_before_cursor)], cursor_position=document.cursor_position - len(word_before_cursor)) completions = list( self.completer.get_completions(document2, complete_event)) fuzzy_matches: List[_FuzzyMatch] = [] pat = '.*?'.join(map(re.escape, word_before_cursor)) pat = '(?=({0}))'.format( pat) # lookahead regex to manage overlapping matches regex = re.compile(pat, re.IGNORECASE) for compl in completions: matches = list(regex.finditer(compl.text)) if matches: # Prefer the match, closest to the left, then shortest. best = min(matches, key=lambda m: (m.start(), len(m.group(1)))) fuzzy_matches.append( _FuzzyMatch(len(best.group(1)), best.start(), compl)) def sort_key(fuzzy_match: '_FuzzyMatch') -> Tuple[int, int]: " Sort by start position, then by the length of the match. " return fuzzy_match.start_pos, fuzzy_match.match_length fuzzy_matches = sorted(fuzzy_matches, key=sort_key) for match in fuzzy_matches: # Include these completions, but set the correct `display` # attribute and `start_position`. yield Completion(match.completion.text, start_position=match.completion.start_position - len(word_before_cursor), display_meta=match.completion.display_meta, display=self._get_display(match, word_before_cursor), style=match.completion.style)
def test_word_completer_dynamic_word_list(): called = [0] def get_words(): called[0] += 1 return ['abc', 'def', 'aaa'] completer = WordCompleter(get_words) # Dynamic list on empty input. completions = completer.get_completions(Document(''), CompleteEvent()) assert [c.text for c in completions] == ['abc', 'def', 'aaa'] assert called[0] == 1 # Static list on non-empty input. completions = completer.get_completions(Document('a'), CompleteEvent()) assert [c.text for c in completions] == ['abc', 'aaa'] assert called[0] == 2
def accept(buff): # Evaluate "calculator" expression. try: output = '\n\nIn: {}\nOut: {}'.format( input_field.text, eval(input_field.text)) # Don't do 'eval' in real code! except BaseException as e: output = '\n\n{}'.format(e) new_text = output_field.text + output # Add text to output buffer. output_field.buffer.document = Document(text=new_text, cursor_position=len(new_text))
def test_word_completer_static_word_list(): completer = WordCompleter(['abc', 'def', 'aaa']) # Static list on empty input. completions = completer.get_completions(Document(''), CompleteEvent()) assert [c.text for c in completions] == ['abc', 'def', 'aaa'] # Static list on non-empty input. completions = completer.get_completions(Document('a'), CompleteEvent()) assert [c.text for c in completions] == ['abc', 'aaa'] completions = completer.get_completions(Document('A'), CompleteEvent()) assert [c.text for c in completions] == [] # Multiple words ending with space. (Accept all options) completions = completer.get_completions(Document('test '), CompleteEvent()) assert [c.text for c in completions] == ['abc', 'def', 'aaa'] # Multiple words. (Check last only.) completions = completer.get_completions(Document('test a'), CompleteEvent()) assert [c.text for c in completions] == ['abc', 'aaa']
def test_fuzzy_completer(): collection = [ 'migrations.py', 'django_migrations.py', 'django_admin_log.py', 'api_user.doc', 'user_group.doc', 'users.txt', 'accounts.txt', '123.py', 'test123test.py' ] completer = FuzzyWordCompleter(collection) completions = completer.get_completions(Document('txt'), CompleteEvent()) assert [c.text for c in completions] == ['users.txt', 'accounts.txt'] completions = completer.get_completions(Document('djmi'), CompleteEvent()) assert [c.text for c in completions ] == ['django_migrations.py', 'django_admin_log.py'] completions = completer.get_completions(Document('mi'), CompleteEvent()) assert [c.text for c in completions] == [ 'migrations.py', 'django_migrations.py', 'django_admin_log.py' ] completions = completer.get_completions(Document('user'), CompleteEvent()) assert [c.text for c in completions ] == ['user_group.doc', 'users.txt', 'api_user.doc'] completions = completer.get_completions(Document('123'), CompleteEvent()) assert [c.text for c in completions] == ['123.py', 'test123test.py'] completions = completer.get_completions(Document('miGr'), CompleteEvent()) assert [c.text for c in completions] == [ 'migrations.py', 'django_migrations.py', ] # Multiple words ending with space. (Accept all options) completions = completer.get_completions(Document('test '), CompleteEvent()) assert [c.text for c in completions] == collection # Multiple words. (Check last only.) completions = completer.get_completions(Document('test txt'), CompleteEvent()) assert [c.text for c in completions] == ['users.txt', 'accounts.txt']
def test_pathcompleter_completes_files_in_current_directory(): # setup: create a test dir with 10 files test_dir = tempfile.mkdtemp() write_test_files(test_dir) expected = sorted([str(i) for i in range(10)]) if not test_dir.endswith(os.path.sep): test_dir += os.path.sep with chdir(test_dir): completer = PathCompleter() # this should complete on the cwd doc_text = '' doc = Document(doc_text, len(doc_text)) event = CompleteEvent() completions = list(completer.get_completions(doc, event)) result = sorted(c.text for c in completions) assert expected == result # cleanup shutil.rmtree(test_dir)
def test_pathcompleter_completes_files_in_absolute_directory(): # setup: create a test dir with 10 files test_dir = tempfile.mkdtemp() write_test_files(test_dir) expected = sorted([str(i) for i in range(10)]) test_dir = os.path.abspath(test_dir) if not test_dir.endswith(os.path.sep): test_dir += os.path.sep completer = PathCompleter() # force unicode doc_text = text_type(test_dir) doc = Document(doc_text, len(doc_text)) event = CompleteEvent() completions = list(completer.get_completions(doc, event)) result = sorted([c.text for c in completions]) assert expected == result # cleanup shutil.rmtree(test_dir)
def test_pathcompleter_can_apply_file_filter(): # setup: create a test dir with 10 files test_dir = tempfile.mkdtemp() write_test_files(test_dir) # add a .csv file with open(os.path.join(test_dir, 'my.csv'), 'wb') as out: out.write(b'') file_filter = lambda f: f and f.endswith('.csv') with chdir(test_dir): completer = PathCompleter(file_filter=file_filter) doc_text = '' doc = Document(doc_text, len(doc_text)) event = CompleteEvent() completions = list(completer.get_completions(doc, event)) result = [c.text for c in completions] assert ['my.csv'] == result # cleanup shutil.rmtree(test_dir)
def _get_text_fragments(self, text: str) -> StyleAndTextTuples: m = self.compiled_grammar.match_prefix(text) if m: characters: StyleAndTextTuples = [(self.default_style, c) for c in text] for v in m.variables(): # If we have a `Lexer` instance for this part of the input. # Tokenize recursively and apply tokens. lexer = self.lexers.get(v.varname) if lexer: document = Document(text[v.start:v.stop]) lexer_tokens_for_line = lexer.lex_document(document) text_fragments: StyleAndTextTuples = [] for i in range(len(document.lines)): text_fragments.extend(lexer_tokens_for_line(i)) text_fragments.append(('', '\n')) if text_fragments: text_fragments.pop() i = v.start for t, s, *_ in text_fragments: for c in s: if characters[i][0] == self.default_style: characters[i] = (t, characters[i][1]) i += 1 # Highlight trailing input. trailing_input = m.trailing_input() if trailing_input: for i in range(trailing_input.start, trailing_input.stop): characters[i] = ('class:trailing-input', characters[i][1]) return characters else: return [('', text)]
def test_pathcompleter_get_paths_constrains_path(): # setup: create a test dir with 10 files test_dir = tempfile.mkdtemp() write_test_files(test_dir) # add a subdir with 10 other files with different names subdir = os.path.join(test_dir, 'subdir') os.mkdir(subdir) write_test_files(subdir, 'abcdefghij') get_paths = lambda: ['subdir'] with chdir(test_dir): completer = PathCompleter(get_paths=get_paths) doc_text = '' doc = Document(doc_text, len(doc_text)) event = CompleteEvent() completions = list(completer.get_completions(doc, event)) result = [c.text for c in completions] expected = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'] assert expected == result # cleanup shutil.rmtree(test_dir)
def insert_comment(event: E) -> None: """ Without numeric argument, comment all lines. With numeric argument, uncomment all lines. In any case accept the input. """ buff = event.current_buffer # Transform all lines. if event.arg != 1: def change(line: str) -> str: return line[1:] if line.startswith('#') else line else: def change(line: str) -> str: return '#' + line buff.document = Document(text='\n'.join(map(change, buff.text.splitlines())), cursor_position=0) # Accept input. buff.validate_and_handle()
def text(self, value: str) -> None: self.buffer.set_document(Document(value, 0), bypass_readonly=True)
def __init__(self, text: str = '', multiline: FilterOrBool = True, password: FilterOrBool = False, lexer: Optional[Lexer] = None, auto_suggest: Optional[AutoSuggest] = None, completer: Optional[Completer] = None, complete_while_typing: FilterOrBool = True, accept_handler: Optional[BufferAcceptHandler] = None, history: Optional[History] = None, focusable: FilterOrBool = True, focus_on_click: FilterOrBool = False, wrap_lines: FilterOrBool = True, read_only: FilterOrBool = False, width: AnyDimension = None, height: AnyDimension = None, dont_extend_height: FilterOrBool = False, dont_extend_width: FilterOrBool = False, line_numbers: bool = False, get_line_prefix: Optional[GetLinePrefixCallable] = None, scrollbar: bool = False, style: str = '', search_field: Optional[SearchToolbar] = None, preview_search: FilterOrBool = True, prompt: AnyFormattedText = '', input_processors: Optional[List[Processor]] = None) -> None: if search_field is None: search_control = None elif isinstance(search_field, SearchToolbar): search_control = search_field.control if input_processors is None: input_processors = [] # Writeable attributes. self.completer = completer self.complete_while_typing = complete_while_typing self.lexer = lexer self.auto_suggest = auto_suggest self.read_only = read_only self.wrap_lines = wrap_lines self.buffer = Buffer( document=Document(text, 0), multiline=multiline, read_only=Condition(lambda: is_true(self.read_only)), completer=DynamicCompleter(lambda: self.completer), complete_while_typing=Condition( lambda: is_true(self.complete_while_typing)), auto_suggest=DynamicAutoSuggest(lambda: self.auto_suggest), accept_handler=accept_handler, history=history) self.control = BufferControl( buffer=self.buffer, lexer=DynamicLexer(lambda: self.lexer), input_processors=[ ConditionalProcessor(AppendAutoSuggestion(), has_focus(self.buffer) & ~is_done), ConditionalProcessor(processor=PasswordProcessor(), filter=to_filter(password)), BeforeInput(prompt, style='class:text-area.prompt'), ] + input_processors, search_buffer_control=search_control, preview_search=preview_search, focusable=focusable, focus_on_click=focus_on_click) if multiline: if scrollbar: right_margins = [ScrollbarMargin(display_arrows=True)] else: right_margins = [] if line_numbers: left_margins = [NumberedMargin()] else: left_margins = [] else: height = D.exact(1) left_margins = [] right_margins = [] style = 'class:text-area ' + style self.window = Window( height=height, width=width, dont_extend_height=dont_extend_height, dont_extend_width=dont_extend_width, content=self.control, style=style, wrap_lines=Condition(lambda: is_true(self.wrap_lines)), left_margins=left_margins, right_margins=right_margins, get_line_prefix=get_line_prefix)
def document(): return Document('line 1\n' + 'line 2\n' + 'line 3\n' + 'line 4\n', len('line 1\n' + 'lin'))
def test_word_completer_match_middle(): completer = WordCompleter(['abc', 'def', 'abca'], match_middle=True) completions = completer.get_completions(Document('bc'), CompleteEvent()) assert [c.text for c in completions] == ['abc', 'abca']
"[q] Quit [a] Focus left top [b] Right top [c] Left bottom [d] Right bottom." ) LIPSUM = """Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas quis interdum enim. Nam viverra, mauris et blandit malesuada, ante est bibendum mauris, ac dignissim dui tellus quis ligula. Aenean condimentum leo at dignissim placerat. In vel dictum ex, vulputate accumsan mi. Donec ut quam placerat massa tempor elementum. Sed tristique mauris ac suscipit euismod. Ut tempus vehicula augue non venenatis. Mauris aliquam velit turpis, nec congue risus aliquam sit amet. Pellentesque blandit scelerisque felis, faucibus consequat ante. Curabitur tempor tortor a imperdiet tincidunt. Nam sed justo sit amet odio bibendum congue. Quisque varius ligula nec ligula gravida, sed convallis augue faucibus. Nunc ornare pharetra bibendum. Praesent blandit ex quis sodales maximus. """ left_top = Window(BufferControl(Buffer(document=Document(LIPSUM)))) left_bottom = Window(BufferControl(Buffer(document=Document(LIPSUM)))) right_top = Window(BufferControl(Buffer(document=Document(LIPSUM)))) right_bottom = Window(BufferControl(Buffer(document=Document(LIPSUM)))) body = HSplit([ Window(FormattedTextControl(top_text), height=2, style='reverse'), Window(height=1, char='-'), # Horizontal line in the middle. VSplit([left_top, Window(width=1, char='|'), right_top]), Window(height=1, char='-'), # Horizontal line in the middle. VSplit([left_bottom, Window(width=1, char='|'), right_bottom]), ]) # 2. Key bindings kb = KeyBindings()