Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement quit to match vim #125

Open
wants to merge 16 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 33 additions & 22 deletions pyvim/commands/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,8 @@ def buffer_add(editor, location):
editor.window_arrangement.open_buffer(location)


@cmd('files')
@cmd('ls')
@cmd('buffers')
def buffer_list(editor):
"""
Expand Down Expand Up @@ -279,22 +281,18 @@ def buffer_edit(editor, location, force=False):

@cmd('q', accepts_force=True)
@cmd('quit', accepts_force=True)
def quit(editor, all_=False, force=False):
def quit(editor, force=False):
"""
Quit.
"""
ebs = editor.window_arrangement.editor_buffers

# When there are buffers that have unsaved changes, show balloon.
if not force and any(eb.has_unsaved_changes for eb in ebs):
eb = editor.window_arrangement.active_editor_buffer
eb_is_open_in_another_window = len(list(editor.window_arrangement.get_windows_for_buffer(eb))) > 1
if not force and eb.has_unsaved_changes and not eb_is_open_in_another_window:
editor.show_message(_NO_WRITE_SINCE_LAST_CHANGE_TEXT)

# When there is more than one buffer open.
elif not all_ and len(ebs) > 1:
editor.show_message('%i more files to edit' % (len(ebs) - 1))

else:
elif editor.window_arrangement.active_tab.window_count() == 1 and len(editor.window_arrangement.tab_pages) == 1:
editor.application.exit()
else:
editor.window_arrangement.close_window()


@cmd('qa', accepts_force=True)
Expand All @@ -303,7 +301,11 @@ def quit_all(editor, force=False):
"""
Quit all.
"""
quit(editor, all_=True, force=force)
ebs = editor.window_arrangement.editor_buffers
if not force and any(eb.has_unsaved_changes for eb in ebs):
editor.show_message(_NO_WRITE_SINCE_LAST_CHANGE_TEXT)
else:
editor.application.exit()


@location_cmd('w', accepts_force=True)
Expand All @@ -328,7 +330,7 @@ def write_and_quit(editor, location, force=False):
Write file and quit.
"""
write(editor, location, force=force)
editor.application.exit()
quit(editor)


@cmd('cq')
Expand All @@ -342,17 +344,26 @@ def quit_nonzero(editor):
editor.application.exit()


@cmd('wa')
def write_all(editor):
"""
Write all changed buffers
"""
for eb in editor.window_arrangement.editor_buffers:
if eb.location is None:
editor.show_message(_NO_FILE_NAME)
break
else:
eb.write()


@cmd('wqa')
def write_and_quit_all(editor):
"""
Write current buffer and quit all.
Write all changed buffers and quit all.
"""
eb = editor.window_arrangement.active_editor_buffer
if eb.location is None:
editor.show_message(_NO_FILE_NAME)
else:
eb.write()
quit(editor, all_=True, force=False)
write_all(editor)
quit_all(editor)


@cmd('h')
Expand Down Expand Up @@ -409,10 +420,10 @@ def pwd(editor):


@location_cmd('cd', accepts_force=False)
def pwd(editor, location):
def cd(editor, location):
" Change working directory. "
try:
os.chdir(location)
os.chdir(os.path.expanduser(location))
except OSError as e:
editor.show_message('{}'.format(e))

Expand Down
27 changes: 26 additions & 1 deletion pyvim/key_bindings.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
from prompt_toolkit.filters import Condition, has_focus, vi_insert_mode, vi_navigation_mode
from prompt_toolkit.key_binding import KeyBindings

from .commands.commands import write_and_quit, quit

import os

