1from __future__ import unicode_literals
2
3import os
4import re
5import sys
6import time
7
8from lineedit import Mode, ModalPromptSession, ModalInMemoryHistory, ModalFileHistory
9from prompt_toolkit.formatted_text import ANSI
10from prompt_toolkit.layout.processors import HighlightMatchingBracketProcessor
11from prompt_toolkit.lexers import PygmentsLexer
12from prompt_toolkit.output import ColorDepth
13from prompt_toolkit.styles import style_from_pygments_cls
14from prompt_toolkit.utils import is_windows, get_term_environment_variable
15
16from pygments.styles import get_style_by_name
17
18from rchitect import rcopy, rcall
19from rchitect.interface import setoption, process_events, peek_event, polled_events
20
21from six import string_types
22
23from . import shell
24from .rutils import prase_text_complete
25from .key_bindings import create_r_key_bindings, create_shell_key_bindings, create_key_bindings
26from .completion import RCompleter, SmartPathCompleter
27from .io import CustomInput, CustomOutput
28from .lexer import CustomSLexer as SLexer
29
30
31PROMPT = "\x1b[34mr$>\x1b[0m "
32SHELL_PROMPT = "\x1b[31m#!>\x1b[0m "
33BROWSE_PROMPT = "\x1b[33mBrowse[{}]>\x1b[0m "
34BROWSE_PATTERN = re.compile(r"Browse\[([0-9]+)\]> $")
35VI_MODE_PROMPT = "\x1b[34m[{}]\x1b[0m "
36
37
38class RadianMode(Mode):
39    def __init__(
40            self,
41            name,
42            activator=None,
43            on_post_accept=None,
44            insert_new_line=False,
45            **kwargs):
46        self.activator = activator
47        self.on_post_accept = on_post_accept
48        self.insert_new_line = insert_new_line
49        super(RadianMode, self).__init__(name, **kwargs)
50
51
52def apply_settings(session, settings):
53    setoption("prompt", settings.prompt)
54
55    if settings.auto_width:
56        output_width = session.app.output.get_size().columns
57        if output_width:
58            setoption("width", output_width)
59
60    # necessary on windows
61    setoption("menu.graphics", False)
62
63    def askpass(message):
64        from prompt_toolkit import prompt
65        return prompt(message, is_password=True)
66
67    setoption("askpass", askpass)
68
69    # enables completion of installed package names
70    if rcopy(rcall(("utils", "rc.settings"), "ipck")) is None:
71        rcall(("utils", "rc.settings"), ipck=True)
72
73
74def create_radian_prompt_session(options, settings):
75
76    history_file = ".radian_history"
77    if options.no_history:
78        history = ModalInMemoryHistory()
79    elif not options.global_history and os.path.exists(history_file):
80        history = ModalFileHistory(os.path.abspath(history_file))
81    else:
82        history = ModalFileHistory(os.path.join(os.path.expanduser("~"), history_file))
83
84    if is_windows():
85        output = None
86    else:
87        output = CustomOutput.from_pty(sys.stdout, term=get_term_environment_variable())
88
89    def get_inputhook():
90        # make testing more robust
91        if "CIRCLECI" in os.environ or "GITHUB_REPOSITORY" in os.environ:
92            return None
93
94        terminal_width = [None]
95
96        def _(context):
97            output_width = session.app.output.get_size().columns
98            if output_width and terminal_width[0] != output_width:
99                terminal_width[0] = output_width
100                setoption("width", max(terminal_width[0], 20))
101
102            while True:
103                if context.input_is_ready():
104                    break
105                try:
106                    if peek_event():
107                        with session.app.input.detach():
108                            with session.app.input.rare_mode():
109                                process_events()
110                    else:
111                        polled_events()
112
113                except Exception:
114                    pass
115                time.sleep(1.0 / 30)
116
117        return _
118
119    def vi_mode_prompt():
120        if session.editing_mode.lower() == "vi" and settings.show_vi_mode_prompt:
121            im = session.app.vi_state.input_mode
122            vi_mode_prompt = settings.vi_mode_prompt
123            if isinstance(vi_mode_prompt, string_types):
124                return vi_mode_prompt.format(str(im)[3:6])
125            else:
126                return vi_mode_prompt[str(im)[3:6]]
127        return ""
128
129    def message():
130        if hasattr(session.current_mode, "get_message"):
131            return ANSI(vi_mode_prompt() + session.current_mode.get_message())
132        elif hasattr(session.current_mode, "message"):
133            message = session.current_mode.message
134            if callable(message):
135                return ANSI(vi_mode_prompt() + message())
136            else:
137                return ANSI(vi_mode_prompt() + message)
138        else:
139            return ""
140
141    session = ModalPromptSession(
142        message=message,
143        color_depth=ColorDepth.default(term=os.environ.get("TERM")),
144        style=style_from_pygments_cls(get_style_by_name(settings.color_scheme)),
145        editing_mode="VI" if settings.editing_mode in ["vim", "vi"] else "EMACS",
146        history=history,
147        enable_history_search=True,
148        history_search_no_duplicates=settings.history_search_no_duplicates,
149        search_ignore_case=settings.history_search_ignore_case,
150        enable_suspend=True,
151        tempfile_suffix=".R",
152        input=CustomInput(sys.stdin),
153        output=output,
154        inputhook=get_inputhook(),
155        mode_class=RadianMode
156    )
157
158    apply_settings(session, settings)
159
160    def browse_activator(session):
161        message = session.prompt_text
162        if BROWSE_PATTERN.match(message):
163            session.browse_level = BROWSE_PATTERN.match(message).group(1)
164            return True
165        else:
166            return False
167
168    def browse_on_pre_accept(session):
169        if session.default_buffer.text.strip() in [
170                "n", "s", "f", "c", "cont", "Q", "where", "help"]:
171            session.add_history = False
172
173    def shell_process_text(session):
174        text = session.default_buffer.text
175        shell.run_command(text)
176
177    input_processors = []
178    if settings.highlight_matching_bracket:
179        input_processors.append(HighlightMatchingBracketProcessor())
180
181    session.register_mode(
182        "r",
183        activator=lambda session: session.prompt_text == settings.prompt,
184        insert_new_line=True,
185        history_share_with="browse",
186        get_message=lambda: settings.prompt,
187        multiline=settings.indent_lines,
188        complete_while_typing=settings.complete_while_typing,
189        lexer=PygmentsLexer(SLexer),
190        completer=RCompleter(timeout=settings.completion_timeout),
191        key_bindings=create_key_bindings(),
192        input_processors=input_processors,
193        prompt_key_bindings=create_r_key_bindings(prase_text_complete)
194    )
195    session.register_mode(
196        "shell",
197        on_post_accept=shell_process_text,
198        insert_new_line=True,
199        get_message=lambda: settings.shell_prompt,
200        multiline=settings.indent_lines,
201        complete_while_typing=settings.complete_while_typing,
202        lexer=None,
203        completer=SmartPathCompleter(),
204        prompt_key_bindings=create_shell_key_bindings()
205    )
206    session.register_mode(
207        "browse",
208        activator=browse_activator,
209        # on_pre_accept=browse_on_pre_accept,  # disable
210        insert_new_line=True,
211        history_share_with="r",
212        get_message=lambda: settings.browse_prompt.format(session.browse_level),
213        multiline=settings.indent_lines,
214        complete_while_typing=True,
215        lexer=PygmentsLexer(SLexer),
216        completer=RCompleter(timeout=settings.completion_timeout),
217        input_processors=input_processors,
218        prompt_key_bindings=create_r_key_bindings(prase_text_complete),
219        switchable_from=False,
220        switchable_to=False
221    )
222    session.register_mode(
223        "unknown",
224        insert_new_line=False,
225        get_message=lambda: session.prompt_text,
226        complete_while_typing=False,
227        lexer=None,
228        completer=None,
229        prompt_key_bindings=None,
230        switchable_from=False,
231        switchable_to=False,
232        input_processors=[]
233    )
234
235    return session
236