예제 #1
0
 def test(c):
     m = Mocker()
     menu = m.mock(ak.NSMenu)
     ctl = TextCommandController("<history>")
     for command in c.commands:
         ctl.add_command(command, None, menu)
     eq_(ctl.lookup(c.lookup), c.result)
예제 #2
0
 def test(c):
     m = Mocker()
     menu = m.mock(ak.NSMenu)
     ctl = TextCommandController("<history>")
     for command in c.commands:
         ctl.add_command(command, None, menu)
     eq_(ctl.lookup(c.lookup), c.result)
예제 #3
0
 def test(c):
     m = Mocker()
     menu = m.mock(ak.NSMenu)
     ctl = TextCommandController("<history>")
     for command in c.commands:
         ctl.add_command(command, None, menu)
         menu.insertItem_atIndex_(ANY, ANY)
     eq_(ctl.lookup_full_command(c.lookup), c.result)
예제 #4
0
 def test(c):
     m = Mocker()
     menu = m.mock(ak.NSMenu)
     ctl = TextCommandController("<history>")
     for command in c.commands:
         ctl.add_command(command, None, menu)
         menu.insertItem_atIndex_(ANY, ANY)
     eq_(ctl.lookup_full_command(c.lookup), c.result)
예제 #5
0
 def __init__(self, profile=None):
     if profile is None:
         profile = self.default_profile()
     self.profile_path = os.path.expanduser(profile)
     assert os.path.isabs(self.profile_path), \
         'profile path cannot be relative (%s)' % self.profile_path
     self._setup_profile = set()
     self.editors = []
     self.path_opener = None
     self.config = Config(self.profile_path)
     self.context = ContextMap()
     self.syntax_factory = None
     state_dir = os.path.join(self.profile_path, const.STATE_DIR)
     command_history = CommandHistory(state_dir)
     self.text_commander = TextCommandController(command_history)
     register_value_transformers()
예제 #6
0
 def test(c):
     m = Mocker()
     lg = m.replace("editxt.textcommand.log")
     mi = m.mock(ak.NSMenuItem)
     tv = m.mock(ak.NSTextView)
     tc = m.mock()
     tcc = TextCommandController("<history>")
     cmds = m.replace(tcc, 'commands')
     cmds.get(mi.tag() >> 42) >> (tc if c.has_command else None)
     if c.has_command:
         tc(tv, mi, None)
         if c.error:
             m.throw(Exception)
             lg.error("%s.execute failed", ANY, exc_info=True)
     with m:
         tcc.do_textview_command(tv, mi)
예제 #7
0
 def test(c):
     m = Mocker()
     lg = m.replace("editxt.textcommand.log")
     mi = m.mock(ak.NSMenuItem)
     tv = m.mock(ak.NSTextView)
     tc = m.mock()
     tcc = TextCommandController("<history>")
     cmds = m.replace(tcc, 'commands')
     cmds.get(mi.tag() >> 42) >> (tc if c.has_command else None)
     if c.has_command:
         tc(tv, mi, None)
         if c.error:
             m.throw(Exception)
             lg.error("%s.execute failed", ANY, exc_info=True)
     with m:
         tcc.do_textview_command(tv, mi)
예제 #8
0
 def test(c):
     m = Mocker()
     lg = m.replace("editxt.textcommand.log", passthrough=False)
     mi = m.mock(NSMenuItem)
     tv = m.mock(NSTextView)
     tc = m.mock(TextCommand)
     tcc = TextCommandController(None)
     cmds = m.replace(tcc.commands)
     cmd = cmds.get(mi.tag() >> 42) >> (tc if c.has_command else None)
     if c.has_command:
         cmd.execute(tv, mi)
         if c.error:
             m.throw(Exception)
             lg.error("%s.execute failed", ANY, exc_info=True)
     with m:
         tcc.do_textview_command(tv, mi)
예제 #9
0
 def test(c):
     m = Mocker()
     lg = m.replace("editxt.textcommand.log")
     tv = m.mock(ak.NSTextView)
     tcc = TextCommandController("<history>")
     sel = "<selector>"
     callback = m.mock()
     handlers = m.replace(tcc, 'input_handlers')
     cmd = handlers.get(sel) >> (callback if c.has_selector else None)
     if c.has_selector:
         callback(tv, None, None)
         if c.error:
             m.throw(Exception)
             lg.error("%s failed", callback, exc_info=True)
     with m:
         result = tcc.do_textview_command_by_selector(tv, sel)
         eq_(result, c.result)
예제 #10
0
 def test(c):
     m = Mocker()
     lg = m.replace("editxt.textcommand.log")
     tv = m.mock(ak.NSTextView)
     tcc = TextCommandController("<history>")
     sel = "<selector>"
     callback = m.mock()
     handlers = m.replace(tcc, 'input_handlers')
     cmd = handlers.get(sel) >> (callback if c.has_selector else None)
     if c.has_selector:
         callback(tv, None, None)
         if c.error:
             m.throw(Exception)
             lg.error("%s failed", callback, exc_info=True)
     with m:
         result = tcc.do_textview_command_by_selector(tv, sel)
         eq_(result, c.result)
