prompt-toolkit / pyvim

Pure Python Vim clone.
BSD 3-Clause "New" or "Revised" License
2.52k stars 161 forks source link

color scheme #66

Open htzh opened 8 years ago

htzh commented 8 years ago

In my ConEmu terminal the cursor position is pale green over grey with very low contrast and is barely readable. I cycled through the themes offered by ConEmu and even the best contrast is still hard to read. I tried to customize through .pyvimrc by adding the following:

    editor.use_colorscheme('friendly')

    my_style_extensions = {
        # Toolbar colors.
        Token.Toolbar.Status:                '#ffffff bg:#333333',
        Token.Toolbar.Status.CursorPosition: '#bbffbb bg:#333333',
        Token.Toolbar.Status.Percentage:     '#ffbbbb bg:#333333',
    }

    editor.current_style.styles.update(my_style_extensions)

I am trying to darken the toolbar background but it seems to have no effect on the mode line. I must not be understanding something correctly however there is no documentation on how these colors are used. I.e. I am merely guess that toolbar style controls the mode line but it is not clear that is indeed the case.

On a more basic level I noticed that terminal color themes do seem to change font colors as well, sometimes a little but sometimes dramatically. Is there any pointer on how these pygment styles are translated to terminal colors as the terminal supposedly only supports 256 colors?

htzh commented 8 years ago

I think I understand part of the reason for the behavior. The Editor instance initializes the default style for the underlying Application. If I call use_colorscheme it generates a new current_style which can be detected and trigger an update of the final styles dict. However if I merely update editor.current_style.styles this update is not triggered. As shown in the following interactive session in ptpython, where e is an Editor instance:

>>> e.application.style.get_token_to_attributes_dict()[Token.Toolbar.Status]        
Attrs(color='ffffff', bgcolor='444444', bold=False, underline=False, reverse=False) 

>>> e.current_style.styles.update({Token.Toolbar.Status : '#ffffff bg:#222222'})    

>>> e.application.style.get_token_to_attributes_dict()[Token.Toolbar.Status]        
Attrs(color='ffffff', bgcolor='444444', bold=False, underline=False, reverse=False) 

However use_colorscheme does change the dynamic style attributes by overwriting the current_style attribute:

>>> e.application.style.get_token_to_attributes_dict()[Token.Name.Entity]
Attrs(color='999999', bgcolor=None, bold=True, underline=False, reverse=False)

>>> e.use_colorscheme('colorful')

>>> e.application.style.get_token_to_attributes_dict()[Token.Name.Entity]
Attrs(color='880000', bgcolor=None, bold=True, underline=False, reverse=False)

So it seems for now I need to either modify styles.py directly or if I want to overload the behavior I need to emulate the use_colorscheme method and overwrite current_style.

htzh commented 8 years ago

The overloading and indirection are kind of confusing here.

>>> ms = get_editor_style_by_name('friendly')                                                 

>>> ms.styles[Token.Toolbar.Status]                                                           
'#ffffff bg:#444444'                                                                          

>>> ms.styles.update({Token.Toolbar.Status : '#ffffff bg:#222222'})                           

>>> e.current_style = ms                                                                      

>>> e.application.style.get_token_to_attributes_dict()[Token.Name.Entity]                     
Attrs(color='d55537', bgcolor=None, bold=True, underline=False, reverse=False)                

>>> e.application.style.get_token_to_attributes_dict()[Token.Toolbar.Status]                  
Attrs(color='ffffff', bgcolor='444444', bold=False, underline=False, reverse=False)           

>>> ms.styles[Token.Toolbar.Status]                                                           
'#ffffff bg:#222222'                                                                          

>>> ms.styles[Token.Name.Entity]                                                              
'bold #d55537'                                                                                

How does the dict know to update certain property (Token.Name.Entity) but not others (Token.Toolbar.Status)?

htzh commented 8 years ago

The following works for me but I still don't fully understand the magic:

    # Apply colorscheme. (:colorscheme emacs)
    my_style_extensions = {
        # Toolbar colors.
        Token.Toolbar.Status:                '#ffffff bg:#222222',
        Token.Toolbar.Status.CursorPosition: '#ffffff bg:#222222',
        Token.Toolbar.Status.Percentage:     '#ffbbbb bg:#222222',
    }
    pyvim.style.style_extensions.update(my_style_extensions)
    editor.use_colorscheme('emacs')
htzh commented 8 years ago

Okay this is my understanding. The base class is pygments.Style

@add_metaclass(StyleMeta)
class Style(object):

The StyleMeta metaclass overloads __new__ and during the construction of the Style class creates a cached dict _styles from the class attribute styles. But apparently (related to the implementation of the add_metaclass decorator?) according to Python semantics the statements in the scope of our class EditorStyle and the base class Style get executed first before the metaclass __new__ is called so the updates done in the body of the class definition show up in the cached object _styles. However after the class is constructed any new updates to styles has no effect on _styles which explains the weirdness I was seeing.

This (i.e., the execution order of the class scope statements and the metaclass __new__ method) seems to me to border on undefined behavior voodoo territory but then I am not a Python programmer worthy of anything.

jonathanslenders commented 8 years ago

Hi @htzh,

Thanks for taking the time to troubleshoot the issue! I should admit that I didn't create an API to install new color schemes. Probably, just like ptpython, we would need an install_ui_colorscheme function: https://github.com/jonathanslenders/ptpython/blob/master/examples/ptpython_config/config.py#L102 In ptpython, it's possible to choose the color of the Python source code and the UI decorations independently. This is what I want in Pyvim.

Styles are immutable classes. Well, actually they are mutable, but as you experienced, it's not a good idea to alter an existing style, because not all the changes are propagated through the application. There are also caching layers in between. And I think there is one style class that acts as a proxy to the text highlighting style and the ui style.

So, what we need is an install_ui_colorscheme function and a use_ui_colorscheme function. I take this issue as a feature request to do that. It's possible that you find a workaround, but that's not a clean way.

Cheers, Jonathan