__all__ = (
Expand Down Expand Up @@ -38,6 +40,29 @@ def vi_buffer_focussed():
in_insert_mode = vi_insert_mode & vi_buffer_focussed
in_navigation_mode = vi_navigation_mode & vi_buffer_focussed

@kb.add('Z', 'Z', filter=in_navigation_mode)
def _(event):
"""
Write and quit.
"""
write_and_quit(editor, None)
editor.sync_with_prompt_toolkit()

@kb.add('Z', 'Q', filter=in_navigation_mode)
def _(event):
"""
Quit and discard changes.
"""
quit(editor, force=True)
editor.sync_with_prompt_toolkit()

@kb.add('c-z', filter=in_navigation_mode)
def _(event):
"""
Suspend process to background.
"""
event.app.suspend_to_background()

@kb.add('c-t')
def _(event):
"""
Expand All @@ -52,7 +77,7 @@ def indent_line(event):
"""
Indent current line.
"""
b = event.application.current_buffer
b = event.app.current_buffer

# Move to start of line.
pos = b.document.get_start_of_line_position(after_whitespace=True)
Expand Down
15 changes: 13 additions & 2 deletions pyvim/window_arrangement.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ class HSplit(list):


class VSplit(list):
""" Horizontal split. """
""" Vertical split. """


class Window(object):
Expand Down Expand Up @@ -103,6 +103,10 @@ def _get_split_parent(self, split):
if child == split:
return parent

def get_windows_for_buffer(self, editor_buffer):
""" Return a list of all windows in this tab page. """
return (window for _, window in self._walk_through_windows() if window.editor_buffer == editor_buffer)

def _split(self, split_cls, editor_buffer=None):
"""
Split horizontal or vertical.
Expand Down Expand Up @@ -260,11 +264,18 @@ def get_editor_buffer_for_buffer_name(self, buffer_name):
if eb.buffer_name == buffer_name:
return eb

def get_windows_for_buffer(self, editor_buffer):
""" Return a list of all windows in this tab page. """
return (b for t in self.tab_pages for b in t.get_windows_for_buffer(editor_buffer))

def close_window(self):
"""
Close active window of active tab.
"""
self.active_tab.close_active_window()
if self.active_tab.window_count() > 1:
self.active_tab.close_active_window()
else:
self.close_tab()

# Clean up buffers.
self._auto_close_new_empty_buffers()
Expand Down
19 changes: 17 additions & 2 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,10 @@

import pytest

from prompt_toolkit.buffer import Buffer
from prompt_toolkit.output import DummyOutput
from prompt_toolkit.input import DummyInput
from pyvim.editor import Editor
from pyvim.window_arrangement import TabPage, EditorBuffer, Window
from pyvim.window_arrangement import TabPage, EditorBuffer, Window, WindowArrangement


@pytest.fixture
Expand All @@ -27,3 +26,19 @@ def window(editor_buffer):
@pytest.fixture
def tab_page(window):
return TabPage(window)


@pytest.fixture
def tab_page_with_splits(editor_buffer, window):
editor_buffer2 = EditorBuffer(editor)

tab_page = TabPage(Window(editor_buffer))
tab_page.vsplit(editor_buffer)
tab_page.vsplit(editor_buffer2)
tab_page.hsplit(editor_buffer)
return tab_page


@pytest.fixture
def window_arrangement(editor):
return WindowArrangement(editor)
39 changes: 37 additions & 2 deletions tests/test_window_arrangements.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
from __future__ import unicode_literals

from prompt_toolkit.buffer import Buffer
from pyvim.window_arrangement import EditorBuffer, VSplit
from pyvim.window_arrangement import EditorBuffer, VSplit, TabPage, Window


def test_initial(window, tab_page):
Expand All @@ -18,3 +17,39 @@ def test_vsplit(editor, tab_page):

assert isinstance(tab_page.root, VSplit)
assert len(tab_page.root) == 2


def test_tab_page_get_windows_for_buffer(editor, editor_buffer, tab_page_with_splits):
tab_page1 = tab_page_with_splits

windows = list(tab_page1.get_windows_for_buffer(editor_buffer))
assert all(w.editor_buffer == editor_buffer for w in windows)
assert len(windows) == 3


def test_window_arrangement_get_windows_for_buffer(editor, editor_buffer, tab_page_with_splits, window_arrangement):
tab_page1 = tab_page_with_splits
tab_page2 = TabPage(Window(editor_buffer))

window_arrangement.tab_pages[:] = [tab_page1, tab_page2]
windows = list(window_arrangement.get_windows_for_buffer(editor_buffer))
assert all(w.editor_buffer == editor_buffer for w in windows)
assert len(windows) == 4


def test_close_window_closes_split(editor):
editor.window_arrangement.create_tab()
editor.window_arrangement.hsplit()
assert len(editor.window_arrangement.tab_pages) == 2

assert editor.window_arrangement.active_tab.window_count() == 2
editor.window_arrangement.close_window()
assert editor.window_arrangement.active_tab.window_count() == 1


def test_close_window_also_closes_empty_tab(editor):
editor.window_arrangement.create_tab()

assert len(editor.window_arrangement.tab_pages) == 2
editor.window_arrangement.close_window()
assert len(editor.window_arrangement.tab_pages) == 1