예제 #11
0
def test_CommandBar_get_history_concurrently():
    with tempdir() as tmp:
        history = mod.CommandHistory(tmp)
        for item in reversed("abc"):
            history.append(item)
        editor = type("FakeEditor", (object, ), {})()
        commander = TextCommandController(history)
        bar1 = mod.CommandBar(editor, commander)
        bar2 = mod.CommandBar(editor, commander)
        bar3 = mod.CommandBar(editor, commander)
        bar4 = mod.CommandBar(editor, commander)

        eq_(bar1.get_history("x"), "a")

        eq_(bar2.get_history(""), "a")
        eq_(bar2.get_history("y"), "b")

        eq_(bar3.get_history(""), "a")
        eq_(bar3.get_history("a"), "b")
        eq_(bar3.get_history("z"), "c")  # <-- "z" will move to 0 (with "b")

        history.append("b")

        # current index "a", "x" in new command buffer
        eq_(bar1.get_history("a"), "c")
        eq_(bar1.get_history("c"), None)
        eq_(bar1.get_history("c", True), "a")
        eq_(bar1.get_history("a", True), "b")
        eq_(bar1.get_history("b", True), "x")
        eq_(bar1.get_history("x", True), None)

        # current index "b", "y" at 0
        eq_(bar2.get_history("B"), "y")  # <-- "B" now at 0
        eq_(bar2.get_history("y"), "c")
        eq_(bar2.get_history("c"), None)
        eq_(bar2.get_history("c", True), "y")
        eq_(bar2.get_history("y", True), "B")
        eq_(bar2.get_history("B", True), "")
        eq_(bar2.get_history("", True), None)

        # current index "c", "z" at 1
        eq_(bar3.get_history("c"), None)
        eq_(bar3.get_history("C", True), "a")
        eq_(bar3.get_history("a"), "C")
        eq_(bar3.get_history("C", True), "a")
        eq_(bar3.get_history("a", True), "z")
        eq_(bar3.get_history("z", True), "")  # <-- "z" moved to 0
        eq_(bar3.get_history("", True), None)

        eq_(bar4.get_history("A", True), None)
        eq_(bar4.get_history("A"), "b")
        eq_(bar4.get_history("b"), "a")
        eq_(bar4.get_history("a"), "c")
        eq_(bar4.get_history("c"), None)
        eq_(bar4.get_history("c", True), "a")
        eq_(bar4.get_history("a", True), "b")
        eq_(bar4.get_history("b", True), "A")
        eq_(bar4.get_history("A", True), None)
예제 #12
0
 def test(c):
     m = Mocker()
     lg = m.replace("editxt.textcommand.log")
     mi = m.mock(ak.NSMenuItem)
     tv = m.mock(ak.NSTextView)
     tc = m.mock()
     tcc = TextCommandController("<history>")
     cmds = m.replace(tcc, 'commands')
     cmds.get(mi.tag() >> 42) >> (tc if c.has_command else None)
     if c.has_command:
         if c.error:
             expect(tc.is_enabled(tv, mi)).throw(Exception)
             lg.error("%s.is_enabled failed", ANY, exc_info=True)
         else:
             tc.is_enabled(tv, mi) >> c.enabled
     with m:
         result = tcc.is_textview_command_enabled(tv, mi)
         eq_(result, c.enabled)
예제 #13
0
 def test(c):
     m = Mocker()
     lg = m.replace("editxt.textcommand.log")
     mi = m.mock(ak.NSMenuItem)
     tv = m.mock(ak.NSTextView)
     tc = m.mock()
     tcc = TextCommandController("<history>")
     cmds = m.replace(tcc, 'commands')
     cmds.get(mi.tag() >> 42) >> (tc if c.has_command else None)
     if c.has_command:
         if c.error:
             expect(tc.is_enabled(tv, mi)).throw(Exception)
             lg.error("%s.is_enabled failed", ANY, exc_info=True)
         else:
             tc.is_enabled(tv, mi) >> c.enabled
     with m:
         result = tcc.is_textview_command_enabled(tv, mi)
         eq_(result, c.enabled)
예제 #14
0
 def test(c):
     m = Mocker()
     menu = m.mock(ak.NSMenu)
     ctl = TextCommandController("<history>")
     handlers = ctl.input_handlers = m.mock(dict)
     add = m.method(ctl.add_command)
     mod = m.mock(dict)
     m.method(ctl.iter_command_modules)() >> [("<path>", mod)]
     cmds = []; mod.get("text_menu_commands", []) >> cmds
     for i in range(c.commands):
         cmd = "<command %s>" % i
         add(cmd, "<path>", menu)
         cmds.append(cmd)
     hnds = mod.get("input_handlers", {}) >> {}
     for i in range(c.handlers):
         hnds["handle%s" % i] = "<handle %s>" % i
     handlers.update(hnds)
     with m:
         ctl.load_commands(menu)
예제 #15
0
def test_TextCommandController_init():
    m = Mocker()
    menu = m.mock(ak.NSMenu)
    with m:
        ctl = TextCommandController("<history>")
        eq_(ctl.history, "<history>")
        eq_(ctl.commands, {})
        eq_(ctl.commands_by_path, {})
        eq_(ctl.input_handlers, {})
        eq_(ctl.editems, {})
예제 #16
0
 def test(c):
     m = Mocker()
     menu = m.mock(ak.NSMenu)
     ctl = TextCommandController("<history>")
     handlers = ctl.input_handlers = m.mock(dict)
     add = m.method(ctl.add_command)
     mod = m.mock(dict)
     m.method(ctl.iter_command_modules)() >> [("<path>", mod)]
     cmds = []
     mod.get("text_menu_commands", []) >> cmds
     for i in range(c.commands):
         cmd = "<command %s>" % i
         add(cmd, "<path>", menu)
         cmds.append(cmd)
     hnds = mod.get("input_handlers", {}) >> {}
     for i in range(c.handlers):
         hnds["handle%s" % i] = "<handle %s>" % i
     handlers.update(hnds)
     with m:
         ctl.load_commands(menu)
예제 #17
0
 def test(c):
     m = Mocker()
     menu = m.mock(NSMenu)
     mi_class = m.replace(NSMenuItem, passthrough=False)
     ctl = TextCommandController(menu)
     cmds = m.replace(ctl.commands)
     handlers = m.replace(ctl.input_handlers, passthrough=False)
     validate = m.method(ctl.validate_hotkey)
     cmd = m.mock(TextCommand)
     tag = cmd._TextCommandController__tag = ctl.tagger.next() + 1
     validate(cmd.preferred_hotkey() >> "<hotkey>") >> ("<hotkey>", "<keymask>")
     mi = mi_class.alloc() >> m.mock(NSMenuItem)
     mi.initWithTitle_action_keyEquivalent_(
         cmd.title() >> "<title>", "performTextCommand:" ,"<hotkey>") >> mi
     mi.setKeyEquivalentModifierMask_("<keymask>")
     mi.setTag_(tag)
     menu.insertItem_atIndex_(mi, tag)
     ctl.commands[tag] = cmd
     with m:
         ctl.add_command(cmd, None)
예제 #18
0
def test_CommandBar_history_reset_on_execute():
    from editxt.document import TextDocumentView
    from editxt.textcommand import CommandHistory, TextCommandController
    with tempdir() as tmp:
        m = Mocker()
        editor = m.mock()
        history = CommandHistory(tmp)
        commander = TextCommandController(history)
        bar = mod.CommandBar(editor, commander)
        args = ["cmd"]
        view = editor.current_view >> m.mock(TextDocumentView)
        view.text_view >> '<view>'
        @command
        def cmd(textview, sender, args):
            pass
        commander.add_command(cmd, None, None)
        with m:
            bar.get_history("cmd")
            bar.execute("cmd")
            eq_(bar.history_view, None)
            eq_(list(history), ["cmd"])
예제 #19
0
 def test(c):
     m = Mocker()
     menu = m.mock(ak.NSMenu)
     mi_class = m.replace(ak, 'NSMenuItem')
     ctl = TextCommandController("<history>")
     handlers = m.replace(ctl, 'input_handlers')
     validate = m.method(ctl.validate_hotkey)
     cmd = m.mock()
     cmd.names >> []
     cmd.lookup_with_arg_parser >> False
     tag = cmd._TextCommandController__tag = next(ctl.tagger) + 1
     validate(cmd.hotkey >> "<hotkey>") >> ("<hotkey>", "<keymask>")
     mi = mi_class.alloc() >> m.mock(ak.NSMenuItem)
     (cmd.title << "<title>").count(2)
     mi.initWithTitle_action_keyEquivalent_(
         '<title>', "performTextCommand:" ,"<hotkey>") >> mi
     mi.setKeyEquivalentModifierMask_("<keymask>")
     mi.setTag_(tag)
     menu.insertItem_atIndex_(mi, tag)
     with m:
         ctl.add_command(cmd, None, menu)
         assert ctl.commands[tag] is cmd, (ctl.commands[tag], cmd)
예제 #20
0
 def test(c):
     m = Mocker()
     menu = m.mock(ak.NSMenu)
     mi_class = m.replace(ak, 'NSMenuItem')
     ctl = TextCommandController("<history>")
     handlers = m.replace(ctl, 'input_handlers')
     validate = m.method(ctl.validate_hotkey)
     cmd = m.mock()
     cmd.names >> []
     cmd.lookup_with_arg_parser >> False
     tag = cmd._TextCommandController__tag = next(ctl.tagger) + 1
     validate(cmd.hotkey >> "<hotkey>") >> ("<hotkey>", "<keymask>")
     mi = mi_class.alloc() >> m.mock(ak.NSMenuItem)
     (cmd.title << "<title>").count(2)
     mi.initWithTitle_action_keyEquivalent_(
         '<title>', "performTextCommand:", "<hotkey>") >> mi
     mi.setKeyEquivalentModifierMask_("<keymask>")
     mi.setTag_(tag)
     menu.insertItem_atIndex_(mi, tag)
     with m:
         ctl.add_command(cmd, None, menu)
         assert ctl.commands[tag] is cmd, (ctl.commands[tag], cmd)
예제 #21
0
    def test(nav):
        with tempdir() as tmp:
            history = mod.CommandHistory(tmp)
            for item in reversed("abc"):
                history.append(item)
            editor = type("FakeEditor", (object, ), {})()
            commander = TextCommandController(history)
            bar = mod.CommandBar(editor, commander)

            for input, direction, history in nav:
                dirchar = "v" if direction else "A"
                print("{}({!r}, {!r})".format(dirchar, input, history))
                eq_(bar.get_history(input, forward=direction), history)
예제 #22
0
def test_CommandBar_history_reset_on_execute():
    from editxt.document import TextDocumentView
    from editxt.textcommand import CommandHistory, TextCommandController
    with tempdir() as tmp:
        m = Mocker()
        editor = m.mock()
        history = CommandHistory(tmp)
        commander = TextCommandController(history)
        bar = mod.CommandBar(editor, commander)
        args = ["cmd"]
        view = editor.current_view >> m.mock(TextDocumentView)
        view.text_view >> '<view>'

        @command
        def cmd(textview, sender, args):
            pass

        commander.add_command(cmd, None, None)
        with m:
            bar.get_history("cmd")
            bar.execute("cmd")
            eq_(bar.history_view, None)
            eq_(list(history), ["cmd"])
예제 #23
0
 def __init__(self, profile=None):
     if profile is None:
         profile = self.default_profile()
     self.profile_path = os.path.expanduser(profile)
     assert os.path.isabs(self.profile_path), \
         'profile path cannot be relative (%s)' % self.profile_path
     self._setup_profile = set()
     self.editors = []
     self.path_opener = None
     self.config = Config(self.profile_path)
     self.context = ContextMap()
     self.syntax_factory = None
     state_dir = os.path.join(self.profile_path, const.STATE_DIR)
     command_history = CommandHistory(state_dir)
     self.text_commander = TextCommandController(command_history)
     register_value_transformers()
예제 #24
0
class Application(object):

    def __init__(self, profile=None):
        if profile is None:
            profile = self.default_profile()
        self.profile_path = os.path.expanduser(profile)
        assert os.path.isabs(self.profile_path), \
            'profile path cannot be relative (%s)' % self.profile_path
        self._setup_profile = set()
        self.editors = []
        self.path_opener = None
        self.config = Config(self.profile_path)
        self.context = ContextMap()
        self.syntax_factory = None
        state_dir = os.path.join(self.profile_path, const.STATE_DIR)
        command_history = CommandHistory(state_dir)
        self.text_commander = TextCommandController(command_history)
        register_value_transformers()

    @classmethod
    def name(cls):
        return fn.NSBundle.mainBundle().objectForInfoDictionaryKey_("CFBundleName")

    @classmethod
    def resource_path(cls):
        return fn.NSBundle.mainBundle().resourcePath()

    @classmethod
    def default_profile(cls):
        return '~/.' + cls.name().lower()

    def init_syntax_definitions(self):
        from editxt.syntax import SyntaxFactory
        self.syntax_factory = sf = SyntaxFactory()
        paths = [(self.resource_path(), False), (self.profile_path, True)]
        for path, log_info in paths:
            path = os.path.join(path, const.SYNTAX_DEFS_DIR)
            sf.load_definitions(path, log_info)
        sf.index_definitions()

    @property
    def syntaxdefs(self):
        return self.syntax_factory.definitions

    def application_will_finish_launching(self, app, doc_ctrl):
        self.init_syntax_definitions()
        self.text_commander.load_commands(doc_ctrl.textMenu)
        states = list(self.iter_saved_editor_states())
        if states:
            for state in reversed(states):
                self.create_editor(state)
        else:
            self.create_editor()

    def create_editor(self, state=None):
        from editxt.editor import EditorWindowController, Editor
        wc = EditorWindowController.alloc().initWithWindowNibName_("EditorWindow")
        ed = Editor(self, wc, state)
        wc.editor = ed
        self.editors.append(ed)
        wc.showWindow_(self)
        return ed

    def open_path_dialog(self):
        if self.path_opener is None:
            opc = OpenPathController.alloc().initWithWindowNibName_("OpenPath")
            opc.showWindow_(self)
            self.path_opener = opc
        else:
           self.path_opener.window().makeKeyAndOrderFront_(self)
        self.path_opener.populateWithClipboard()

    def new_project(self):
        editor = self.current_editor()
        if editor is not None:
            return editor.new_project()

    def open_documents_with_paths(self, paths):
        from editxt.document import TextDocumentView
        editor = self.current_editor()
        if editor is None:
            editor = self.create_editor()
        focus = None
        views = []
        for path in paths:
            if os.path.isfile(path) or not os.path.exists(path):
                view = TextDocumentView.create_with_path(path)
                focus = editor.add_document_view(view)
                views.append(view)
            else:
                log.info("cannot open path: %s", path)
        if focus is not None:
            editor.current_view = focus
        return views

    def open_config_file(self):
        views = self.open_documents_with_paths([self.config.path])
        if not os.path.exists(self.config.path):
            assert len(views) == 1, views
            views[0].document.text = self.config.default_config

    def open_error_log(self, set_current=True):
        from editxt.document import TextDocumentView
        doc = errlog.document
        try:
            view = next(self.iter_views_of_document(doc))
        except StopIteration:
            editor = self.current_editor()
            if editor is None:
                editor = self.create_editor()
            view = TextDocumentView.create_with_document(doc)
            editor.add_document_view(view)
            if set_current:
                editor.current_view = view
        else:
            if set_current:
                self.set_current_document_view(view)

    def iter_dirty_documents(self):
        seen = set()
        for editor in self.iter_editors():
            for proj in editor.projects:
                dirty = False
                for view in proj.dirty_documents():
                    if view.document.id not in seen:
                        seen.add(view.document.id)
                        yield view
                        dirty = True
                if dirty:
                    yield proj

    def set_current_document_view(self, doc_view):
        ed = self.find_editor_with_document_view(doc_view)
        ed.current_view = doc_view

    def close_current_document(self):
        editor = self.current_editor()
        if editor is not None:
            view = editor.current_view
            if view is not None:
                view.perform_close(editor)

    def iter_views_of_document(self, doc):
        for editor in self.iter_editors():
            for view in editor.iter_views_of_document(doc):
                yield view

#   def find_view_with_document(self, doc):
#       """find a view of the given document
# 
#       Returns a view in the topmost window with the given document, or None
#       if there are no views of this document.
#       """
#       try:
#           return next(self.iter_views_of_document(doc))
#       except StopIteration:
#           return None

    def count_views_of_document(self, doc):
        return len(list(self.iter_views_of_document(doc)))

    def iter_editors_with_view_of_document(self, document):
        for editor in self.iter_editors():
            try:
                next(editor.iter_views_of_document(document))
            except StopIteration:
                pass
            else:
                yield editor

    def find_editor_with_document_view(self, doc_view):
        for editor in self.iter_editors():
            for proj in editor.projects:
                for dv in proj.documents():
                    if dv is doc_view:
                        return editor
        return None

    def find_editors_with_project(self, project):
        return [ed for ed in self.editors if project in ed.projects]

    def find_project_with_path(self, path):
        for ed in self.editors:
            proj = ed.find_project_with_path(path)
            if proj is not None:
                return proj
        return None

    def find_item_with_id(self, ident):
        # HACK slow implementation, violates encapsulation
        for editor in self.editors:
            for proj in editor.projects:
                if proj.id == ident:
                    return proj
                for doc in proj.documents():
                    if doc.id == ident:
                        return doc
        return None

    def item_changed(self, item, change_type=None):
        for editor in self.editors:
            editor.item_changed(item, change_type)

    def iter_editors(self, app=None):
        """Iterate over editors in on-screen z-order starting with the
        front-most editor window"""
        from editxt.editor import EditorWindowController
        if app is None:
            app = ak.NSApp()
        z_ordered_eds = set()
        for win in app.orderedWindows():
            wc = win.windowController()
            if isinstance(wc, EditorWindowController) and wc.editor in self.editors:
                z_ordered_eds.add(wc.editor)
                yield wc.editor
        for ed in self.editors:
            if ed not in z_ordered_eds:
                yield ed

    def add_editor(self, editor):
        self.editors.append(editor)

    def current_editor(self):
        try:
            return next(self.iter_editors())
        except StopIteration:
            return None

    def discard_editor(self, editor):
        try:
            self.editors.remove(editor)
        except ValueError:
            pass
        editor.close()

    def setup_profile(self, editors=False):
        """Ensure that profile dir exists

        This will create the profile directory if it does not exist.

        :returns: True on success, otherwise False.
        """
        if not ('.' in self._setup_profile or os.path.isdir(self.profile_path)):
            try:
                os.mkdir(self.profile_path)
            except OSError:
                log.error('cannot create %s', self.profile_path, exc_info=True)
                return False
            self._setup_profile.add('.')
        if editors and 'editors' not in self._setup_profile:
            state_path = os.path.join(self.profile_path, const.STATE_DIR)
            if not os.path.exists(state_path):
                try:
                    os.mkdir(state_path)
                except OSError:
                    log.error('cannot create %s', state_path, exc_info=True)
                    return False
            self._setup_profile.add('editors')
        return True

    def _legacy_editor_states(self):
        # TODO remove once all users have upraded to new state persistence
        def pythonify(value):
            if isinstance(value, (str, int, float, bool)):
                return value
            if isinstance(value, (dict, fn.NSDictionary)):
                return {k: pythonify(v) for k, v in value.items()}
            if isinstance(value, (list, fn.NSArray)):
                return [pythonify(v) for v in value]
            raise ValueError('unknown value type: {} {}'
                .format(type(value), repr(value)))
        defaults = fn.NSUserDefaults.standardUserDefaults()
        serials = defaults.arrayForKey_(const.WINDOW_CONTROLLERS_DEFAULTS_KEY)
        settings = defaults.arrayForKey_(const.WINDOW_SETTINGS_DEFAULTS_KEY)
        serials = list(reversed(serials))
        for serial, setting in zip(serials, chain(settings, repeat(None))):
            try:
                state = dict(serial)
                if setting is not None:
                    state['window_settings'] = setting
                yield pythonify(state)
            except Exception:
                log.warn('cannot load legacy state: %r', serial, exc_info=True)

    def iter_saved_editor_states(self):
        """Yield saved editor states"""
        state_path = os.path.join(self.profile_path, const.STATE_DIR)
        if not os.path.exists(state_path):
            if self.profile_path == os.path.expanduser(self.default_profile()):
                # TODO remove once all users have upraded
                for state in self._legacy_editor_states():
                    yield state
            return
        state_glob = os.path.join(state_path, const.EDITOR_STATE.format('*'))
        for path in sorted(glob.glob(state_glob)):
            try:
                with open(path) as f:
                    yield load_yaml(f)
            except Exception:
                log.error('cannot load %s', path, exc_info=True)

    def save_editor_state(self, editor, ident=None):
        """Save a single editor's state

        :param editor: The editor with state to be saved.
        :param ident: The identifier to use when saving editor state. It
            is assumed that the profile has been setup when this
            argument is provided; ``editor.id`` will be used when
            not provided.
        :returns: The name of the state file.
        """
        if ident is None:
            raise NotImplementedError
            ident = editor.id
        self.setup_profile(editors=True)
        state_name = const.EDITOR_STATE.format(ident)
        state_file = os.path.join(
            self.profile_path, const.STATE_DIR, state_name)
        state = editor.state
        try:
            with atomicfile(state_file, encoding="utf-8") as fh:
                dump_yaml(state, fh)
        except Exception:
            log.error('cannot write %s\n%s\n', state_file, state, exc_info=True)
        return state_name

    def save_editor_states(self):
        """Save all editors' states"""
        state_path = os.path.join(self.profile_path, const.STATE_DIR)
        old_glob = os.path.join(state_path, const.EDITOR_STATE.format('*'))
        old = {os.path.basename(name) for name in glob.glob(old_glob)}
        for i, editor in enumerate(self.iter_editors()):
            state_name = self.save_editor_state(editor, i)
            old.discard(state_name)
        for name in old:
            state_file = os.path.join(state_path, name)
            try:
                os.remove(state_file)
            except Exception:
                log.error('cannot remove %s', state_file, exc_info=True)

    def app_will_terminate(self, app):
        self.save_editor_states()
예제 #25
0
class Application(object):
    def __init__(self, profile=None):
        if profile is None:
            profile = self.default_profile()
        self.profile_path = os.path.expanduser(profile)
        assert os.path.isabs(self.profile_path), \
            'profile path cannot be relative (%s)' % self.profile_path
        self._setup_profile = set()
        self.editors = []
        self.path_opener = None
        self.config = Config(self.profile_path)
        self.context = ContextMap()
        self.syntax_factory = None
        state_dir = os.path.join(self.profile_path, const.STATE_DIR)
        command_history = CommandHistory(state_dir)
        self.text_commander = TextCommandController(command_history)
        register_value_transformers()

    @classmethod
    def name(cls):
        return fn.NSBundle.mainBundle().objectForInfoDictionaryKey_(
            "CFBundleName")

    @classmethod
    def resource_path(cls):
        return fn.NSBundle.mainBundle().resourcePath()

    @classmethod
    def default_profile(cls):
        return '~/.' + cls.name().lower()

    def init_syntax_definitions(self):
        from editxt.syntax import SyntaxFactory
        self.syntax_factory = sf = SyntaxFactory()
        paths = [(self.resource_path(), False), (self.profile_path, True)]
        for path, log_info in paths:
            path = os.path.join(path, const.SYNTAX_DEFS_DIR)
            sf.load_definitions(path, log_info)
        sf.index_definitions()

    @property
    def syntaxdefs(self):
        return self.syntax_factory.definitions

    def application_will_finish_launching(self, app, doc_ctrl):
        self.init_syntax_definitions()
        self.text_commander.load_commands(doc_ctrl.textMenu)
        states = list(self.iter_saved_editor_states())
        if states:
            for state in reversed(states):
                self.create_editor(state)
        else:
            self.create_editor()

    def create_editor(self, state=None):
        from editxt.editor import EditorWindowController, Editor
        wc = EditorWindowController.alloc().initWithWindowNibName_(
            "EditorWindow")
        ed = Editor(self, wc, state)
        wc.editor = ed
        self.editors.append(ed)
        wc.showWindow_(self)
        return ed

    def open_path_dialog(self):
        if self.path_opener is None:
            opc = OpenPathController.alloc().initWithWindowNibName_("OpenPath")
            opc.showWindow_(self)
            self.path_opener = opc
        else:
            self.path_opener.window().makeKeyAndOrderFront_(self)
        self.path_opener.populateWithClipboard()

    def new_project(self):
        editor = self.current_editor()
        if editor is not None:
            return editor.new_project()

    def open_documents_with_paths(self, paths):
        from editxt.document import TextDocumentView
        editor = self.current_editor()
        if editor is None:
            editor = self.create_editor()
        focus = None
        views = []
        for path in paths:
            if os.path.isfile(path) or not os.path.exists(path):
                view = TextDocumentView.create_with_path(path)
                focus = editor.add_document_view(view)
                views.append(view)
            else:
                log.info("cannot open path: %s", path)
        if focus is not None:
            editor.current_view = focus
        return views

    def open_config_file(self):
        views = self.open_documents_with_paths([self.config.path])
        if not os.path.exists(self.config.path):
            assert len(views) == 1, views
            views[0].document.text = self.config.default_config

    def open_error_log(self, set_current=True):
        from editxt.document import TextDocumentView
        doc = errlog.document
        try:
            view = next(self.iter_views_of_document(doc))
        except StopIteration:
            editor = self.current_editor()
            if editor is None:
                editor = self.create_editor()
            view = TextDocumentView.create_with_document(doc)
            editor.add_document_view(view)
            if set_current:
                editor.current_view = view
        else:
            if set_current:
                self.set_current_document_view(view)

    def iter_dirty_documents(self):
        seen = set()
        for editor in self.iter_editors():
            for proj in editor.projects:
                dirty = False
                for view in proj.dirty_documents():
                    if view.document.id not in seen:
                        seen.add(view.document.id)
                        yield view
                        dirty = True
                if dirty:
                    yield proj

    def set_current_document_view(self, doc_view):
        ed = self.find_editor_with_document_view(doc_view)
        ed.current_view = doc_view

    def close_current_document(self):
        editor = self.current_editor()
        if editor is not None:
            view = editor.current_view
            if view is not None:
                view.perform_close(editor)

    def iter_views_of_document(self, doc):
        for editor in self.iter_editors():
            for view in editor.iter_views_of_document(doc):
                yield view


#   def find_view_with_document(self, doc):
#       """find a view of the given document
#
#       Returns a view in the topmost window with the given document, or None
#       if there are no views of this document.
#       """
#       try:
#           return next(self.iter_views_of_document(doc))
#       except StopIteration:
#           return None

    def count_views_of_document(self, doc):
        return len(list(self.iter_views_of_document(doc)))

    def iter_editors_with_view_of_document(self, document):
        for editor in self.iter_editors():
            try:
                next(editor.iter_views_of_document(document))
            except StopIteration:
                pass
            else:
                yield editor

    def find_editor_with_document_view(self, doc_view):
        for editor in self.iter_editors():
            for proj in editor.projects:
                for dv in proj.documents():
                    if dv is doc_view:
                        return editor
        return None

    def find_editors_with_project(self, project):
        return [ed for ed in self.editors if project in ed.projects]

    def find_project_with_path(self, path):
        for ed in self.editors:
            proj = ed.find_project_with_path(path)
            if proj is not None:
                return proj
        return None

    def find_item_with_id(self, ident):
        # HACK slow implementation, violates encapsulation
        for editor in self.editors:
            for proj in editor.projects:
                if proj.id == ident:
                    return proj
                for doc in proj.documents():
                    if doc.id == ident:
                        return doc
        return None

    def item_changed(self, item, change_type=None):
        for editor in self.editors:
            editor.item_changed(item, change_type)

    def iter_editors(self, app=None):
        """Iterate over editors in on-screen z-order starting with the
        front-most editor window"""
        from editxt.editor import EditorWindowController
        if app is None:
            app = ak.NSApp()
        z_ordered_eds = set()
        for win in app.orderedWindows():
            wc = win.windowController()
            if isinstance(
                    wc, EditorWindowController) and wc.editor in self.editors:
                z_ordered_eds.add(wc.editor)
                yield wc.editor
        for ed in self.editors:
            if ed not in z_ordered_eds:
                yield ed

    def add_editor(self, editor):
        self.editors.append(editor)

    def current_editor(self):
        try:
            return next(self.iter_editors())
        except StopIteration:
            return None

    def discard_editor(self, editor):
        try:
            self.editors.remove(editor)
        except ValueError:
            pass
        editor.close()

    def setup_profile(self, editors=False):
        """Ensure that profile dir exists

        This will create the profile directory if it does not exist.

        :returns: True on success, otherwise False.
        """
        if not ('.' in self._setup_profile
                or os.path.isdir(self.profile_path)):
            try:
                os.mkdir(self.profile_path)
            except OSError:
                log.error('cannot create %s', self.profile_path, exc_info=True)
                return False
            self._setup_profile.add('.')
        if editors and 'editors' not in self._setup_profile:
            state_path = os.path.join(self.profile_path, const.STATE_DIR)
            if not os.path.exists(state_path):
                try:
                    os.mkdir(state_path)
                except OSError:
                    log.error('cannot create %s', state_path, exc_info=True)
                    return False
            self._setup_profile.add('editors')
        return True

    def _legacy_editor_states(self):
        # TODO remove once all users have upraded to new state persistence
        def pythonify(value):
            if isinstance(value, (str, int, float, bool)):
                return value
            if isinstance(value, (dict, fn.NSDictionary)):
                return {k: pythonify(v) for k, v in value.items()}
            if isinstance(value, (list, fn.NSArray)):
                return [pythonify(v) for v in value]
            raise ValueError('unknown value type: {} {}'.format(
                type(value), repr(value)))

        defaults = fn.NSUserDefaults.standardUserDefaults()
        serials = defaults.arrayForKey_(const.WINDOW_CONTROLLERS_DEFAULTS_KEY)
        settings = defaults.arrayForKey_(const.WINDOW_SETTINGS_DEFAULTS_KEY)
        serials = list(reversed(serials))
        for serial, setting in zip(serials, chain(settings, repeat(None))):
            try:
                state = dict(serial)
                if setting is not None:
                    state['window_settings'] = setting
                yield pythonify(state)
            except Exception:
                log.warn('cannot load legacy state: %r', serial, exc_info=True)

    def iter_saved_editor_states(self):
        """Yield saved editor states"""
        state_path = os.path.join(self.profile_path, const.STATE_DIR)
        if not os.path.exists(state_path):
            if self.profile_path == os.path.expanduser(self.default_profile()):
                # TODO remove once all users have upraded
                for state in self._legacy_editor_states():
                    yield state
            return
        state_glob = os.path.join(state_path, const.EDITOR_STATE.format('*'))
        for path in sorted(glob.glob(state_glob)):
            try:
                with open(path) as f:
                    yield load_yaml(f)
            except Exception:
                log.error('cannot load %s', path, exc_info=True)

    def save_editor_state(self, editor, ident=None):
        """Save a single editor's state

        :param editor: The editor with state to be saved.
        :param ident: The identifier to use when saving editor state. It
            is assumed that the profile has been setup when this
            argument is provided; ``editor.id`` will be used when
            not provided.
        :returns: The name of the state file.
        """
        if ident is None:
            raise NotImplementedError
            ident = editor.id
        self.setup_profile(editors=True)
        state_name = const.EDITOR_STATE.format(ident)
        state_file = os.path.join(self.profile_path, const.STATE_DIR,
                                  state_name)
        state = editor.state
        try:
            with atomicfile(state_file, encoding="utf-8") as fh:
                dump_yaml(state, fh)
        except Exception:
            log.error('cannot write %s\n%s\n',
                      state_file,
                      state,
                      exc_info=True)
        return state_name

    def save_editor_states(self):
        """Save all editors' states"""
        state_path = os.path.join(self.profile_path, const.STATE_DIR)
        old_glob = os.path.join(state_path, const.EDITOR_STATE.format('*'))
        old = {os.path.basename(name) for name in glob.glob(old_glob)}
        for i, editor in enumerate(self.iter_editors()):
            state_name = self.save_editor_state(editor, i)
            old.discard(state_name)
        for name in old:
            state_file = os.path.join(state_path, name)
            try:
                os.remove(state_file)
            except Exception:
                log.error('cannot remove %s', state_file, exc_info=True)

    def app_will_terminate(self, app):
        self.save_editor_states()
예제 #26
0
def test_TextCommandController_validate_hotkey():
    tc = TextCommandController("<history>")
    eq_(tc.validate_hotkey(None), ("", 0))
    eq_(tc.validate_hotkey(("a", 1)), ("a", 1))
    assert_raises(AssertionError, tc.validate_hotkey, ("a", "b", "c"))
예제 #27
0
def test_TextCommandController_validate_hotkey():
    tc = TextCommandController("<history>")
    eq_(tc.validate_hotkey(None), ("", 0))
    eq_(tc.validate_hotkey(("a", 1)), ("a", 1))
    assert_raises(AssertionError, tc.validate_hotkey, ("a", "b", "c"))